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