2019-07-11 14:06:01 +02:00
|
|
|
import { Component, HostListener, OnInit, ViewChild } from '@angular/core';
|
2019-07-26 11:46:59 +02:00
|
|
|
import { FormControl, FormGroup } from '@angular/forms';
|
2019-07-01 11:23:33 +02:00
|
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
|
|
import { MatSidenav } from '@angular/material/sidenav';
|
|
|
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
2019-03-11 10:38:18 +01:00
|
|
|
import { Title } from '@angular/platform-browser';
|
2019-07-26 11:46:59 +02:00
|
|
|
import { ActivationEnd, NavigationEnd, Router } from '@angular/router';
|
2018-06-13 18:34:10 +02:00
|
|
|
|
2018-08-29 13:21:25 +02:00
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
2019-07-26 11:46:59 +02:00
|
|
|
import { filter } from 'rxjs/operators';
|
2018-11-07 08:43:48 +01:00
|
|
|
|
2019-07-26 11:46:59 +02:00
|
|
|
import { navItemAnim, pageTransition } from '../shared/animations';
|
2019-08-20 14:57:20 +02:00
|
|
|
import { OfflineService } from 'app/core/core-services/offline.service';
|
2019-07-11 14:06:01 +02:00
|
|
|
import { OverlayService } from 'app/core/ui-services/overlay.service';
|
2019-07-26 11:46:59 +02:00
|
|
|
import { UpdateService } from 'app/core/ui-services/update.service';
|
|
|
|
import { langToLocale } from 'app/shared/utils/lang-to-locale';
|
2019-01-31 13:40:27 +01:00
|
|
|
import { AuthService } from '../core/core-services/auth.service';
|
2018-11-07 08:43:48 +01:00
|
|
|
import { BaseComponent } from '../base.component';
|
2019-01-31 13:40:27 +01:00
|
|
|
import { MainMenuService } from '../core/core-services/main-menu.service';
|
|
|
|
import { OpenSlidesStatusService } from '../core/core-services/openslides-status.service';
|
2019-07-26 11:46:59 +02:00
|
|
|
import { OperatorService } from '../core/core-services/operator.service';
|
2019-01-31 13:40:27 +01:00
|
|
|
import { TimeTravelService } from '../core/core-services/time-travel.service';
|
2019-07-26 11:46:59 +02:00
|
|
|
import { ViewportService } from '../core/ui-services/viewport.service';
|
2019-05-10 13:04:40 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Interface to describe possible routing data
|
|
|
|
*/
|
|
|
|
interface RoutingData {
|
|
|
|
basePerm?: string;
|
|
|
|
noInterruption?: boolean;
|
|
|
|
}
|
2018-07-03 11:52:16 +02:00
|
|
|
|
2018-06-13 18:34:10 +02:00
|
|
|
@Component({
|
2018-09-03 17:57:20 +02:00
|
|
|
selector: 'os-site',
|
2018-07-31 15:46:55 +02:00
|
|
|
animations: [pageTransition, navItemAnim],
|
2018-06-16 18:05:46 +02:00
|
|
|
templateUrl: './site.component.html',
|
2018-07-26 16:40:34 +02:00
|
|
|
styleUrls: ['./site.component.scss']
|
2018-06-13 18:34:10 +02:00
|
|
|
})
|
2018-07-04 17:51:31 +02:00
|
|
|
export class SiteComponent extends BaseComponent implements OnInit {
|
2018-08-02 16:39:08 +02:00
|
|
|
/**
|
2018-08-23 16:49:51 +02:00
|
|
|
* HTML element of the side panel
|
2018-08-02 16:39:08 +02:00
|
|
|
*/
|
2019-07-01 11:23:33 +02:00
|
|
|
@ViewChild('sideNav', { static: true })
|
2018-09-18 18:27:14 +02:00
|
|
|
public sideNav: MatSidenav;
|
2018-06-13 18:34:10 +02:00
|
|
|
|
2018-08-02 16:39:08 +02:00
|
|
|
/**
|
2018-08-23 16:49:51 +02:00
|
|
|
* Get the username from the operator (should be known already)
|
2018-08-02 16:39:08 +02:00
|
|
|
*/
|
2018-08-28 11:07:10 +02:00
|
|
|
public username: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* is the user logged in, or the anonymous is active.
|
|
|
|
*/
|
|
|
|
public isLoggedIn: boolean;
|
2018-08-07 12:30:21 +02:00
|
|
|
|
2019-08-20 14:57:20 +02:00
|
|
|
/**
|
|
|
|
* Indicates, whether the user is offline or not.
|
|
|
|
*/
|
|
|
|
public isOffline: boolean;
|
|
|
|
|
2018-11-07 08:43:48 +01:00
|
|
|
/**
|
|
|
|
* Holds the typed search query.
|
|
|
|
*/
|
|
|
|
public searchform: FormGroup;
|
|
|
|
|
2019-05-10 13:04:40 +02:00
|
|
|
/**
|
|
|
|
* Hold the current routing data to make certain checks
|
|
|
|
*/
|
|
|
|
private routingData: RoutingData;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set to true if an update was suppressed
|
|
|
|
*/
|
|
|
|
private delayedUpdateAvailable = false;
|
|
|
|
|
2018-08-02 16:39:08 +02:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
* @param authService
|
2018-11-09 13:44:39 +01:00
|
|
|
* @param route
|
2018-08-02 16:39:08 +02:00
|
|
|
* @param operator
|
2018-08-28 11:07:10 +02:00
|
|
|
* @param vp
|
2018-08-02 16:39:08 +02:00
|
|
|
* @param translate
|
|
|
|
* @param dialog
|
2018-11-09 13:44:39 +01:00
|
|
|
* @param mainMenuService
|
|
|
|
* @param OSStatus
|
|
|
|
* @param timeTravel
|
2018-08-02 16:39:08 +02:00
|
|
|
*/
|
2018-08-29 13:21:25 +02:00
|
|
|
public constructor(
|
2019-03-11 10:38:18 +01:00
|
|
|
title: Title,
|
2019-05-29 09:42:34 +02:00
|
|
|
protected translate: TranslateService,
|
2019-08-20 14:57:20 +02:00
|
|
|
offlineService: OfflineService,
|
2019-05-10 13:04:40 +02:00
|
|
|
private updateService: UpdateService,
|
2018-06-25 17:03:52 +02:00
|
|
|
private authService: AuthService,
|
2018-09-18 18:27:14 +02:00
|
|
|
private router: Router,
|
2018-09-06 07:14:55 +02:00
|
|
|
public operator: OperatorService,
|
2018-08-23 16:49:51 +02:00
|
|
|
public vp: ViewportService,
|
2018-09-20 13:03:51 +02:00
|
|
|
public dialog: MatDialog,
|
2018-11-09 13:44:39 +01:00
|
|
|
public mainMenuService: MainMenuService,
|
|
|
|
public OSStatus: OpenSlidesStatusService,
|
2019-05-10 13:04:40 +02:00
|
|
|
public timeTravel: TimeTravelService,
|
2019-07-11 14:06:01 +02:00
|
|
|
private matSnackBar: MatSnackBar,
|
|
|
|
private overlayService: OverlayService
|
2018-07-04 17:51:31 +02:00
|
|
|
) {
|
2019-03-11 10:38:18 +01:00
|
|
|
super(title, translate);
|
2019-07-11 14:06:01 +02:00
|
|
|
overlayService.setSpinner(true, translate.instant('Loading data. Please wait...'));
|
2018-08-28 11:07:10 +02:00
|
|
|
|
2019-02-01 13:56:08 +01:00
|
|
|
this.operator.getViewUserObservable().subscribe(user => {
|
2019-09-02 14:53:28 +02:00
|
|
|
this.username = user ? user.short_name : translate.instant('Guest');
|
2018-08-28 11:07:10 +02:00
|
|
|
this.isLoggedIn = !!user;
|
|
|
|
});
|
2018-11-07 08:43:48 +01:00
|
|
|
|
2019-08-20 14:57:20 +02:00
|
|
|
offlineService.isOffline().subscribe(offline => {
|
|
|
|
this.isOffline = offline;
|
|
|
|
});
|
|
|
|
|
2018-11-07 08:43:48 +01:00
|
|
|
this.searchform = new FormGroup({ query: new FormControl([]) });
|
2019-05-10 13:04:40 +02:00
|
|
|
|
|
|
|
// detect routing data such as base perm and noInterruption
|
|
|
|
this.router.events
|
|
|
|
.pipe(filter(event => event instanceof ActivationEnd && event.snapshot.children.length === 0))
|
|
|
|
.subscribe((event: ActivationEnd) => {
|
|
|
|
this.routingData = event.snapshot.data as RoutingData;
|
|
|
|
|
|
|
|
// if the current route has no "noInterruption" flag and an update is available, show the update
|
|
|
|
if (this.delayedUpdateAvailable && !this.routingData.noInterruption) {
|
|
|
|
this.showUpdateNotification();
|
|
|
|
}
|
|
|
|
});
|
2018-07-04 17:51:31 +02:00
|
|
|
}
|
2018-06-25 17:03:52 +02:00
|
|
|
|
2018-08-02 16:39:08 +02:00
|
|
|
/**
|
|
|
|
* Initialize the site component
|
|
|
|
*/
|
2018-09-07 13:12:59 +02:00
|
|
|
public ngOnInit(): void {
|
2018-08-23 16:49:51 +02:00
|
|
|
this.vp.checkForChange();
|
2018-06-28 17:11:04 +02:00
|
|
|
|
2018-10-05 16:34:08 +02:00
|
|
|
// observe the mainMenuService to receive toggle-requests
|
|
|
|
this.mainMenuService.toggleMenuSubject.subscribe((value: void) => this.toggleSideNav());
|
|
|
|
|
2018-08-22 16:03:49 +02:00
|
|
|
// get a translation via code: use the translation service
|
2018-08-16 17:03:39 +02:00
|
|
|
// this.translate.get('Motions').subscribe((res: string) => {
|
2018-08-22 16:03:49 +02:00
|
|
|
// console.log('translation of motions in the target language: ' + res);
|
|
|
|
// });
|
2018-10-05 16:34:08 +02:00
|
|
|
|
2019-05-07 14:22:39 +02:00
|
|
|
// TODO: Remove this, when the ESR version of Firefox >= 64.
|
|
|
|
const agent = navigator.userAgent.toLowerCase();
|
|
|
|
if (agent.indexOf('firefox') > -1) {
|
|
|
|
const index = agent.indexOf('firefox') + 8;
|
|
|
|
const version = +agent.slice(index, index + 2);
|
|
|
|
|
|
|
|
if (version < 64) {
|
|
|
|
const sideNav = document.querySelector(
|
|
|
|
'mat-sidenav.side-panel > div.mat-drawer-inner-container'
|
|
|
|
) as HTMLElement;
|
|
|
|
sideNav.style.overflow = 'hidden';
|
|
|
|
sideNav.addEventListener('MozMousePixelScroll', (event: any) => {
|
|
|
|
sideNav.scrollBy(0, event.detail);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-05 16:34:08 +02:00
|
|
|
this.router.events.subscribe(event => {
|
|
|
|
// Scroll to top if accessing a page, not via browser history stack
|
|
|
|
if (event instanceof NavigationEnd) {
|
|
|
|
const contentContainer = document.querySelector('.mat-sidenav-content');
|
2019-03-04 11:45:15 +01:00
|
|
|
if (contentContainer) {
|
|
|
|
contentContainer.scrollTo(0, 0);
|
|
|
|
}
|
2018-10-05 16:34:08 +02:00
|
|
|
}
|
|
|
|
});
|
2019-05-10 13:04:40 +02:00
|
|
|
|
|
|
|
// check for updates
|
|
|
|
this.updateService.updateObservable.subscribe(() => {
|
|
|
|
if (this.routingData.noInterruption) {
|
|
|
|
this.delayedUpdateAvailable = true;
|
|
|
|
} else {
|
|
|
|
this.showUpdateNotification();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the update notification
|
|
|
|
*/
|
|
|
|
private showUpdateNotification(): void {
|
2019-05-29 09:42:34 +02:00
|
|
|
const ref = this.matSnackBar.open(
|
|
|
|
this.translate.instant('A new update is available!'),
|
|
|
|
this.translate.instant('Refresh'),
|
|
|
|
{
|
|
|
|
duration: 0
|
|
|
|
}
|
|
|
|
);
|
2019-05-10 13:04:40 +02:00
|
|
|
|
|
|
|
// Enforces an update
|
|
|
|
ref.onAction().subscribe(() => {
|
|
|
|
this.updateService.applyUpdate();
|
|
|
|
});
|
2018-08-24 13:05:03 +02:00
|
|
|
}
|
|
|
|
|
2018-08-23 16:49:51 +02:00
|
|
|
/**
|
2019-01-19 11:07:37 +01:00
|
|
|
* Toggles the side nav
|
2018-08-23 16:49:51 +02:00
|
|
|
*/
|
2018-09-07 13:12:59 +02:00
|
|
|
public toggleSideNav(): void {
|
2019-01-17 14:07:54 +01:00
|
|
|
this.sideNav.toggle();
|
2018-08-23 16:49:51 +02:00
|
|
|
}
|
|
|
|
|
2019-01-19 11:07:37 +01:00
|
|
|
/**
|
|
|
|
* Automatically close the navigation in while navigating in mobile mode
|
|
|
|
*/
|
|
|
|
public mobileAutoCloseNav(): void {
|
|
|
|
if (this.vp.isMobile) {
|
|
|
|
this.sideNav.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-02 16:39:08 +02:00
|
|
|
/**
|
|
|
|
* Let the user change the language
|
2018-11-06 16:57:36 +01:00
|
|
|
* @param lang the desired language (en, de, cs, ...)
|
2018-08-02 16:39:08 +02:00
|
|
|
*/
|
2018-08-29 13:21:25 +02:00
|
|
|
public selectLang(selection: string): void {
|
2018-08-07 12:30:21 +02:00
|
|
|
this.translate.use(selection).subscribe();
|
|
|
|
}
|
2018-06-29 17:24:44 +02:00
|
|
|
|
2018-08-07 12:30:21 +02:00
|
|
|
/**
|
|
|
|
* Get the name of a Language by abbreviation.
|
2019-03-11 10:38:18 +01:00
|
|
|
*
|
|
|
|
* @param abbreviation The abbreviation of the languate or null, if the current
|
|
|
|
* language should be used.
|
2018-08-07 12:30:21 +02:00
|
|
|
*/
|
2019-03-11 10:38:18 +01:00
|
|
|
public getLangName(abbreviation?: string): string {
|
|
|
|
if (!abbreviation) {
|
|
|
|
abbreviation = this.translate.currentLang;
|
|
|
|
}
|
|
|
|
|
2018-08-07 12:30:21 +02:00
|
|
|
if (abbreviation === 'en') {
|
2019-02-14 22:17:16 +01:00
|
|
|
return 'English';
|
2018-08-07 12:30:21 +02:00
|
|
|
} else if (abbreviation === 'de') {
|
2019-02-14 22:17:16 +01:00
|
|
|
return 'Deutsch';
|
2018-11-06 16:57:36 +01:00
|
|
|
} else if (abbreviation === 'cs') {
|
2019-02-14 22:17:16 +01:00
|
|
|
return 'Čeština';
|
2019-09-04 09:34:55 +02:00
|
|
|
} else if (abbreviation === 'ru') {
|
|
|
|
return 'русский';
|
2018-08-07 12:30:21 +02:00
|
|
|
}
|
2018-06-25 17:03:52 +02:00
|
|
|
}
|
2018-06-13 18:34:10 +02:00
|
|
|
|
2018-08-02 16:39:08 +02:00
|
|
|
/**
|
|
|
|
* Function to log out the current user
|
|
|
|
*/
|
2018-09-07 13:12:59 +02:00
|
|
|
public logout(): void {
|
2018-08-28 11:07:10 +02:00
|
|
|
this.authService.logout();
|
2019-07-11 14:06:01 +02:00
|
|
|
this.overlayService.logout();
|
2018-06-19 16:55:50 +02:00
|
|
|
}
|
2018-10-05 16:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle swipes and gestures
|
|
|
|
*/
|
|
|
|
public swipe(e: TouchEvent, when: string): void {
|
|
|
|
const coord: [number, number] = [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
|
|
|
|
const time = new Date().getTime();
|
|
|
|
|
|
|
|
if (when === 'start') {
|
|
|
|
this.swipeCoord = coord;
|
|
|
|
this.swipeTime = time;
|
|
|
|
} else if (when === 'end') {
|
|
|
|
const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]];
|
|
|
|
const duration = time - this.swipeTime;
|
|
|
|
if (
|
|
|
|
duration < 1000 &&
|
|
|
|
Math.abs(direction[0]) > 30 && // swipe length to be detected
|
2019-02-26 15:30:16 +01:00
|
|
|
Math.abs(direction[0]) > Math.abs(direction[1] * 3) // 30° should be "horizontal enough"
|
2018-10-05 16:34:08 +02:00
|
|
|
) {
|
2019-02-26 15:30:16 +01:00
|
|
|
// definition of a "swipe right" gesture to move in the navigation
|
|
|
|
// only works in the far left edge of the screen
|
|
|
|
if (
|
|
|
|
direction[0] > 0 && // swipe left to right
|
|
|
|
this.swipeCoord[0] < 20
|
|
|
|
) {
|
|
|
|
this.sideNav.open();
|
|
|
|
}
|
|
|
|
|
|
|
|
// definition of a "swipe left" gesture to remove the navigation
|
|
|
|
// should only work in mobile mode to prevent unwanted closing of the nav
|
|
|
|
// works anywhere on the screen
|
|
|
|
if (
|
|
|
|
direction[0] < 0 && // swipe left to right
|
|
|
|
this.vp.isMobile
|
|
|
|
) {
|
|
|
|
this.sideNav.close();
|
|
|
|
}
|
2018-10-05 16:34:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-07 08:43:48 +01:00
|
|
|
|
2019-02-25 14:19:56 +01:00
|
|
|
/**
|
|
|
|
* Get the timestamp for the current point in history mode.
|
|
|
|
* Tries to detect the ideal timestamp format using the translation service
|
|
|
|
*
|
|
|
|
* @returns the timestamp as string
|
|
|
|
*/
|
|
|
|
public getHistoryTimestamp(): string {
|
|
|
|
return this.OSStatus.getHistoryTimeStamp(langToLocale(this.translate.currentLang));
|
|
|
|
}
|
2019-07-11 14:06:01 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to open the global `super-search.component`.
|
|
|
|
*
|
|
|
|
* @param event KeyboardEvent to listen to keyboard-inputs.
|
|
|
|
*/
|
|
|
|
@HostListener('document:keydown', ['$event']) public onKeyNavigation(event: KeyboardEvent): void {
|
|
|
|
if (event.altKey && event.shiftKey && event.code === 'KeyF') {
|
2019-09-04 11:15:57 +02:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2019-07-11 14:06:01 +02:00
|
|
|
this.overlayService.showSearch();
|
|
|
|
}
|
|
|
|
}
|
2018-06-13 18:34:10 +02:00
|
|
|
}
|