]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/app.component.ts
Lazy load client script scopes
[github/Chocobozzz/PeerTube.git] / client / src / app / app.component.ts
CommitLineData
fd45e8f4 1import { Component, OnInit } from '@angular/core'
00b5556c 2import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
489290b8 3import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router'
1a00c561 4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
989e526a 5import { is18nPath } from '../../../shared/models/i18n'
bbe0f064 6import { ScreenService } from '@app/shared/misc/screen.service'
489290b8
C
7import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators'
8import { Hotkey, HotkeysService } from 'angular2-hotkeys'
e33f888b 9import { I18n } from '@ngx-translate/i18n-polyfill'
a5858241 10import { fromEvent } from 'rxjs'
489290b8 11import { ViewportScroller } from '@angular/common'
18a6f04c 12import { PluginService } from '@app/core/plugins/plugin.service'
93cae479 13import { HooksService } from '@app/core/plugins/hooks.service'
e2a2d6c8 14
dc8bc31b 15@Component({
3154f382
C
16 selector: 'my-app',
17 templateUrl: './app.component.html',
18 styleUrls: [ './app.component.scss' ]
dc8bc31b 19})
e2a2d6c8 20export class AppComponent implements OnInit {
df98563e 21 isMenuDisplayed = true
a5858241 22 isMenuChangedByUser = false
67167390 23
00b5556c
C
24 customCSS: SafeHtml
25
df98563e 26 constructor (
e33f888b 27 private i18n: I18n,
489290b8 28 private viewportScroller: ViewportScroller,
3154f382 29 private router: Router,
e2a2d6c8 30 private authService: AuthService,
00b5556c 31 private serverService: ServerService,
18a6f04c 32 private pluginService: PluginService,
901637bb 33 private domSanitizer: DomSanitizer,
bbe0f064 34 private redirectService: RedirectService,
ee1fc23a 35 private screenService: ScreenService,
1a00c561 36 private hotkeysService: HotkeysService,
93cae479
C
37 private themeService: ThemeService,
38 private hooks: HooksService
989e526a 39 ) { }
a99593ed 40
915c5bbe
C
41 get serverVersion () {
42 return this.serverService.getConfig().serverVersion
43 }
44
abb2c792
RK
45 get serverCommit () {
46 const commit = this.serverService.getConfig().serverCommit || ''
47 return (commit !== '') ? '...' + commit : commit
48 }
49
36f9424f
C
50 get instanceName () {
51 return this.serverService.getConfig().instance.name
52 }
53
29f9b562
C
54 get defaultRoute () {
55 return RedirectService.DEFAULT_ROUTE
56 }
57
df98563e 58 ngOnInit () {
b3eeb529 59 document.getElementById('incompatible-browser').className += ' browser-ok'
73e09f27 60
d592e0a9
C
61 this.authService.loadClientCredentials()
62
d414207f 63 if (this.isUserLoggedIn()) {
e2a2d6c8 64 // The service will automatically redirect to the login page if the token is not valid anymore
bcd9f81e 65 this.authService.refreshUserInformation()
e2a2d6c8 66 }
6e07c3de 67
db7af09b
C
68 // Load custom data from server
69 this.serverService.loadConfig()
70 this.serverService.loadVideoCategories()
71 this.serverService.loadVideoLanguages()
72 this.serverService.loadVideoLicences()
fd45e8f4 73 this.serverService.loadVideoPrivacies()
830b4faf 74 this.serverService.loadVideoPlaylistPrivacies()
3eeeb87f 75
18a6f04c 76 this.loadPlugins()
ffb321be 77 this.themeService.initialize()
18a6f04c 78
3eeeb87f 79 // Do not display menu on small screens
bbe0f064 80 if (this.screenService.isInSmallView()) {
df98563e 81 this.isMenuDisplayed = false
3eeeb87f 82 }
c8cf5952 83
489290b8
C
84 this.initRouteEvents()
85 this.injectJS()
86 this.injectCSS()
87
88 this.initHotkeys()
89
90 fromEvent(window, 'resize')
91 .pipe(debounceTime(200))
92 .subscribe(() => this.onResize())
93 }
94
95 isUserLoggedIn () {
96 return this.authService.isLoggedIn()
97 }
98
99 toggleMenu () {
100 this.isMenuDisplayed = !this.isMenuDisplayed
101 this.isMenuChangedByUser = true
102 }
103
104 onResize () {
105 this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
106 }
107
108 private initRouteEvents () {
109 let resetScroll = true
110 const eventsObs = this.router.events
111
112 const scrollEvent = eventsObs.pipe(filter((e: Event): e is Scroll => e instanceof Scroll))
113 const navigationEndEvent = eventsObs.pipe(filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd))
114
115 scrollEvent.subscribe(e => {
116 if (e.position) {
117 return this.viewportScroller.scrollToPosition(e.position)
c8cf5952 118 }
00b5556c 119
489290b8
C
120 if (e.anchor) {
121 return this.viewportScroller.scrollToAnchor(e.anchor)
122 }
123
124 if (resetScroll) {
125 return this.viewportScroller.scrollToPosition([ 0, 0 ])
126 }
127 })
128
129 // When we add the a-state parameter, we don't want to alter the scroll
130 navigationEndEvent.pipe(pairwise())
131 .subscribe(([ e1, e2 ]) => {
132 try {
133 resetScroll = false
134
722bca90
C
135 const previousUrl = new URL(window.location.origin + e1.urlAfterRedirects)
136 const nextUrl = new URL(window.location.origin + e2.urlAfterRedirects)
489290b8
C
137
138 if (previousUrl.pathname !== nextUrl.pathname) {
139 resetScroll = true
140 return
141 }
142
143 const nextSearchParams = nextUrl.searchParams
144 nextSearchParams.delete('a-state')
145
146 const previousSearchParams = previousUrl.searchParams
147
148 nextSearchParams.sort()
149 previousSearchParams.sort()
150
151 if (nextSearchParams.toString() !== previousSearchParams.toString()) {
152 resetScroll = true
153 }
154 } catch (e) {
155 console.error('Cannot parse URL to check next scroll.', e)
156 resetScroll = true
157 }
158 })
159
160 navigationEndEvent.pipe(
161 map(() => window.location.pathname),
162 filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
163 ).subscribe(() => this.redirectService.redirectToHomepage(true))
164
165 eventsObs.pipe(
166 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
167 filter(() => this.screenService.isInSmallView())
168 ).subscribe(() => this.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
169 }
170
171 private injectJS () {
e032aec9 172 // Inject JS
00b5556c 173 this.serverService.configLoaded
e032aec9
C
174 .subscribe(() => {
175 const config = this.serverService.getConfig()
176
177 if (config.instance.customizations.javascript) {
178 try {
179 // tslint:disable:no-eval
180 eval(config.instance.customizations.javascript)
181 } catch (err) {
182 console.error('Cannot eval custom JavaScript.', err)
183 }
184 }
185 })
489290b8 186 }
00b5556c 187
489290b8 188 private injectCSS () {
e032aec9
C
189 // Inject CSS if modified (admin config settings)
190 this.serverService.configLoaded
191 .pipe(skip(1)) // We only want to subscribe to reloads, because the CSS is already injected by the server
192 .subscribe(() => {
193 const headStyle = document.querySelector('style.custom-css-style')
194 if (headStyle) headStyle.parentNode.removeChild(headStyle)
00b5556c 195
e032aec9
C
196 const config = this.serverService.getConfig()
197
198 // We test customCSS if the admin removed the css
199 if (this.customCSS || config.instance.customizations.css) {
200 const styleTag = '<style>' + config.instance.customizations.css + '</style>'
201 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
00b5556c 202 }
e032aec9 203 })
489290b8 204 }
ee1fc23a 205
18a6f04c
C
206 private async loadPlugins () {
207 this.pluginService.initializePlugins()
208
c9e3eeed 209 this.hooks.runAction('action:application.init', 'common')
18a6f04c
C
210 }
211
489290b8 212 private initHotkeys () {
ee1fc23a 213 this.hotkeysService.add([
8542dc33 214 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => {
ee1fc23a 215 document.getElementById('search-video').focus()
8542dc33 216 return false
e33f888b 217 }, undefined, this.i18n('Focus the search bar')),
8542dc33
RK
218 new Hotkey('b', (event: KeyboardEvent): boolean => {
219 this.toggleMenu()
220 return false
e33f888b 221 }, undefined, this.i18n('Toggle the left menu')),
20d21199 222 new Hotkey('g o', (event: KeyboardEvent): boolean => {
a54991da
RK
223 this.router.navigate([ '/videos/overview' ])
224 return false
e33f888b 225 }, undefined, this.i18n('Go to the videos overview page')),
20d21199 226 new Hotkey('g t', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
227 this.router.navigate([ '/videos/trending' ])
228 return false
e33f888b 229 }, undefined, this.i18n('Go to the trending videos page')),
20d21199 230 new Hotkey('g r', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
231 this.router.navigate([ '/videos/recently-added' ])
232 return false
e33f888b 233 }, undefined, this.i18n('Go to the recently added videos page')),
20d21199 234 new Hotkey('g l', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
235 this.router.navigate([ '/videos/local' ])
236 return false
e33f888b 237 }, undefined, this.i18n('Go to the local videos page')),
20d21199 238 new Hotkey('g u', (event: KeyboardEvent): boolean => {
ee1fc23a
RK
239 this.router.navigate([ '/videos/upload' ])
240 return false
ffb321be 241 }, undefined, this.i18n('Go to the videos upload page'))
ee1fc23a 242 ])
67167390 243 }
dc8bc31b 244}