diff options
author | Kim <1877318+kimsible@users.noreply.github.com> | 2020-07-28 15:18:38 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-28 15:18:38 +0200 |
commit | dfe3f7b72ef46401206f6f461077a7984a0c72f0 (patch) | |
tree | 775a747f2dd4bc098afc2ec254792e2e8e8cbbb4 | |
parent | 0579dee3b29e301838387f53b91b58bff2ffb19a (diff) | |
download | PeerTube-dfe3f7b72ef46401206f6f461077a7984a0c72f0.tar.gz PeerTube-dfe3f7b72ef46401206f6f461077a7984a0c72f0.tar.zst PeerTube-dfe3f7b72ef46401206f6f461077a7984a0c72f0.zip |
Add alert and hide upload view when no upload is possible (#2966)
* Add alert and hide upload view when no upload is possible
* Add about instance link to alert
* Hide videos and imports links when no upload is possible
* Correct curly spacing lint
* Put logic canUpload to User model + add isHidden param to to-menu-dropdown
* Use canSeeVideoLinks from user model
* Rename and change logic canUpload to isUploadDisabled
* Use isDisplayed() method intead of isHidden value
* Refactor client and check videos count using quota
Co-authored-by: kimsible <kimsible@users.noreply.github.com>
Co-authored-by: Chocobozzz <me@florianbigard.com>
11 files changed, 186 insertions, 57 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index 319d50cda..7fc83351b 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts | |||
@@ -74,12 +74,24 @@ export class AdminComponent implements OnInit { | |||
74 | }) | 74 | }) |
75 | } | 75 | } |
76 | 76 | ||
77 | if (this.hasUsersRight()) this.menuEntries.push({ label: this.i18n('Users'), routerLink: '/admin/users' }) | 77 | if (this.hasUsersRight()) { |
78 | this.menuEntries.push({ label: this.i18n('Users'), routerLink: '/admin/users' }) | ||
79 | } | ||
80 | |||
78 | if (this.hasServerFollowRight()) this.menuEntries.push(federationItems) | 81 | if (this.hasServerFollowRight()) this.menuEntries.push(federationItems) |
79 | if (this.hasAbusesRight() || this.hasVideoBlocklistRight()) this.menuEntries.push(moderationItems) | 82 | if (this.hasAbusesRight() || this.hasVideoBlocklistRight()) this.menuEntries.push(moderationItems) |
80 | if (this.hasConfigRight()) this.menuEntries.push({ label: this.i18n('Configuration'), routerLink: '/admin/config' }) | 83 | |
81 | if (this.hasPluginsRight()) this.menuEntries.push({ label: this.i18n('Plugins/Themes'), routerLink: '/admin/plugins' }) | 84 | if (this.hasConfigRight()) { |
82 | if (this.hasJobsRight() || this.hasLogsRight() || this.hasDebugRight()) this.menuEntries.push({ label: this.i18n('System'), routerLink: '/admin/system' }) | 85 | this.menuEntries.push({ label: this.i18n('Configuration'), routerLink: '/admin/config' }) |
86 | } | ||
87 | |||
88 | if (this.hasPluginsRight()) { | ||
89 | this.menuEntries.push({ label: this.i18n('Plugins/Themes'), routerLink: '/admin/plugins' }) | ||
90 | } | ||
91 | |||
92 | if (this.hasJobsRight() || this.hasLogsRight() || this.hasDebugRight()) { | ||
93 | this.menuEntries.push({ label: this.i18n('System'), routerLink: '/admin/system' }) | ||
94 | } | ||
83 | } | 95 | } |
84 | 96 | ||
85 | hasUsersRight () { | 97 | hasUsersRight () { |
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index 07cab37fc..5b2238f5a 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { AuthService, ServerService, AuthUser } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { ServerConfig } from '@shared/models' | 4 | import { ServerConfig } from '@shared/models' |
5 | import { TopMenuDropdownParam } from '../shared/shared-main/misc/top-menu-dropdown.component' | 5 | import { TopMenuDropdownParam } from '../shared/shared-main/misc/top-menu-dropdown.component' |
@@ -11,11 +11,13 @@ import { TopMenuDropdownParam } from '../shared/shared-main/misc/top-menu-dropdo | |||
11 | }) | 11 | }) |
12 | export class MyAccountComponent implements OnInit { | 12 | export class MyAccountComponent implements OnInit { |
13 | menuEntries: TopMenuDropdownParam[] = [] | 13 | menuEntries: TopMenuDropdownParam[] = [] |
14 | user: AuthUser | ||
14 | 15 | ||
15 | private serverConfig: ServerConfig | 16 | private serverConfig: ServerConfig |
16 | 17 | ||
17 | constructor ( | 18 | constructor ( |
18 | private serverService: ServerService, | 19 | private serverService: ServerService, |
20 | private authService: AuthService, | ||
19 | private i18n: I18n | 21 | private i18n: I18n |
20 | ) { } | 22 | ) { } |
21 | 23 | ||
@@ -24,6 +26,20 @@ export class MyAccountComponent implements OnInit { | |||
24 | this.serverService.getConfig() | 26 | this.serverService.getConfig() |
25 | .subscribe(config => this.serverConfig = config) | 27 | .subscribe(config => this.serverConfig = config) |
26 | 28 | ||
29 | this.user = this.authService.getUser() | ||
30 | |||
31 | this.authService.userInformationLoaded.subscribe( | ||
32 | () => this.buildMenu() | ||
33 | ) | ||
34 | } | ||
35 | |||
36 | isVideoImportEnabled () { | ||
37 | const importConfig = this.serverConfig.import.videos | ||
38 | |||
39 | return importConfig.http.enabled || importConfig.torrent.enabled | ||
40 | } | ||
41 | |||
42 | private buildMenu () { | ||
27 | const libraryEntries: TopMenuDropdownParam = { | 43 | const libraryEntries: TopMenuDropdownParam = { |
28 | label: this.i18n('My library'), | 44 | label: this.i18n('My library'), |
29 | children: [ | 45 | children: [ |
@@ -35,7 +51,8 @@ export class MyAccountComponent implements OnInit { | |||
35 | { | 51 | { |
36 | label: this.i18n('My videos'), | 52 | label: this.i18n('My videos'), |
37 | routerLink: '/my-account/videos', | 53 | routerLink: '/my-account/videos', |
38 | iconName: 'videos' | 54 | iconName: 'videos', |
55 | isDisplayed: () => this.user.canSeeVideosLink | ||
39 | }, | 56 | }, |
40 | { | 57 | { |
41 | label: this.i18n('My playlists'), | 58 | label: this.i18n('My playlists'), |
@@ -45,7 +62,7 @@ export class MyAccountComponent implements OnInit { | |||
45 | { | 62 | { |
46 | label: this.i18n('My subscriptions'), | 63 | label: this.i18n('My subscriptions'), |
47 | routerLink: '/my-account/subscriptions', | 64 | routerLink: '/my-account/subscriptions', |
48 | iconName: 'subscriptions' | 65 | iconName: 'inbox-full' |
49 | }, | 66 | }, |
50 | { | 67 | { |
51 | label: this.i18n('My history'), | 68 | label: this.i18n('My history'), |
@@ -59,7 +76,8 @@ export class MyAccountComponent implements OnInit { | |||
59 | libraryEntries.children.push({ | 76 | libraryEntries.children.push({ |
60 | label: 'My imports', | 77 | label: 'My imports', |
61 | routerLink: '/my-account/video-imports', | 78 | routerLink: '/my-account/video-imports', |
62 | iconName: 'cloud-download' | 79 | iconName: 'cloud-download', |
80 | isDisplayed: () => this.user.canSeeVideosLink | ||
63 | }) | 81 | }) |
64 | } | 82 | } |
65 | 83 | ||
@@ -97,11 +115,4 @@ export class MyAccountComponent implements OnInit { | |||
97 | miscEntries | 115 | miscEntries |
98 | ] | 116 | ] |
99 | } | 117 | } |
100 | |||
101 | isVideoImportEnabled () { | ||
102 | const importConfig = this.serverConfig.import.videos | ||
103 | |||
104 | return importConfig.http.enabled || importConfig.torrent.enabled | ||
105 | } | ||
106 | |||
107 | } | 118 | } |
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 5690ac37f..14d41f95b 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.html +++ b/client/src/app/+videos/+video-edit/video-add.component.html | |||
@@ -1,4 +1,12 @@ | |||
1 | <div class="margin-content"> | 1 | <div *ngIf="user.isUploadDisabled()" class="no-upload"> |
2 | <div class="alert alert-warning"> | ||
3 | <div i18n>Sorry, the upload feature is disabled for your account. If you want to add videos, an admin must unlock your quota.</div> | ||
4 | <a i18n routerLink="/about/instance" class="about-link">Read instance rules for help</a> | ||
5 | </div> | ||
6 | <img src="/client/assets/images/mascot/defeated.svg" alt="defeated mascot"> | ||
7 | </div> | ||
8 | |||
9 | <div *ngIf="!user.isUploadDisabled()" class="margin-content"> | ||
2 | <div class="alert alert-warning" *ngIf="isRootUser()" i18n> | 10 | <div class="alert alert-warning" *ngIf="isRootUser()" i18n> |
3 | We recommend you to not use the <strong>root</strong> user to publish your videos, since it's the super-admin account of your instance. | 11 | We recommend you to not use the <strong>root</strong> user to publish your videos, since it's the super-admin account of your instance. |
4 | <br /> | 12 | <br /> |
@@ -45,4 +53,4 @@ | |||
45 | </div> | 53 | </div> |
46 | 54 | ||
47 | <div [ngbNavOutlet]="nav"></div> | 55 | <div [ngbNavOutlet]="nav"></div> |
48 | </div> | 56 | </div> \ No newline at end of file |
diff --git a/client/src/app/+videos/+video-edit/video-add.component.scss b/client/src/app/+videos/+video-edit/video-add.component.scss index f9977bda0..5db9e823d 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.scss +++ b/client/src/app/+videos/+video-edit/video-add.component.scss | |||
@@ -6,6 +6,34 @@ $border-type: solid; | |||
6 | $border-color: #EAEAEA; | 6 | $border-color: #EAEAEA; |
7 | $nav-link-height: 40px; | 7 | $nav-link-height: 40px; |
8 | 8 | ||
9 | .no-upload { | ||
10 | height: 100%; | ||
11 | width: 100%; | ||
12 | text-align: center; | ||
13 | |||
14 | .about-link { | ||
15 | @include peertube-button-link; | ||
16 | @include orange-button; | ||
17 | |||
18 | height: fit-content; | ||
19 | margin-top: 10px; | ||
20 | } | ||
21 | |||
22 | img { | ||
23 | margin-top: 10px; | ||
24 | margin-bottom: 75px; | ||
25 | width: 220px; | ||
26 | height: auto; | ||
27 | } | ||
28 | |||
29 | @media screen and (max-height: 600px) { | ||
30 | img { | ||
31 | margin-top: 5px; | ||
32 | width: 160px; | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | |||
9 | .margin-content { | 37 | .margin-content { |
10 | padding-top: 20px; | 38 | padding-top: 20px; |
11 | } | 39 | } |
diff --git a/client/src/app/+videos/+video-edit/video-add.component.ts b/client/src/app/+videos/+video-edit/video-add.component.ts index 016791d59..94e85efc1 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.ts +++ b/client/src/app/+videos/+video-edit/video-add.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, HostListener, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, HostListener, OnInit, ViewChild } from '@angular/core' |
2 | import { AuthService, CanComponentDeactivate, ServerService, User } from '@app/core' | 2 | import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core' |
3 | import { ServerConfig } from '@shared/models' | 3 | import { ServerConfig } from '@shared/models' |
4 | import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' | 4 | import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' |
5 | import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' | 5 | import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' |
@@ -15,7 +15,7 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
15 | @ViewChild('videoImportUrl') videoImportUrl: VideoImportUrlComponent | 15 | @ViewChild('videoImportUrl') videoImportUrl: VideoImportUrlComponent |
16 | @ViewChild('videoImportTorrent') videoImportTorrent: VideoImportTorrentComponent | 16 | @ViewChild('videoImportTorrent') videoImportTorrent: VideoImportTorrentComponent |
17 | 17 | ||
18 | user: User = null | 18 | user: AuthUser = null |
19 | 19 | ||
20 | secondStepType: 'upload' | 'import-url' | 'import-torrent' | 20 | secondStepType: 'upload' | 'import-url' | 'import-torrent' |
21 | videoName: string | 21 | videoName: string |
@@ -37,6 +37,8 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
37 | 37 | ||
38 | this.serverService.getConfig() | 38 | this.serverService.getConfig() |
39 | .subscribe(config => this.serverConfig = config) | 39 | .subscribe(config => this.serverConfig = config) |
40 | |||
41 | this.user = this.auth.getUser() | ||
40 | } | 42 | } |
41 | 43 | ||
42 | onFirstStepDone (type: 'upload' | 'import-url' | 'import-torrent', videoName: string) { | 44 | onFirstStepDone (type: 'upload' | 'import-url' | 'import-torrent', videoName: string) { |
@@ -80,6 +82,6 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
80 | } | 82 | } |
81 | 83 | ||
82 | isRootUser () { | 84 | isRootUser () { |
83 | return this.auth.getUser().username === 'root' | 85 | return this.user.username === 'root' |
84 | } | 86 | } |
85 | } | 87 | } |
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index 4e7801550..88b730938 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | import { Observable, of } from 'rxjs' | ||
2 | import { map } from 'rxjs/operators' | ||
1 | import { User } from '@app/core/users/user.model' | 3 | import { User } from '@app/core/users/user.model' |
2 | import { peertubeLocalStorage } from '@app/helpers/peertube-web-storage' | 4 | import { peertubeLocalStorage } from '@app/helpers/peertube-web-storage' |
3 | import { | 5 | import { |
@@ -7,7 +9,8 @@ import { | |||
7 | NSFWPolicyType, | 9 | NSFWPolicyType, |
8 | User as ServerUserModel, | 10 | User as ServerUserModel, |
9 | UserRight, | 11 | UserRight, |
10 | UserRole | 12 | UserRole, |
13 | UserVideoQuota | ||
11 | } from '@shared/models' | 14 | } from '@shared/models' |
12 | 15 | ||
13 | export type TokenOptions = { | 16 | export type TokenOptions = { |
@@ -74,6 +77,8 @@ export class AuthUser extends User implements ServerMyUserModel { | |||
74 | tokens: Tokens | 77 | tokens: Tokens |
75 | specialPlaylists: MyUserSpecialPlaylist[] | 78 | specialPlaylists: MyUserSpecialPlaylist[] |
76 | 79 | ||
80 | canSeeVideosLink = true | ||
81 | |||
77 | static load () { | 82 | static load () { |
78 | const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME) | 83 | const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME) |
79 | if (usernameLocalStorage) { | 84 | if (usernameLocalStorage) { |
@@ -150,4 +155,26 @@ export class AuthUser extends User implements ServerMyUserModel { | |||
150 | peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo)) | 155 | peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo)) |
151 | this.tokens.save() | 156 | this.tokens.save() |
152 | } | 157 | } |
158 | |||
159 | computeCanSeeVideosLink (quotaObservable: Observable<UserVideoQuota>): Observable<boolean> { | ||
160 | if (!this.isUploadDisabled()) { | ||
161 | this.canSeeVideosLink = true | ||
162 | return of(this.canSeeVideosLink) | ||
163 | } | ||
164 | |||
165 | // Check if the user has videos | ||
166 | return quotaObservable.pipe( | ||
167 | map(({ videoQuotaUsed }) => { | ||
168 | if (videoQuotaUsed !== 0) { | ||
169 | // User already uploaded videos, so it can see the link | ||
170 | this.canSeeVideosLink = true | ||
171 | } else { | ||
172 | // No videos, no upload so the user don't need to see the videos link | ||
173 | this.canSeeVideosLink = false | ||
174 | } | ||
175 | |||
176 | return this.canSeeVideosLink | ||
177 | }) | ||
178 | ) | ||
179 | } | ||
153 | } | 180 | } |
diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts index 31b9c2152..6a56786d9 100644 --- a/client/src/app/core/users/user.model.ts +++ b/client/src/app/core/users/user.model.ts | |||
@@ -149,4 +149,8 @@ export class User implements UserServerModel { | |||
149 | updateAccountAvatar (newAccountAvatar: Avatar) { | 149 | updateAccountAvatar (newAccountAvatar: Avatar) { |
150 | this.account.updateAvatar(newAccountAvatar) | 150 | this.account.updateAvatar(newAccountAvatar) |
151 | } | 151 | } |
152 | |||
153 | isUploadDisabled () { | ||
154 | return this.videoQuota === 0 || this.videoQuotaDaily === 0 | ||
155 | } | ||
152 | } | 156 | } |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 8faa37ed6..71fb2c154 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -81,7 +81,7 @@ | |||
81 | <div *ngIf="isLoggedIn" class="panel-block"> | 81 | <div *ngIf="isLoggedIn" class="panel-block"> |
82 | <div i18n class="block-title">MY LIBRARY</div> | 82 | <div i18n class="block-title">MY LIBRARY</div> |
83 | 83 | ||
84 | <a routerLink="/my-account/videos" routerLinkActive="active"> | 84 | <a *ngIf="user.canSeeVideosLink" routerLink="/my-account/videos" routerLinkActive="active"> |
85 | <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon> | 85 | <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon> |
86 | <ng-container i18n>Videos</ng-container> | 86 | <ng-container i18n>Videos</ng-container> |
87 | </a> | 87 | </a> |
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 0ea251f1c..2c55b9a84 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts | |||
@@ -1,21 +1,25 @@ | |||
1 | import { HotkeysService } from 'angular2-hotkeys' | 1 | import { HotkeysService } from 'angular2-hotkeys' |
2 | import * as debug from 'debug' | ||
3 | import { switchMap } from 'rxjs/operators' | ||
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 4 | import { Component, OnInit, ViewChild } from '@angular/core' |
3 | import { AuthService, AuthStatus, RedirectService, ScreenService, ServerService, User, UserService } from '@app/core' | 5 | import { AuthService, AuthStatus, AuthUser, RedirectService, ScreenService, ServerService, UserService } from '@app/core' |
4 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | 6 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' |
5 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | 7 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { ServerConfig, UserRight, VideoConstant } from '@shared/models' | 9 | import { ServerConfig, UserRight, VideoConstant } from '@shared/models' |
8 | 10 | ||
11 | const logger = debug('peertube:menu:MenuComponent') | ||
12 | |||
9 | @Component({ | 13 | @Component({ |
10 | selector: 'my-menu', | 14 | selector: 'my-menu', |
11 | templateUrl: './menu.component.html', | 15 | templateUrl: './menu.component.html', |
12 | styleUrls: [ './menu.component.scss' ] | 16 | styleUrls: ['./menu.component.scss'] |
13 | }) | 17 | }) |
14 | export class MenuComponent implements OnInit { | 18 | export class MenuComponent implements OnInit { |
15 | @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent | 19 | @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent |
16 | @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent | 20 | @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent |
17 | 21 | ||
18 | user: User | 22 | user: AuthUser |
19 | isLoggedIn: boolean | 23 | isLoggedIn: boolean |
20 | 24 | ||
21 | userHasAdminAccess = false | 25 | userHasAdminAccess = false |
@@ -25,7 +29,7 @@ export class MenuComponent implements OnInit { | |||
25 | 29 | ||
26 | private languages: VideoConstant<string>[] = [] | 30 | private languages: VideoConstant<string>[] = [] |
27 | private serverConfig: ServerConfig | 31 | private serverConfig: ServerConfig |
28 | private routesPerRight: { [ role in UserRight ]?: string } = { | 32 | private routesPerRight: { [role in UserRight]?: string } = { |
29 | [UserRight.MANAGE_USERS]: '/admin/users', | 33 | [UserRight.MANAGE_USERS]: '/admin/users', |
30 | [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', | 34 | [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', |
31 | [UserRight.MANAGE_ABUSES]: '/admin/moderation/abuses', | 35 | [UserRight.MANAGE_ABUSES]: '/admin/moderation/abuses', |
@@ -62,21 +66,30 @@ export class MenuComponent implements OnInit { | |||
62 | .subscribe(config => this.serverConfig = config) | 66 | .subscribe(config => this.serverConfig = config) |
63 | 67 | ||
64 | this.isLoggedIn = this.authService.isLoggedIn() | 68 | this.isLoggedIn = this.authService.isLoggedIn() |
65 | if (this.isLoggedIn === true) this.user = this.authService.getUser() | 69 | if (this.isLoggedIn === true) { |
66 | this.computeIsUserHasAdminAccess() | 70 | this.user = this.authService.getUser() |
71 | this.computeVideosLink() | ||
72 | } | ||
73 | |||
74 | this.computeAdminAccess() | ||
67 | 75 | ||
68 | this.authService.loginChangedSource.subscribe( | 76 | this.authService.loginChangedSource.subscribe( |
69 | status => { | 77 | status => { |
70 | if (status === AuthStatus.LoggedIn) { | 78 | if (status === AuthStatus.LoggedIn) { |
71 | this.isLoggedIn = true | 79 | this.isLoggedIn = true |
72 | this.user = this.authService.getUser() | 80 | this.user = this.authService.getUser() |
73 | this.computeIsUserHasAdminAccess() | 81 | |
74 | console.log('Logged in.') | 82 | this.computeAdminAccess() |
83 | this.computeVideosLink() | ||
84 | |||
85 | logger('Logged in.') | ||
75 | } else if (status === AuthStatus.LoggedOut) { | 86 | } else if (status === AuthStatus.LoggedOut) { |
76 | this.isLoggedIn = false | 87 | this.isLoggedIn = false |
77 | this.user = undefined | 88 | this.user = undefined |
78 | this.computeIsUserHasAdminAccess() | 89 | |
79 | console.log('Logged out.') | 90 | this.computeAdminAccess() |
91 | |||
92 | logger('Logged out.') | ||
80 | } else { | 93 | } else { |
81 | console.error('Unknown auth status: ' + status) | 94 | console.error('Unknown auth status: ' + status) |
82 | } | 95 | } |
@@ -84,15 +97,15 @@ export class MenuComponent implements OnInit { | |||
84 | ) | 97 | ) |
85 | 98 | ||
86 | this.hotkeysService.cheatSheetToggle | 99 | this.hotkeysService.cheatSheetToggle |
87 | .subscribe(isOpen => this.helpVisible = isOpen) | 100 | .subscribe(isOpen => this.helpVisible = isOpen) |
88 | 101 | ||
89 | this.serverService.getVideoLanguages() | 102 | this.serverService.getVideoLanguages() |
90 | .subscribe(languages => { | 103 | .subscribe(languages => { |
91 | this.languages = languages | 104 | this.languages = languages |
92 | 105 | ||
93 | this.authService.userInformationLoaded | 106 | this.authService.userInformationLoaded |
94 | .subscribe(() => this.buildUserLanguages()) | 107 | .subscribe(() => this.buildUserLanguages()) |
95 | }) | 108 | }) |
96 | } | 109 | } |
97 | 110 | ||
98 | get language () { | 111 | get language () { |
@@ -116,7 +129,7 @@ export class MenuComponent implements OnInit { | |||
116 | 129 | ||
117 | isRegistrationAllowed () { | 130 | isRegistrationAllowed () { |
118 | return this.serverConfig.signup.allowed && | 131 | return this.serverConfig.signup.allowed && |
119 | this.serverConfig.signup.allowedForCurrentIP | 132 | this.serverConfig.signup.allowedForCurrentIP |
120 | } | 133 | } |
121 | 134 | ||
122 | getFirstAdminRightAvailable () { | 135 | getFirstAdminRightAvailable () { |
@@ -172,7 +185,7 @@ export class MenuComponent implements OnInit { | |||
172 | this.user.webTorrentEnabled = !this.user.webTorrentEnabled | 185 | this.user.webTorrentEnabled = !this.user.webTorrentEnabled |
173 | 186 | ||
174 | this.userService.updateMyProfile({ webTorrentEnabled: this.user.webTorrentEnabled }) | 187 | this.userService.updateMyProfile({ webTorrentEnabled: this.user.webTorrentEnabled }) |
175 | .subscribe(() => this.authService.refreshUserInformation()) | 188 | .subscribe(() => this.authService.refreshUserInformation()) |
176 | } | 189 | } |
177 | 190 | ||
178 | langForLocale (localeId: string) { | 191 | langForLocale (localeId: string) { |
@@ -188,18 +201,28 @@ export class MenuComponent implements OnInit { | |||
188 | } | 201 | } |
189 | 202 | ||
190 | if (!this.user.videoLanguages) { | 203 | if (!this.user.videoLanguages) { |
191 | this.videoLanguages = [ this.i18n('any language') ] | 204 | this.videoLanguages = [this.i18n('any language')] |
192 | return | 205 | return |
193 | } | 206 | } |
194 | 207 | ||
195 | this.videoLanguages = this.user.videoLanguages | 208 | this.videoLanguages = this.user.videoLanguages |
196 | .map(locale => this.langForLocale(locale)) | 209 | .map(locale => this.langForLocale(locale)) |
197 | .map(value => value === undefined ? '?' : value) | 210 | .map(value => value === undefined ? '?' : value) |
198 | } | 211 | } |
199 | 212 | ||
200 | private computeIsUserHasAdminAccess () { | 213 | private computeAdminAccess () { |
201 | const right = this.getFirstAdminRightAvailable() | 214 | const right = this.getFirstAdminRightAvailable() |
202 | 215 | ||
203 | this.userHasAdminAccess = right !== undefined | 216 | this.userHasAdminAccess = right !== undefined |
204 | } | 217 | } |
218 | |||
219 | private computeVideosLink () { | ||
220 | this.authService.userInformationLoaded | ||
221 | .pipe( | ||
222 | switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed())) | ||
223 | ).subscribe(res => { | ||
224 | if (res === true) logger('User can see videos link.') | ||
225 | else logger('User cannot see videos link.') | ||
226 | }) | ||
227 | } | ||
205 | } | 228 | } |
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html index c737b40c7..530b9e376 100644 --- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html +++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html | |||
@@ -1,9 +1,9 @@ | |||
1 | <div class="sub-menu" [ngClass]="{ 'no-scroll': isModalOpened }"> | 1 | <div class="sub-menu" [ngClass]="{ 'no-scroll': isModalOpened }"> |
2 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> | 2 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> |
3 | 3 | ||
4 | <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings">{{ menuEntry.label }}</a> | 4 | <a *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings">{{ menuEntry.label }}</a> |
5 | 5 | ||
6 | <div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry" | 6 | <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry" |
7 | #dropdown="ngbDropdown" autoClose="outside"> | 7 | #dropdown="ngbDropdown" autoClose="outside"> |
8 | <span | 8 | <span |
9 | *ngIf="isInSmallView" | 9 | *ngIf="isInSmallView" |
@@ -25,11 +25,15 @@ | |||
25 | </span> | 25 | </span> |
26 | 26 | ||
27 | <div ngbDropdownMenu> | 27 | <div ngbDropdownMenu> |
28 | <a *ngFor="let menuChild of menuEntry.children" class="dropdown-item" [ngClass]="{ icon: hasIcons, active: suffixLabels[menuEntry.label] === menuChild.label }" [routerLink]="menuChild.routerLink"> | 28 | <ng-container *ngFor="let menuChild of menuEntry.children"> |
29 | <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> | 29 | <a *ngIf="isDisplayed(menuChild)" class="dropdown-item" |
30 | [ngClass]="{ icon: hasIcons, active: suffixLabels[menuEntry.label] === menuChild.label }" | ||
31 | [routerLink]="menuChild.routerLink"> | ||
32 | <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> | ||
30 | 33 | ||
31 | {{ menuChild.label }} | 34 | {{ menuChild.label }} |
32 | </a> | 35 | </a> |
36 | </ng-container> | ||
33 | </div> | 37 | </div> |
34 | </div> | 38 | </div> |
35 | </ng-container> | 39 | </ng-container> |
@@ -39,13 +43,15 @@ | |||
39 | <div class="modal-body"> | 43 | <div class="modal-body"> |
40 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> | 44 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> |
41 | <div [ngClass]="{ hidden: id !== currentMenuEntryIndex }"> | 45 | <div [ngClass]="{ hidden: id !== currentMenuEntryIndex }"> |
42 | <a *ngFor="let menuChild of menuEntry.children" | 46 | <ng-container *ngFor="let menuChild of menuEntry.children"> |
43 | [ngClass]="{ icon: hasIcons }" | 47 | <a *ngIf="isDisplayed(menuChild)" |
44 | [routerLink]="menuChild.routerLink" routerLinkActive="active" (click)="dismissOtherModals()"> | 48 | [ngClass]="{ icon: hasIcons }" |
45 | <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> | 49 | [routerLink]="menuChild.routerLink" routerLinkActive="active" (click)="dismissOtherModals()"> |
50 | <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> | ||
46 | 51 | ||
47 | {{ menuChild.label }} | 52 | {{ menuChild.label }} |
48 | </a> | 53 | </a> |
54 | </ng-container> | ||
49 | </div> | 55 | </div> |
50 | </ng-container> | 56 | </ng-container> |
51 | </div> | 57 | </div> |
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts index 66f8f7e55..c3cd22307 100644 --- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts +++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts | |||
@@ -9,12 +9,14 @@ import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' | |||
9 | export type TopMenuDropdownParam = { | 9 | export type TopMenuDropdownParam = { |
10 | label: string | 10 | label: string |
11 | routerLink?: string | 11 | routerLink?: string |
12 | isDisplayed?: () => boolean // Default: () => true | ||
12 | 13 | ||
13 | children?: { | 14 | children?: { |
14 | label: string | 15 | label: string |
15 | routerLink: string | 16 | routerLink: string |
16 | |||
17 | iconName?: GlobalIconName | 17 | iconName?: GlobalIconName |
18 | |||
19 | isDisplayed?: () => boolean // Default: () => true | ||
18 | }[] | 20 | }[] |
19 | } | 21 | } |
20 | 22 | ||
@@ -92,6 +94,12 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy { | |||
92 | this.modalService.dismissAll() | 94 | this.modalService.dismissAll() |
93 | } | 95 | } |
94 | 96 | ||
97 | isDisplayed (obj: { isDisplayed?: () => boolean }) { | ||
98 | if (typeof obj.isDisplayed !== 'function') return true | ||
99 | |||
100 | return obj.isDisplayed() | ||
101 | } | ||
102 | |||
95 | private updateChildLabels (path: string) { | 103 | private updateChildLabels (path: string) { |
96 | this.suffixLabels = {} | 104 | this.suffixLabels = {} |
97 | 105 | ||