]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/app.component.ts
Inform user to fill account profile and channels (#4352)
[github/Chocobozzz/PeerTube.git] / client / src / app / app.component.ts
CommitLineData
67ed6552 1import { Hotkey, HotkeysService } from 'angular2-hotkeys'
dd24f1bb
C
2import { filter, map, switchMap } from 'rxjs/operators'
3import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
67ed6552 4import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
00b5556c 5import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
dd24f1bb
C
6import { Event, GuardsCheckStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router } from '@angular/router'
7import {
8 AuthService,
9 MarkdownService,
10 PeerTubeRouterService,
11 RedirectService,
12 ScreenService,
13 ScrollService,
14 ServerService,
15 ThemeService,
16 User
17} from '@app/core'
93cae479 18import { HooksService } from '@app/core/plugins/hooks.service'
67ed6552 19import { PluginService } from '@app/core/plugins/plugin.service'
437e8e06 20import { CustomModalComponent } from '@app/modal/custom-modal.component'
67ed6552
C
21import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
22import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
7dca45f9 23import { AccountSetupModalComponent } from '@app/modal/account-setup-modal.component'
4f926722 24import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
6d0110ad 25import { LoadingBarService } from '@ngx-loading-bar/core'
66357162 26import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
fc21ef5c 27import { getShortLocale } from '@shared/core-utils/i18n'
2989628b 28import { BroadcastMessageLevel, HTMLServerConfig, ServerConfig, UserRole } from '@shared/models'
3b20bdd6 29import { MenuService } from './core/menu/menu.service'
4504f09f 30import { POP_STATE_MODAL_DISMISS } from './helpers'
67ed6552 31import { InstanceService } from './shared/shared-instance'
e2a2d6c8 32
dc8bc31b 33@Component({
3154f382
C
34 selector: 'my-app',
35 templateUrl: './app.component.html',
36 styleUrls: [ './app.component.scss' ]
dc8bc31b 37})
437e8e06 38export class AppComponent implements OnInit, AfterViewInit {
72c33e71
C
39 private static BROADCAST_MESSAGE_KEY = 'app-broadcast-message-dismissed'
40
7dca45f9 41 @ViewChild('accountSetupModal') accountSetupModal: AccountSetupModalComponent
2f5d2ec5
C
42 @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent
43 @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent
437e8e06 44 @ViewChild('customModal') customModal: CustomModalComponent
43d0ea7f 45
00b5556c 46 customCSS: SafeHtml
72c33e71 47 broadcastMessage: { message: string, dismissable: boolean, class: string } | null = null
00b5556c 48
2989628b 49 private serverConfig: HTMLServerConfig
ba430d75 50
df98563e 51 constructor (
140ea386 52 @Inject(DOCUMENT) private document: Document,
81fe3c67 53 @Inject(LOCALE_ID) private localeId: string,
3154f382 54 private router: Router,
e2a2d6c8 55 private authService: AuthService,
00b5556c 56 private serverService: ServerService,
dd24f1bb 57 private peertubeRouter: PeerTubeRouterService,
18a6f04c 58 private pluginService: PluginService,
43d0ea7f 59 private instanceService: InstanceService,
901637bb 60 private domSanitizer: DomSanitizer,
bbe0f064 61 private redirectService: RedirectService,
ee1fc23a 62 private screenService: ScreenService,
1a00c561 63 private hotkeysService: HotkeysService,
93cae479 64 private themeService: ThemeService,
4334445d
C
65 private hooks: HooksService,
66 private location: PlatformLocation,
3b20bdd6 67 private modalService: NgbModal,
72c33e71 68 private markdownService: MarkdownService,
4f926722 69 private ngbConfig: NgbConfig,
6d0110ad 70 private loadingBar: LoadingBarService,
dd24f1bb 71 private scrollService: ScrollService,
3b20bdd6 72 public menu: MenuService
4f926722
C
73 ) {
74 this.ngbConfig.animation = false
75 }
a99593ed 76
36f9424f 77 get instanceName () {
ba430d75 78 return this.serverConfig.instance.name
36f9424f
C
79 }
80
ba5d4a84 81 goToDefaultRoute () {
aea0b0e7 82 return this.router.navigateByUrl(this.redirectService.getDefaultRoute())
29f9b562
C
83 }
84
df98563e 85 ngOnInit () {
b3eeb529 86 document.getElementById('incompatible-browser').className += ' browser-ok'
73e09f27 87
2989628b 88 this.serverConfig = this.serverService.getHTMLConfig()
ba430d75 89
fc21ef5c 90 this.hooks.runAction('action:application.init', 'common')
a2ffd046
C
91 this.themeService.initialize()
92
d592e0a9
C
93 this.authService.loadClientCredentials()
94
d414207f 95 if (this.isUserLoggedIn()) {
e2a2d6c8 96 // The service will automatically redirect to the login page if the token is not valid anymore
bcd9f81e 97 this.authService.refreshUserInformation()
e2a2d6c8 98 }
6e07c3de 99
489290b8 100 this.initRouteEvents()
dd24f1bb 101 this.scrollService.enableScrollRestoration()
2989628b 102
489290b8
C
103 this.injectJS()
104 this.injectCSS()
72c33e71 105 this.injectBroadcastMessage()
489290b8 106
2989628b
C
107 this.serverService.configReloaded
108 .subscribe(config => {
109 this.serverConfig = config
110
111 this.injectBroadcastMessage()
112 this.injectCSS()
113
114 // Don't reinject JS since it could conflict with existing one
115 })
116
489290b8
C
117 this.initHotkeys()
118
60c2bc80 119 this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS))
43d0ea7f
C
120
121 this.openModalsIfNeeded()
81fe3c67
RK
122
123 this.document.documentElement.lang = getShortLocale(this.localeId)
27bc9586 124 this.document.documentElement.dir = getLocaleDirection(this.localeId)
489290b8
C
125 }
126
437e8e06
K
127 ngAfterViewInit () {
128 this.pluginService.initializeCustomModal(this.customModal)
129 }
130
d95bc702
C
131 getToggleTitle () {
132 if (this.menu.isDisplayed()) return $localize`Close the left menu`
133
134 return $localize`Open the left menu`
135 }
136
489290b8
C
137 isUserLoggedIn () {
138 return this.authService.isLoggedIn()
139 }
140
72c33e71
C
141 hideBroadcastMessage () {
142 peertubeLocalStorage.setItem(AppComponent.BROADCAST_MESSAGE_KEY, this.serverConfig.broadcastMessage.message)
143
144 this.broadcastMessage = null
7034b3c9 145 this.screenService.isBroadcastMessageDisplayed = false
72c33e71
C
146 }
147
489290b8 148 private initRouteEvents () {
489290b8
C
149 const eventsObs = this.router.events
150
6d0110ad 151 // Plugin hooks
dd24f1bb 152 this.peertubeRouter.getNavigationEndEvents().subscribe(e => {
23bdacf8
C
153 this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url })
154 })
155
6d0110ad 156 // Automatically hide/display the menu
489290b8
C
157 eventsObs.pipe(
158 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
1bfc7b73 159 filter(() => this.screenService.isInSmallView() || this.screenService.isInTouchScreen())
245b9d27 160 ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page
6d0110ad
C
161
162 // Handle lazy loaded module
163 eventsObs.pipe(
164 filter((e: Event): e is RouteConfigLoadStart => e instanceof RouteConfigLoadStart)
165 ).subscribe(() => this.loadingBar.useRef().start())
166
167 eventsObs.pipe(
168 filter((e: Event): e is RouteConfigLoadEnd => e instanceof RouteConfigLoadEnd)
169 ).subscribe(() => this.loadingBar.useRef().complete())
489290b8
C
170 }
171
2989628b
C
172 private async injectBroadcastMessage () {
173 this.broadcastMessage = null
174 this.screenService.isBroadcastMessageDisplayed = false
72c33e71 175
2989628b 176 const messageConfig = this.serverConfig.broadcastMessage
72c33e71 177
2989628b
C
178 if (messageConfig.enabled) {
179 // Already dismissed this message?
180 if (messageConfig.dismissable && localStorage.getItem(AppComponent.BROADCAST_MESSAGE_KEY) === messageConfig.message) {
181 return
182 }
72c33e71 183
2989628b
C
184 const classes: { [id in BroadcastMessageLevel]: string } = {
185 info: 'alert-info',
186 warning: 'alert-warning',
187 error: 'alert-danger'
188 }
7034b3c9 189
2989628b
C
190 this.broadcastMessage = {
191 message: await this.markdownService.unsafeMarkdownToHTML(messageConfig.message, true),
192 dismissable: messageConfig.dismissable,
193 class: classes[messageConfig.level]
72c33e71 194 }
2989628b
C
195
196 this.screenService.isBroadcastMessageDisplayed = true
197 }
72c33e71
C
198 }
199
489290b8 200 private injectJS () {
e032aec9 201 // Inject JS
2989628b
C
202 if (this.serverConfig.instance.customizations.javascript) {
203 try {
9df52d66 204 /* eslint-disable no-eval */
2989628b
C
205 eval(this.serverConfig.instance.customizations.javascript)
206 } catch (err) {
207 console.error('Cannot eval custom JavaScript.', err)
208 }
209 }
489290b8 210 }
00b5556c 211
489290b8 212 private injectCSS () {
2989628b
C
213 const headStyle = document.querySelector('style.custom-css-style')
214 if (headStyle) headStyle.parentNode.removeChild(headStyle)
215
216 // We test customCSS if the admin removed the css
217 if (this.customCSS || this.serverConfig.instance.customizations.css) {
218 const styleTag = '<style>' + this.serverConfig.instance.customizations.css + '</style>'
219 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
220 }
489290b8 221 }
ee1fc23a 222
43d0ea7f 223 private async openModalsIfNeeded () {
ba430d75 224 this.authService.userInformationLoaded
43d0ea7f 225 .pipe(
43d0ea7f 226 map(() => this.authService.getUser()),
2989628b
C
227 filter(user => user.role === UserRole.ADMINISTRATOR),
228 switchMap(user => {
229 return this.serverService.getConfig()
230 .pipe(map(serverConfig => ({ serverConfig, user })))
231 })
232 ).subscribe(({ serverConfig, user }) => this._openAdminModalsIfNeeded(serverConfig, user))
43d0ea7f
C
233 }
234
2989628b 235 private async _openAdminModalsIfNeeded (serverConfig: ServerConfig, user: User) {
43d0ea7f
C
236 if (user.noWelcomeModal !== true) return this.welcomeModal.show()
237
2989628b 238 if (user.noInstanceConfigWarningModal === true || !serverConfig.signup.allowed) return
589d9f55
C
239
240 this.instanceService.getAbout()
241 .subscribe(about => {
242 if (
ba430d75 243 this.serverConfig.instance.name.toLowerCase() === 'peertube' ||
589d9f55
C
244 !about.instance.terms ||
245 !about.instance.administrator ||
246 !about.instance.maintenanceLifetime
247 ) {
248 this.instanceConfigWarningModal.show(about)
249 }
250 })
43d0ea7f
C
251 }
252
489290b8 253 private initHotkeys () {
ee1fc23a 254 this.hotkeysService.add([
9df52d66 255 new Hotkey([ '/', 's' ], (event: KeyboardEvent): boolean => {
ee1fc23a 256 document.getElementById('search-video').focus()
8542dc33 257 return false
66357162 258 }, undefined, $localize`Focus the search bar`),
43d0ea7f 259
8542dc33 260 new Hotkey('b', (event: KeyboardEvent): boolean => {
3b20bdd6 261 this.menu.toggleMenu()
8542dc33 262 return false
66357162 263 }, undefined, $localize`Toggle the left menu`),
43d0ea7f 264
20d21199 265 new Hotkey('g o', (event: KeyboardEvent): boolean => {
a54991da
RK
266 this.router.navigate([ '/videos/overview' ])
267 return false
66357162 268 }, undefined, $localize`Go to the discover videos page`),
43d0ea7f 269
20d21199 270 new Hotkey('g t', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
271 this.router.navigate([ '/videos/trending' ])
272 return false
66357162 273 }, undefined, $localize`Go to the trending videos page`),
43d0ea7f 274
20d21199 275 new Hotkey('g r', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
276 this.router.navigate([ '/videos/recently-added' ])
277 return false
66357162 278 }, undefined, $localize`Go to the recently added videos page`),
43d0ea7f 279
20d21199 280 new Hotkey('g l', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
281 this.router.navigate([ '/videos/local' ])
282 return false
66357162 283 }, undefined, $localize`Go to the local videos page`),
43d0ea7f 284
20d21199 285 new Hotkey('g u', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
286 this.router.navigate([ '/videos/upload' ])
287 return false
66357162 288 }, undefined, $localize`Go to the videos upload page`)
ee1fc23a 289 ])
67167390 290 }
dc8bc31b 291}