diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts index 6b136742f..629c5c6a9 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts @@ -156,13 +156,24 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges { } buildLandingPageOptions () { - this.defaultLandingPageOptions = this.menuService.buildCommonLinks(this.serverConfig) - .links - .map(o => ({ - id: o.path, - label: o.label, - description: o.path - })) + let links: { label: string, path: string }[] = [] + + if (this.serverConfig.homepage.enabled) { + links.push({ label: $localize`Home`, path: '/home' }) + } + + links = links.concat([ + { label: $localize`Discover`, path: '/videos/overview' }, + { label: $localize`Trending`, path: '/videos/trending' }, + { label: $localize`Recently added`, path: '/videos/recently-added' }, + { label: $localize`Local videos`, path: '/videos/local' } + ]) + + this.defaultLandingPageOptions = links.map(o => ({ + id: o.path, + label: o.label, + description: o.path + })) } getDefaultThemeLabel () { diff --git a/client/src/app/+error-page/routes.ts b/client/src/app/+error-page/routes.ts index 07bfc34b3..7f7a6969e 100644 --- a/client/src/app/+error-page/routes.ts +++ b/client/src/app/+error-page/routes.ts @@ -1,13 +1,10 @@ import { Routes } from '@angular/router' import { ErrorPageComponent } from './error-page.component' -import { MenuGuards } from '@app/core' export default [ { path: '', component: ErrorPageComponent, - canActivate: [ MenuGuards.close(true) ], - canDeactivate: [ MenuGuards.open(true) ], data: { meta: { title: $localize`Not found` diff --git a/client/src/app/+my-library/my-library.component.ts b/client/src/app/+my-library/my-library.component.ts index a11182d94..a9150f712 100644 --- a/client/src/app/+my-library/my-library.component.ts +++ b/client/src/app/+my-library/my-library.component.ts @@ -45,15 +45,13 @@ export class MyLibraryComponent implements OnInit { { label: $localize`Channels`, routerLink: '/my-library/video-channels' - } - ] + }, - if (this.user.canSeeVideosLink) { - this.menuEntries.push({ + { label: $localize`Videos`, routerLink: '/my-library/videos' - }) - } + } + ] this.menuEntries = this.menuEntries.concat([ { diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html index 3869f048f..0ec9ddb76 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.html +++ b/client/src/app/+videos/+video-edit/video-add.component.html @@ -6,7 +6,7 @@ } - +
{{ uploadMessages?.noQuota }}
@@ -17,7 +17,7 @@
- +
{{ uploadMessages?.autoBlock }}
@@ -42,7 +42,7 @@
-
+
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index ff972d05a..428d9b37f 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -8,14 +8,14 @@ class="peertube-container" [ngClass]="{ 'user-logged-in': isUserLoggedIn(), 'user-not-logged-in': !isUserLoggedIn(), 'hotkeys-modal-opened': hotkeysModalOpened }" > -
+
- + -
+
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 7fbe0208a..eaac85592 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss @@ -36,6 +36,7 @@ position: fixed; top: 0; width: 100%; + background: pvar(--mainBackgroundColor); } .broadcast-message { diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 6fede6e00..be9d41b76 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -196,7 +196,7 @@ export class AppComponent implements OnInit, AfterViewInit { eventsObs.pipe( filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), filter(() => this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) - ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page + ).subscribe(() => this.menu.setMenuCollapsed(true)) // User clicked on a link in the menu, change the page // Handle lazy loaded module eventsObs.pipe( diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index 90d67e377..b82fac828 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts @@ -1,5 +1,4 @@ import { Routes, UrlMatchResult, UrlSegment } from '@angular/router' -import { MenuGuards } from '@app/core/routing/menu-guard.service' import { POSSIBLE_LOCALES } from '@peertube/peertube-core-utils' import { MetaGuard } from './core' import { EmptyComponent } from './empty.component' @@ -10,8 +9,6 @@ import { ActorRedirectGuard } from './shared/shared-main/router/actor-redirect-g const routes: Routes = [ { path: 'admin', - canActivate: [ MenuGuards.close() ], - canDeactivate: [ MenuGuards.open() ], loadChildren: () => import('./+admin/routes'), canActivateChild: [ MetaGuard ] }, diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index a1e23bfbb..690d818a2 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts @@ -1,14 +1,11 @@ -import { Observable, of } from 'rxjs' -import { map } from 'rxjs/operators' import { User } from '@app/core/users/user.model' import { hasUserRight } from '@peertube/peertube-core-utils' import { - MyUser as ServerMyUserModel, MyUserSpecialPlaylist, + MyUser as ServerMyUserModel, User as ServerUserModel, UserRightType, - UserRole, - UserVideoQuota + UserRole } from '@peertube/peertube-models' import { OAuthUserTokens } from '@root-helpers/users' @@ -16,8 +13,6 @@ export class AuthUser extends User implements ServerMyUserModel { oauthTokens: OAuthUserTokens specialPlaylists: MyUserSpecialPlaylist[] - canSeeVideosLink = true - constructor (userHash: Partial, hashTokens: Partial) { super(userHash) @@ -54,26 +49,4 @@ export class AuthUser extends User implements ServerMyUserModel { // I'm a moderator: I can only manage users return user.role.id === UserRole.USER } - - computeCanSeeVideosLink (quotaObservable: Observable): Observable { - if (!this.isUploadDisabled()) { - this.canSeeVideosLink = true - return of(this.canSeeVideosLink) - } - - // Check if the user has videos - return quotaObservable.pipe( - map(({ videoQuotaUsed }) => { - if (videoQuotaUsed !== 0) { - // User already uploaded videos, so it can see the link - this.canSeeVideosLink = true - } else { - // No videos, no upload so the user don't need to see the videos link - this.canSeeVideosLink = false - } - - return this.canSeeVideosLink - }) - ) - } } diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index 51d4bc578..28afa7e3f 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts @@ -3,7 +3,7 @@ import { debounceTime } from 'rxjs/operators' import { Injectable } from '@angular/core' import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component' import { HTMLServerConfig } from '@peertube/peertube-models' -import { ScreenService } from '../wrappers' +import { LocalStorageService, ScreenService } from '../wrappers' export type MenuLink = { icon: GlobalIconName @@ -14,6 +14,8 @@ export type MenuLink = { shortLabel: string path: string + + isPrimaryButton?: boolean // default false } export type MenuSection = { @@ -24,39 +26,45 @@ export type MenuSection = { @Injectable() export class MenuService { - isMenuDisplayed = true + private static LS_MENU_COLLAPSED = 'menu-collapsed' + + isMenuCollapsed = false isMenuChangedByUser = false - menuWidth = 240 // should be kept equal to $menu-width constructor ( - private screenService: ScreenService + private screenService: ScreenService, + private localStorageService: LocalStorageService ) { // Do not display menu on small or touch screens if (this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) { - this.setMenuDisplay(false) + this.setMenuCollapsed(true) } this.handleWindowResize() + + this.isMenuCollapsed = this.localStorageService.getItem(MenuService.LS_MENU_COLLAPSED) === 'true' } toggleMenu () { - this.setMenuDisplay(!this.isMenuDisplayed) + this.setMenuCollapsed(!this.isMenuCollapsed) this.isMenuChangedByUser = true + + this.localStorageService.setItem(MenuService.LS_MENU_COLLAPSED, this.isMenuCollapsed + '') } - isDisplayed () { - return this.isMenuDisplayed + isCollapsed () { + return this.isMenuCollapsed } - setMenuDisplay (display: boolean) { - this.isMenuDisplayed = display + setMenuCollapsed (collapsed: boolean) { + this.isMenuCollapsed = collapsed if (!this.screenService.isInTouchScreen()) return // On touch screens, lock body scroll and display content overlay when memu is opened - if (this.isMenuDisplayed) { + if (!this.isMenuCollapsed) { document.body.classList.add('menu-open') - this.screenService.onFingerSwipe('left', () => this.setMenuDisplay(false)) + this.screenService.onFingerSwipe('left', () => this.setMenuCollapsed(true)) return } @@ -64,102 +72,10 @@ export class MenuService { } onResize () { - this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser + this.isMenuCollapsed = window.innerWidth < 800 && !this.isMenuChangedByUser } - buildLibraryLinks (userCanSeeVideosLink: boolean): MenuSection { - let links: MenuLink[] = [] - - if (userCanSeeVideosLink) { - links.push({ - path: '/my-library/video-channels', - icon: 'channel' as GlobalIconName, - iconClass: 'channel-icon', - shortLabel: $localize`Channels`, - label: $localize`My channels` - }) - - links.push({ - path: '/my-library/videos', - icon: 'videos' as GlobalIconName, - shortLabel: $localize`Videos`, - label: $localize`My videos` - }) - } - - links = links.concat([ - { - path: '/my-library/video-playlists', - icon: 'playlists' as GlobalIconName, - shortLabel: $localize`Playlists`, - label: $localize`My playlists` - }, - { - path: '/videos/subscriptions', - icon: 'subscriptions' as GlobalIconName, - shortLabel: $localize`Subscriptions`, - label: $localize`My subscriptions` - }, - { - path: '/my-library/history/videos', - icon: 'history' as GlobalIconName, - shortLabel: $localize`History`, - label: $localize`My history` - } - ]) - - return { - key: 'in-my-library', - title: $localize`In my library`, - links - } - } - - buildCommonLinks (config: HTMLServerConfig): MenuSection { - let links: MenuLink[] = [] - - if (config.homepage.enabled) { - links.push({ - icon: 'home' as 'home', - label: $localize`Home`, - shortLabel: $localize`Home`, - path: '/home' - }) - } - - links = links.concat([ - { - icon: 'globe' as 'globe', - label: $localize`Discover videos`, - shortLabel: $localize`Discover`, - path: '/videos/overview' - }, - { - icon: 'trending' as 'trending', - label: $localize`Trending videos`, - shortLabel: $localize`Trending`, - path: '/videos/trending' - }, - { - icon: 'add' as 'add', - label: $localize`Recently added videos`, - shortLabel: $localize`Recently added`, - path: '/videos/recently-added' - }, - { - icon: 'local' as 'local', - label: $localize`Local videos`, - shortLabel: $localize`Local videos`, - path: '/videos/local' - } - ]) - - return { - key: 'on-instance', - title: $localize`ON ${config.instance.name}`, - links - } - } + // --------------------------------------------------------------------------- private handleWindowResize () { // On touch screens, do not handle window resize event since opened menu is handled with a content overlay diff --git a/client/src/app/core/routing/index.ts b/client/src/app/core/routing/index.ts index 7443d5c78..bb5bda577 100644 --- a/client/src/app/core/routing/index.ts +++ b/client/src/app/core/routing/index.ts @@ -2,7 +2,6 @@ export * from './can-deactivate-guard.service' export * from './custom-reuse-strategy' export * from './disable-for-reuse-hook' export * from './login-guard.service' -export * from './menu-guard.service' export * from './meta-guard.service' export * from './peertube-router.service' export * from './meta.service' diff --git a/client/src/app/core/routing/menu-guard.service.ts b/client/src/app/core/routing/menu-guard.service.ts deleted file mode 100644 index 3b3723b5a..000000000 --- a/client/src/app/core/routing/menu-guard.service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Injectable } from '@angular/core' -import { MenuService } from '../menu' -import { ScreenService } from '../wrappers' - -abstract class MenuGuard { - canDeactivate = this.canActivate.bind(this) - - constructor (protected menu: MenuService, protected screen: ScreenService, protected display: boolean) { - - } - - canActivate (): boolean { - // small screens already have the site-wide onResize from screenService - // > medium screens have enough space to fit the administrative menus - if (!this.screen.isInMobileView() && this.screen.isInMediumView()) { - this.menu.setMenuDisplay(this.display) - } - - return true - } -} - -@Injectable() -export class OpenMenuGuard extends MenuGuard { - constructor (menu: MenuService, screen: ScreenService) { - super(menu, screen, true) - } -} - -@Injectable() -export class OpenMenuAlwaysGuard extends MenuGuard { - constructor (menu: MenuService, screen: ScreenService) { - super(menu, screen, true) - } - - canActivate (): boolean { - this.menu.setMenuDisplay(this.display) - - return true - } -} - -@Injectable() -export class CloseMenuGuard extends MenuGuard { - constructor (menu: MenuService, screen: ScreenService) { - super(menu, screen, false) - } -} - -@Injectable() -export class CloseMenuAlwaysGuard extends MenuGuard { - constructor (menu: MenuService, screen: ScreenService) { - super(menu, screen, false) - } - - canActivate (): boolean { - this.menu.setMenuDisplay(this.display) - return true - } -} - -@Injectable() -export class MenuGuards { - public static guards = [ - OpenMenuGuard, - OpenMenuAlwaysGuard, - CloseMenuGuard, - CloseMenuAlwaysGuard - ] - - static open (always?: boolean) { - return always - ? OpenMenuAlwaysGuard - : OpenMenuGuard - } - - static close (always?: boolean) { - return always - ? CloseMenuAlwaysGuard - : CloseMenuGuard - } -} diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts index f075ece2b..f98e05106 100644 --- a/client/src/app/core/users/user.model.ts +++ b/client/src/app/core/users/user.model.ts @@ -102,7 +102,7 @@ export class User implements UserServerModel { else this.account.resetAvatar() } - isUploadDisabled () { + hasUploadDisabled () { return this.videoQuota === 0 || this.videoQuotaDaily === 0 } diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html index b5af6dbee..68efdf09c 100644 --- a/client/src/app/header/header.component.html +++ b/client/src/app/header/header.component.html @@ -8,7 +8,7 @@ @if (!isLoggedIn) { - + + +
+
+ Platform powered by PeerTube +
+ + Discover more platforms +
+
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 623fe1967..55795b9e9 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss @@ -2,10 +2,6 @@ @use '_variables' as *; @use '_mixins' as *; -$menu-link-icon-size: 22px; -$menu-link-icon-margin-right: 18px; -$footer-links-base-opacity: .8; - ul { margin: 0; padding: 0; @@ -15,11 +11,119 @@ ul { } } -.footer-bottom { - li { +.menu-wrapper { + --menuXPadding: 1.5rem; + + position: fixed; + height: calc(100vh - #{$header-height}); + + z-index: z(menu); + scrollbar-color: pvar(--actionButtonColor) pvar(--menuBackgroundColor); + + width: calc(#{$menu-width} - 2rem); + + @include margin-left(2rem); + + &.collapsed { + --menuXPadding: 0.25rem; + width: auto; - display: inline-flex; + + @include margin-left(0); } + + @media screen and (max-width: $mobile-view) { + width: 100% !important; + + .main-menu { + overflow-y: auto !important; + } + } +} + +.toggle-menu { + color: pvar(--menuForegroundColor); + width: 24px; + height: 24px; + border-radius: 100%; + background-color: #3C2E2E; + color: #C1B0B0; + + @include button-with-icon(20px, 0, -1px, 1px); +} + +.menu-wrapper:not(.collapsed) .toggle-menu { + position: absolute; + top: 20px; + + @include right(24px); +} + +.collapsed .toggle-menu my-global-icon { + right: -1px; + top: 0; +} + +.main-menu { + position: relative; + padding-top: 1.5rem; + padding-bottom: 1.5rem; + border-radius: 14px; + background-color: pvar(--menuBackgroundColor); + color: pvar(--menuForegroundColor); + overflow-y: auto; + scrollbar-color: transparent transparent; + + max-height: calc(100% - 50px); // Space for links below the menu + + &:focus, + &:hover { + scrollbar-color: auto; + } + + @media not all and (hover: hover) and (pointer: fine) { + scrollbar-color: auto; + } +} + +.collapsed .main-menu { + max-height: calc(100% - 10px); + + border-start-start-radius: 0; + border-end-start-radius: 0; + + padding-top: 1rem; + padding-bottom: 1rem; +} + +.menu-link, +.menu-button, +.block-title, +.about .description, +.about my-button { + @include padding-left(var(--menuXPadding)); + @include padding-right(var(--menuXPadding)); +} + +.menu-block, +.collapsed .toggle-menu-container { + &::after { + content: ''; + display: block; + height: 2px; + background: #3C2E2E; + margin: 1rem var(--menuXPadding); + } +} + +.collapsed .toggle-menu-container { + text-align: center; +} + +.block-title { + font-weight: $font-bold; + font-size: 14px; + margin-bottom: 0.5rem; } .menu-link { @@ -27,16 +131,14 @@ ul { align-items: center; color: pvar(--menuForegroundColor); - cursor: pointer; white-space: normal; word-break: break-word; transition: background-color .1s ease-in-out; - line-height: $line-height-normal; width: 100%; + padding-top: 0.5rem; + padding-bottom: 0.5rem; @include disable-default-a-behaviour; - @include padding-left($menu-lateral-padding); - @include padding-right(20px); &.active { background-color: rgba(255, 255, 255, 0.15); @@ -48,118 +150,28 @@ ul { } my-global-icon { - display: flex; - width: $menu-link-icon-size; - height: $menu-link-icon-size; + width: 22px; + height: 22px; + position: relative; + top: -1px; - @include apply-svg-color(#808080); - @include margin-right($menu-link-icon-margin-right); - } -} - -.menu-wrapper { - position: fixed; - height: calc(100vh - #{$header-height}); - padding: 0; - width: $menu-width; - z-index: z(menu); - scrollbar-color: pvar(--actionButtonColor) pvar(--menuBackgroundColor); -} - -nav { - background-color: pvar(--menuBackgroundColor); - color: pvar(--menuForegroundColor); - - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - margin: 0; - padding: 0; - - @include ellipsis; - - &:focus, - &:hover { - overflow-y: auto; - } - - @media not all and (hover: hover) and (pointer: fine) { - overflow-y: auto; - } -} - -.menu-block, -.footer-block { - margin-bottom: 15px; - - .block-container { - @include margin-left(26px); - @include margin-right(15px); - @include ellipsis; - } - - .block-title { - text-transform: uppercase; - font-weight: $font-bold; - font-size: 13px; - } - - a, - button { - min-height: 40px; - } -} - -.footer { - padding-bottom: 15px; -} - -.footer-bottom { - padding: 0 $menu-lateral-padding; -} - -.footer-links { - a, - button { - color: pvar(--menuForegroundColor); - opacity: $footer-links-base-opacity; - white-space: nowrap; - font-size: 0.75rem; - line-height: 1.4rem; - font-weight: $font-semibold; - - @include margin-right(8px); - @include disable-default-a-behaviour; - - &:hover { - opacity: $footer-links-base-opacity + .2; + + *:not(.visually-hidden) { + @include margin-left(12px); } } } -.footer-copyleft a { - color: pvar(--menuForegroundColor); - opacity: $footer-links-base-opacity - .2; - font-size: 0.85rem; - - @include disable-default-a-behaviour; - - &:hover { - opacity: $footer-links-base-opacity; - } +.menu-wrapper.collapsed .menu-link { + justify-content: center; } -@media screen and (max-width: $mobile-view) { - .menu-wrapper { - width: 100% !important; - nav { - overflow-y: auto; - } - } +.menu-button { + margin-top: 0.5rem; +} - .footer { - width: 100% !important; +.about { + .description { + font-size: 14px; } } diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 6a436782e..2cc5f4eb8 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts @@ -1,31 +1,28 @@ -import { CommonModule, ViewportScroller } from '@angular/common' -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' -import { Router, RouterLink, RouterLinkActive } from '@angular/router' +import { CommonModule } from '@angular/common' +import { Component, OnDestroy, OnInit } from '@angular/core' +import { RouterLink, RouterLinkActive } from '@angular/router' import { AuthService, AuthStatus, AuthUser, HooksService, - HotkeysService, + MenuLink, MenuSection, MenuService, - RedirectService, - ScreenService, ServerService, UserService } from '@app/core' -import { scrollToTop } from '@app/helpers' import { ActorAvatarComponent } from '@app/shared/shared-actor-image/actor-avatar.component' import { InputSwitchComponent } from '@app/shared/shared-forms/input-switch.component' -import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component' -import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service' +import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component' +import { ButtonComponent } from '@app/shared/shared-main/buttons/button.component' import { LoginLinkComponent } from '@app/shared/shared-main/users/login-link.component' import { SignupLabelComponent } from '@app/shared/shared-main/users/signup-label.component' -import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' -import { HTMLServerConfig, ServerConfig, UserRight, UserRightType, VideoConstant } from '@peertube/peertube-models' +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' +import { ServerConfig, UserRight } from '@peertube/peertube-models' import debug from 'debug' -import { forkJoin, Subscription } from 'rxjs' -import { first, switchMap } from 'rxjs/operators' +import { of, Subscription } from 'rxjs' +import { first, map, switchMap } from 'rxjs/operators' import { LanguageChooserComponent } from './language-chooser.component' import { NotificationDropdownComponent } from './notification-dropdown.component' import { QuickSettingsModalComponent } from './quick-settings-modal.component' @@ -49,308 +46,218 @@ const debugLogger = debug('peertube:menu:MenuComponent') GlobalIconComponent, RouterLink, RouterLinkActive, - NgbDropdownModule + NgbDropdownModule, + ButtonComponent ] }) export class MenuComponent implements OnInit, OnDestroy { - @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent - @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent - @ViewChild('dropdown') dropdown: NgbDropdown - - user: AuthUser - isLoggedIn: boolean - - userHasAdminAccess = false - helpVisible = false - - videoLanguages: string[] = [] - nsfwPolicy: string - - currentInterfaceLanguage: string - menuSections: MenuSection[] = [] - private languages: VideoConstant[] = [] + private isLoggedIn: boolean + private user: AuthUser + private canSeeVideoMakerBlock: boolean - private htmlServerConfig: HTMLServerConfig private serverConfig: ServerConfig - private routesPerRight: { [role in UserRightType]?: string } = { - [UserRight.MANAGE_USERS]: '/admin/users', - [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', - [UserRight.MANAGE_ABUSES]: '/admin/moderation/abuses', - [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/moderation/video-blocks', - [UserRight.MANAGE_JOBS]: '/admin/jobs', - [UserRight.MANAGE_CONFIGURATION]: '/admin/config' - } - - private languagesSub: Subscription - private modalSub: Subscription - private hotkeysSub: Subscription private authSub: Subscription constructor ( - private viewportScroller: ViewportScroller, private authService: AuthService, private userService: UserService, private serverService: ServerService, - private redirectService: RedirectService, - private hotkeysService: HotkeysService, - private screenService: ScreenService, - private menuService: MenuService, - private modalService: PeertubeModalService, - private router: Router, - private hooks: HooksService + private hooks: HooksService, + private menu: MenuService ) { } - get isInMobileView () { - return this.screenService.isInMobileView() + get shortDescription () { + return this.serverConfig.instance.shortDescription } - get language () { - return this.languageChooserModal.getCurrentLanguage() - } - - get requiresApproval () { - return this.serverConfig.signup.requiresApproval + get collapsed () { + return this.menu.isCollapsed() } ngOnInit () { - this.htmlServerConfig = this.serverService.getHTMLConfig() - this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage() - this.isLoggedIn = this.authService.isLoggedIn() - this.updateUserState() - this.buildMenuSections() + this.onUserStateChange() this.authSub = this.authService.loginChangedSource.subscribe(status => { - if (status === AuthStatus.LoggedIn) { - this.isLoggedIn = true - } else if (status === AuthStatus.LoggedOut) { - this.isLoggedIn = false - } + if (status === AuthStatus.LoggedIn) this.isLoggedIn = true + else if (status === AuthStatus.LoggedOut) this.isLoggedIn = false - this.updateUserState() - this.buildMenuSections() - }) - - this.hotkeysSub = this.hotkeysService.cheatSheetToggle - .subscribe(isOpen => this.helpVisible = isOpen) - - this.languagesSub = forkJoin([ - this.serverService.getVideoLanguages(), - this.authService.userInformationLoaded.pipe(first()) - ]).subscribe(([ languages ]) => { - this.languages = languages - - this.buildUserLanguages() + this.onUserStateChange() }) this.serverService.getConfig() .subscribe(config => this.serverConfig = config) - - this.modalSub = this.modalService.openQuickSettingsSubject - .subscribe(() => this.openQuickSettings()) } ngOnDestroy () { - if (this.modalSub) this.modalSub.unsubscribe() - if (this.languagesSub) this.languagesSub.unsubscribe() - if (this.hotkeysSub) this.hotkeysSub.unsubscribe() if (this.authSub) this.authSub.unsubscribe() } - isRegistrationAllowed () { - if (!this.serverConfig) return false + // --------------------------------------------------------------------------- - return this.serverConfig.signup.allowed && - this.serverConfig.signup.allowedForCurrentIP + toggleMenu () { + this.menu.toggleMenu() } - getFirstAdminRightAvailable () { - const user = this.authService.getUser() - if (!user) return undefined + // --------------------------------------------------------------------------- - const adminRights = [ - UserRight.MANAGE_USERS, - UserRight.MANAGE_SERVER_FOLLOW, - UserRight.MANAGE_ABUSES, - UserRight.MANAGE_VIDEO_BLACKLIST, - UserRight.MANAGE_JOBS, - UserRight.MANAGE_CONFIGURATION - ] + private async buildMenuSections () { + this.menuSections = [] - for (const adminRight of adminRights) { - if (user.hasRight(adminRight)) { - return adminRight + for (const section of [ this.buildLibraryLinks(), this.buildVideoMakerLinks(), this.buildAdminLinks() ]) { + if (section.links.length !== 0) { + this.menuSections.push(section) } } - return undefined + this.menuSections = await this.hooks.wrapObject(this.menuSections, 'common', 'filter:left-menu.links.create.result') } - getFirstAdminRouteAvailable () { - const right = this.getFirstAdminRightAvailable() - - return this.routesPerRight[right] - } - - logout (event: Event) { - event.preventDefault() - - this.authService.logout() - // Redirect to home page - this.redirectService.redirectToHomepage() - } - - openLanguageChooser () { - this.languageChooserModal.show() - } - - openHotkeysCheatSheet () { - this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) - } - - openQuickSettings () { - this.quickSettingsModal.show() - } - - toggleUseP2P () { - if (!this.user) return - this.user.p2pEnabled = !this.user.p2pEnabled - - this.userService.updateMyProfile({ p2pEnabled: this.user.p2pEnabled }) - .subscribe(() => this.authService.refreshUserInformation()) - } - - langForLocale (localeId: string) { - if (localeId === '_unknown') return $localize`Unknown` - - return this.languages.find(lang => lang.id === localeId).label - } - - onActiveLinkScrollToAnchor (link: HTMLAnchorElement) { - const linkURL = link.getAttribute('href') - const linkHash = link.getAttribute('fragment') - - // On same url without fragment restore top scroll position - if (!linkHash && this.router.url.includes(linkURL)) { - scrollToTop('smooth') - } - - // On same url with fragment restore anchor scroll position - if (linkHash && this.router.url === linkURL) { - this.viewportScroller.scrollToAnchor(linkHash) - } - - if (this.screenService.isInSmallView()) { - this.menuService.toggleMenu() - } - } - - // Lock menu scroll when menu scroll to avoid fleeing / detached dropdown - onMenuScrollEvent () { - document.querySelector('nav').scrollTo(0, 0) - } - - onDropdownOpenChange (opened: boolean) { - if (this.screenService.isInMobileView()) return - - // Close dropdown when window scroll to avoid dropdown quick jump for re-position - const onWindowScroll = () => { - this.dropdown?.close() - window.removeEventListener('scroll', onWindowScroll) - } - - if (opened) { - window.addEventListener('scroll', onWindowScroll) - document.querySelector('nav').scrollTo(0, 0) // Reset menu scroll to easy lock - // eslint-disable-next-line @typescript-eslint/unbound-method - document.querySelector('nav').addEventListener('scroll', this.onMenuScrollEvent) - } else { - // eslint-disable-next-line @typescript-eslint/unbound-method - document.querySelector('nav').removeEventListener('scroll', this.onMenuScrollEvent) - } - } - - private async buildMenuSections () { - const menuSections = [] + private buildLibraryLinks (): MenuSection { + let links: MenuLink[] = [] if (this.isLoggedIn) { - menuSections.push( - this.menuService.buildLibraryLinks(this.user?.canSeeVideosLink) - ) + links = links.concat([ + { + path: '/my-library/video-playlists', + icon: 'playlists' as GlobalIconName, + shortLabel: $localize`Playlists`, + label: $localize`My Playlists` + }, + { + path: '/videos/subscriptions', + icon: 'subscriptions' as GlobalIconName, + shortLabel: $localize`Subscriptions`, + label: $localize`My Subscriptions` + }, + { + path: '/my-library/history/videos', + icon: 'history' as GlobalIconName, + shortLabel: $localize`History`, + label: $localize`My History` + } + ]) } - menuSections.push( - this.menuService.buildCommonLinks(this.htmlServerConfig) - ) - - this.menuSections = await this.hooks.wrapObject(menuSections, 'common', 'filter:left-menu.links.create.result') + return { + key: 'my-library', + title: $localize`My library`, + links + } } - private buildUserLanguages () { - if (!this.user) { - this.videoLanguages = [] - return + private buildVideoMakerLinks (): MenuSection { + let links: MenuLink[] = [] + + if (this.isLoggedIn && this.canSeeVideoMakerBlock) { + links = links.concat([ + { + path: '/my-library/video-channels', + icon: 'channel' as GlobalIconName, + iconClass: 'channel-icon', + shortLabel: $localize`Channels`, + label: $localize`My channels` + }, + + { + path: '/my-library/videos', + icon: 'videos' as GlobalIconName, + shortLabel: $localize`Videos`, + label: $localize`My videos` + }, + + { + path: '/videos/upload', + icon: 'upload' as GlobalIconName, + shortLabel: $localize`Publish`, + label: $localize`Publish`, + isPrimaryButton: true + } + ]) } - if (!this.user.videoLanguages) { - this.videoLanguages = [ $localize`any language` ] - return + return { + key: 'my-video-space', + title: $localize`My video space`, + links + } + } + + private buildAdminLinks (): MenuSection { + const links: MenuLink[] = [] + + if (this.isLoggedIn) { + if (this.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { + links.push({ + path: '/admin/videos/list', + icon: 'overview' as GlobalIconName, + shortLabel: $localize`Overview`, + label: $localize`Overview` + }) + } + + if (this.user.hasRight(UserRight.MANAGE_ABUSES)) { + links.push({ + path: '/admin/moderation/abuses/list', + icon: 'moderation' as GlobalIconName, + shortLabel: $localize`Moderation`, + label: $localize`Moderation` + }) + } + + if (this.user.hasRight(UserRight.MANAGE_CONFIGURATION)) { + links.push({ + path: '/admin/config/edit-custom', + icon: 'config' as GlobalIconName, + shortLabel: $localize`Advanced parameters`, + label: $localize`Advanced parameters` + }) + } } - this.videoLanguages = this.user.videoLanguages - .map(locale => this.langForLocale(locale)) - .map(value => value === undefined ? '?' : value) + return { + key: 'admin', + title: $localize`Administration`, + links + } } - private computeAdminAccess () { - const right = this.getFirstAdminRightAvailable() + // --------------------------------------------------------------------------- - this.userHasAdminAccess = right !== undefined - } + private computeCanSeeVideoMakerBlock () { + if (!this.isLoggedIn) return of(false) + if (!this.user.hasUploadDisabled()) return of(true) - private computeVideosLink () { - if (!this.isLoggedIn) return - - this.authService.userInformationLoaded + return this.authService.userInformationLoaded .pipe( - switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed())) - ).subscribe(res => { - if (res === true) debugLogger('User can see videos link.') - else debugLogger('User cannot see videos link.') - }) + first(), + switchMap(() => this.userService.getMyVideoQuotaUsed()), + map(({ videoQuotaUsed }) => { + // User already uploaded videos, so it can see the link + if (videoQuotaUsed !== 0) return true + + // No videos, no upload so the user don't need to see the videos link + return false + }) + ) } - private computeNSFWPolicy () { - if (!this.user) { - this.nsfwPolicy = null - return - } - - switch (this.user.nsfwPolicy) { - case 'do_not_list': - this.nsfwPolicy = $localize`hide` - break - - case 'blur': - this.nsfwPolicy = $localize`blur` - break - - case 'display': - this.nsfwPolicy = $localize`display` - break - } - } - - private updateUserState () { + private onUserStateChange () { this.user = this.isLoggedIn ? this.authService.getUser() : undefined - this.computeAdminAccess() - this.computeNSFWPolicy() - this.computeVideosLink() + this.computeCanSeeVideoMakerBlock() + .subscribe(res => { + this.canSeeVideoMakerBlock = res + + if (this.canSeeVideoMakerBlock) debugLogger('User can see videos link.') + else debugLogger('User cannot see videos link.') + + this.buildMenuSections() + }) } } 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 b9fa242f9..13d01b083 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts @@ -10,23 +10,26 @@ const icons = { 'support': require('../../../assets/images/misc/support.svg'), 'peertube-x': require('../../../assets/images/misc/peertube-x.svg'), 'robot': require('../../../assets/images/misc/miscellaneous-services.svg'), // material ui - 'videos': require('../../../assets/images/misc/video-library.svg'), // material ui - 'history': require('../../../assets/images/misc/history.svg'), // material ui - 'subscriptions': require('../../../assets/images/misc/subscriptions.svg'), // material ui 'playlist-add': require('../../../assets/images/misc/playlist-add.svg'), // material ui 'follower': require('../../../assets/images/misc/account-arrow-left.svg'), // material ui 'following': require('../../../assets/images/misc/account-arrow-right.svg'), // material ui 'tip': require('../../../assets/images/misc/tip.svg'), // material ui 'flame': require('../../../assets/images/misc/flame.svg'), - 'local': require('../../../assets/images/misc/local.svg'), // feather/lucide icons + 'history': require('../../../assets/images/feather/history.svg'), + 'subscriptions': require('../../../assets/images/feather/subscriptions.svg'), + 'videos': require('../../../assets/images/feather/videos.svg'), 'add': require('../../../assets/images/feather/plus-circle.svg'), 'alert': require('../../../assets/images/feather/alert.svg'), + 'overview': require('../../../assets/images/feather/overview.svg'), + 'moderation': require('../../../assets/images/feather/moderation.svg'), + 'config': require('../../../assets/images/feather/config.svg'), 'award': require('../../../assets/images/feather/award.svg'), 'bell': require('../../../assets/images/feather/bell.svg'), - 'channel': require('../../../assets/images/feather/tv.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'), 'circle-tick': require('../../../assets/images/feather/check-circle.svg'), 'clock-arrow-down': require('../../../assets/images/feather/clock-arrow-down.svg'), 'clock': require('../../../assets/images/feather/clock.svg'), @@ -64,7 +67,7 @@ const icons = { 'ownership-change': require('../../../assets/images/feather/share.svg'), 'p2p': require('../../../assets/images/feather/airplay.svg'), 'play': require('../../../assets/images/feather/play.svg'), - 'playlists': require('../../../assets/images/feather/list.svg'), + 'playlists': require('../../../assets/images/feather/playlists.svg'), 'refresh': require('../../../assets/images/feather/refresh-cw.svg'), 'repeat': require('../../../assets/images/feather/repeat.svg'), 'search': require('../../../assets/images/feather/search.svg'), @@ -94,7 +97,7 @@ export type GlobalIconName = keyof typeof icons standalone: true }) export class GlobalIconComponent implements OnInit { - @Input() iconName: GlobalIconName + @Input({ required: true }) iconName: GlobalIconName constructor ( private el: ElementRef, 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 b3f8375c8..067c33f64 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.html +++ b/client/src/app/shared/shared-main/buttons/button.component.html @@ -1,16 +1,22 @@ - - - - - +@if (ptRouterLink) { + + + +} @else { + +} - {{ label }} - - + + @if (label) { + {{ label }} + } @else { + + } + diff --git a/client/src/app/shared/shared-main/buttons/button.component.scss b/client/src/app/shared/shared-main/buttons/button.component.scss index 8ed7aa31d..0646f0882 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.scss +++ b/client/src/app/shared/shared-main/buttons/button.component.scss @@ -1,31 +1,10 @@ @use '_variables' as *; @use '_mixins' as *; -@mixin responsive-label { - .action-button { - padding: 0 13px; - } - - .button-label { - display: none; - } - - my-global-icon { - margin: 0 !important; - } -} - :host { display: inline-block; } -a[class$=-button], -span[class$=-button] { - > span { - @include margin-left(5px); - } -} - .action-button { width: 100%; // useful for ellipsis, allow to define a max-width on host component @@ -38,16 +17,22 @@ span[class$=-button] { } &.has-icon { - @include button-with-icon(21px); + @include button-with-icon(21px, 0); } - &.icon-only my-global-icon { - margin: 0 !important; + &.icon-only { + padding: 6px 8px !important; } } -.button-label { - @include ellipsis; +@mixin responsive-label { + .action-button { + padding: 6px 8px !important; + } + + .button-label { + display: none; + } } // In a table, try to minimize the space taken by this button 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 3a437e33f..82f8af56f 100644 --- a/client/src/app/shared/shared-main/buttons/button.component.ts +++ b/client/src/app/shared/shared-main/buttons/button.component.ts @@ -1,5 +1,5 @@ import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common' -import { ChangeDetectionStrategy, Component, Input, OnChanges, booleanAttribute } from '@angular/core' +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, ViewChild, booleanAttribute } from '@angular/core' import { RouterLink } from '@angular/router' import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component' import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap' @@ -15,9 +15,9 @@ import { LoaderComponent } from '../common/loader.component' imports: [ NgIf, NgClass, NgbTooltip, NgTemplateOutlet, RouterLink, LoaderComponent, GlobalIconComponent ] }) -export class ButtonComponent implements OnChanges { +export class ButtonComponent implements OnChanges, AfterViewInit { @Input() label = '' - @Input() theme: 'orange' | 'grey' = 'grey' + @Input() theme: 'orange' | 'grey' | 'primary' | 'secondary' = 'grey' @Input() icon: GlobalIconName @Input() ptRouterLink: string[] | string @Input() title: string @@ -25,22 +25,28 @@ export class ButtonComponent implements OnChanges { @Input({ transform: booleanAttribute }) disabled = false @Input({ transform: booleanAttribute }) responsiveLabel = false + @ViewChild('labelContent') labelContent: ElementRef + classes: { [id: string]: boolean } = {} ngOnChanges () { this.buildClasses() } - private buildClasses () { - console.log('build classes') + ngAfterViewInit () { + this.buildClasses() + } + private buildClasses () { this.classes = { 'peertube-button': !this.ptRouterLink, 'peertube-button-link': !!this.ptRouterLink, 'orange-button': this.theme === 'orange', 'grey-button': this.theme === 'grey', - 'icon-only': !this.label, + 'primary-button': this.theme === 'primary', + 'secondary-button': this.theme === 'secondary', 'has-icon': !!this.icon, + 'icon-only': !(this.labelContent?.nativeElement as HTMLElement)?.innerText, 'responsive-label': this.responsiveLabel } } diff --git a/client/src/app/shared/shared-main/menu/top-menu-dropdown.component.ts b/client/src/app/shared/shared-main/menu/top-menu-dropdown.component.ts index 6370daa7c..28f9dade0 100644 --- a/client/src/app/shared/shared-main/menu/top-menu-dropdown.component.ts +++ b/client/src/app/shared/shared-main/menu/top-menu-dropdown.component.ts @@ -1,13 +1,13 @@ -import { Subscription } from 'rxjs' -import { filter } from 'rxjs/operators' +import { NgClass, NgFor, NgIf } from '@angular/common' import { Component, Input, OnChanges, OnDestroy, OnInit, ViewChild } from '@angular/core' -import { NavigationEnd, Router, RouterLinkActive, RouterLink } from '@angular/router' -import { MenuService, ScreenService } from '@app/core' +import { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router' +import { ScreenService } from '@app/core' import { scrollToTop } from '@app/helpers' import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component' -import { NgbDropdown, NgbModal, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem } from '@ng-bootstrap/ng-bootstrap' +import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { Subscription } from 'rxjs' +import { filter } from 'rxjs/operators' import { GlobalIconComponent } from '../../shared-icons/global-icon.component' -import { NgClass, NgFor, NgIf } from '@angular/common' export type TopMenuDropdownParam = { label: string @@ -57,17 +57,11 @@ export class TopMenuDropdownComponent implements OnInit, OnChanges, OnDestroy { constructor ( private router: Router, private modalService: NgbModal, - private screen: ScreenService, - private menuService: MenuService + private screen: ScreenService ) { } get isInSmallView () { - let marginLeft = 0 - if (this.menuService.isMenuDisplayed) { - marginLeft = this.menuService.menuWidth - } - - return this.screen.isInSmallView(marginLeft) + return this.screen.isInSmallView() } get isBroadcastMessageDisplayed () { diff --git a/client/src/assets/images/feather/channel.svg b/client/src/assets/images/feather/channel.svg new file mode 100644 index 000000000..007976680 --- /dev/null +++ b/client/src/assets/images/feather/channel.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/src/assets/images/feather/chevron-left.svg b/client/src/assets/images/feather/chevron-left.svg new file mode 100644 index 000000000..2e68eed40 --- /dev/null +++ b/client/src/assets/images/feather/chevron-left.svg @@ -0,0 +1 @@ + diff --git a/client/src/assets/images/feather/config.svg b/client/src/assets/images/feather/config.svg new file mode 100644 index 000000000..c1208aacf --- /dev/null +++ b/client/src/assets/images/feather/config.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/images/feather/help.svg b/client/src/assets/images/feather/help.svg index 51fddd80d..5ecb0eb4d 100644 --- a/client/src/assets/images/feather/help.svg +++ b/client/src/assets/images/feather/help.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + + diff --git a/client/src/assets/images/feather/history.svg b/client/src/assets/images/feather/history.svg new file mode 100644 index 000000000..0797c39ac --- /dev/null +++ b/client/src/assets/images/feather/history.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/src/assets/images/feather/list.svg b/client/src/assets/images/feather/list.svg deleted file mode 100644 index 5ce38eaa9..000000000 --- a/client/src/assets/images/feather/list.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/assets/images/feather/moderation.svg b/client/src/assets/images/feather/moderation.svg new file mode 100644 index 000000000..462212c93 --- /dev/null +++ b/client/src/assets/images/feather/moderation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/src/assets/images/feather/more-vertical.svg b/client/src/assets/images/feather/more-vertical.svg index cba6958f2..1d277c830 100644 --- a/client/src/assets/images/feather/more-vertical.svg +++ b/client/src/assets/images/feather/more-vertical.svg @@ -1 +1,8 @@ - \ No newline at end of file + + + + + + + + diff --git a/client/src/assets/images/feather/overview.svg b/client/src/assets/images/feather/overview.svg new file mode 100644 index 000000000..461e56051 --- /dev/null +++ b/client/src/assets/images/feather/overview.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/src/assets/images/feather/playlists.svg b/client/src/assets/images/feather/playlists.svg new file mode 100644 index 000000000..e814dedfd --- /dev/null +++ b/client/src/assets/images/feather/playlists.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/client/src/assets/images/feather/subscriptions.svg b/client/src/assets/images/feather/subscriptions.svg new file mode 100644 index 000000000..6be367a57 --- /dev/null +++ b/client/src/assets/images/feather/subscriptions.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/src/assets/images/feather/tv.svg b/client/src/assets/images/feather/tv.svg deleted file mode 100644 index 955bbfff0..000000000 --- a/client/src/assets/images/feather/tv.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/assets/images/feather/videos.svg b/client/src/assets/images/feather/videos.svg new file mode 100644 index 000000000..44b25ab39 --- /dev/null +++ b/client/src/assets/images/feather/videos.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/src/assets/images/misc/history.svg b/client/src/assets/images/misc/history.svg deleted file mode 100644 index 0386fae1c..000000000 --- a/client/src/assets/images/misc/history.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/assets/images/misc/local.svg b/client/src/assets/images/misc/local.svg deleted file mode 100644 index 99012b98a..000000000 --- a/client/src/assets/images/misc/local.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/src/assets/images/misc/menu.svg b/client/src/assets/images/misc/menu.svg deleted file mode 100644 index b8bb1b128..000000000 --- a/client/src/assets/images/misc/menu.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/src/assets/images/misc/subscriptions.svg b/client/src/assets/images/misc/subscriptions.svg deleted file mode 100644 index 3a37266be..000000000 --- a/client/src/assets/images/misc/subscriptions.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/assets/images/misc/video-library.svg b/client/src/assets/images/misc/video-library.svg deleted file mode 100644 index f5f623a44..000000000 --- a/client/src/assets/images/misc/video-library.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/main.ts b/client/src/main.ts index 6ccbc0137..842ed53d6 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -16,7 +16,6 @@ import { AppComponent } from './app/app.component' import routes from './app/app.routes' import { CustomReuseStrategy, - MenuGuards, PluginService, PreloadSelectedModulesList, RedirectService, @@ -73,7 +72,6 @@ const bootstrap = () => bootstrapApplication(AppComponent, { getFormProviders(), PreloadSelectedModulesList, - ...MenuGuards.guards, { provide: RouteReuseStrategy, useClass: CustomReuseStrategy }, provideRouter(routes, diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index f060b93f1..f95638059 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -194,9 +194,9 @@ code { --horizontalMarginContent: #{$expanded-horizontal-margins}; --mainColWidth: 100vw; - width: 100%; + width: calc(100% - 48px); - @include margin-left(0); + @include margin-left(48px); } &.lock-scroll .main-row > router-outlet + * { /* stylelint-disable-line selector-max-compound-selectors */ diff --git a/client/src/sass/class-helpers/_buttons.scss b/client/src/sass/class-helpers/_buttons.scss index 3f0311e9e..4d8a09af8 100644 --- a/client/src/sass/class-helpers/_buttons.scss +++ b/client/src/sass/class-helpers/_buttons.scss @@ -16,7 +16,8 @@ @include peertube-button-big-link; } -.orange-button { +.orange-button, +.primary-button { @include orange-button; } @@ -24,7 +25,8 @@ @include orange-button-inverted; } -.grey-button { +.grey-button, +.secondary-button { @include grey-button; } diff --git a/client/src/sass/class-helpers/_common.scss b/client/src/sass/class-helpers/_common.scss index 327e5b678..5b9bfd935 100644 --- a/client/src/sass/class-helpers/_common.scss +++ b/client/src/sass/class-helpers/_common.scss @@ -143,3 +143,9 @@ .outline-0 { outline: none; } + +// --------------------------------------------------------------------------- + +.transform-rotate-180 { + transform: rotate(180deg); +} diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 87fb476db..7c77e7deb 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -264,12 +264,12 @@ } @mixin peertube-button { - padding: 4px 13px; + padding: 6px 20px; border: 0; font-weight: $font-semibold; - border-radius: 3px; + border-radius: 4px; text-align: center; cursor: pointer; @@ -278,9 +278,9 @@ @include rounded-line-height-1-5($button-font-size); - my-global-icon + * { - @include margin-right(4px); - @include margin-left(4px); + my-global-icon + *:not(:empty) { + @include margin-right(8px); + @include margin-left(8px); } } @@ -318,12 +318,13 @@ @include peertube-button; } -@mixin button-with-icon($width: 20px, $margin-right: 3px, $top: -1px) { +@mixin button-with-icon($width: 20px, $margin-right: 3px, $top: -1px, $right: 0) { my-global-icon { position: relative; display: inline-block; width: $width; top: $top; + right: $right; @include margin-right($margin-right); } diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 888dc385c..c6cad33e1 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss @@ -42,10 +42,9 @@ $button-font-size: 15px; $header-height: 106px; -$menu-background: #000; -$menu-color: #fff; -$menu-width: 240px; -$menu-lateral-padding: 26px; +$menu-background: #221A1A; +$menu-color: #E9DFDF; +$menu-width: 248px; $sub-menu-background-color: #F7F7F7; $sub-menu-height: 81px; diff --git a/client/src/sass/player/playlist.scss b/client/src/sass/player/playlist.scss index 8199e8425..c3e571b00 100644 --- a/client/src/sass/player/playlist.scss +++ b/client/src/sass/player/playlist.scss @@ -92,8 +92,8 @@ $playlist-menu-width: 350px; } .vjs-playlist-icon { - mask-image: url('#{$assets-path}/images/feather/list.svg'); - -webkit-mask-image: url('#{$assets-path}/images/feather/list.svg'); + mask-image: url('#{$assets-path}/images/feather/playlists.svg'); + -webkit-mask-image: url('#{$assets-path}/images/feather/playlists.svg'); mask-size: cover; -webkit-mask-size: cover;