1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-11-28 08:58:49 +02:00

upgrading bootstrap, fixing navigation issue

Router active line broke navigation, replacing it with custom function
This commit is contained in:
Patrik J. Braun 2021-04-11 15:59:30 +02:00
parent 669ad818c3
commit 7371fda5a8
24 changed files with 278 additions and 217 deletions

17
.browserslistrc Normal file
View File

@ -0,0 +1,17 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

View File

@ -8,6 +8,9 @@ indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md] [*.md]
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false

View File

@ -7,6 +7,8 @@
"root": "", "root": "",
"sourceRoot": "src/frontend", "sourceRoot": "src/frontend",
"projectType": "application", "projectType": "application",
"schematics": {},
"prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
@ -21,10 +23,10 @@
"src/frontend/assets" "src/frontend/assets"
], ],
"styles": [ "styles": [
"./node_modules/bootstrap/dist/css/bootstrap.min.css", "bootstrap/dist/css/bootstrap.min.css",
"./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", "ngx-bootstrap/datepicker/bs-datepicker.css",
"./node_modules/open-iconic/font/css/open-iconic-bootstrap.css", "open-iconic/font/css/open-iconic-bootstrap.css",
"./node_modules/ngx-toastr/toastr.css", "ngx-toastr/toastr.css",
"src/frontend/styles.css" "src/frontend/styles.css"
], ],
"scripts": [] "scripts": []
@ -34,7 +36,6 @@
"optimization": true, "optimization": true,
"outputHashing": "all", "outputHashing": "all",
"sourceMap": false, "sourceMap": false,
"extractCss": true,
"namedChunks": false, "namedChunks": false,
"aot": true, "aot": true,
"extractLicenses": true, "extractLicenses": true,
@ -75,11 +76,11 @@
"tsConfig": "src/frontend/tsconfig.spec.json", "tsConfig": "src/frontend/tsconfig.spec.json",
"scripts": [], "scripts": [],
"styles": [ "styles": [
"./node_modules/bootstrap/dist/css/bootstrap.min.css", "bootstrap/dist/css/bootstrap.min.css",
"node_modules/ngx-toastr/toastr.css", "ngx-toastr/toastr.css",
"node_modules/bootstrap/dist/css/bootstrap.css", "bootstrap/dist/css/bootstrap.css",
"node_modules/open-iconic/font/css/open-iconic-bootstrap.css", "open-iconic/font/css/open-iconic-bootstrap.css",
"node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", "ngx-bootstrap/datepicker/bs-datepicker.css",
"src/frontend/styles.css" "src/frontend/styles.css"
], ],
"assets": [ "assets": [

View File

@ -263,7 +263,7 @@ gulp.task('extract-locale', async (cb) => {
console.log('creating source translation file: locale.source.xlf'); console.log('creating source translation file: locale.source.xlf');
try { try {
{ {
const {stdout, stderr} = await execPr('ng xi18n --out-file=./../../locale.source.xlf --i18n-format=xlf --i18n-locale=en', const {stdout, stderr} = await execPr('ng extract-i18n --out-file=./../../locale.source.xlf --i18n-format=xlf --i18n-locale=en',
{maxBuffer: 1024 * 1024}); {maxBuffer: 1024 * 1024});
console.log(stdout); console.log(stdout);
console.error(stderr); console.error(stderr);

6
package-lock.json generated
View File

@ -14530,9 +14530,9 @@
"dev": true "dev": true
}, },
"ngx-bootstrap": { "ngx-bootstrap": {
"version": "5.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-6.2.0.tgz",
"integrity": "sha512-fh+OmaEdxCZnVLQFLqexdw4Xv0Lp2Ueq7un52gF26lTENhTAypGWgf2c92HXzbp4W/B0tnwIZ9mzQPwdDMH91w==", "integrity": "sha512-5WKHo6/ltkenw4UyXZwED8rODCgp2RGbWurzYzZsF/gH1JO5SN7TJ+AL6kXYk6XM42sDA2WhN9Db+ZPNjiyHnA==",
"dev": true "dev": true
}, },
"ngx-clipboard": { "ngx-clipboard": {

View File

@ -9,6 +9,7 @@
"bin": "./src/backend/index.js", "bin": "./src/backend/index.js",
"scripts": { "scripts": {
"build": "tsc && gulp build-prod", "build": "tsc && gulp build-prod",
"build-en": "tsc && gulp build-prod --languages=en",
"create-release": "gulp create-release", "create-release": "gulp create-release",
"build-backend": "tsc", "build-backend": "tsc",
"pretest": "tsc", "pretest": "tsc",
@ -120,7 +121,7 @@
"mocha": "8.3.2", "mocha": "8.3.2",
"natural-orderby": "2.0.3", "natural-orderby": "2.0.3",
"ng2-cookies": "1.0.12", "ng2-cookies": "1.0.12",
"ngx-bootstrap": "5.2.0", "ngx-bootstrap": "6.2.0",
"ngx-clipboard": "14.0.1", "ngx-clipboard": "14.0.1",
"ngx-toastr": "13.2.1", "ngx-toastr": "13.2.1",
"nyc": "15.1.0", "nyc": "15.1.0",

View File

@ -17,17 +17,17 @@ export class AppComponent implements OnInit, OnDestroy {
private subscription: Subscription = null; private subscription: Subscription = null;
constructor(private _router: Router, constructor(private router: Router,
private _authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private _shareService: ShareService, private shareService: ShareService,
private _title: Title) { private title: Title) {
} }
async ngOnInit() { async ngOnInit(): Promise<void> {
this._title.setTitle(Config.Client.applicationTitle); this.title.setTitle(Config.Client.applicationTitle);
await this._shareService.wait(); await this.shareService.wait();
this.subscription = this._authenticationService.user.subscribe(() => { this.subscription = this.authenticationService.user.subscribe(() => {
if (this._authenticationService.isAuthenticated()) { if (this.authenticationService.isAuthenticated()) {
if (this.isLoginPage()) { if (this.isLoginPage()) {
return this.toGallery(); return this.toGallery();
} }
@ -41,31 +41,35 @@ export class AppComponent implements OnInit, OnDestroy {
} }
ngOnDestroy() { ngOnDestroy(): void {
if (this.subscription != null) { if (this.subscription != null) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
} }
private isLoginPage() { private isLoginPage(): boolean {
return this._router.isActive('login', true) || this._router.isActive('shareLogin', false); return this.router.isActive('login', true) || this.router.isActive('shareLogin', false);
} }
private toLogin() { private async toLogin(): Promise<void> {
if (this._shareService.isSharing()) { if (this.shareService.isSharing()) {
const q: any = {}; const q: any = {};
q[QueryParams.gallery.sharingKey_query] = this._shareService.getSharingKey(); q[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey();
return this._router.navigate(['shareLogin'], {queryParams: q}); await this.router.navigate(['shareLogin'], {queryParams: q});
return;
} else { } else {
return this._router.navigate(['login']); await this.router.navigate(['login']);
return;
} }
} }
private toGallery() { private async toGallery(): Promise<void> {
if (this._shareService.isSharing()) { if (this.shareService.isSharing()) {
return this._router.navigate(['share', this._shareService.getSharingKey()]); await this.router.navigate(['share', this.shareService.getSharingKey()]);
return;
} else { } else {
return this._router.navigate(['gallery', '']); await this.router.navigate(['gallery', '']);
return;
} }
} }
} }

View File

@ -2,7 +2,6 @@ import {Injectable, LOCALE_ID, NgModule, TRANSLATIONS, TRANSLATIONS_FORMAT} from
import {BrowserModule, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from '@angular/platform-browser'; import {BrowserModule, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {AppComponent} from './app.component'; import {AppComponent} from './app.component';
import {appRoutes} from './app.routing';
import {UserService} from './model/network/user.service'; import {UserService} from './model/network/user.service';
import {GalleryService} from './ui/gallery/gallery.service'; import {GalleryService} from './ui/gallery/gallery.service';
import {NetworkService} from './model/network/network.service'; import {NetworkService} from './model/network/network.service';
@ -12,7 +11,7 @@ import {AuthenticationService} from './model/network/authentication.service';
import {UserMangerSettingsComponent} from './ui/settings/usermanager/usermanager.settings.component'; import {UserMangerSettingsComponent} from './ui/settings/usermanager/usermanager.settings.component';
import {FrameComponent} from './ui/frame/frame.component'; import {FrameComponent} from './ui/frame/frame.component';
import {YagaModule} from '@yaga/leaflet-ng2'; import {YagaModule} from '@yaga/leaflet-ng2';
import { LoadingBarModule } from '@ngx-loading-bar/core'; import {LoadingBarModule} from '@ngx-loading-bar/core';
import {GalleryLightboxMediaComponent} from './ui/gallery/lightbox/media/media.lightbox.gallery.component'; import {GalleryLightboxMediaComponent} from './ui/gallery/lightbox/media/media.lightbox.gallery.component';
import {GalleryPhotoLoadingComponent} from './ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component'; import {GalleryPhotoLoadingComponent} from './ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component';
import {GalleryNavigatorComponent} from './ui/gallery/navigator/navigator.gallery.component'; import {GalleryNavigatorComponent} from './ui/gallery/navigator/navigator.gallery.component';
@ -98,6 +97,7 @@ import {StringifySearchQuery} from './pipes/StringifySearchQuery';
import {AutoCompleteService} from './ui/gallery/search/autocomplete.service'; import {AutoCompleteService} from './ui/gallery/search/autocomplete.service';
import {SearchQueryParserService} from './ui/gallery/search/search-query-parser.service'; import {SearchQueryParserService} from './ui/gallery/search/search-query-parser.service';
import {GallerySearchFieldComponent} from './ui/gallery/search/search-field/search-field.gallery.component'; import {GallerySearchFieldComponent} from './ui/gallery/search/search-field/search-field.gallery.component';
import {AppRoutingModule} from './app.routing';
@Injectable() @Injectable()
@ -112,31 +112,31 @@ export class MyHammerConfig extends HammerGestureConfig {
export class CustomUrlSerializer implements UrlSerializer { export class CustomUrlSerializer implements UrlSerializer {
private _defaultUrlSerializer: DefaultUrlSerializer = new DefaultUrlSerializer(); private defaultUrlSerializer: DefaultUrlSerializer = new DefaultUrlSerializer();
parse(url: string): UrlTree { parse(url: string): UrlTree {
// Encode parentheses // Encode parentheses
url = url.replace(/\(/g, '%28').replace(/\)/g, '%29'); url = url.replace(/\(/g, '%28').replace(/\)/g, '%29');
// Use the default serializer. // Use the default serializer.
return this._defaultUrlSerializer.parse(url); return this.defaultUrlSerializer.parse(url);
} }
serialize(tree: UrlTree): string { serialize(tree: UrlTree): string {
return this._defaultUrlSerializer.serialize(tree).replace(/%28/g, '(').replace(/%29/g, ')'); return this.defaultUrlSerializer.serialize(tree).replace(/%28/g, '(').replace(/%29/g, ')');
} }
} }
// use the require method provided by webpack // use the require method provided by webpack
declare const require: (path: string) => string; declare const require: (path: string) => string;
export function translationsFactory(locale: string) { export function translationsFactory(locale: string): string {
locale = locale || 'en'; // default to english if no locale locale = locale || 'en'; // default to english if no locale
// default locale, nothing to translate // default locale, nothing to translate
if (locale === 'en') { if (locale === 'en') {
return ''; return '';
} }
return (<any>require(`raw-loader!../translate/messages.${locale}.xlf`)).default; return (require(`raw-loader!../translate/messages.${locale}.xlf`) as any).default;
} }
@NgModule({ @NgModule({
@ -145,7 +145,7 @@ export function translationsFactory(locale: string) {
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
BrowserAnimationsModule, BrowserAnimationsModule,
appRoutes, AppRoutingModule,
ClipboardModule, ClipboardModule,
JwBootstrapSwitchNg2Module, JwBootstrapSwitchNg2Module,
TooltipModule.forRoot(), TooltipModule.forRoot(),

View File

@ -1,4 +1,4 @@
import {ModuleWithProviders} from '@angular/core'; import {NgModule} from '@angular/core';
import {RouterModule, Routes, UrlMatchResult, UrlSegment} from '@angular/router'; import {RouterModule, Routes, UrlMatchResult, UrlSegment} from '@angular/router';
import {LoginComponent} from './ui/login/login.component'; import {LoginComponent} from './ui/login/login.component';
import {GalleryComponent} from './ui/gallery/gallery.component'; import {GalleryComponent} from './ui/gallery/gallery.component';
@ -40,7 +40,7 @@ export function galleryMatcherFunction(
return null; return null;
} }
const ROUTES: Routes = [ const routes: Routes = [
{ {
path: 'login', path: 'login',
component: LoginComponent component: LoginComponent
@ -73,7 +73,10 @@ const ROUTES: Routes = [
{path: '**', redirectTo: '/login', pathMatch: 'full'} {path: '**', redirectTo: '/login', pathMatch: 'full'}
]; ];
export const appRoutes: ModuleWithProviders<RouterModule> = RouterModule.forRoot(ROUTES, {
anchorScrolling: 'enabled'
});
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View File

@ -10,6 +10,8 @@ import {ErrorCodes, ErrorDTO} from '../../../../common/entities/Error';
import {CookieNames} from '../../../../common/CookieNames'; import {CookieNames} from '../../../../common/CookieNames';
import {ShareService} from '../../ui/gallery/share.service'; import {ShareService} from '../../ui/gallery/share.service';
/* Injected config / user from server side */
// tslint:disable-next-line:no-internal-module no-namespace
declare module ServerInject { declare module ServerInject {
export let user: UserDTO; export let user: UserDTO;
} }
@ -19,8 +21,8 @@ export class AuthenticationService {
public readonly user: BehaviorSubject<UserDTO>; public readonly user: BehaviorSubject<UserDTO>;
constructor(private _userService: UserService, constructor(private userService: UserService,
private _networkService: NetworkService, private networkService: NetworkService,
private shareService: ShareService) { private shareService: ShareService) {
this.user = new BehaviorSubject(null); this.user = new BehaviorSubject(null);
@ -29,20 +31,23 @@ export class AuthenticationService {
if (typeof ServerInject !== 'undefined' && typeof ServerInject.user !== 'undefined') { if (typeof ServerInject !== 'undefined' && typeof ServerInject.user !== 'undefined') {
this.user.next(ServerInject.user); this.user.next(ServerInject.user);
} }
this.getSessionUser(); this.getSessionUser().catch(console.error);
} else { } else {
if (Config.Client.authenticationRequired === false) { if (Config.Client.authenticationRequired === false) {
this.user.next(<UserDTO>{name: UserRoles[Config.Client.unAuthenticatedUserRole], role: Config.Client.unAuthenticatedUserRole}); this.user.next({
name: UserRoles[Config.Client.unAuthenticatedUserRole],
role: Config.Client.unAuthenticatedUserRole
} as UserDTO);
} }
} }
_networkService.addGlobalErrorHandler((error: ErrorDTO) => { networkService.addGlobalErrorHandler((error: ErrorDTO) => {
if (error.code === ErrorCodes.NOT_AUTHENTICATED) { if (error.code === ErrorCodes.NOT_AUTHENTICATED) {
this.user.next(null); this.user.next(null);
return true; return true;
} }
if (error.code === ErrorCodes.NOT_AUTHORISED) { if (error.code === ErrorCodes.NOT_AUTHORISED) {
this.logout(); this.logout().catch(console.error);
return true; return true;
} }
return false; return false;
@ -51,19 +56,19 @@ export class AuthenticationService {
// TODO: refactor architecture remove shareService dependency // TODO: refactor architecture remove shareService dependency
window.setTimeout(() => { window.setTimeout(() => {
this.user.subscribe((u) => { this.user.subscribe((u) => {
this.shareService.onNewUser(u); this.shareService.onNewUser(u).catch(console.error);
}); });
}, 0); }, 0);
} }
public async login(credential: LoginCredential): Promise<UserDTO> { public async login(credential: LoginCredential): Promise<UserDTO> {
const user = await this._userService.login(credential); const user = await this.userService.login(credential);
this.user.next(user); this.user.next(user);
return user; return user;
} }
public async shareLogin(password: string): Promise<UserDTO> { public async shareLogin(password: string): Promise<UserDTO> {
const user = await this._userService.shareLogin(password); const user = await this.userService.shareLogin(password);
this.user.next(user); this.user.next(user);
return user; return user;
} }
@ -75,18 +80,18 @@ export class AuthenticationService {
return !!this.user.value; return !!this.user.value;
} }
public isAuthorized(role: UserRoles) { public isAuthorized(role: UserRoles): boolean {
return this.user.value && this.user.value.role >= role; return this.user.value && this.user.value.role >= role;
} }
public async logout() { public async logout(): Promise<void> {
await this._userService.logout(); await this.userService.logout();
this.user.next(null); this.user.next(null);
} }
private async getSessionUser(): Promise<void> { private async getSessionUser(): Promise<void> {
try { try {
this.user.next(await this._userService.getSessionUser()); this.user.next(await this.userService.getSessionUser());
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -10,7 +10,7 @@ export class AuthGuard implements CanActivate {
private navigationService: NavigationService) { private navigationService: NavigationService) {
} }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.authenticationService.isAuthenticated() === true) { if (this.authenticationService.isAuthenticated() === true) {
return true; return true;
} }

View File

@ -11,15 +11,15 @@ import {VersionService} from '../version.service';
@Injectable() @Injectable()
export class NetworkService { export class NetworkService {
readonly _apiBaseUrl = Utils.concatUrls(Config.Client.urlBase, '/api'); readonly apiBaseUrl = Utils.concatUrls(Config.Client.urlBase, '/api');
private globalErrorHandlers: Array<(error: ErrorDTO) => boolean> = []; private globalErrorHandlers: Array<(error: ErrorDTO) => boolean> = [];
constructor(private _http: HttpClient, constructor(private http: HttpClient,
private loadingBarService: LoadingBarService, private loadingBarService: LoadingBarService,
private versionService: VersionService) { private versionService: VersionService) {
} }
public static buildUrl(url: string, data?: { [key: string]: any }) { public static buildUrl(url: string, data?: { [key: string]: any }): string {
if (data) { if (data) {
const keys = Object.getOwnPropertyNames(data); const keys = Object.getOwnPropertyNames(data);
if (keys.length > 0) { if (keys.length > 0) {
@ -51,7 +51,7 @@ export class NetworkService {
return this.handleError(error); return this.handleError(error);
}; };
return this._http.get(this._apiBaseUrl + url, {responseType: 'text'}) return this.http.get(this.apiBaseUrl + url, {responseType: 'text'})
.toPromise() .toPromise()
.then(process) .then(process)
.catch(err); .catch(err);
@ -73,7 +73,7 @@ export class NetworkService {
return this.callJson('delete', url); return this.callJson('delete', url);
} }
addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean) { addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean): void {
this.globalErrorHandlers.push(fn); this.globalErrorHandlers.push(fn);
} }
@ -90,7 +90,7 @@ export class NetworkService {
} }
if (!!msg.error) { if (!!msg.error) {
if (msg.error.code) { if (msg.error.code) {
(<any>msg.error)['title'] = ErrorCodes[msg.error.code]; (msg.error as any).title = ErrorCodes[msg.error.code];
} }
throw msg.error; throw msg.error;
} }
@ -104,22 +104,22 @@ export class NetworkService {
switch (method) { switch (method) {
case 'get': case 'get':
return this._http.get<Message<T>>(this._apiBaseUrl + url, {observe: 'response'}) return this.http.get<Message<T>>(this.apiBaseUrl + url, {observe: 'response'})
.toPromise() .toPromise()
.then(process) .then(process)
.catch(err); .catch(err);
case 'delete': case 'delete':
return this._http.delete<Message<T>>(this._apiBaseUrl + url, {observe: 'response'}) return this.http.delete<Message<T>>(this.apiBaseUrl + url, {observe: 'response'})
.toPromise() .toPromise()
.then(process) .then(process)
.catch(err); .catch(err);
case 'post': case 'post':
return this._http.post<Message<T>>(this._apiBaseUrl + url, body, {observe: 'response'}) return this.http.post<Message<T>>(this.apiBaseUrl + url, body, {observe: 'response'})
.toPromise() .toPromise()
.then(process) .then(process)
.catch(err); .catch(err);
case 'put': case 'put':
return this._http.put<Message<T>>(this._apiBaseUrl + url, body, {observe: 'response'}) return this.http.put<Message<T>>(this.apiBaseUrl + url, body, {observe: 'response'})
.toPromise() .toPromise()
.then(process) .then(process)
.catch(err); .catch(err);
@ -129,10 +129,10 @@ export class NetworkService {
} }
private handleError(error: any) { private handleError(error: any): Promise<any> {
if (typeof error.code !== 'undefined') { if (typeof error.code !== 'undefined') {
for (let i = 0; i < this.globalErrorHandlers.length; i++) { for (const item of this.globalErrorHandlers) {
if (this.globalErrorHandlers[i](error) === true) { if (item(error) === true) {
return; return;
} }
} }

View File

@ -1,6 +1,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {ShareService} from '../ui/gallery/share.service'; import {ShareService} from '../ui/gallery/share.service';
import {MediaBaseDTO, MediaDTO} from '../../../common/entities/MediaDTO'; import {MediaBaseDTO} from '../../../common/entities/MediaDTO';
import {QueryParams} from '../../../common/QueryParams'; import {QueryParams} from '../../../common/QueryParams';
import {Utils} from '../../../common/Utils'; import {Utils} from '../../../common/Utils';
import {GalleryService} from '../ui/gallery/gallery.service'; import {GalleryService} from '../ui/gallery/gallery.service';
@ -36,7 +36,7 @@ export class QueryService {
return query; return query;
} }
getParamsForDirs(directory: DirectoryDTO) { getParamsForDirs(directory: DirectoryDTO): { [key: string]: any } {
const params: { [key: string]: any } = {}; const params: { [key: string]: any } = {};
if (Config.Client.Sharing.enabled === true) { if (Config.Client.Sharing.enabled === true) {
if (this.shareService.isSharing()) { if (this.shareService.isSharing()) {

View File

@ -1,7 +1,7 @@
import {Component, Input, OnDestroy, OnInit} from '@angular/core'; import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {RouterLink} from '@angular/router'; import {RouterLink} from '@angular/router';
import {PersonDTO} from '../../../../../common/entities/PersonDTO'; import {PersonDTO} from '../../../../../common/entities/PersonDTO';
import {DomSanitizer} from '@angular/platform-browser'; import {DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {PersonThumbnail, ThumbnailManagerService} from '../../gallery/thumbnailManager.service'; import {PersonThumbnail, ThumbnailManagerService} from '../../gallery/thumbnailManager.service';
import {FacesService} from '../faces.service'; import {FacesService} from '../faces.service';
import {AuthenticationService} from '../../../model/network/authentication.service'; import {AuthenticationService} from '../../../model/network/authentication.service';
@ -22,7 +22,7 @@ export class FaceComponent implements OnInit, OnDestroy {
public searchQueryDTOstr: string; public searchQueryDTOstr: string;
constructor(private thumbnailService: ThumbnailManagerService, constructor(private thumbnailService: ThumbnailManagerService,
private _sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private faceService: FacesService, private faceService: FacesService,
public authenticationService: AuthenticationService) { public authenticationService: AuthenticationService) {
@ -32,31 +32,31 @@ export class FaceComponent implements OnInit, OnDestroy {
return this.authenticationService.user.getValue().role >= Config.Client.Faces.writeAccessMinRole; return this.authenticationService.user.getValue().role >= Config.Client.Faces.writeAccessMinRole;
} }
ngOnInit() { ngOnInit(): void {
this.thumbnail = this.thumbnailService.getPersonThumbnail(this.person); this.thumbnail = this.thumbnailService.getPersonThumbnail(this.person);
this.searchQueryDTOstr = JSON.stringify(<TextSearch>{ this.searchQueryDTOstr = JSON.stringify({
type: SearchQueryTypes.person, type: SearchQueryTypes.person,
text: this.person.name, text: this.person.name,
matchType: TextSearchQueryMatchTypes.exact_match matchType: TextSearchQueryMatchTypes.exact_match
}); } as TextSearch);
} }
getSanitizedThUrl() { getSanitizedThUrl(): SafeStyle {
return this._sanitizer.bypassSecurityTrustStyle('url(' + return this.sanitizer.bypassSecurityTrustStyle('url(' +
encodeURI(this.thumbnail.Src) encodeURI(this.thumbnail.Src)
.replace(/\(/g, '%28') .replace(/\(/g, '%28')
.replace(/'/g, '%27') .replace(/'/g, '%27')
.replace(/\)/g, '%29') + ')'); .replace(/\)/g, '%29') + ')');
} }
ngOnDestroy() { ngOnDestroy(): void {
if (this.thumbnail != null) { if (this.thumbnail != null) {
this.thumbnail.destroy(); this.thumbnail.destroy();
} }
} }
async toggleFavourite($event: MouseEvent) { async toggleFavourite($event: MouseEvent): Promise<void> {
$event.preventDefault(); $event.preventDefault();
$event.stopPropagation(); $event.stopPropagation();
await this.faceService.setFavourite(this.person, !this.person.isFavourite).catch(console.error); await this.faceService.setFavourite(this.person, !this.person.isFavourite).catch(console.error);

View File

@ -34,11 +34,11 @@ export class FacesComponent implements OnInit {
} }
ngOnInit() { ngOnInit(): void {
this.updateSize(); this.updateSize();
} }
private updateSize() { private updateSize(): void {
const size = 220 + 5; const size = 220 + 5;
// body - container margin // body - container margin
const containerWidth = this.container.nativeElement.clientWidth - 30; const containerWidth = this.container.nativeElement.clientWidth - 30;

View File

@ -1,6 +1,6 @@
<ngx-loading-bar [includeSpinner]="false" color="#337ab7" height="3px"></ngx-loading-bar> <ngx-loading-bar [includeSpinner]="false" color="#337ab7" height="3px"></ngx-loading-bar>
<nav class="navbar navbar-dark bg-dark navbar-expand-md"> <nav class="navbar navbar-dark bg-dark navbar-expand-md">
<a class="navbar-brand" [routerLink]="['/gallery','/']" <a class="navbar-brand" [routerLink]="['/gallery']"
[queryParams]="queryService.getParams()"> [queryParams]="queryService.getParams()">
<img src="assets/icon_inv.png" width="30" height="30" class="d-inline-block align-top" alt=""> <img src="assets/icon_inv.png" width="30" height="30" class="d-inline-block align-top" alt="">
<strong>{{title}}</strong> <strong>{{title}}</strong>
@ -12,13 +12,13 @@
<div class="collapse navbar-collapse" id="navbarNav" [collapse]="collapsed"> <div class="collapse navbar-collapse" id="navbarNav" [collapse]="collapsed">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item" [routerLinkActive]="['active']"> <li class="nav-item" >
<a class="nav-link" <a class="nav-link"
[routerLink]="['/gallery']" [routerLink]="['/gallery']"
[queryParams]="queryService.getParams()" i18n>Gallery</a> [queryParams]="queryService.getParams()" [class.active]="isLinkActive('/gallery')" i18n>Gallery</a>
</li> </li>
<li class="nav-item" [routerLinkActive]="['active']" *ngIf="isFacesAvailable()"> <li class="nav-item" *ngIf="isFacesAvailable()">
<a class="nav-link" [routerLink]="['/faces']" i18n>Faces</a> <a class="nav-link" [routerLink]="['/faces']" [class.active]="isLinkActive('/faces')" i18n>Faces</a>
</li> </li>
</ul> </ul>
<ul class="navbar-nav navbar-right ml-auto"> <ul class="navbar-nav navbar-right ml-auto">

View File

@ -1,5 +1,5 @@
import {Component, ViewEncapsulation} from '@angular/core'; import {Component, ViewEncapsulation} from '@angular/core';
import {RouterLink} from '@angular/router'; import {Router, RouterLink} from '@angular/router';
import {AuthenticationService} from '../../model/network/authentication.service'; import {AuthenticationService} from '../../model/network/authentication.service';
import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO';
import {Config} from '../../../../common/config/public/Config'; import {Config} from '../../../../common/config/public/Config';
@ -16,29 +16,34 @@ import {QueryService} from '../../model/query.service';
}) })
export class FrameComponent { export class FrameComponent {
user: BehaviorSubject<UserDTO>; public readonly user: BehaviorSubject<UserDTO>;
public readonly authenticationRequired = Config.Client.authenticationRequired; public readonly authenticationRequired = Config.Client.authenticationRequired;
public readonly title = Config.Client.applicationTitle; public readonly title = Config.Client.applicationTitle;
collapsed = true; public collapsed = true;
constructor(private _authService: AuthenticationService, constructor(private authService: AuthenticationService,
public notificationService: NotificationService, public notificationService: NotificationService,
public queryService: QueryService) { public queryService: QueryService,
this.user = this._authService.user; private router: Router) {
this.user = this.authService.user;
} }
isAdmin() { isAdmin(): boolean {
return this.user.value && this.user.value.role >= UserRoles.Admin; return this.user.value && this.user.value.role >= UserRoles.Admin;
} }
isFacesAvailable() { isFacesAvailable(): boolean {
return Config.Client.Faces.enabled && this.user.value && this.user.value.role >= Config.Client.Faces.readAccessMinRole; return Config.Client.Faces.enabled && this.user.value && this.user.value.role >= Config.Client.Faces.readAccessMinRole;
} }
isLinkActive(url: string): boolean {
logout() { return this.router.url.startsWith(url);
this._authService.logout();
} }
logout(): void {
this.authService.logout();
}
} }

View File

@ -76,27 +76,27 @@ export class ShareService {
return this.ReadyPR; return this.ReadyPR;
} }
public createSharing(dir: string, includeSubfolders: boolean, valid: number): Promise<SharingDTO> { public createSharing(dir: string, includeSubFolders: boolean, valid: number): Promise<SharingDTO> {
return this.networkService.postJson('/share/' + dir, { return this.networkService.postJson('/share/' + dir, {
createSharing: <CreateSharingDTO>{ createSharing: {
includeSubfolders: includeSubfolders, includeSubfolders: includeSubFolders,
valid: valid valid
} } as CreateSharingDTO
}); });
} }
public updateSharing(dir: string, sharingId: number, includeSubfolders: boolean, password: string, valid: number): Promise<SharingDTO> { public updateSharing(dir: string, sharingId: number, includeSubFolders: boolean, password: string, valid: number): Promise<SharingDTO> {
return this.networkService.putJson('/share/' + dir, { return this.networkService.putJson('/share/' + dir, {
updateSharing: <CreateSharingDTO>{ updateSharing: {
id: sharingId, id: sharingId,
includeSubfolders: includeSubfolders, includeSubfolders: includeSubFolders,
valid: valid, valid,
password: password password
} } as CreateSharingDTO
}); });
} }
public getSharingKey() { public getSharingKey(): string {
return this.sharingKey; return this.sharingKey;
} }

View File

@ -1,7 +1,7 @@
import {Component, Input, OnChanges, OnDestroy, TemplateRef} from '@angular/core'; import {Component, Input, OnChanges, OnDestroy, TemplateRef} from '@angular/core';
import {JobProgressDTO, JobProgressStates} from '../../../../../../common/entities/job/JobProgressDTO'; import {JobProgressDTO, JobProgressStates} from '../../../../../../common/entities/job/JobProgressDTO';
import {Subscription, timer} from 'rxjs'; import {Subscription, timer} from 'rxjs';
import {BsModalRef, BsModalService} from 'ngx-bootstrap'; import {BsModalRef, BsModalService} from 'ngx-bootstrap/modal';
import {I18n} from '@ngx-translate/i18n-polyfill'; import {I18n} from '@ngx-translate/i18n-polyfill';
import {BackendtextService} from '../../../../model/backendtext.service'; import {BackendtextService} from '../../../../model/backendtext.service';

View File

@ -1,8 +1,16 @@
// The file contents for the current environment will overwrite these during build. // This file can be replaced during build by using the `fileReplacements` array.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// `ng build --env=prod` then `environment.prod.ts` will be used instead. // The list of file replacements can be found in `angular.json`.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = { export const environment = {
production: false production: false
}; };
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@ -1,16 +1,21 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../out-tsc/app", "outDir": "../../out-tsc/app",
"module": "es2015", "types": [],
"baseUrl": "tsconfig.app", "target": "es2015",
"types": [] "module": "es2020",
"lib": [
"es2018",
"dom"
]
}, },
"exclude": [ "files": [
"test.ts", "./main.ts",
"**/*.spec.ts" "./polyfills.ts"
], ],
"include": [ "include": [
"./**/*" "./**/*.d.ts"
] ]
} }

View File

@ -1,13 +1,12 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../out-tsc/spec", "outDir": "../../out-tsc/spec",
"module": "commonjs", "target": "es2015",
"target": "es5", "module": "es2020",
"baseUrl": "tsconfig.spec",
"types": [ "types": [
"jasmine", "jasmine"
"node"
] ]
}, },
"files": [ "files": [

View File

@ -2,19 +2,20 @@
"compileOnSave": true, "compileOnSave": true,
"compilerOptions": { "compilerOptions": {
"noImplicitAny": true, "noImplicitAny": true,
"module": "commonjs",
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"moduleResolution": "node", "moduleResolution": "node",
"downlevelIteration": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "es6", "target": "es2015",
"typeRoots": [ "module": "commonjs",
"node_modules/@types"
],
"lib": [ "lib": [
"es2017", "es2018",
"dom" "dom"
] ]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false
} }
} }

View File

@ -1,37 +1,37 @@
{ {
"extends": "tslint:recommended",
"rulesDirectory": [ "rulesDirectory": [
"node_modules/codelyzer" "codelyzer"
], ],
"rules": { "rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true, "arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true, "curly": true,
"deprecation": { "deprecation": {
"severity": "warn" "severity": "warning"
}, },
"eofline": true, "eofline": true,
"forin": true,
"import-blacklist": [ "import-blacklist": [
true, true,
"rxjs/Rx" "rxjs/Rx"
], ],
"import-spacing": true, "import-spacing": true,
"indent": [ "indent": {
true, "options": [
"spaces" "spaces"
], ]
"interface-over-type-literal": true, },
"label-position": true, "max-classes-per-file": false,
"max-line-length": [ "max-line-length": [
true, true,
140 140
], ],
"member-access": false,
"member-ordering": [ "member-ordering": [
true, true,
{ {
@ -43,8 +43,6 @@
] ]
} }
], ],
"no-arg": true,
"no-bitwise": true,
"no-console": [ "no-console": [
true, true,
"debug", "debug",
@ -53,70 +51,91 @@
"timeEnd", "timeEnd",
"trace" "trace"
], ],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false, "no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [ "no-inferrable-types": [
true, true,
"ignore-params" "ignore-params"
], ],
"no-misused-new": true,
"no-non-null-assertion": true, "no-non-null-assertion": true,
"no-shadowed-variable": true, "no-redundant-jsdoc": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-trailing-whitespace": true, "no-var-requires": false,
"no-unnecessary-initializer": true, "object-literal-key-quotes": [
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true, true,
"check-open-brace", "as-needed"
"check-catch",
"check-else",
"check-whitespace"
], ],
"prefer-const": true,
"quotemark": [ "quotemark": [
true, true,
"single" "single"
], ],
"radix": true, "semicolon": {
"semicolon": [ "options": [
true, "always"
"always", ]
"ignore-bound-class-methods" },
], "space-before-function-paren": {
"triple-equals": [ "options": {
true, "anonymous": "never",
"allow-null-check" "asyncArrow": "always",
], "constructor": "never",
"typedef-whitespace": [ "method": "never",
true, "named": "never"
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
} }
], },
"unified-signatures": true, "typedef": [
"variable-name": false,
"whitespace": [
true, true,
"check-branch", "call-signature"
"check-decl",
"check-operator",
"check-separator",
"check-type"
], ],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"directive-selector": [ "directive-selector": [
true, true,
"attribute", "attribute",
@ -128,16 +147,6 @@
"element", "element",
"app", "app",
"kebab-case" "kebab-case"
], ]
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
} }
} }