]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/app.component.ts
4d5a9f75f0e13d8d305d27c7b5bbac58db95cf25
[github/Chocobozzz/PeerTube.git] / client / src / app / app.component.ts
1 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
2 import { filter, map, switchMap } from 'rxjs/operators'
3 import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
4 import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
5 import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
6 import { Event, GuardsCheckStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router } from '@angular/router'
7 import {
8 AuthService,
9 MarkdownService,
10 PeerTubeRouterService,
11 RedirectService,
12 ScreenService,
13 ScrollService,
14 ServerService,
15 ThemeService,
16 User
17 } from '@app/core'
18 import { HooksService } from '@app/core/plugins/hooks.service'
19 import { PluginService } from '@app/core/plugins/plugin.service'
20 import { CustomModalComponent } from '@app/modal/custom-modal.component'
21 import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
22 import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
23 import { AccountSetupModalComponent } from '@app/modal/account-setup-modal.component'
24 import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
25 import { LoadingBarService } from '@ngx-loading-bar/core'
26 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
27 import { getShortLocale } from '@shared/core-utils/i18n'
28 import { BroadcastMessageLevel, HTMLServerConfig, ServerConfig, UserRole } from '@shared/models'
29 import { MenuService } from './core/menu/menu.service'
30 import { POP_STATE_MODAL_DISMISS } from './helpers'
31 import { InstanceService } from './shared/shared-instance'
32
33 @Component({
34 selector: 'my-app',
35 templateUrl: './app.component.html',
36 styleUrls: [ './app.component.scss' ]
37 })
38 export class AppComponent implements OnInit, AfterViewInit {
39 private static BROADCAST_MESSAGE_KEY = 'app-broadcast-message-dismissed'
40
41 @ViewChild('accountSetupModal') accountSetupModal: AccountSetupModalComponent
42 @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent
43 @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent
44 @ViewChild('customModal') customModal: CustomModalComponent
45
46 customCSS: SafeHtml
47 broadcastMessage: { message: string, dismissable: boolean, class: string } | null = null
48
49 private serverConfig: HTMLServerConfig
50
51 constructor (
52 @Inject(DOCUMENT) private document: Document,
53 @Inject(LOCALE_ID) private localeId: string,
54 private router: Router,
55 private authService: AuthService,
56 private serverService: ServerService,
57 private peertubeRouter: PeerTubeRouterService,
58 private pluginService: PluginService,
59 private instanceService: InstanceService,
60 private domSanitizer: DomSanitizer,
61 private redirectService: RedirectService,
62 private screenService: ScreenService,
63 private hotkeysService: HotkeysService,
64 private themeService: ThemeService,
65 private hooks: HooksService,
66 private location: PlatformLocation,
67 private modalService: NgbModal,
68 private markdownService: MarkdownService,
69 private ngbConfig: NgbConfig,
70 private loadingBar: LoadingBarService,
71 private scrollService: ScrollService,
72 public menu: MenuService
73 ) {
74 this.ngbConfig.animation = false
75 }
76
77 get instanceName () {
78 return this.serverConfig.instance.name
79 }
80
81 goToDefaultRoute () {
82 return this.router.navigateByUrl(this.redirectService.getDefaultRoute())
83 }
84
85 ngOnInit () {
86 document.getElementById('incompatible-browser').className += ' browser-ok'
87
88 this.serverConfig = this.serverService.getHTMLConfig()
89
90 this.hooks.runAction('action:application.init', 'common')
91 this.themeService.initialize()
92
93 this.authService.loadClientCredentials()
94
95 if (this.isUserLoggedIn()) {
96 // The service will automatically redirect to the login page if the token is not valid anymore
97 this.authService.refreshUserInformation()
98 }
99
100 this.initRouteEvents()
101 this.scrollService.enableScrollRestoration()
102
103 this.injectJS()
104 this.injectCSS()
105 this.injectBroadcastMessage()
106
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
117 this.initHotkeys()
118
119 this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS))
120
121 this.openModalsIfNeeded()
122
123 this.document.documentElement.lang = getShortLocale(this.localeId)
124 this.document.documentElement.dir = getLocaleDirection(this.localeId)
125 }
126
127 ngAfterViewInit () {
128 this.pluginService.initializeCustomModal(this.customModal)
129 }
130
131 getToggleTitle () {
132 if (this.menu.isDisplayed()) return $localize`Close the left menu`
133
134 return $localize`Open the left menu`
135 }
136
137 isUserLoggedIn () {
138 return this.authService.isLoggedIn()
139 }
140
141 hideBroadcastMessage () {
142 peertubeLocalStorage.setItem(AppComponent.BROADCAST_MESSAGE_KEY, this.serverConfig.broadcastMessage.message)
143
144 this.broadcastMessage = null
145 this.screenService.isBroadcastMessageDisplayed = false
146 }
147
148 private initRouteEvents () {
149 const eventsObs = this.router.events
150
151 // Plugin hooks
152 this.peertubeRouter.getNavigationEndEvents().subscribe(e => {
153 this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url })
154 })
155
156 // Automatically hide/display the menu
157 eventsObs.pipe(
158 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
159 filter(() => this.screenService.isInSmallView() || this.screenService.isInTouchScreen())
160 ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page
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())
170 }
171
172 private async injectBroadcastMessage () {
173 this.broadcastMessage = null
174 this.screenService.isBroadcastMessageDisplayed = false
175
176 const messageConfig = this.serverConfig.broadcastMessage
177
178 if (messageConfig.enabled) {
179 // Already dismissed this message?
180 if (messageConfig.dismissable && localStorage.getItem(AppComponent.BROADCAST_MESSAGE_KEY) === messageConfig.message) {
181 return
182 }
183
184 const classes: { [id in BroadcastMessageLevel]: string } = {
185 info: 'alert-info',
186 warning: 'alert-warning',
187 error: 'alert-danger'
188 }
189
190 this.broadcastMessage = {
191 message: await this.markdownService.unsafeMarkdownToHTML(messageConfig.message, true),
192 dismissable: messageConfig.dismissable,
193 class: classes[messageConfig.level]
194 }
195
196 this.screenService.isBroadcastMessageDisplayed = true
197 }
198 }
199
200 private injectJS () {
201 // Inject JS
202 if (this.serverConfig.instance.customizations.javascript) {
203 try {
204 /* eslint-disable no-eval */
205 eval(this.serverConfig.instance.customizations.javascript)
206 } catch (err) {
207 console.error('Cannot eval custom JavaScript.', err)
208 }
209 }
210 }
211
212 private injectCSS () {
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 }
221 }
222
223 private async openModalsIfNeeded () {
224 this.authService.userInformationLoaded
225 .pipe(
226 map(() => this.authService.getUser()),
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))
233 }
234
235 private async _openAdminModalsIfNeeded (serverConfig: ServerConfig, user: User) {
236 if (user.noWelcomeModal !== true) return this.welcomeModal.show()
237
238 if (user.noInstanceConfigWarningModal === true || !serverConfig.signup.allowed) return
239
240 this.instanceService.getAbout()
241 .subscribe(about => {
242 if (
243 this.serverConfig.instance.name.toLowerCase() === 'peertube' ||
244 !about.instance.terms ||
245 !about.instance.administrator ||
246 !about.instance.maintenanceLifetime
247 ) {
248 this.instanceConfigWarningModal.show(about)
249 }
250 })
251 }
252
253 private initHotkeys () {
254 this.hotkeysService.add([
255 new Hotkey([ '/', 's' ], (event: KeyboardEvent): boolean => {
256 document.getElementById('search-video').focus()
257 return false
258 }, undefined, $localize`Focus the search bar`),
259
260 new Hotkey('b', (event: KeyboardEvent): boolean => {
261 this.menu.toggleMenu()
262 return false
263 }, undefined, $localize`Toggle the left menu`),
264
265 new Hotkey('g o', (event: KeyboardEvent): boolean => {
266 this.router.navigate([ '/videos/overview' ])
267 return false
268 }, undefined, $localize`Go to the discover videos page`),
269
270 new Hotkey('g t', (event: KeyboardEvent): boolean => {
271 this.router.navigate([ '/videos/trending' ])
272 return false
273 }, undefined, $localize`Go to the trending videos page`),
274
275 new Hotkey('g r', (event: KeyboardEvent): boolean => {
276 this.router.navigate([ '/videos/recently-added' ])
277 return false
278 }, undefined, $localize`Go to the recently added videos page`),
279
280 new Hotkey('g l', (event: KeyboardEvent): boolean => {
281 this.router.navigate([ '/videos/local' ])
282 return false
283 }, undefined, $localize`Go to the local videos page`),
284
285 new Hotkey('g u', (event: KeyboardEvent): boolean => {
286 this.router.navigate([ '/videos/upload' ])
287 return false
288 }, undefined, $localize`Go to the videos upload page`)
289 ])
290 }
291 }