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