]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/app.component.ts
decouple video abuse details from embed, add embed to block list details
[github/Chocobozzz/PeerTube.git] / client / src / app / app.component.ts
CommitLineData
67ed6552
C
1import { Hotkey, HotkeysService } from 'angular2-hotkeys'
2import { concat } from 'rxjs'
3import { filter, first, map, pairwise } from 'rxjs/operators'
4import { DOCUMENT, PlatformLocation, ViewportScroller } from '@angular/common'
5import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
00b5556c 6import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
489290b8 7import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router'
67ed6552 8import { AuthService, MarkdownService, RedirectService, ScreenService, ServerService, ThemeService, User } from '@app/core'
93cae479 9import { HooksService } from '@app/core/plugins/hooks.service'
67ed6552 10import { PluginService } from '@app/core/plugins/plugin.service'
437e8e06 11import { CustomModalComponent } from '@app/modal/custom-modal.component'
67ed6552
C
12import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
13import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
14import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
15import { I18n } from '@ngx-translate/i18n-polyfill'
16import { BroadcastMessageLevel, getShortLocale, is18nPath, ServerConfig, UserRole } from '@shared/models'
3b20bdd6 17import { MenuService } from './core/menu/menu.service'
67ed6552
C
18import { peertubeLocalStorage, POP_STATE_MODAL_DISMISS } from './helpers'
19import { InstanceService } from './shared/shared-instance'
e2a2d6c8 20
dc8bc31b 21@Component({
3154f382
C
22 selector: 'my-app',
23 templateUrl: './app.component.html',
24 styleUrls: [ './app.component.scss' ]
dc8bc31b 25})
437e8e06 26export class AppComponent implements OnInit, AfterViewInit {
72c33e71
C
27 private static BROADCAST_MESSAGE_KEY = 'app-broadcast-message-dismissed'
28
2f5d2ec5
C
29 @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent
30 @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent
437e8e06 31 @ViewChild('customModal') customModal: CustomModalComponent
43d0ea7f 32
00b5556c 33 customCSS: SafeHtml
72c33e71 34 broadcastMessage: { message: string, dismissable: boolean, class: string } | null = null
00b5556c 35
ba430d75
C
36 private serverConfig: ServerConfig
37
df98563e 38 constructor (
140ea386 39 @Inject(DOCUMENT) private document: Document,
81fe3c67 40 @Inject(LOCALE_ID) private localeId: string,
e33f888b 41 private i18n: I18n,
489290b8 42 private viewportScroller: ViewportScroller,
3154f382 43 private router: Router,
e2a2d6c8 44 private authService: AuthService,
00b5556c 45 private serverService: ServerService,
18a6f04c 46 private pluginService: PluginService,
43d0ea7f 47 private instanceService: InstanceService,
901637bb 48 private domSanitizer: DomSanitizer,
bbe0f064 49 private redirectService: RedirectService,
ee1fc23a 50 private screenService: ScreenService,
1a00c561 51 private hotkeysService: HotkeysService,
93cae479 52 private themeService: ThemeService,
4334445d
C
53 private hooks: HooksService,
54 private location: PlatformLocation,
3b20bdd6 55 private modalService: NgbModal,
72c33e71 56 private markdownService: MarkdownService,
3b20bdd6 57 public menu: MenuService
989e526a 58 ) { }
a99593ed 59
36f9424f 60 get instanceName () {
ba430d75 61 return this.serverConfig.instance.name
36f9424f
C
62 }
63
29f9b562
C
64 get defaultRoute () {
65 return RedirectService.DEFAULT_ROUTE
66 }
67
df98563e 68 ngOnInit () {
b3eeb529 69 document.getElementById('incompatible-browser').className += ' browser-ok'
73e09f27 70
ba430d75
C
71 this.serverConfig = this.serverService.getTmpConfig()
72 this.serverService.getConfig()
73 .subscribe(config => this.serverConfig = config)
74
a2ffd046
C
75 this.loadPlugins()
76 this.themeService.initialize()
77
d592e0a9
C
78 this.authService.loadClientCredentials()
79
d414207f 80 if (this.isUserLoggedIn()) {
e2a2d6c8 81 // The service will automatically redirect to the login page if the token is not valid anymore
bcd9f81e 82 this.authService.refreshUserInformation()
e2a2d6c8 83 }
6e07c3de 84
489290b8
C
85 this.initRouteEvents()
86 this.injectJS()
87 this.injectCSS()
72c33e71 88 this.injectBroadcastMessage()
489290b8
C
89
90 this.initHotkeys()
91
60c2bc80 92 this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS))
43d0ea7f
C
93
94 this.openModalsIfNeeded()
81fe3c67
RK
95
96 this.document.documentElement.lang = getShortLocale(this.localeId)
489290b8
C
97 }
98
437e8e06
K
99 ngAfterViewInit () {
100 this.pluginService.initializeCustomModal(this.customModal)
101 }
102
489290b8
C
103 isUserLoggedIn () {
104 return this.authService.isLoggedIn()
105 }
106
72c33e71
C
107 hideBroadcastMessage () {
108 peertubeLocalStorage.setItem(AppComponent.BROADCAST_MESSAGE_KEY, this.serverConfig.broadcastMessage.message)
109
110 this.broadcastMessage = null
111 }
112
489290b8
C
113 private initRouteEvents () {
114 let resetScroll = true
115 const eventsObs = this.router.events
116
117 const scrollEvent = eventsObs.pipe(filter((e: Event): e is Scroll => e instanceof Scroll))
489290b8
C
118
119 scrollEvent.subscribe(e => {
120 if (e.position) {
121 return this.viewportScroller.scrollToPosition(e.position)
c8cf5952 122 }
00b5556c 123
489290b8
C
124 if (e.anchor) {
125 return this.viewportScroller.scrollToAnchor(e.anchor)
126 }
127
128 if (resetScroll) {
129 return this.viewportScroller.scrollToPosition([ 0, 0 ])
130 }
131 })
132
60c2bc80
C
133 const navigationEndEvent = eventsObs.pipe(filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd))
134
489290b8
C
135 // When we add the a-state parameter, we don't want to alter the scroll
136 navigationEndEvent.pipe(pairwise())
137 .subscribe(([ e1, e2 ]) => {
138 try {
139 resetScroll = false
140
722bca90
C
141 const previousUrl = new URL(window.location.origin + e1.urlAfterRedirects)
142 const nextUrl = new URL(window.location.origin + e2.urlAfterRedirects)
489290b8
C
143
144 if (previousUrl.pathname !== nextUrl.pathname) {
145 resetScroll = true
146 return
147 }
148
149 const nextSearchParams = nextUrl.searchParams
150 nextSearchParams.delete('a-state')
151
152 const previousSearchParams = previousUrl.searchParams
153
154 nextSearchParams.sort()
155 previousSearchParams.sort()
156
157 if (nextSearchParams.toString() !== previousSearchParams.toString()) {
158 resetScroll = true
159 }
160 } catch (e) {
161 console.error('Cannot parse URL to check next scroll.', e)
162 resetScroll = true
163 }
164 })
165
166 navigationEndEvent.pipe(
167 map(() => window.location.pathname),
168 filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
169 ).subscribe(() => this.redirectService.redirectToHomepage(true))
170
23bdacf8
C
171 navigationEndEvent.subscribe(e => {
172 this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url })
173 })
174
489290b8
C
175 eventsObs.pipe(
176 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
177 filter(() => this.screenService.isInSmallView())
3b20bdd6 178 ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
489290b8
C
179 }
180
72c33e71
C
181 private injectBroadcastMessage () {
182 concat(
183 this.serverService.getConfig().pipe(first()),
184 this.serverService.configReloaded
185 ).subscribe(async config => {
186 this.broadcastMessage = null
187
188 const messageConfig = config.broadcastMessage
189
190 if (messageConfig.enabled) {
191 // Already dismissed this message?
192 if (messageConfig.dismissable && localStorage.getItem(AppComponent.BROADCAST_MESSAGE_KEY) === messageConfig.message) {
193 return
194 }
195
196 const classes: { [id in BroadcastMessageLevel]: string } = {
197 info: 'alert-info',
198 warning: 'alert-warning',
199 error: 'alert-danger'
200 }
201
202 this.broadcastMessage = {
203 message: await this.markdownService.completeMarkdownToHTML(messageConfig.message),
204 dismissable: messageConfig.dismissable,
205 class: classes[messageConfig.level]
206 }
207 }
208 })
209 }
210
489290b8 211 private injectJS () {
e032aec9 212 // Inject JS
ba430d75
C
213 this.serverService.getConfig()
214 .subscribe(config => {
e032aec9
C
215 if (config.instance.customizations.javascript) {
216 try {
217 // tslint:disable:no-eval
218 eval(config.instance.customizations.javascript)
219 } catch (err) {
220 console.error('Cannot eval custom JavaScript.', err)
221 }
222 }
223 })
489290b8 224 }
00b5556c 225
489290b8 226 private injectCSS () {
e032aec9 227 // Inject CSS if modified (admin config settings)
72c33e71
C
228 concat(
229 this.serverService.getConfig().pipe(first()),
230 this.serverService.configReloaded
231 ).subscribe(config => {
232 const headStyle = document.querySelector('style.custom-css-style')
233 if (headStyle) headStyle.parentNode.removeChild(headStyle)
234
235 // We test customCSS if the admin removed the css
236 if (this.customCSS || config.instance.customizations.css) {
237 const styleTag = '<style>' + config.instance.customizations.css + '</style>'
238 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
239 }
240 })
489290b8 241 }
ee1fc23a 242
18a6f04c
C
243 private async loadPlugins () {
244 this.pluginService.initializePlugins()
245
c9e3eeed 246 this.hooks.runAction('action:application.init', 'common')
18a6f04c
C
247 }
248
43d0ea7f 249 private async openModalsIfNeeded () {
ba430d75 250 this.authService.userInformationLoaded
43d0ea7f 251 .pipe(
43d0ea7f
C
252 map(() => this.authService.getUser()),
253 filter(user => user.role === UserRole.ADMINISTRATOR)
ba430d75 254 ).subscribe(user => setTimeout(() => this._openAdminModalsIfNeeded(user))) // setTimeout because of ngIf in template
43d0ea7f
C
255 }
256
ba430d75 257 private async _openAdminModalsIfNeeded (user: User) {
43d0ea7f
C
258 if (user.noWelcomeModal !== true) return this.welcomeModal.show()
259
ba430d75 260 if (user.noInstanceConfigWarningModal === true || !this.serverConfig.signup.allowed) return
589d9f55
C
261
262 this.instanceService.getAbout()
263 .subscribe(about => {
264 if (
ba430d75 265 this.serverConfig.instance.name.toLowerCase() === 'peertube' ||
589d9f55
C
266 !about.instance.terms ||
267 !about.instance.administrator ||
268 !about.instance.maintenanceLifetime
269 ) {
270 this.instanceConfigWarningModal.show(about)
271 }
272 })
43d0ea7f
C
273 }
274
489290b8 275 private initHotkeys () {
ee1fc23a 276 this.hotkeysService.add([
8542dc33 277 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => {
ee1fc23a 278 document.getElementById('search-video').focus()
8542dc33 279 return false
e33f888b 280 }, undefined, this.i18n('Focus the search bar')),
43d0ea7f 281
8542dc33 282 new Hotkey('b', (event: KeyboardEvent): boolean => {
3b20bdd6 283 this.menu.toggleMenu()
8542dc33 284 return false
e33f888b 285 }, undefined, this.i18n('Toggle the left menu')),
43d0ea7f 286
20d21199 287 new Hotkey('g o', (event: KeyboardEvent): boolean => {
a54991da
RK
288 this.router.navigate([ '/videos/overview' ])
289 return false
79a89941 290 }, undefined, this.i18n('Go to the discover videos page')),
43d0ea7f 291
20d21199 292 new Hotkey('g t', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
293 this.router.navigate([ '/videos/trending' ])
294 return false
e33f888b 295 }, undefined, this.i18n('Go to the trending videos page')),
43d0ea7f 296
20d21199 297 new Hotkey('g r', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
298 this.router.navigate([ '/videos/recently-added' ])
299 return false
e33f888b 300 }, undefined, this.i18n('Go to the recently added videos page')),
43d0ea7f 301
20d21199 302 new Hotkey('g l', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
303 this.router.navigate([ '/videos/local' ])
304 return false
e33f888b 305 }, undefined, this.i18n('Go to the local videos page')),
43d0ea7f 306
20d21199 307 new Hotkey('g u', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
308 this.router.navigate([ '/videos/upload' ])
309 return false
ffb321be 310 }, undefined, this.i18n('Go to the videos upload page'))
ee1fc23a 311 ])
67167390 312 }
dc8bc31b 313}