aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/app.component.html5
-rw-r--r--client/src/app/app.component.ts45
-rw-r--r--client/src/app/app.module.ts7
-rw-r--r--client/src/app/modal/instance-config-warning-modal.component.html15
-rw-r--r--client/src/app/modal/instance-config-warning-modal.component.scss6
-rw-r--r--client/src/app/modal/instance-config-warning-modal.component.ts23
-rw-r--r--client/src/app/modal/welcome-modal.component.html66
-rw-r--r--client/src/app/modal/welcome-modal.component.scss31
-rw-r--r--client/src/app/modal/welcome-modal.component.ts40
-rw-r--r--client/src/app/shared/users/user.model.ts33
10 files changed, 259 insertions, 12 deletions
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index 07a576083..81b4351c5 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -54,3 +54,8 @@
54 </div> 54 </div>
55 </ng-template> 55 </ng-template>
56</p-toast> 56</p-toast>
57
58<ng-template [ngIf]="isUserLoggedIn()">
59 <my-welcome-modal #welcomeModal></my-welcome-modal>
60 <my-instance-config-warning-modal #instanceConfigWarningModal></my-instance-config-warning-modal>
61</ng-template>
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 64bfb9671..f68641047 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,10 +1,10 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit, ViewChild } from '@angular/core'
2import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 2import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
3import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' 3import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router'
4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' 4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
5import { is18nPath } from '../../../shared/models/i18n' 5import { is18nPath } from '../../../shared/models/i18n'
6import { ScreenService } from '@app/shared/misc/screen.service' 6import { ScreenService } from '@app/shared/misc/screen.service'
7import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators' 7import { debounceTime, filter, map, pairwise, skip, switchMap } from 'rxjs/operators'
8import { Hotkey, HotkeysService } from 'angular2-hotkeys' 8import { Hotkey, HotkeysService } from 'angular2-hotkeys'
9import { I18n } from '@ngx-translate/i18n-polyfill' 9import { I18n } from '@ngx-translate/i18n-polyfill'
10import { fromEvent } from 'rxjs' 10import { fromEvent } from 'rxjs'
@@ -13,6 +13,11 @@ import { PluginService } from '@app/core/plugins/plugin.service'
13import { HooksService } from '@app/core/plugins/hooks.service' 13import { HooksService } from '@app/core/plugins/hooks.service'
14import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 14import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
15import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' 15import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants'
16import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
17import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
18import { UserRole } from '@shared/models'
19import { User } from '@app/shared'
20import { InstanceService } from '@app/shared/instance/instance.service'
16 21
17@Component({ 22@Component({
18 selector: 'my-app', 23 selector: 'my-app',
@@ -20,6 +25,9 @@ import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants'
20 styleUrls: [ './app.component.scss' ] 25 styleUrls: [ './app.component.scss' ]
21}) 26})
22export class AppComponent implements OnInit { 27export class AppComponent implements OnInit {
28 @ViewChild('welcomeModal', { static: false }) welcomeModal: WelcomeModalComponent
29 @ViewChild('instanceConfigWarningModal', { static: false }) instanceConfigWarningModal: InstanceConfigWarningModalComponent
30
23 isMenuDisplayed = true 31 isMenuDisplayed = true
24 isMenuChangedByUser = false 32 isMenuChangedByUser = false
25 33
@@ -32,6 +40,7 @@ export class AppComponent implements OnInit {
32 private authService: AuthService, 40 private authService: AuthService,
33 private serverService: ServerService, 41 private serverService: ServerService,
34 private pluginService: PluginService, 42 private pluginService: PluginService,
43 private instanceService: InstanceService,
35 private domSanitizer: DomSanitizer, 44 private domSanitizer: DomSanitizer,
36 private redirectService: RedirectService, 45 private redirectService: RedirectService,
37 private screenService: ScreenService, 46 private screenService: ScreenService,
@@ -96,6 +105,8 @@ export class AppComponent implements OnInit {
96 .subscribe(() => this.onResize()) 105 .subscribe(() => this.onResize())
97 106
98 this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) 107 this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS))
108
109 this.openModalsIfNeeded()
99 } 110 }
100 111
101 isUserLoggedIn () { 112 isUserLoggedIn () {
@@ -220,32 +231,62 @@ export class AppComponent implements OnInit {
220 this.hooks.runAction('action:application.init', 'common') 231 this.hooks.runAction('action:application.init', 'common')
221 } 232 }
222 233
234 private async openModalsIfNeeded () {
235 this.serverService.configLoaded
236 .pipe(
237 switchMap(() => this.authService.userInformationLoaded),
238 map(() => this.authService.getUser()),
239 filter(user => user.role === UserRole.ADMINISTRATOR)
240 ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template
241 }
242
243 private async openAdminModals (user: User) {
244 if (user.noWelcomeModal !== true) return this.welcomeModal.show()
245
246 const config = this.serverService.getConfig()
247
248 if (user.noInstanceConfigWarningModal !== true && config.signup.allowed && config.instance.name.toLowerCase() === 'peertube') {
249 this.instanceService.getAbout()
250 .subscribe(about => {
251 if (!about.instance.terms) {
252 this.instanceConfigWarningModal.show()
253 }
254 })
255 }
256 }
257
223 private initHotkeys () { 258 private initHotkeys () {
224 this.hotkeysService.add([ 259 this.hotkeysService.add([
225 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { 260 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => {
226 document.getElementById('search-video').focus() 261 document.getElementById('search-video').focus()
227 return false 262 return false
228 }, undefined, this.i18n('Focus the search bar')), 263 }, undefined, this.i18n('Focus the search bar')),
264
229 new Hotkey('b', (event: KeyboardEvent): boolean => { 265 new Hotkey('b', (event: KeyboardEvent): boolean => {
230 this.toggleMenu() 266 this.toggleMenu()
231 return false 267 return false
232 }, undefined, this.i18n('Toggle the left menu')), 268 }, undefined, this.i18n('Toggle the left menu')),
269
233 new Hotkey('g o', (event: KeyboardEvent): boolean => { 270 new Hotkey('g o', (event: KeyboardEvent): boolean => {
234 this.router.navigate([ '/videos/overview' ]) 271 this.router.navigate([ '/videos/overview' ])
235 return false 272 return false
236 }, undefined, this.i18n('Go to the discover videos page')), 273 }, undefined, this.i18n('Go to the discover videos page')),
274
237 new Hotkey('g t', (event: KeyboardEvent): boolean => { 275 new Hotkey('g t', (event: KeyboardEvent): boolean => {
238 this.router.navigate([ '/videos/trending' ]) 276 this.router.navigate([ '/videos/trending' ])
239 return false 277 return false
240 }, undefined, this.i18n('Go to the trending videos page')), 278 }, undefined, this.i18n('Go to the trending videos page')),
279
241 new Hotkey('g r', (event: KeyboardEvent): boolean => { 280 new Hotkey('g r', (event: KeyboardEvent): boolean => {
242 this.router.navigate([ '/videos/recently-added' ]) 281 this.router.navigate([ '/videos/recently-added' ])
243 return false 282 return false
244 }, undefined, this.i18n('Go to the recently added videos page')), 283 }, undefined, this.i18n('Go to the recently added videos page')),
284
245 new Hotkey('g l', (event: KeyboardEvent): boolean => { 285 new Hotkey('g l', (event: KeyboardEvent): boolean => {
246 this.router.navigate([ '/videos/local' ]) 286 this.router.navigate([ '/videos/local' ])
247 return false 287 return false
248 }, undefined, this.i18n('Go to the local videos page')), 288 }, undefined, this.i18n('Go to the local videos page')),
289
249 new Hotkey('g u', (event: KeyboardEvent): boolean => { 290 new Hotkey('g u', (event: KeyboardEvent): boolean => {
250 this.router.navigate([ '/videos/upload' ]) 291 this.router.navigate([ '/videos/upload' ])
251 return false 292 return false
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 1e2936a37..a3ea33ca9 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -18,6 +18,8 @@ import { VideosModule } from './videos'
18import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' 18import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n'
19import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' 19import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
20import { SearchModule } from '@app/search' 20import { SearchModule } from '@app/search'
21import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
22import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
21 23
22export function metaFactory (serverService: ServerService): MetaLoader { 24export function metaFactory (serverService: ServerService): MetaLoader {
23 return new MetaStaticLoader({ 25 return new MetaStaticLoader({
@@ -39,7 +41,10 @@ export function metaFactory (serverService: ServerService): MetaLoader {
39 MenuComponent, 41 MenuComponent,
40 LanguageChooserComponent, 42 LanguageChooserComponent,
41 AvatarNotificationComponent, 43 AvatarNotificationComponent,
42 HeaderComponent 44 HeaderComponent,
45
46 WelcomeModalComponent,
47 InstanceConfigWarningModalComponent
43 ], 48 ],
44 imports: [ 49 imports: [
45 BrowserModule, 50 BrowserModule,
diff --git a/client/src/app/modal/instance-config-warning-modal.component.html b/client/src/app/modal/instance-config-warning-modal.component.html
new file mode 100644
index 000000000..595afb103
--- /dev/null
+++ b/client/src/app/modal/instance-config-warning-modal.component.html
@@ -0,0 +1,15 @@
1<ng-template #modal let-hide="close">
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Warning!</h4>
4 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
5 </div>
6
7 <div class="modal-body">
8
9 </div>
10
11 <div class="modal-footer inputs">
12 <span i18n class="action-button action-button-cancel" (click)="hide()">Close</span>
13 </div>
14
15</ng-template>
diff --git a/client/src/app/modal/instance-config-warning-modal.component.scss b/client/src/app/modal/instance-config-warning-modal.component.scss
new file mode 100644
index 000000000..51834c649
--- /dev/null
+++ b/client/src/app/modal/instance-config-warning-modal.component.scss
@@ -0,0 +1,6 @@
1@import '_mixins';
2@import '_variables';
3
4.action-button-cancel {
5 margin-right: 0 !important;
6}
diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts
new file mode 100644
index 000000000..5cc9207cd
--- /dev/null
+++ b/client/src/app/modal/instance-config-warning-modal.component.ts
@@ -0,0 +1,23 @@
1import { Component, ElementRef, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5
6@Component({
7 selector: 'my-instance-config-warning-modal',
8 templateUrl: './instance-config-warning-modal.component.html',
9 styleUrls: [ './instance-config-warning-modal.component.scss' ]
10})
11export class InstanceConfigWarningModalComponent {
12 @ViewChild('modal', { static: true }) modal: ElementRef
13
14 constructor (
15 private modalService: NgbModal,
16 private notifier: Notifier,
17 private i18n: I18n
18 ) { }
19
20 show () {
21 this.modalService.open(this.modal)
22 }
23}
diff --git a/client/src/app/modal/welcome-modal.component.html b/client/src/app/modal/welcome-modal.component.html
new file mode 100644
index 000000000..c83b53c2c
--- /dev/null
+++ b/client/src/app/modal/welcome-modal.component.html
@@ -0,0 +1,66 @@
1<ng-template #modal let-hide="close">
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Welcome on PeerTube dear administrator!</h4>
4 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
5 </div>
6
7 <div class="modal-body">
8
9 <div class="block-links">
10 <div class="subtitle">Useful links</div>
11
12 <ul>
13 <li>
14 Official PeerTube website: <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">https://joinpeertube.org</a>
15 </li>
16
17 <li>
18 Discover CLI PeerTube tools (to upload or import videos, parse logs, prune storage directories, reset user password...):
19 <a href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/maintain-tools</a>
20 </li>
21
22 <li>
23 Understand how to administer your instance (managing users, following other instances, dealing with spammers...):
24 <a href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/admin-following-instances</a>
25 </li>
26
27 <li>
28 Learn how to use PeerTube (setup your account, managing video playlists, discover third-party applications...):
29 <a href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/use-setup-account</a>
30 </li>
31 </ul>
32 </div>
33
34 <div class="block-configuration">
35 <div class="subtitle">Configure your instance</div>
36
37 <p>
38 Now it's time to configure your instance! Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>,
39 specifying <strong>who you are</strong> and <strong>how long</strong> you plan to <strong>maintain your instance</strong>
40 is very important for visitors to understand on what type of instance they are.
41 </p>
42
43 <p>
44 If you want to open registrations, please decide what are <strong>your moderation rules</strong>, fill your <strong>instance terms</strong>
45 and specify the categories and languages you speak. This way, users that are looking for a PeerTube instance on which they can register
46 will be able to choose <strong>the right one</strong>.
47 </p>
48
49 <div class="configure-instance">
50 <a href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure your instance</a>
51 </div>
52 </div>
53
54 <div class="block-instance">
55 <div class="subtitle">Index your instance</div>
56
57 If you want, you can index your PeerTube instance on the public PeerTube instances list:
58 <a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a>
59 </div>
60 </div>
61
62 <div class="modal-footer inputs">
63 <span i18n class="action-button action-button-submit" (click)="hide()">Understood!</span>
64 </div>
65
66</ng-template>
diff --git a/client/src/app/modal/welcome-modal.component.scss b/client/src/app/modal/welcome-modal.component.scss
new file mode 100644
index 000000000..ab57bb993
--- /dev/null
+++ b/client/src/app/modal/welcome-modal.component.scss
@@ -0,0 +1,31 @@
1@import '_mixins';
2@import '_variables';
3
4.modal-body {
5 font-size: 15px;
6}
7
8.action-button-cancel {
9 margin-right: 0 !important;
10}
11
12.subtitle {
13 font-weight: $font-semibold;
14 margin-bottom: 10px;
15 font-size: 16px;
16}
17
18.block-configuration,
19.block-instance {
20 margin-top: 30px;
21}
22
23li {
24 margin-bottom: 10px;
25}
26
27.configure-instance {
28 text-align: center;
29 font-weight: 600;
30 font-size: 18px;
31}
diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts
new file mode 100644
index 000000000..bff2968d4
--- /dev/null
+++ b/client/src/app/modal/welcome-modal.component.ts
@@ -0,0 +1,40 @@
1import { Component, ElementRef, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { UserService } from '@app/shared'
5
6@Component({
7 selector: 'my-welcome-modal',
8 templateUrl: './welcome-modal.component.html',
9 styleUrls: [ './welcome-modal.component.scss' ]
10})
11export class WelcomeModalComponent {
12 @ViewChild('modal', { static: true }) modal: ElementRef
13
14 constructor (
15 private userService: UserService,
16 private modalService: NgbModal,
17 private notifier: Notifier
18 ) { }
19
20 show () {
21 const ref = this.modalService.open(this.modal,{
22 backdrop: 'static',
23 keyboard: false,
24 size: 'lg'
25 })
26
27 ref.result.finally(() => this.doNotOpenAgain())
28 }
29
30 private doNotOpenAgain () {
31 this.userService.updateMyProfile({ noWelcomeModal: true })
32 .subscribe(
33 () => console.log('We will not open the welcome modal again.'),
34
35 err => this.notifier.error(err.message)
36 )
37
38 return true
39 }
40}
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 53809f82c..656b73dd2 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -9,31 +9,38 @@ export class User implements UserServerModel {
9 username: string 9 username: string
10 email: string 10 email: string
11 pendingEmail: string | null 11 pendingEmail: string | null
12
12 emailVerified: boolean 13 emailVerified: boolean
13 nsfwPolicy: NSFWPolicyType 14 nsfwPolicy: NSFWPolicyType
14 15
15 role: UserRole 16 adminFlags?: UserAdminFlag
16 roleLabel: string
17 17
18 webTorrentEnabled: boolean
19 autoPlayVideo: boolean 18 autoPlayVideo: boolean
19 webTorrentEnabled: boolean
20 videosHistoryEnabled: boolean 20 videosHistoryEnabled: boolean
21 videoLanguages: string[] 21 videoLanguages: string[]
22 22
23 role: UserRole
24 roleLabel: string
25
23 videoQuota: number 26 videoQuota: number
24 videoQuotaDaily: number 27 videoQuotaDaily: number
25 account: Account 28 videoQuotaUsed?: number
26 videoChannels: VideoChannel[] 29 videoQuotaUsedDaily?: number
27 createdAt: Date
28 30
29 theme: string 31 theme: string
30 32
31 adminFlags?: UserAdminFlag 33 account: Account
34 notificationSettings?: UserNotificationSetting
35 videoChannels?: VideoChannel[]
32 36
33 blocked: boolean 37 blocked: boolean
34 blockedReason?: string 38 blockedReason?: string
35 39
36 notificationSettings?: UserNotificationSetting 40 noInstanceConfigWarningModal: boolean
41 noWelcomeModal: boolean
42
43 createdAt: Date
37 44
38 constructor (hash: Partial<UserServerModel>) { 45 constructor (hash: Partial<UserServerModel>) {
39 this.id = hash.id 46 this.id = hash.id
@@ -43,13 +50,16 @@ export class User implements UserServerModel {
43 this.role = hash.role 50 this.role = hash.role
44 51
45 this.videoChannels = hash.videoChannels 52 this.videoChannels = hash.videoChannels
53
46 this.videoQuota = hash.videoQuota 54 this.videoQuota = hash.videoQuota
47 this.videoQuotaDaily = hash.videoQuotaDaily 55 this.videoQuotaDaily = hash.videoQuotaDaily
56 this.videoQuotaUsed = hash.videoQuotaUsed
57 this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
58
48 this.nsfwPolicy = hash.nsfwPolicy 59 this.nsfwPolicy = hash.nsfwPolicy
49 this.webTorrentEnabled = hash.webTorrentEnabled 60 this.webTorrentEnabled = hash.webTorrentEnabled
50 this.videosHistoryEnabled = hash.videosHistoryEnabled 61 this.videosHistoryEnabled = hash.videosHistoryEnabled
51 this.autoPlayVideo = hash.autoPlayVideo 62 this.autoPlayVideo = hash.autoPlayVideo
52 this.createdAt = hash.createdAt
53 63
54 this.theme = hash.theme 64 this.theme = hash.theme
55 65
@@ -58,8 +68,13 @@ export class User implements UserServerModel {
58 this.blocked = hash.blocked 68 this.blocked = hash.blocked
59 this.blockedReason = hash.blockedReason 69 this.blockedReason = hash.blockedReason
60 70
71 this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal
72 this.noWelcomeModal = hash.noWelcomeModal
73
61 this.notificationSettings = hash.notificationSettings 74 this.notificationSettings = hash.notificationSettings
62 75
76 this.createdAt = hash.createdAt
77
63 if (hash.account !== undefined) { 78 if (hash.account !== undefined) {
64 this.account = new Account(hash.account) 79 this.account = new Account(hash.account)
65 } 80 }