diff --git a/client/src/app/+admin/admin-settings.component.ts b/client/src/app/+admin/admin-settings.component.ts index b69800742..eb32f7f3d 100644 --- a/client/src/app/+admin/admin-settings.component.ts +++ b/client/src/app/+admin/admin-settings.component.ts @@ -26,8 +26,8 @@ export class AdminSettingsComponent implements OnInit { private buildMenu () { this.menuEntries = [] - this.buildFederationItems() this.buildConfigurationItems() + this.buildFederationItems() this.buildPluginItems() this.buildRunnerItems() this.buildSystemItems() diff --git a/client/src/app/+videos/routes.ts b/client/src/app/+videos/routes.ts index 2ede24dea..d1bf29b72 100644 --- a/client/src/app/+videos/routes.ts +++ b/client/src/app/+videos/routes.ts @@ -48,7 +48,7 @@ export default [ redirectTo: () => { const redirectService = inject(RedirectService) - return 'browse?scope=federated&sort=-' + redirectService.getDefaultTrendingAlgorithm() + return 'browse?scope=federated&sort=-' + redirectService.getDefaultTrendingSort() } }, { diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.html b/client/src/app/+videos/video-list/overview/video-overview.component.html index a23e4b6d5..767258d9e 100644 --- a/client/src/app/+videos/video-list/overview/video-overview.component.html +++ b/client/src/app/+videos/video-list/overview/video-overview.component.html @@ -9,7 +9,7 @@ Quick access: @for (quickAccess of quickAccessLinks; track quickAccess.label) { - {{ quickAccess.label }} + {{ quickAccess.label }} } @@ -22,7 +22,7 @@ >
-
+

@@ -32,7 +32,7 @@ {{ object.type }}

- {{ object.buttonLabel }} + {{ object.buttonLabel }}
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.scss b/client/src/app/+videos/video-list/overview/video-overview.component.scss index 5da626f77..c03703112 100644 --- a/client/src/app/+videos/video-list/overview/video-overview.component.scss +++ b/client/src/app/+videos/video-list/overview/video-overview.component.scss @@ -29,9 +29,21 @@ white-space: nowrap; } -.quick-access-links:not(.see-all-quick-links) { - overflow: hidden; - text-overflow: ellipsis; +.quick-access-links { + + a { + color: pvar(--fg-300); + text-decoration: underline; + + &:hover { + opacity: 0.8; + } + } + + &:not(.see-all-quick-links) { + overflow: hidden; + text-overflow: ellipsis; + } } .see-all-quick-links { diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts index 0cb0b099c..23f77c6fd 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts @@ -15,7 +15,7 @@ export class RedirectService { private static SESSION_STORAGE_LATEST_SESSION_URL_KEY = 'redirect-latest-session-url' // Default route could change according to the instance configuration - static INIT_DEFAULT_ROUTE = '/videos/trending' + static INIT_DEFAULT_ROUTE = '/videos/browse' static INIT_DEFAULT_TRENDING_ALGORITHM = 'most-viewed' private previousUrl: string @@ -70,7 +70,7 @@ export class RedirectService { return this.defaultRoute } - getDefaultTrendingAlgorithm () { + getDefaultTrendingSort () { const algorithm = this.defaultTrendingAlgorithm switch (algorithm) { diff --git a/client/src/app/header/notification-dropdown.component.html b/client/src/app/header/notification-dropdown.component.html index a79dc27d0..849f20b83 100644 --- a/client/src/app/header/notification-dropdown.component.html +++ b/client/src/app/header/notification-dropdown.component.html @@ -3,12 +3,20 @@
99+
+ + @if (unreadNotifications) { + + } @else { + + } + + @if (isInMobileView) { } @else { @@ -22,8 +30,7 @@ ngbDropdownToggle > - - +
diff --git a/client/src/app/header/notification-dropdown.component.scss b/client/src/app/header/notification-dropdown.component.scss index 2b1e05cbf..aed983b67 100644 --- a/client/src/app/header/notification-dropdown.component.scss +++ b/client/src/app/header/notification-dropdown.component.scss @@ -97,7 +97,6 @@ background-color: pvar(--primary); color: pvar(--on-primary); - border: 1px solid pvar(--on-primary); font-weight: $font-bold; font-size: 10px; diff --git a/client/src/app/menu/home-menu.component.ts b/client/src/app/menu/home-menu.component.ts index 89ccf705a..fd7cfff58 100644 --- a/client/src/app/menu/home-menu.component.ts +++ b/client/src/app/menu/home-menu.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit } from '@angular/core' import { AuthService, ServerService } from '@app/core' -import { HorizontalMenuComponent } from '@app/shared/shared-main/menu/horizontal-menu.component' -import { ListOverflowItem } from '@app/shared/shared-main/menu/list-overflow.component' +import { HorizontalMenuComponent, HorizontalMenuEntry } from '@app/shared/shared-main/menu/horizontal-menu.component' @Component({ selector: 'my-home-menu', @@ -10,7 +9,7 @@ import { ListOverflowItem } from '@app/shared/shared-main/menu/list-overflow.com imports: [ HorizontalMenuComponent ] }) export class HomeMenuComponent implements OnInit { - menuEntries: ListOverflowItem[] = [] + menuEntries: HorizontalMenuEntry[] = [] constructor ( private server: ServerService, diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts index fc4903454..03c7ad339 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts @@ -27,6 +27,7 @@ const icons = { 'config': require('../../../assets/images/feather/config.svg'), 'award': require('../../../assets/images/feather/award.svg'), 'bell': require('../../../assets/images/feather/bell.svg'), + 'opened-bell': require('../../../assets/images/feather/opened-bell.svg'), 'channel': require('../../../assets/images/feather/channel.svg'), 'chevrons-up': require('../../../assets/images/feather/chevrons-up.svg'), 'chevron-left': require('../../../assets/images/feather/chevron-left.svg'), diff --git a/client/src/app/shared/shared-main/buttons/button.component.html b/client/src/app/shared/shared-main/buttons/button.component.html index 70281a990..4ed71e55f 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.html +++ b/client/src/app/shared/shared-main/buttons/button.component.html @@ -1,5 +1,9 @@ -@if (ptRouterLink) { - +@if (ptRouterLink || ptQueryParams) { + } @else { diff --git a/client/src/app/shared/shared-main/buttons/button.component.ts b/client/src/app/shared/shared-main/buttons/button.component.ts index 2847ef93a..6becbdf8f 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.ts +++ b/client/src/app/shared/shared-main/buttons/button.component.ts @@ -1,12 +1,22 @@ +import { ObserversModule } from '@angular/cdk/observers' import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common' -import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnChanges, ViewChild, booleanAttribute } from '@angular/core' -import { RouterLink } from '@angular/router' +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + Input, + OnChanges, + ViewChild, + booleanAttribute +} from '@angular/core' +import { Params, QueryParamsHandling, RouterLink, RouterLinkActive } from '@angular/router' import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component' import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap' +import debug from 'debug' import { GlobalIconComponent } from '../../shared-icons/global-icon.component' import { LoaderComponent } from '../common/loader.component' -import debug from 'debug' -import { ObserversModule } from '@angular/cdk/observers' const debugLogger = debug('peertube:button') @@ -16,15 +26,29 @@ const debugLogger = debug('peertube:button') templateUrl: './button.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [ NgIf, NgClass, NgbTooltip, NgTemplateOutlet, RouterLink, LoaderComponent, GlobalIconComponent, ObserversModule ] + imports: [ + NgIf, + NgClass, + NgbTooltip, + NgTemplateOutlet, + RouterLink, + LoaderComponent, + GlobalIconComponent, + ObserversModule + ] }) export class ButtonComponent implements OnChanges, AfterViewInit { @Input() label = '' @Input() theme: 'primary' | 'secondary' | 'tertiary' = 'secondary' @Input() icon: GlobalIconName + @Input() ptRouterLink: string[] | string + @Input() ptQueryParams: Params + @Input() ptQueryParamsHandling: QueryParamsHandling + @Input() title: string + @Input({ transform: booleanAttribute }) active = false @Input({ transform: booleanAttribute }) loading = false @Input({ transform: booleanAttribute }) disabled = false @@ -46,9 +70,12 @@ export class ButtonComponent implements OnChanges, AfterViewInit { } private buildClasses () { + const isButtonLink = !!this.ptRouterLink + this.classes = { - 'peertube-button': !this.ptRouterLink, - 'peertube-button-link': !!this.ptRouterLink, + 'active': this.active, + 'peertube-button': !isButtonLink, + 'peertube-button-link': isButtonLink, 'primary-button': this.theme === 'primary', 'secondary-button': this.theme === 'secondary', 'tertiary-button': this.theme === 'tertiary', diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html index 32fe8a915..21bcdbef6 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html @@ -2,12 +2,24 @@
- Recently added +
+ @for (quickFilter of quickFilters; track quickFilter) { + {{ quickFilter.label }} + } +
- Trending - -
-
{{ getActiveFilters() }}
+
+
+ @for (filter of filters.getActiveFilters(); track filter.key + (filter.value || '')) { +
+ {{ filter.label }}: + {{ getFilterValue(filter) || filter.label }} +
+ } +
-
- +
+
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss b/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss index 4d5b2f997..4b1c325f2 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss @@ -20,6 +20,7 @@ $filters-background: pvar(--bg-secondary-400); font-weight: normal; margin-bottom: 0; color: pvar(--fg-200); + font-size: 1rem; @include margin-right(0.5rem); } @@ -66,12 +67,21 @@ $filters-background: pvar(--bg-secondary-400); } } +.active-filters { + > div:not(:last-child)::after { + content: '•'; + font-weight: normal; + display: inline-block; + margin: 0 5px; + } +} + .filters-summary { color: pvar(--fg-200); } .filters-toggle { - padding: pvar(--input-y-padding) 0.25rem calc(#{pvar(--input-y-padding)} + 0.75rem) 0.5rem; + padding: pvar(--input-y-padding) 0.5rem calc(#{pvar(--input-y-padding)} + 0.75rem) 0.75rem; @include margin-left(0.25rem); diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts b/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts index c61ece221..726b7d8b7 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts @@ -1,8 +1,8 @@ -import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common' +import { NgClass, NgIf } from '@angular/common' import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms' -import { RouterLink } from '@angular/router' -import { AuthService } from '@app/core' +import { ActivatedRoute, Params, RouterLink } from '@angular/router' +import { AuthService, RedirectService } from '@app/core' import { ServerService } from '@app/core/server/server.service' import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap' import { UserRight, VideoConstant } from '@peertube/peertube-models' @@ -13,14 +13,20 @@ import { PeertubeCheckboxComponent } from '../shared-forms/peertube-checkbox.com import { SelectCategoriesComponent } from '../shared-forms/select/select-categories.component' import { SelectLanguagesComponent } from '../shared-forms/select/select-languages.component' import { SelectOptionsComponent } from '../shared-forms/select/select-options.component' -import { GlobalIconComponent } from '../shared-icons/global-icon.component' +import { GlobalIconComponent, GlobalIconName } from '../shared-icons/global-icon.component' import { ButtonComponent } from '../shared-main/buttons/button.component' -import { PeerTubeTemplateDirective } from '../shared-main/common/peertube-template.directive' import { PeertubeModalService } from '../shared-main/peertube-modal/peertube-modal.service' import { VideoFilterActive, VideoFilters } from './video-filters.model' const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent') +type QuickFilter = { + iconName: GlobalIconName + label: string + isActive: () => boolean + queryParams: Params +} + @Component({ selector: 'my-video-filters-header', styleUrls: [ './video-filters-header.component.scss' ], @@ -34,12 +40,10 @@ const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent') NgIf, GlobalIconComponent, NgbCollapse, - NgTemplateOutlet, SelectLanguagesComponent, SelectCategoriesComponent, PeertubeCheckboxComponent, SelectOptionsComponent, - PeerTubeTemplateDirective, ButtonComponent ] }) @@ -57,6 +61,8 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { sortItems: SelectOptionsItem[] = [] availableScopes: SelectOptionsItem[] = [] + quickFilters: QuickFilter[] = [] + private videoCategories: VideoConstant[] = [] private videoLanguages: VideoConstant[] = [] @@ -66,7 +72,9 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { private auth: AuthService, private serverService: ServerService, private fb: FormBuilder, - private modalService: PeertubeModalService + private modalService: PeertubeModalService, + private redirectService: RedirectService, + private route: ActivatedRoute ) { } @@ -83,6 +91,11 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { this.patchForm(false) + this.routeSub = this.route.queryParams.subscribe(query => { + this.filters.load(query) + this.filtersChanged.emit() + }) + this.filters.onChange(() => { this.patchForm(false) }) @@ -100,12 +113,13 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { this.serverService.getVideoLanguages() .subscribe(languages => this.videoLanguages = languages) - this.buildSortItems() - this.availableScopes = [ { id: 'local', label: $localize`This platform only` }, { id: 'federated', label: $localize`All platforms` } ] + + this.buildSortItems() + this.buildQuickFilters() } ngOnDestroy () { @@ -119,6 +133,30 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { return this.auth.getUser().hasRight(UserRight.SEE_ALL_VIDEOS) } + // --------------------------------------------------------------------------- + + private buildQuickFilters () { + const trendingSort = this.redirectService.getDefaultTrendingSort() + + this.quickFilters = [ + { + label: $localize`Recently added`, + iconName: 'add', + isActive: () => this.filters.sort === '-publishedAt', + queryParams: { sort: '-publishedAt' } + }, + + { + label: $localize`Trending`, + iconName: 'trending', + isActive: () => this.filters.sort === trendingSort, + queryParams: { sort: trendingSort } + } + ] + } + + // --------------------------------------------------------------------------- + private buildSortItems () { this.sortItems = [ { id: '-publishedAt', label: $localize`Recently Added` }, @@ -147,29 +185,7 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { return serverConfig.trending.videos.algorithms.enabled.includes(sort) } - getActiveFilters () { - const store: string[] = [] - - for (const activeFilter of this.filters.getActiveFilters()) { - if (activeFilter.value) { - store.push($localize`${activeFilter.label}\: ${this.getFilterValue(activeFilter)}`) - } else { - store.push(activeFilter.label) - } - } - - const output = store.reduce((p, c) => { - if (!p) return c - - return $localize`${p}, ${c}` - }, '') - - if (output) return `${output}.` - - return output - } - - private getFilterValue (filter: VideoFilterActive) { + getFilterValue (filter: VideoFilterActive) { if ((filter.key === 'categoryOneOf' || filter.key === 'languageOneOf') && Array.isArray(filter.rawValue)) { if (filter.rawValue.length > 2) { return filter.rawValue.length diff --git a/client/src/app/shared/shared-video-miniature/video-filters.model.ts b/client/src/app/shared/shared-video-miniature/video-filters.model.ts index bb635dea7..c746a1047 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters.model.ts +++ b/client/src/app/shared/shared-video-miniature/video-filters.model.ts @@ -192,13 +192,13 @@ export class VideoFilters { this.activeFilters.push({ key: 'live', canRemove: true, - label: $localize`Live videos` + label: $localize`Only lives` }) } else if (this.live === 'false') { this.activeFilters.push({ key: 'live', canRemove: true, - label: $localize`VOD videos` + label: $localize`Only VOD` }) } diff --git a/client/src/assets/images/feather/opened-bell.svg b/client/src/assets/images/feather/opened-bell.svg new file mode 100644 index 000000000..0031406bf --- /dev/null +++ b/client/src/assets/images/feather/opened-bell.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + diff --git a/client/src/sass/include/_button-mixins.scss b/client/src/sass/include/_button-mixins.scss index 9c4e644cf..1a6d97305 100644 --- a/client/src/sass/include/_button-mixins.scss +++ b/client/src/sass/include/_button-mixins.scss @@ -11,6 +11,7 @@ } &:active, + &.active, &:focus, &:focus-visible { color: $fg; @@ -48,6 +49,7 @@ &, &:active, + &.active, &:focus { color: pvar(--on-primary); background-color: pvar(--primary); @@ -127,6 +129,7 @@ &:hover, &:active, + &.active, &:focus:not(:focus-visible) { opacity: 0.8; } diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 68cb08a11..7fd43d8ff 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -243,7 +243,7 @@ body .p-dropdown-panel .p-dropdown-items .p-dropdown-item { } .p-dropdown-panel .p-dropdown-items .p-dropdown-item:not(.p-highlight):not(.p-disabled):hover { color: pvar(--fg); - background: pvar(--bg-secondary-500); + background: pvar(--bg-secondary-400); } .p-dropdown-panel .p-dropdown-items .p-dropdown-item-group { margin: 0; @@ -565,7 +565,7 @@ p-chips.p-chips-clearable .p-chips-clear-icon { background: pvar(--bg-secondary-500); } .p-multiselect.p-variant-filled:not(.p-disabled):hover { - background-color: pvar(--bg-secondary-500); + background-color: pvar(--bg-secondary-400); } .p-multiselect.p-variant-filled:not(.p-disabled).p-focus { background-color: pvar(--bg-secondary-500);