From 52cc0d54850e0acf069d2f95d063826f16ff5238 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Mon, 3 Feb 2020 14:04:42 +0100 Subject: [PATCH] Gracefully downsize search bar for mobile devices --- client/src/app/app.component.html | 2 +- client/src/app/header/header.component.html | 8 +- client/src/app/header/header.component.scss | 12 --- client/src/app/header/header.component.ts | 41 --------- .../header/search-typeahead.component.scss | 16 +++- .../app/header/search-typeahead.component.ts | 86 ++++++++++++++----- client/src/app/header/suggestion.component.ts | 7 +- .../src/app/header/suggestions.component.ts | 1 - client/src/app/menu/menu.component.scss | 1 + client/src/sass/application.scss | 5 ++ 10 files changed, 93 insertions(+), 86 deletions(-) diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 2660c5377..f5a8dbd34 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -15,7 +15,7 @@
- +
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html index 074bebf21..561ee6c16 100644 --- a/client/src/app/header/header.component.html +++ b/client/src/app/header/header.component.html @@ -1,9 +1,9 @@ - + - + diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss index b602cf0a8..2f0a407fc 100644 --- a/client/src/app/header/header.component.scss +++ b/client/src/app/header/header.component.scss @@ -14,14 +14,6 @@ my-search-typeahead { &::placeholder { color: var(--inputPlaceholderColor); } - - @media screen and (max-width: 800px) { - width: calc(100% - 150px); - } - - @media screen and (max-width: 600px) { - width: calc(100% - 70px); - } } .icon.icon-search { @@ -45,10 +37,6 @@ my-search-typeahead { color: var(--mainBackgroundColor) !important; margin-right: 25px; - @media screen and (max-width: 800px) { - margin-right: 0; - } - @media screen and (max-width: 600px) { margin-right: 10px; padding: 0 10px; diff --git a/client/src/app/header/header.component.ts b/client/src/app/header/header.component.ts index ca4a32cbc..d9311c554 100644 --- a/client/src/app/header/header.component.ts +++ b/client/src/app/header/header.component.ts @@ -1,9 +1,4 @@ -import { filter, first, map, tap } from 'rxjs/operators' import { Component, OnInit } from '@angular/core' -import { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router' -import { getParameterByName } from '../shared/misc/utils' -import { AuthService } from '@app/core' -import { of } from 'rxjs' import { I18n } from '@ngx-translate/i18n-polyfill' @Component({ @@ -17,46 +12,10 @@ export class HeaderComponent implements OnInit { ariaLabelTextForSearch = '' constructor ( - private router: Router, - private route: ActivatedRoute, - private auth: AuthService, private i18n: I18n ) {} ngOnInit () { this.ariaLabelTextForSearch = this.i18n('Search videos, channels') - - this.router.events - .pipe( - filter(e => e instanceof NavigationEnd), - map(() => getParameterByName('search', window.location.href)) - ) - .subscribe(searchQuery => this.searchValue = searchQuery || '') - } - - doSearch () { - const queryParams: Params = {} - - if (window.location.pathname === '/search' && this.route.snapshot.queryParams) { - Object.assign(queryParams, this.route.snapshot.queryParams) - } - - Object.assign(queryParams, { search: this.searchValue }) - - const o = this.auth.isLoggedIn() - ? this.loadUserLanguagesIfNeeded(queryParams) - : of(true) - - o.subscribe(() => this.router.navigate([ '/search' ], { queryParams })) - } - - private loadUserLanguagesIfNeeded (queryParams: any) { - if (queryParams && queryParams.languageOneOf) return of(queryParams) - - return this.auth.userInformationLoaded - .pipe( - first(), - tap(() => Object.assign(queryParams, { languageOneOf: this.auth.getUser().videoLanguages })) - ) } } diff --git a/client/src/app/header/search-typeahead.component.scss b/client/src/app/header/search-typeahead.component.scss index c410d4734..c2f5a1828 100644 --- a/client/src/app/header/search-typeahead.component.scss +++ b/client/src/app/header/search-typeahead.component.scss @@ -46,6 +46,18 @@ my-suggestions ::ng-deep ul { transition: box-shadow .3s ease, width .2s ease; } + @media screen and (min-width: 500px) { + margin-left: 10px; + } + + @media screen and (max-width: 800px) { + flex: 1; + + ::ng-deep input { + width: unset; + } + } + ::ng-deep span { right: 10px; } @@ -59,7 +71,9 @@ my-suggestions ::ng-deep ul { &:focus, ::ng-deep &:focus-within { & > div:last-child { - display: initial !important; + @media screen and (min-width: 500px) { + display: initial !important; + } #typeahead-help, #typeahead-instructions, diff --git a/client/src/app/header/search-typeahead.component.ts b/client/src/app/header/search-typeahead.component.ts index 084bdd58b..514c04704 100644 --- a/client/src/app/header/search-typeahead.component.ts +++ b/client/src/app/header/search-typeahead.component.ts @@ -7,13 +7,15 @@ import { OnDestroy, QueryList } from '@angular/core' -import { Router, NavigationEnd } from '@angular/router' +import { Router, NavigationEnd, Params, ActivatedRoute } from '@angular/router' import { AuthService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' -import { filter } from 'rxjs/operators' +import { filter, first, tap, map } from 'rxjs/operators' import { ListKeyManager } from '@angular/cdk/a11y' import { UP_ARROW, DOWN_ARROW, ENTER, TAB } from '@angular/cdk/keycodes' -import { SuggestionComponent } from './suggestion.component' +import { SuggestionComponent, Result } from './suggestion.component' +import { of } from 'rxjs' +import { getParameterByName } from '@app/shared/misc/utils' @Component({ selector: 'my-search-typeahead', @@ -41,6 +43,7 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni constructor ( private authService: AuthService, private router: Router, + private route: ActivatedRoute, private i18n: I18n ) { this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content, or if your instance only allows doing so for instances it follows.') @@ -50,12 +53,19 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni ngOnInit () { this.router.events - .pipe(filter(event => event instanceof NavigationEnd)) + .pipe(filter(e => e instanceof NavigationEnd)) .subscribe((event: NavigationEnd) => { this.hasChannel = event.url.startsWith('/videos/watch') this.inChannel = event.url.startsWith('/video-channels') this.computeResults() }) + + this.router.events + .pipe( + filter(e => e instanceof NavigationEnd), + map(() => getParameterByName('search', window.location.href)) + ) + .subscribe(searchQuery => this.searchInput.value = searchQuery || '') } ngOnDestroy () { @@ -82,33 +92,33 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni computeResults () { this.newSearch = true - let results = [ - { - text: 'Maître poney', - type: 'channel' - } - ] + let results: Result[] = [] if (this.hasSearch) { results = [ + /* Channel search is still unimplemented. Uncomment when it is. { text: this.searchInput.value, type: 'search-channel' }, + */ { text: this.searchInput.value, - type: 'search-instance' + type: 'search-instance', + default: true }, + /* Global search is still unimplemented. Uncomment when it is. { text: this.searchInput.value, type: 'search-global' }, + */ ...results ] } this.results = results.filter( - result => { + (result: Result) => { // if we're not in a channel or one of its videos/playlits, show all channel-related results if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel') // if we're in a channel, show all channel-related results except for the channel redirection itself @@ -118,19 +128,26 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni ) } + setEventItems (event: { items: QueryList, index?: number }) { + event.items.forEach(e => { + if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === e) { + this.keyboardEventsManager.activeItem.active = true + } else { + e.active = false + } + }) + } + initKeyboardEventsManager (event: { items: QueryList, index?: number }) { if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() this.keyboardEventsManager = new ListKeyManager(event.items) if (event.index !== undefined) { this.keyboardEventsManager.setActiveItem(event.index) - event.items.forEach(e => e.active = false) - this.keyboardEventsManager.activeItem.active = true + } else { + this.keyboardEventsManager.setFirstItemActive() } this.keyboardEventsManager.change.subscribe( - val => { - event.items.forEach(e => e.active = false) - this.keyboardEventsManager.activeItem.active = true - } + _ => this.setEventItems(event) ) } @@ -141,17 +158,40 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni handleKeyUp (event: KeyboardEvent, indexSelected?: number) { event.stopImmediatePropagation() if (this.keyboardEventsManager) { - if (event.keyCode === TAB) { - this.keyboardEventsManager.setNextItemActive() - return false - } else if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) { + if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) { this.keyboardEventsManager.onKeydown(event) return false } else if (event.keyCode === ENTER) { this.newSearch = false - // this.router.navigate(this.keyboardEventsManager.activeItem.result) + this.doSearch() return false } } } + + doSearch () { + const queryParams: Params = {} + + if (window.location.pathname === '/search' && this.route.snapshot.queryParams) { + Object.assign(queryParams, this.route.snapshot.queryParams) + } + + Object.assign(queryParams, { search: this.searchInput.value }) + + const o = this.authService.isLoggedIn() + ? this.loadUserLanguagesIfNeeded(queryParams) + : of(true) + + o.subscribe(() => this.router.navigate([ '/search' ], { queryParams })) + } + + private loadUserLanguagesIfNeeded (queryParams: any) { + if (queryParams && queryParams.languageOneOf) return of(queryParams) + + return this.authService.userInformationLoaded + .pipe( + first(), + tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages })) + ) + } } diff --git a/client/src/app/header/suggestion.component.ts b/client/src/app/header/suggestion.component.ts index 75c44a583..bdcb3e03f 100644 --- a/client/src/app/header/suggestion.component.ts +++ b/client/src/app/header/suggestion.component.ts @@ -3,10 +3,11 @@ import { RouterLink } from '@angular/router' import { I18n } from '@ngx-translate/i18n-polyfill' import { ListKeyManagerOption } from '@angular/cdk/a11y' -type Result = { +export type Result = { text: string type: 'channel' | 'suggestion' | 'search-channel' | 'search-instance' | 'search-global' | 'search-any' - routerLink?: RouterLink + routerLink?: RouterLink, + default?: boolean } @Component({ @@ -39,7 +40,7 @@ export class SuggestionComponent implements OnInit, ListKeyManagerOption { } ngOnInit () { - this.active = false + if (this.result.default) this.active = true } selectItem () { diff --git a/client/src/app/header/suggestions.component.ts b/client/src/app/header/suggestions.component.ts index 122c09388..fac7fe2f9 100644 --- a/client/src/app/header/suggestions.component.ts +++ b/client/src/app/header/suggestions.component.ts @@ -19,7 +19,6 @@ export class SuggestionsComponent implements AfterViewInit { @Output() init = new EventEmitter() ngAfterViewInit () { - this.init.emit({ items: this.listItems }) this.listItems.changes.subscribe( val => this.init.emit({ items: this.listItems }) ) diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 43654504c..cb5f90723 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss @@ -7,6 +7,7 @@ padding: 0; width: $menu-width; z-index: z(menu); + scrollbar-color: var(--actionButtonColor) var(--menuBackgroundColor); } menu { diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index e4840dd81..4ad14ea5b 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -47,6 +47,11 @@ body { font-size: 14px; } +::selection { + color: var(--mainBackgroundColor); + background-color: var(--mainHoverColor); +} + #incompatible-browser { display: none; text-align: center; -- 2.25.1