mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-11-24 08:42:24 +02:00
implementing advanced settings
This commit is contained in:
parent
73fee438ec
commit
d152900e21
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pigallery2",
|
"name": "pigallery2",
|
||||||
"version": "1.8.0",
|
"version": "1.8.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -18721,9 +18721,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typeconfig": {
|
"typeconfig": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.0.6.tgz",
|
||||||
"integrity": "sha512-IwZH+P8J4qhyrOfKzJZJx6raEkaBjjZIiE+rzrWgdOhqVhO5Cv+pkOQdNo+z/Wq5wER5YWeDNX7Wdbqv0jt6IA==",
|
"integrity": "sha512-OnXPXSDaK1mzH6dJ1HB9Q70ruJYngEhemwL9Y8+nG5E40Je4MMODuEY+tfjMtIiFIN772DU5Q+NebulLZNDjpw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"optimist": "0.6.1"
|
"optimist": "0.6.1"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pigallery2",
|
"name": "pigallery2",
|
||||||
"version": "1.8.0",
|
"version": "1.8.1",
|
||||||
"description": "This is a photo gallery optimised for running low resource servers (especially on raspberry pi)",
|
"description": "This is a photo gallery optimised for running low resource servers (especially on raspberry pi)",
|
||||||
"author": "Patrik J. Braun",
|
"author": "Patrik J. Braun",
|
||||||
"homepage": "https://github.com/bpatrik/PiGallery2",
|
"homepage": "https://github.com/bpatrik/PiGallery2",
|
||||||
@ -16,6 +16,7 @@
|
|||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"start": "node ./src/backend/index",
|
"start": "node ./src/backend/index",
|
||||||
"run-dev": "ng build --aot --watch --output-path=./dist --i18n-locale en --i18n-file src/frontend/translate/messages.en.xlf --i18n-missing-translation warning",
|
"run-dev": "ng build --aot --watch --output-path=./dist --i18n-locale en --i18n-file src/frontend/translate/messages.en.xlf --i18n-missing-translation warning",
|
||||||
|
"run-dev-hu": "ng build --aot --watch --output-path=./dist --i18n-locale hu --i18n-file src/frontend/translate/messages.hu.xlf --i18n-missing-translation warning",
|
||||||
"build-stats": "ng build --aot --prod --stats-json --output-path=./dist --i18n-locale en --i18n-file src/frontend/translate/messages.en.xlf --i18n-missing-translation warning",
|
"build-stats": "ng build --aot --prod --stats-json --output-path=./dist --i18n-locale en --i18n-file src/frontend/translate/messages.en.xlf --i18n-missing-translation warning",
|
||||||
"merge-new-translation": "gulp merge-new-translation",
|
"merge-new-translation": "gulp merge-new-translation",
|
||||||
"add-translation": "gulp add-translation",
|
"add-translation": "gulp add-translation",
|
||||||
@ -47,7 +48,7 @@
|
|||||||
"sqlite3": "4.1.1",
|
"sqlite3": "4.1.1",
|
||||||
"ts-exif-parser": "0.1.4",
|
"ts-exif-parser": "0.1.4",
|
||||||
"ts-node-iptc": "1.0.11",
|
"ts-node-iptc": "1.0.11",
|
||||||
"typeconfig": "2.0.0",
|
"typeconfig": "2.0.6",
|
||||||
"typeorm": "0.2.21",
|
"typeorm": "0.2.21",
|
||||||
"winston": "2.4.4"
|
"winston": "2.4.4"
|
||||||
},
|
},
|
||||||
|
@ -63,7 +63,10 @@ export class RenderingMWs {
|
|||||||
public static async renderConfig(req: Request, res: Response, next: NextFunction) {
|
public static async renderConfig(req: Request, res: Response, next: NextFunction) {
|
||||||
const originalConf = await Config.original();
|
const originalConf = await Config.original();
|
||||||
originalConf.Server.sessionSecret = null;
|
originalConf.Server.sessionSecret = null;
|
||||||
const message = new Message<PrivateConfigClass>(null, <any>originalConf.toJSON({attachDefaults: true}));
|
const message = new Message<PrivateConfigClass>(null, <any>originalConf.toJSON({
|
||||||
|
attachState: true,
|
||||||
|
attachVolatile: true
|
||||||
|
}));
|
||||||
res.json(message);
|
res.json(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ export class PublicRouter {
|
|||||||
res.tpl.user.csrfToken = req.csrfToken();
|
res.tpl.user.csrfToken = req.csrfToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.tpl.clientConfig = {Client: Config.Client};
|
res.tpl.clientConfig = {Client: Config.Client.toJSON({attachVolatile: true})};
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
@ -61,6 +61,10 @@ export class Utils {
|
|||||||
if (!object) {
|
if (!object) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(object) && object.length !== filter.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const keys = Object.keys(filter);
|
const keys = Object.keys(filter);
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
const key = keys[i];
|
const key = keys[i];
|
||||||
@ -158,10 +162,7 @@ export class Utils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static enumToArray(EnumType: any): Array<{
|
public static enumToArray(EnumType: any): { key: number; value: string }[] {
|
||||||
key: number;
|
|
||||||
value: string;
|
|
||||||
}> {
|
|
||||||
const arr: Array<{ key: number; value: string; }> = [];
|
const arr: Array<{ key: number; value: string; }> = [];
|
||||||
for (const enumMember in EnumType) {
|
for (const enumMember in EnumType) {
|
||||||
if (!EnumType.hasOwnProperty(enumMember)) {
|
if (!EnumType.hasOwnProperty(enumMember)) {
|
||||||
|
@ -2,10 +2,8 @@ import {IPrivateConfig, ServerConfig} from './PrivateConfig';
|
|||||||
import {ClientConfig} from '../public/ClientConfig';
|
import {ClientConfig} from '../public/ClientConfig';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {ConfigClass} from 'typeconfig/src/decorators/class/ConfigClass';
|
import {ConfigClass, ConfigClassBuilder} from 'typeconfig/node';
|
||||||
import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
import {ConfigProperty, IConfigClass} from 'typeconfig/common';
|
||||||
import {IConfigClass} from 'typeconfig/src/decorators/class/IConfigClass';
|
|
||||||
import {ConfigClassBuilder} from 'typeconfig/src/decorators/builders/ConfigClassBuilder';
|
|
||||||
|
|
||||||
|
|
||||||
@ConfigClass({
|
@ConfigClass({
|
||||||
@ -16,7 +14,7 @@ import {ConfigClassBuilder} from 'typeconfig/src/decorators/builders/ConfigClass
|
|||||||
cli: {
|
cli: {
|
||||||
enable: {
|
enable: {
|
||||||
configPath: true,
|
configPath: true,
|
||||||
attachDefaults: true,
|
attachState: true,
|
||||||
attachDescription: true,
|
attachDescription: true,
|
||||||
rewriteCLIConfig: true,
|
rewriteCLIConfig: true,
|
||||||
rewriteENVConfig: true,
|
rewriteENVConfig: true,
|
||||||
@ -33,7 +31,7 @@ export class PrivateConfigClass implements IPrivateConfig {
|
|||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
Server: ServerConfig.Config = new ServerConfig.Config();
|
Server: ServerConfig.Config = new ServerConfig.Config();
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
Client: ClientConfig.Config = new ClientConfig.Config();
|
Client: IConfigClass & ClientConfig.Config = <IConfigClass & ClientConfig.Config>(new ClientConfig.Config());
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!this.Server.sessionSecret || this.Server.sessionSecret.length === 0) {
|
if (!this.Server.sessionSecret || this.Server.sessionSecret.length === 0) {
|
||||||
|
@ -3,8 +3,8 @@ import 'reflect-metadata';
|
|||||||
import {DefaultsJobs} from '../../entities/job/JobDTO';
|
import {DefaultsJobs} from '../../entities/job/JobDTO';
|
||||||
import {JobScheduleDTO, JobTrigger, JobTriggerType} from '../../entities/job/JobScheduleDTO';
|
import {JobScheduleDTO, JobTrigger, JobTriggerType} from '../../entities/job/JobScheduleDTO';
|
||||||
import {ClientConfig} from '../public/ClientConfig';
|
import {ClientConfig} from '../public/ClientConfig';
|
||||||
import { SubConfigClass } from 'typeconfig/src/decorators/class/SubConfigClass';
|
import {SubConfigClass} from 'typeconfig/src/decorators/class/SubConfigClass';
|
||||||
import { ConfigProperty } from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
||||||
|
|
||||||
export module ServerConfig {
|
export module ServerConfig {
|
||||||
export enum DatabaseType {
|
export enum DatabaseType {
|
||||||
@ -37,14 +37,14 @@ export module ServerConfig {
|
|||||||
@SubConfigClass()
|
@SubConfigClass()
|
||||||
export class MySQLConfig {
|
export class MySQLConfig {
|
||||||
@ConfigProperty({envAlias: 'MYSQL_HOST'})
|
@ConfigProperty({envAlias: 'MYSQL_HOST'})
|
||||||
host: string = '';
|
host: string = 'localhost';
|
||||||
@ConfigProperty({envAlias: 'MYSQL_PORT'})
|
@ConfigProperty({envAlias: 'MYSQL_PORT', min: 0, max: 65535})
|
||||||
port: number = 3306;
|
port: number = 3306;
|
||||||
@ConfigProperty({envAlias: 'MYSQL_DATABASE'})
|
@ConfigProperty({envAlias: 'MYSQL_DATABASE'})
|
||||||
database: string = '';
|
database: string = 'pigallery2';
|
||||||
@ConfigProperty({envAlias: 'MYSQL_USERNAME'})
|
@ConfigProperty({envAlias: 'MYSQL_USERNAME'})
|
||||||
username: string = '';
|
username: string = '';
|
||||||
@ConfigProperty({envAlias: 'MYSQL_PASSWORD'})
|
@ConfigProperty({envAlias: 'MYSQL_PASSWORD', type: 'password'})
|
||||||
password: string = '';
|
password: string = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,13 +93,13 @@ export module ServerConfig {
|
|||||||
@ConfigProperty({type: ReIndexingSensitivity})
|
@ConfigProperty({type: ReIndexingSensitivity})
|
||||||
reIndexingSensitivity: ReIndexingSensitivity = ReIndexingSensitivity.low;
|
reIndexingSensitivity: ReIndexingSensitivity = ReIndexingSensitivity.low;
|
||||||
@ConfigProperty({
|
@ConfigProperty({
|
||||||
arrayType: String,
|
arrayType: 'string',
|
||||||
description: 'If an entry starts with \'/\' it is treated as an absolute path.' +
|
description: 'If an entry starts with \'/\' it is treated as an absolute path.' +
|
||||||
' If it doesn\'t start with \'/\' but contains a \'/\', the path is relative to the image directory.' +
|
' If it doesn\'t start with \'/\' but contains a \'/\', the path is relative to the image directory.' +
|
||||||
' If it doesn\'t contain a \'/\', any folder with this name will be excluded.'
|
' If it doesn\'t contain a \'/\', any folder with this name will be excluded.'
|
||||||
})
|
})
|
||||||
excludeFolderList: string[] = [];
|
excludeFolderList: string[] = [];
|
||||||
@ConfigProperty({arrayType: String, description: 'Any folder that contains a file with this name will be excluded from indexing.'})
|
@ConfigProperty({arrayType: 'string', description: 'Any folder that contains a file with this name will be excluded from indexing.'})
|
||||||
excludeFileList: string[] = [];
|
excludeFileList: string[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,9 +291,9 @@ export module ServerConfig {
|
|||||||
|
|
||||||
@SubConfigClass()
|
@SubConfigClass()
|
||||||
export class Config {
|
export class Config {
|
||||||
@ConfigProperty({arrayType: String})
|
@ConfigProperty({arrayType: 'string'})
|
||||||
sessionSecret: string[] = [];
|
sessionSecret: string[] = [];
|
||||||
@ConfigProperty({type: 'unsignedInt', envAlias: 'PORT'})
|
@ConfigProperty({type: 'unsignedInt', envAlias: 'PORT', min: 0, max: 65535})
|
||||||
port: number = 80;
|
port: number = 80;
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
host: string = '0.0.0.0';
|
host: string = '0.0.0.0';
|
||||||
@ -305,7 +305,7 @@ export module ServerConfig {
|
|||||||
Database: DataBaseConfig = new DataBaseConfig();
|
Database: DataBaseConfig = new DataBaseConfig();
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
Sharing: SharingConfig = new SharingConfig();
|
Sharing: SharingConfig = new SharingConfig();
|
||||||
@ConfigProperty({description: 'unit: ms'})
|
@ConfigProperty({type: 'unsignedInt', description: 'unit: ms'})
|
||||||
sessionTimeout: number = 1000 * 60 * 60 * 24 * 7; // in ms
|
sessionTimeout: number = 1000 * 60 * 60 * 24 * 7; // in ms
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
Indexing: IndexingConfig = new IndexingConfig();
|
Indexing: IndexingConfig = new IndexingConfig();
|
||||||
|
@ -2,15 +2,14 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import {ClientConfig} from '../public/ClientConfig';
|
import {ClientConfig} from '../public/ClientConfig';
|
||||||
import {ServerConfig} from './PrivateConfig';
|
import {ServerConfig} from './PrivateConfig';
|
||||||
import {WebConfigClass} from 'typeconfig/src/decorators/class/WebConfigClass';
|
import {WebConfigClass} from 'typeconfig/web';
|
||||||
import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
import {ConfigProperty, ConfigState} from 'typeconfig/common';
|
||||||
import {ConfigDefaults} from 'typeconfig/src/decorators/property/ConfigDefaults';
|
|
||||||
|
|
||||||
|
|
||||||
@WebConfigClass()
|
@WebConfigClass()
|
||||||
export class WebConfig {
|
export class WebConfig {
|
||||||
@ConfigDefaults()
|
@ConfigState()
|
||||||
Defaults: WebConfig;
|
State: any;
|
||||||
|
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
Server: ServerConfig.Config = new ServerConfig.Config();
|
Server: ServerConfig.Config = new ServerConfig.Config();
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import {SortingMethods} from '../../entities/SortingMethods';
|
import {SortingMethods} from '../../entities/SortingMethods';
|
||||||
import {UserRoles} from '../../entities/UserDTO';
|
import {UserRoles} from '../../entities/UserDTO';
|
||||||
import { SubConfigClass } from 'typeconfig/src/decorators/class/SubConfigClass';
|
import {SubConfigClass} from 'typeconfig/src/decorators/class/SubConfigClass';
|
||||||
import { ConfigProperty } from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty';
|
||||||
|
|
||||||
|
|
||||||
export module ClientConfig {
|
export module ClientConfig {
|
||||||
|
|
||||||
export enum MapProviders {
|
export enum MapProviders {
|
||||||
OpenStreetMap = 0, Mapbox = 1, Custom = 2
|
OpenStreetMap = 1, Mapbox = 2, Custom = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubConfigClass()
|
@SubConfigClass()
|
||||||
export class AutoCompleteConfig {
|
export class AutoCompleteConfig {
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
enabled = true;
|
enabled: boolean = true;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt'})
|
||||||
maxItemsPerCategory = 5;
|
maxItemsPerCategory: number = 5;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt'})
|
||||||
cacheTimeout: number = 1000 * 60 * 60;
|
cacheTimeout: number = 1000 * 60 * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,11 +28,11 @@ export module ClientConfig {
|
|||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
instantSearchEnabled: boolean = true;
|
instantSearchEnabled: boolean = true;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt'})
|
||||||
InstantSearchTimeout: number = 3000;
|
InstantSearchTimeout: number = 3000;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt'})
|
||||||
instantSearchCacheTimeout: number = 1000 * 60 * 60;
|
instantSearchCacheTimeout: number = 1000 * 60 * 60;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt'})
|
||||||
searchCacheTimeout: number = 1000 * 60 * 60;
|
searchCacheTimeout: number = 1000 * 60 * 60;
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
AutoComplete: AutoCompleteConfig = new AutoCompleteConfig();
|
AutoComplete: AutoCompleteConfig = new AutoCompleteConfig();
|
||||||
@ -66,7 +66,7 @@ export module ClientConfig {
|
|||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
useImageMarkers: boolean = true;
|
useImageMarkers: boolean = true;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: MapProviders})
|
||||||
mapProvider: MapProviders = MapProviders.OpenStreetMap;
|
mapProvider: MapProviders = MapProviders.OpenStreetMap;
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
mapboxAccessToken: string = '';
|
mapboxAccessToken: string = '';
|
||||||
@ -76,11 +76,11 @@ export module ClientConfig {
|
|||||||
|
|
||||||
@SubConfigClass()
|
@SubConfigClass()
|
||||||
export class ThumbnailConfig {
|
export class ThumbnailConfig {
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt', max: 100})
|
||||||
iconSize: number = 45;
|
iconSize: number = 45;
|
||||||
@ConfigProperty()
|
@ConfigProperty({type: 'unsignedInt'})
|
||||||
personThumbnailSize: number = 200;
|
personThumbnailSize: number = 200;
|
||||||
@ConfigProperty({arrayType: Number})
|
@ConfigProperty({arrayType: 'unsignedInt'})
|
||||||
thumbnailSizes: number[] = [240, 480];
|
thumbnailSizes: number[] = [240, 480];
|
||||||
@ConfigProperty({volatile: true})
|
@ConfigProperty({volatile: true})
|
||||||
concurrentThumbnailGenerations: number = 1;
|
concurrentThumbnailGenerations: number = 1;
|
||||||
@ -184,7 +184,7 @@ export module ClientConfig {
|
|||||||
authenticationRequired: boolean = true;
|
authenticationRequired: boolean = true;
|
||||||
@ConfigProperty({type: UserRoles})
|
@ConfigProperty({type: UserRoles})
|
||||||
unAuthenticatedUserRole: UserRoles = UserRoles.Admin;
|
unAuthenticatedUserRole: UserRoles = UserRoles.Admin;
|
||||||
@ConfigProperty({arrayType: String, volatile: true})
|
@ConfigProperty({arrayType: 'string', volatile: true})
|
||||||
languages: string[];
|
languages: string[];
|
||||||
@ConfigProperty()
|
@ConfigProperty()
|
||||||
Media: MediaConfig = new MediaConfig();
|
Media: MediaConfig = new MediaConfig();
|
||||||
|
@ -92,6 +92,7 @@ import {BackendtextService} from './model/backendtext.service';
|
|||||||
import {JobButtonComponent} from './ui/settings/jobs/button/job-button.settings.component';
|
import {JobButtonComponent} from './ui/settings/jobs/button/job-button.settings.component';
|
||||||
import {ErrorInterceptor} from './model/network/helper/error.interceptor';
|
import {ErrorInterceptor} from './model/network/helper/error.interceptor';
|
||||||
import {CSRFInterceptor} from './model/network/helper/csrf.interceptor';
|
import {CSRFInterceptor} from './model/network/helper/csrf.interceptor';
|
||||||
|
import {SettingsEntryComponent} from './ui/settings/_abstract/settings-entry/settings-entry.component';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -187,6 +188,7 @@ export function translationsFactory(locale: string) {
|
|||||||
DuplicateComponent,
|
DuplicateComponent,
|
||||||
DuplicatesPhotoComponent,
|
DuplicatesPhotoComponent,
|
||||||
// Settings
|
// Settings
|
||||||
|
SettingsEntryComponent,
|
||||||
UserMangerSettingsComponent,
|
UserMangerSettingsComponent,
|
||||||
DatabaseSettingsComponent,
|
DatabaseSettingsComponent,
|
||||||
MapSettingsComponent,
|
MapSettingsComponent,
|
||||||
@ -218,6 +220,7 @@ export function translationsFactory(locale: string) {
|
|||||||
{provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true},
|
{provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true},
|
||||||
{provide: UrlSerializer, useClass: CustomUrlSerializer},
|
{provide: UrlSerializer, useClass: CustomUrlSerializer},
|
||||||
{provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig},
|
{provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig},
|
||||||
|
StringifySortingMethod,
|
||||||
NetworkService,
|
NetworkService,
|
||||||
ShareService,
|
ShareService,
|
||||||
UserService,
|
UserService,
|
||||||
|
@ -31,13 +31,15 @@
|
|||||||
id="simplifiedMode"
|
id="simplifiedMode"
|
||||||
class="switch"
|
class="switch"
|
||||||
name="simplifiedMode"
|
name="simplifiedMode"
|
||||||
[switch-off-color]="'warning'"
|
switch-off-color="warning"
|
||||||
[switch-on-color]="'primary'"
|
switch-on-color="primary"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Advanced"
|
switch-off-text="Advanced"
|
||||||
[switch-on-text]="text.Simplified"
|
switch-on-text="Simplified"
|
||||||
[switch-handle-width]="100"
|
i18n-switch-off-text
|
||||||
[switch-label-width]="20"
|
i18n-switch-on-text
|
||||||
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
[(ngModel)]="simplifiedMode">
|
[(ngModel)]="simplifiedMode">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,15 +17,11 @@ import {formatDate} from '@angular/common';
|
|||||||
})
|
})
|
||||||
export class AdminComponent implements OnInit, AfterViewInit {
|
export class AdminComponent implements OnInit, AfterViewInit {
|
||||||
simplifiedMode = true;
|
simplifiedMode = true;
|
||||||
text = {
|
|
||||||
Advanced: 'Advanced',
|
|
||||||
Simplified: 'Simplified'
|
|
||||||
};
|
|
||||||
appVersion = Config.Client.appVersion;
|
appVersion = Config.Client.appVersion;
|
||||||
versionExtra = '';
|
versionExtra = '';
|
||||||
upTime = Config.Client.upTime;
|
upTime = Config.Client.upTime;
|
||||||
@ViewChildren('setting') settingsComponents: QueryList<ISettingsComponent>;
|
@ViewChildren('setting') settingsComponents: QueryList<ISettingsComponent>;
|
||||||
@ViewChildren('setting', {read: ElementRef}) settingsComponents2: QueryList<ElementRef>;
|
@ViewChildren('setting', {read: ElementRef}) settingsComponentsElemRef: QueryList<ElementRef>;
|
||||||
contents: ISettingsComponent[] = [];
|
contents: ISettingsComponent[] = [];
|
||||||
|
|
||||||
constructor(private _authService: AuthenticationService,
|
constructor(private _authService: AuthenticationService,
|
||||||
@ -33,8 +29,6 @@ export class AdminComponent implements OnInit, AfterViewInit {
|
|||||||
public notificationService: NotificationService,
|
public notificationService: NotificationService,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
public i18n: I18n) {
|
public i18n: I18n) {
|
||||||
this.text.Advanced = i18n('Advanced');
|
|
||||||
this.text.Simplified = i18n('Simplified');
|
|
||||||
|
|
||||||
if (Config.Client.buildTime) {
|
if (Config.Client.buildTime) {
|
||||||
this.versionExtra = i18n('Built at') + ': ' + formatDate(Config.Client.buildTime, 'medium', locale);
|
this.versionExtra = i18n('Built at') + ': ' + formatDate(Config.Client.buildTime, 'medium', locale);
|
||||||
@ -50,13 +44,13 @@ export class AdminComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo(i: number) {
|
scrollTo(i: number) {
|
||||||
PageHelper.ScrollY = this.settingsComponents2.toArray()[i].nativeElement.getBoundingClientRect().top +
|
PageHelper.ScrollY = this.settingsComponentsElemRef.toArray()[i].nativeElement.getBoundingClientRect().top +
|
||||||
PageHelper.ScrollY;
|
PageHelper.ScrollY;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (!this._authService.isAuthenticated()
|
if (!this._authService.isAuthenticated()
|
||||||
|| this._authService.user.value.role < UserRoles.Admin) {
|
|| this._authService.user.value.role < UserRoles.Admin) {
|
||||||
this._navigation.toLogin();
|
this._navigation.toLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -16,3 +16,13 @@
|
|||||||
margin-bottom: -4px;
|
margin-bottom: -4px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.changed-settings input {
|
||||||
|
border-color: #007bff;
|
||||||
|
border-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changed-settings label {
|
||||||
|
color: #007bff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@ -10,8 +10,29 @@ import {I18n} from '@ngx-translate/i18n-polyfill';
|
|||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {ISettingsComponent} from './ISettingsComponent';
|
import {ISettingsComponent} from './ISettingsComponent';
|
||||||
import {WebConfig} from '../../../../../common/config/private/WebConfig';
|
import {WebConfig} from '../../../../../common/config/private/WebConfig';
|
||||||
|
import {FormControl} from '@angular/forms';
|
||||||
|
|
||||||
|
interface ConfigState {
|
||||||
|
value: any;
|
||||||
|
original: any;
|
||||||
|
default: any;
|
||||||
|
readonly: any;
|
||||||
|
onChange: any;
|
||||||
|
isEnumType: boolean;
|
||||||
|
isConfigType: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RecursiveState extends ConfigState {
|
||||||
|
value: any;
|
||||||
|
original: any;
|
||||||
|
default: any;
|
||||||
|
readonly: any;
|
||||||
|
onChange: any;
|
||||||
|
isEnumType: any;
|
||||||
|
isConfigType: any;
|
||||||
|
|
||||||
|
[key: string]: RecursiveState;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class SettingsComponent<T extends { [key: string]: any }, S extends AbstractSettingsService<T> = AbstractSettingsService<T>>
|
export abstract class SettingsComponent<T extends { [key: string]: any }, S extends AbstractSettingsService<T> = AbstractSettingsService<T>>
|
||||||
implements OnInit, OnDestroy, OnChanges, ISettingsComponent {
|
implements OnInit, OnDestroy, OnChanges, ISettingsComponent {
|
||||||
@ -20,7 +41,8 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
public simplifiedMode = true;
|
public simplifiedMode = true;
|
||||||
|
|
||||||
@ViewChild('settingsForm', {static: true})
|
@ViewChild('settingsForm', {static: true})
|
||||||
form: HTMLFormElement;
|
form: FormControl;
|
||||||
|
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
hasAvailableSettings = true;
|
hasAvailableSettings = true;
|
||||||
@ -28,14 +50,9 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
public inProgress = false;
|
public inProgress = false;
|
||||||
public error: string = null;
|
public error: string = null;
|
||||||
public changed = false;
|
public changed = false;
|
||||||
public settings: T = <T>{};
|
public states: RecursiveState = <any>{};
|
||||||
public original: T = <T>{};
|
|
||||||
text = {
|
|
||||||
Enabled: 'Enabled',
|
|
||||||
Disabled: 'Disabled',
|
|
||||||
Low: 'Low',
|
|
||||||
High: 'High'
|
|
||||||
};
|
|
||||||
private _subscription: Subscription = null;
|
private _subscription: Subscription = null;
|
||||||
private readonly _settingsSubscription: Subscription = null;
|
private readonly _settingsSubscription: Subscription = null;
|
||||||
|
|
||||||
@ -50,10 +67,6 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
this._settingsSubscription = this._settingsService.Settings.subscribe(this.onNewSettings);
|
this._settingsSubscription = this._settingsService.Settings.subscribe(this.onNewSettings);
|
||||||
this.onNewSettings(this._settingsService._settingsService.settings.value);
|
this.onNewSettings(this._settingsService._settingsService.settings.value);
|
||||||
}
|
}
|
||||||
this.text.Enabled = i18n('Enabled');
|
|
||||||
this.text.Disabled = i18n('Disabled');
|
|
||||||
this.text.Low = i18n('Low');
|
|
||||||
this.text.High = i18n('High');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,8 +83,22 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
}
|
}
|
||||||
|
|
||||||
onNewSettings = (s: WebConfig) => {
|
onNewSettings = (s: WebConfig) => {
|
||||||
this.settings = Utils.clone(this.sliceFN(s));
|
|
||||||
this.original = Utils.clone(this.settings);
|
this.states = Utils.clone(<any>this.sliceFN(s.State));
|
||||||
|
const addOriginal = (obj: any) => {
|
||||||
|
for (const k of Object.keys(obj)) {
|
||||||
|
if (typeof obj[k].value === 'undefined') {
|
||||||
|
if (typeof obj[k] === 'object') {
|
||||||
|
addOriginal(obj[k]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[k].original = Utils.clone(obj[k].value);
|
||||||
|
obj[k].onChange = this.onOptionChange;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addOriginal(this.states);
|
||||||
this.ngOnChanges();
|
this.ngOnChanges();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,12 +131,32 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public testSettingChanges() {
|
onOptionChange = () => {
|
||||||
// TODO: fix after this issue is fixed: https://github.com/angular/angular/issues/24818
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.changed = !this.settingsSame(this.settings, this.original);
|
const settingsSame = (state: RecursiveState): boolean => {
|
||||||
|
if (typeof state === 'undefined') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof state.original === 'object') {
|
||||||
|
return Utils.equalsFilter(state.original, state.value);
|
||||||
|
}
|
||||||
|
if (typeof state.original !== 'undefined') {
|
||||||
|
return state.value === state.original;
|
||||||
|
}
|
||||||
|
const keys = Object.keys(state);
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
if (settingsSame(state[key]) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.changed = !settingsSame(this.states);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (!this._authService.isAuthenticated() ||
|
if (!this._authService.isAuthenticated() ||
|
||||||
@ -121,9 +168,8 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
|
|
||||||
// TODO: fix after this issue is fixed: https://github.com/angular/angular/issues/24818
|
// TODO: fix after this issue is fixed: https://github.com/angular/angular/issues/24818
|
||||||
this._subscription = this.form.valueChanges.subscribe(() => {
|
this._subscription = this.form.valueChanges.subscribe(() => {
|
||||||
this.testSettingChanges();
|
this.onOptionChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
@ -146,11 +192,28 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
this.getSettings();
|
this.getSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stateToSettings(): T {
|
||||||
|
const ret: T = <any>{};
|
||||||
|
|
||||||
|
const add = (obj: any, to: any): void => {
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
to[key] = {};
|
||||||
|
if (obj[key].isConfigType) {
|
||||||
|
return add(obj[key], to[key]);
|
||||||
|
}
|
||||||
|
to[key] = obj[key].value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add(this.states, ret);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public async save() {
|
public async save() {
|
||||||
this.inProgress = true;
|
this.inProgress = true;
|
||||||
this.error = '';
|
this.error = '';
|
||||||
try {
|
try {
|
||||||
await this._settingsService.updateSettings(this.settings);
|
await this._settingsService.updateSettings(this.stateToSettings());
|
||||||
await this.getSettings();
|
await this.getSettings();
|
||||||
this.notification.success(this.Name + ' ' + this.i18n('settings saved'), this.i18n('Success'));
|
this.notification.success(this.Name + ' ' + this.i18n('settings saved'), this.i18n('Success'));
|
||||||
this.inProgress = false;
|
this.inProgress = false;
|
||||||
@ -166,11 +229,11 @@ export abstract class SettingsComponent<T extends { [key: string]: any }, S exte
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async getSettings() {
|
private async getSettings() {
|
||||||
await this._settingsService.getSettings();
|
await this._settingsService.getSettings();
|
||||||
this.changed = false;
|
this.changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<ng-container *ngIf="state">
|
||||||
|
<div class="form-group row" *ngIf="!state.isEnumType && state.type !== 'boolean'"
|
||||||
|
[class.changed-settings]="changed"
|
||||||
|
[hidden]="shouldHide">
|
||||||
|
<label class="col-md-2 control-label" [for]="idName">{{name}}</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input [type]="type" [min]="state.min" [max]="state.max" class="form-control" [placeholder]="PlaceHolder"
|
||||||
|
[title]="title"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
(ngModelChange)="onChange($event)"
|
||||||
|
[name]="idName"
|
||||||
|
[disabled]="state.readonly || _disabled"
|
||||||
|
[id]="idName"
|
||||||
|
required="required">
|
||||||
|
<small class="form-text text-muted" *ngIf="description">{{description}}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row" *ngIf="state.isEnumType === true"
|
||||||
|
[class.changed-settings]="changed"
|
||||||
|
[hidden]="shouldHide">
|
||||||
|
<label class="col-md-2 control-label" [for]="idName">{{name}}</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<select [id]="idName"
|
||||||
|
[name]="idName"
|
||||||
|
[title]="title"
|
||||||
|
(ngModelChange)="onChange($event)"
|
||||||
|
[disabled]="state.readonly || _disabled"
|
||||||
|
class="form-control" [(ngModel)]="state.value">
|
||||||
|
<option *ngFor="let opt of _options" [ngValue]="opt.key">{{opt.value}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<small class="form-text text-muted" *ngIf="description">{{description}}
|
||||||
|
</small>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row" *ngIf="state.type === 'boolean'"
|
||||||
|
[class.changed-settings]="changed"
|
||||||
|
[hidden]="shouldHide">
|
||||||
|
<label class="col-md-2 control-label" [for]="idName">{{name}}</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<bSwitch
|
||||||
|
class="switch"
|
||||||
|
[id]="idName"
|
||||||
|
[name]="idName"
|
||||||
|
[title]="title"
|
||||||
|
[disabled]="state.readonly || _disabled"
|
||||||
|
switch-on-color="primary"
|
||||||
|
switch-inverse="true"
|
||||||
|
switch-off-text="Disabled"
|
||||||
|
switch-on-text="Enabled"
|
||||||
|
i18n-switch-off-text
|
||||||
|
i18n-switch-on-text
|
||||||
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
|
(ngModelChange)="onChange($event)"
|
||||||
|
[(ngModel)]="state.value">
|
||||||
|
</bSwitch>
|
||||||
|
|
||||||
|
<small class="form-text text-muted" *ngIf="description">{{description}}
|
||||||
|
</small>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
@ -0,0 +1,187 @@
|
|||||||
|
import {Component, forwardRef, Input, OnChanges} from '@angular/core';
|
||||||
|
import {I18n} from '@ngx-translate/i18n-polyfill';
|
||||||
|
import {ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator} from '@angular/forms';
|
||||||
|
import {Utils} from '../../../../../../common/Utils';
|
||||||
|
import {propertyTypes} from 'typeconfig/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-settings-entry',
|
||||||
|
templateUrl: './settings-entry.component.html',
|
||||||
|
styleUrls: ['./settings-entry.settings.component.css'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => SettingsEntryComponent),
|
||||||
|
multi: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => SettingsEntryComponent),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SettingsEntryComponent implements ControlValueAccessor, Validator, OnChanges {
|
||||||
|
|
||||||
|
@Input() name: string;
|
||||||
|
@Input() required: boolean;
|
||||||
|
@Input() optionMap: (v: { key: number, value: string }) => { key: number, value: string };
|
||||||
|
@Input() placeholder: string;
|
||||||
|
@Input() options: { key: number | string, value: number | string }[];
|
||||||
|
@Input() simplifiedMode = false;
|
||||||
|
@Input() description: boolean;
|
||||||
|
state: {
|
||||||
|
isEnumType: boolean,
|
||||||
|
isConfigType: boolean,
|
||||||
|
default: any, value: any, min?: number, max?: number,
|
||||||
|
type: propertyTypes, arrayType: propertyTypes,
|
||||||
|
original: any, readonly?: boolean
|
||||||
|
};
|
||||||
|
isNumberArray = false;
|
||||||
|
isNumber = false;
|
||||||
|
type = 'text';
|
||||||
|
_options: { key: number | string; value: string | number; }[] = [];
|
||||||
|
title: string;
|
||||||
|
idName: string;
|
||||||
|
_disabled: boolean;
|
||||||
|
private readonly GUID = Utils.GUID();
|
||||||
|
|
||||||
|
|
||||||
|
// value: { default: any, setting: any, original: any, readonly?: boolean, onChange: () => void };
|
||||||
|
|
||||||
|
constructor(private i18n: I18n) {
|
||||||
|
}
|
||||||
|
|
||||||
|
get changed(): boolean {
|
||||||
|
if (this._disabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.state.type === 'array') {
|
||||||
|
return !Utils.equalsFilter(this.state.value, this.state.default);
|
||||||
|
}
|
||||||
|
return this.state.value !== this.state.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shouldHide(): boolean {
|
||||||
|
if (Array.isArray(this.state.value)) {
|
||||||
|
return this.simplifiedMode && Utils.equalsFilter(this.state.value, this.state.default)
|
||||||
|
&& Utils.equalsFilter(this.state.original, this.state.default);
|
||||||
|
}
|
||||||
|
return this.simplifiedMode && this.state.value === this.state.default && this.state.original === this.state.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get PlaceHolder(): string {
|
||||||
|
return this.placeholder || this.state.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultStr(): string {
|
||||||
|
|
||||||
|
if (this.state.type === 'array' && this.state.arrayType === 'string') {
|
||||||
|
return (this.state.default || []).join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): any {
|
||||||
|
if (this.state.type === 'array' &&
|
||||||
|
(this.state.arrayType === 'string' || this.isNumberArray)) {
|
||||||
|
return this.state.value.join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(value: any) {
|
||||||
|
if (this.state.type === 'array' &&
|
||||||
|
(this.state.arrayType === 'string' || this.isNumberArray)) {
|
||||||
|
value = value.replace(new RegExp(',', 'g'), ';');
|
||||||
|
value = value.replace(new RegExp(' ', 'g'), ';');
|
||||||
|
this.state.value = value.split(';');
|
||||||
|
if (this.isNumberArray) {
|
||||||
|
this.state.value = this.state.value.map((v: string) => parseFloat(v)).filter((v: number) => !isNaN(v));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.state.value = value;
|
||||||
|
if (this.isNumber) {
|
||||||
|
this.state.value = parseFloat(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
this._disabled = isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
if (!this.state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.options) {
|
||||||
|
this.state.isEnumType = true;
|
||||||
|
}
|
||||||
|
this.title = '';
|
||||||
|
if (this.state.readonly) {
|
||||||
|
this.title = this.i18n('readonly') + ', ';
|
||||||
|
}
|
||||||
|
this.title += this.i18n('default value') + ': ' + this.defaultStr;
|
||||||
|
if (this.name) {
|
||||||
|
this.idName = this.GUID + this.name.toLowerCase().replace(new RegExp(' ', 'gm'), '-');
|
||||||
|
}
|
||||||
|
this.isNumberArray = this.state.arrayType === 'unsignedInt' ||
|
||||||
|
this.state.arrayType === 'integer' || this.state.arrayType === 'float' || this.state.arrayType === 'positiveFloat';
|
||||||
|
this.isNumber = this.state.type === 'unsignedInt' ||
|
||||||
|
this.state.type === 'integer' || this.state.type === 'float' || this.state.type === 'positiveFloat';
|
||||||
|
if (this.state.isEnumType) {
|
||||||
|
if (this.options) {
|
||||||
|
this._options = this.options;
|
||||||
|
} else {
|
||||||
|
if (this.optionMap) {
|
||||||
|
this._options = Utils.enumToArray(this.state.type).map(this.optionMap);
|
||||||
|
} else {
|
||||||
|
this._options = Utils.enumToArray(this.state.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isNumber) {
|
||||||
|
this.type = 'number';
|
||||||
|
} else if (this.state.type === 'password') {
|
||||||
|
this.type = 'password';
|
||||||
|
} else {
|
||||||
|
this.type = 'text';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(control: FormControl): ValidationErrors {
|
||||||
|
if (!this.required || (this.state && this.state.value && this.state.value !== '')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {required: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChange(value: any): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onTouched(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeValue(obj: any): void {
|
||||||
|
this.state = obj;
|
||||||
|
this.ngOnChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerOnChange(fn: any): void {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerOnTouched(fn: any): void {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
.changed-settings input, .changed-settings select {
|
||||||
|
border-color: #007bff;
|
||||||
|
border-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changed-settings label {
|
||||||
|
color: #007bff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
@ -7,91 +7,82 @@
|
|||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="applicationTitle" i18n>Page title</label>
|
name="Page title"
|
||||||
<div class="col-md-10">
|
[ngModel]="states.applicationTitle"
|
||||||
<input type="text" class="form-control" placeholder="Pigallery 2"
|
i18n-name
|
||||||
id="applicationTitle"
|
required="true"
|
||||||
[(ngModel)]="settings.applicationTitle"
|
[simplifiedMode]="simplifiedMode">
|
||||||
name="applicationTitle" required>
|
</app-settings-entry>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
|
||||||
<label class="col-md-2 control-label" for="host" i18n>Host</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="0.0.0.0"
|
|
||||||
id="host"
|
|
||||||
[(ngModel)]="settings.host"
|
|
||||||
name="host" required>
|
|
||||||
<small class="form-text text-muted" i18n>Server will accept connections from this IPv6 or IPv4 address.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="port" i18n>Port</label>
|
name="Host"
|
||||||
<div class="col-md-10">
|
[ngModel]="states.host"
|
||||||
<input type="number" class="form-control" placeholder="80"
|
description="Server will accept connections from this IPv6 or IPv4 address."
|
||||||
id="port"
|
i18n-description i18n-name
|
||||||
min="0"
|
placeholder="0.0.0.0"
|
||||||
step="1"
|
required="true"
|
||||||
max="65535"
|
[simplifiedMode]="simplifiedMode">
|
||||||
[(ngModel)]="settings.port"
|
</app-settings-entry>
|
||||||
name="port" required>
|
|
||||||
<small class="form-text text-muted" i18n>Port number. Port 80 is usually what you need.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="imagesFolder" i18n>Images folder</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="path"
|
|
||||||
id="imagesFolder"
|
|
||||||
[(ngModel)]="settings.imagesFolder"
|
|
||||||
name="imagesFolder" required>
|
|
||||||
<small class="form-text text-muted" i18n>Images are loaded from this folder (read permission required)</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="tempFolder" i18n>Temp folder</label>
|
name="Port"
|
||||||
<div class="col-md-10">
|
description="Port number. Port 80 is usually what you need."
|
||||||
<input type="text" class="form-control" placeholder="path"
|
i18n-description i18n-name
|
||||||
id="tempFolder"
|
[ngModel]="states.port"
|
||||||
[(ngModel)]="settings.tempFolder"
|
required="true"
|
||||||
name="tempFolder" required>
|
[simplifiedMode]="simplifiedMode">
|
||||||
<small class="form-text text-muted" i18n>Thumbnails, coverted photos, videos will be stored here (write permission required)</small>
|
</app-settings-entry>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
|
||||||
<label class="col-md-2 control-label" for="publicUrl" i18n>Page public url</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="url" class="form-control" placeholder="{{urlPlaceholder}}"
|
|
||||||
id="publicUrl"
|
|
||||||
[(ngModel)]="settings.publicUrl"
|
|
||||||
(change)="onUrlChanged()"
|
|
||||||
name="publicUrl">
|
|
||||||
<small class="form-text text-muted" i18n>If you access the page form local network its good to know the public
|
|
||||||
url for creating sharing link
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="urlBase" i18n>Url Base</label>
|
name="Images folder"
|
||||||
<div class="col-md-10">
|
description="Images are loaded from this folder (read permission required)"
|
||||||
<input type="url" class="form-control" placeholder="/myGallery"
|
placeholder="path"
|
||||||
id="urlBase"
|
i18n-description i18n-name
|
||||||
[(ngModel)]="settings.urlBase"
|
required
|
||||||
(change)="onUrlBaseChanged()"
|
[ngModel]="states.imagesFolder">
|
||||||
name="urlBase">
|
</app-settings-entry>
|
||||||
<small class="form-text text-muted" i18n>If you access the gallery under a sub url (like:
|
|
||||||
http://mydomain.com/myGallery), set it here. If not working you might miss the '/' from the beginning of the
|
|
||||||
url.
|
<app-settings-entry
|
||||||
</small>
|
name="Temp folder"
|
||||||
</div>
|
description="Thumbnails, converted photos, videos will be stored here (write
|
||||||
</div>
|
permission required)"
|
||||||
|
placeholder="path"
|
||||||
|
i18n-description i18n-name
|
||||||
|
required="true"
|
||||||
|
[ngModel]="states.tempFolder">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Page public url"
|
||||||
|
[(ngModel)]="states.publicUrl"
|
||||||
|
(change)="onUrlChanged()"
|
||||||
|
description="If you access the page form local network its good to know the public
|
||||||
|
url for creating sharing link"
|
||||||
|
i18n-description i18n-name
|
||||||
|
[placeholder]="urlPlaceholder"
|
||||||
|
[simplifiedMode]="simplifiedMode">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Url Base"
|
||||||
|
description="If you access the gallery under a sub url (like:
|
||||||
|
http://mydomain.com/myGallery), set it here. If it is not working you might miss the '/' from the beginning
|
||||||
|
of the
|
||||||
|
url."
|
||||||
|
placeholder="/myGallery"
|
||||||
|
i18n-description i18n-name
|
||||||
|
[ngModel]="states.urlBase"
|
||||||
|
(change)="onUrlBaseChanged()"
|
||||||
|
[simplifiedMode]="simplifiedMode">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
<div *ngIf="urlError===true" class="alert alert-warning" role="alert" i18n>
|
<div *ngIf="urlError===true" class="alert alert-warning" role="alert" i18n>
|
||||||
The public url and the url base are not matching. Some of the functionality might not work.
|
The public url and the url base are not matching. Some of the functionality might not work.
|
||||||
|
@ -46,7 +46,7 @@ export class BasicSettingsComponent extends SettingsComponent<BasicConfigDTO> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
calcBaseUrl(): string {
|
calcBaseUrl(): string {
|
||||||
const url = this.settings.publicUrl.replace(new RegExp('\\\\', 'g'), '/')
|
const url = this.states.publicUrl.value.replace(new RegExp('\\\\', 'g'), '/')
|
||||||
.replace(new RegExp('http://', 'g'), '')
|
.replace(new RegExp('http://', 'g'), '')
|
||||||
.replace(new RegExp('https://', 'g'), '');
|
.replace(new RegExp('https://', 'g'), '');
|
||||||
if (url.indexOf('/') !== -1) {
|
if (url.indexOf('/') !== -1) {
|
||||||
@ -57,23 +57,24 @@ export class BasicSettingsComponent extends SettingsComponent<BasicConfigDTO> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkUrlError() {
|
checkUrlError() {
|
||||||
this.urlError = this.settings.urlBase !== this.calcBaseUrl();
|
this.urlError = this.states.urlBase.value !== this.calcBaseUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
onUrlChanged() {
|
onUrlChanged() {
|
||||||
|
console.log('called');
|
||||||
if (this.urlBaseChanged === false) {
|
if (this.urlBaseChanged === false) {
|
||||||
this.settings.urlBase = this.calcBaseUrl();
|
this.states.urlBase.value = this.calcBaseUrl();
|
||||||
} else {
|
} else {
|
||||||
this.checkUrlError();
|
this.checkUrlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUrlBaseChanged() {
|
onUrlBaseChanged = () => {
|
||||||
this.urlBaseChanged = true;
|
this.urlBaseChanged = true;
|
||||||
|
|
||||||
this.checkUrlError();
|
this.checkUrlError();
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,71 +6,68 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Type"
|
||||||
|
[ngModel]="states.type"
|
||||||
|
i18n-name
|
||||||
|
required="true">
|
||||||
|
<small *ngIf="states.type.value == DatabaseType.mysql"
|
||||||
|
class="form-text text-muted" i18n>Install manually mysql node module to use mysql (npm install mysql)
|
||||||
|
</small>
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="Type" i18n>Type</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<select name="Type" id="Type"
|
|
||||||
class="form-control" [(ngModel)]="settings.type" required>
|
|
||||||
<option *ngFor="let type of types" [ngValue]="type.key">{{type.value}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<small *ngIf="settings.type == DatabaseType.mysql"
|
|
||||||
class="form-text text-muted" i18n>Install manually mysql node module to use mysql (npm install mysql)
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="dbFolder" i18n>Database folder</label>
|
name="Database folder"
|
||||||
<div class="col-md-10">
|
description="All file-based data will be stored here (sqlite database, user database in case of memory db, job history data)"
|
||||||
<input type="text" class="form-control" placeholder="db"
|
[ngModel]="states.dbFolder"
|
||||||
[(ngModel)]="settings.dbFolder" id="dbFolder" name="dbFolder" required>
|
i18n-name i18n-description
|
||||||
<small class="form-text text-muted" i18n>
|
required="true">
|
||||||
All file-based data will be stored here (sqlite database, user database in case of memory db, job history
|
</app-settings-entry>
|
||||||
data)
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="settings.type == DatabaseType.mysql">
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<ng-container *ngIf="states.type.value == DatabaseType.mysql">
|
||||||
<label class="col-md-2 control-label" for="mysql_host" i18n>Host</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="localhost"
|
<app-settings-entry
|
||||||
[(ngModel)]="settings.mysql.host" id="mysql_host" name="mysql_host" required>
|
name="Host"
|
||||||
</div>
|
[ngModel]="states.mysql.host"
|
||||||
</div>
|
i18n-name
|
||||||
<div class="form-group row">
|
required="true">
|
||||||
<label class="col-md-2 control-label" for="mysql_port" i18n>Port</label>
|
</app-settings-entry>
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="number" class="form-control" placeholder="3306" min="0" max="65535"
|
<app-settings-entry
|
||||||
[(ngModel)]="settings.mysql.port" id="mysql_port" name="mysql_port" required>
|
name="Port"
|
||||||
</div>
|
[ngModel]="states.mysql.port"
|
||||||
</div>
|
i18n-name
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Database"
|
||||||
|
[ngModel]="states.mysql.database"
|
||||||
|
i18n-name
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Username"
|
||||||
|
[ngModel]="states.mysql.username"
|
||||||
|
placeholder="username"
|
||||||
|
i18n-name
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Password"
|
||||||
|
[ngModel]="states.mysql.password"
|
||||||
|
placeholder="password"
|
||||||
|
i18n-name
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="mysql_database" i18n>Database</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="pigallery2"
|
|
||||||
[(ngModel)]="settings.mysql.database" id="mysql_database" name="mysql_database" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="mysql_username" i18n>Username</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="username"
|
|
||||||
[(ngModel)]="settings.mysql.username" id="mysql_username" name="mysql_username" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="mysql_password" i18n>Password</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="password" class="form-control" placeholder="password"
|
|
||||||
[(ngModel)]="settings.mysql.password" id="mysql_password" name="mysql_password" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,62 +1,49 @@
|
|||||||
<form #settingsForm="ngForm">
|
<form #settingsForm="ngForm">
|
||||||
<div class="card mb-4"
|
<div class="card mb-4"
|
||||||
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
|
[ngClass]="states.enabled.value && !_settingsService.isSupported()?'panel-warning':''">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
{{Name}}
|
{{Name}}
|
||||||
<div class="switch-wrapper">
|
<div class="switch-wrapper"
|
||||||
|
[class.changed-settings]="states.enabled.value != states.enabled.default">
|
||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
[switch-disabled]="inProgress || (!settings.enabled && !_settingsService.isSupported())"
|
i18n-switch-off-text
|
||||||
[switch-handle-width]="100"
|
i18n-switch-on-text
|
||||||
[switch-label-width]="20"
|
[switch-disabled]="inProgress || (!states.enabled.value && !_settingsService.isSupported())"
|
||||||
[(ngModel)]="settings.enabled">
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
|
[(ngModel)]="states.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
<ng-container *ngIf="settings.enabled || _settingsService.isSupported()">
|
<ng-container *ngIf="states.enabled.value || _settingsService.isSupported()">
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="autocompleteEnabled" i18n>Override keywords</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
|
||||||
id="autocompleteEnabled"
|
|
||||||
class="switch"
|
|
||||||
name="autocompleteEnabled"
|
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-disabled]="!settings.enabled"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.keywordsToPersons">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>If a photo has the same face (person) name and keyword, the app removes the duplicate, keeping the face only.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="writeAccessMinRole" i18n>Face starring right</label>
|
name="Override keywords"
|
||||||
<div class="col-md-10">
|
[ngModel]="states.keywordsToPersons"
|
||||||
<select class="form-control" [(ngModel)]="settings.writeAccessMinRole" name="writeAccessMinRole" id="writeAccessMinRole" required>
|
description="If a photo has the same face (person) name and keyword, the app removes the duplicate, keeping the face only."
|
||||||
<option *ngFor="let repository of userRoles" [value]="repository.key">{{repository.value}}
|
i18n-name i18n-description>
|
||||||
</option>
|
</app-settings-entry>
|
||||||
</select>
|
|
||||||
<small class="form-text text-muted" i18n>Required minimum right to start (favourite) a face.</small>
|
<app-settings-entry
|
||||||
</div>
|
name="Face starring right"
|
||||||
</div>
|
[ngModel]="states.writeAccessMinRole"
|
||||||
|
description="Required minimum right to start (favourite) a face."
|
||||||
|
i18n-name i18n-description>
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="panel-info" *ngIf="(!settings.enabled && !_settingsService.isSupported())" i18n>
|
|
||||||
|
<div class="panel-info" *ngIf="(!states.enabled.value && !_settingsService.isSupported())" i18n>
|
||||||
Faces are not supported with these settings.
|
Faces are not supported with these settings.
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -7,84 +7,66 @@
|
|||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!simplifiedMode">
|
<ng-container *ngIf="!simplifiedMode">
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="cachedFolderTimeout" i18n>Index cache timeout [ms]</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="number" class="form-control" placeholder="36000"
|
|
||||||
id="cachedFolderTimeout"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
[(ngModel)]="settings.cachedFolderTimeout"
|
|
||||||
name="cachedFolderTimeout" required>
|
|
||||||
<small class="form-text text-muted" i18n>If there was no indexing in this time, it reindexes. (skipped if
|
|
||||||
indexes are in DB and sensitivity is low)
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="folderPreviewSize" i18n>Sub folder preview size</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="number" class="form-control" placeholder="1"
|
|
||||||
id="folderPreviewSize"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
[(ngModel)]="settings.folderPreviewSize"
|
|
||||||
name="folderPreviewSize" required>
|
|
||||||
<small class="form-text text-muted" i18n>Reads this many photos from sub folders</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="reIndexingSensitivity" i18n>Folder reindexing sensitivity</label>
|
name="Index cache timeout [ms]"
|
||||||
<div class="col-md-10">
|
description="If there was no indexing in this time, it reindexes. (skipped if indexes are in DB and sensitivity is low)"
|
||||||
<select id="reIndexingSensitivity" class="form-control" [(ngModel)]="settings.reIndexingSensitivity"
|
i18n-description i18n-name
|
||||||
name="reIndexingSensitivity" required>
|
[ngModel]="states.cachedFolderTimeout"
|
||||||
<option *ngFor="let type of types" [ngValue]="type.key">{{type.value}}
|
required="true">
|
||||||
</option>
|
</app-settings-entry>
|
||||||
</select>
|
|
||||||
<small
|
<app-settings-entry
|
||||||
class="form-text text-muted"
|
name="Sub folder preview size"
|
||||||
i18n>Set the reindexing sensitivity. High value check the folders for change more often.
|
description="Reads this many photos from sub folders."
|
||||||
</small>
|
i18n-description i18n-name
|
||||||
</div>
|
[ngModel]="states.folderPreviewSize"
|
||||||
</div>
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Folder reindexing sensitivity"
|
||||||
|
[ngModel]="states.reIndexingSensitivity"
|
||||||
|
description="Set the reindexing sensitivity. High value check the folders for change more often."
|
||||||
|
i18n-description i18n-name
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Exclude Folder List"
|
||||||
|
i18n-name
|
||||||
|
placeholder="/media/images/family/private;private;family/private"
|
||||||
|
[ngModel]="states.excludeFolderList"
|
||||||
|
required="true">
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
<ng-container i18n>Folders to exclude from indexing</ng-container>
|
||||||
|
<br/>
|
||||||
|
<ng-container
|
||||||
|
i18n>';' separated strings. If an entry starts with '/' it is treated as an absolute path. If it doesn't
|
||||||
|
start with '/' but contains a '/', the path is relative to the image directory. If it doesn't contain a
|
||||||
|
'/', any folder with this name will be excluded.
|
||||||
|
</ng-container>
|
||||||
|
</small>
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Exclude File List"
|
||||||
|
i18n-name
|
||||||
|
placeholder=".ignore;.pg2ignore"
|
||||||
|
[ngModel]="states.excludeFileList"
|
||||||
|
required="true">
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
<ng-container i18n>Files that mark a folder to be excluded from indexing</ng-container>
|
||||||
|
<br/>
|
||||||
|
<ng-container
|
||||||
|
i18n>';' separated strings. Any folder that contains a file with this name will be excluded from
|
||||||
|
indexing.
|
||||||
|
</ng-container>
|
||||||
|
</small>
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="excludeFolderList" i18n>Exclude Folder List</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="/media/images/family/private;private;family/private"
|
|
||||||
id="excludeFolderList"
|
|
||||||
[(ngModel)]="excludeFolderList"
|
|
||||||
name="excludeFolderList" required>
|
|
||||||
<small class="form-text text-muted">
|
|
||||||
<ng-container i18n>Folders to exclude from indexing</ng-container>
|
|
||||||
<br/>
|
|
||||||
<ng-container
|
|
||||||
i18n>';' separated strings. If an entry starts with '/' it is treated as an absolute path. If it doesn't
|
|
||||||
start with '/' but contains a '/', the path is relative to the image directory. If it doesn't contain a
|
|
||||||
'/', any folder with this name will be excluded.
|
|
||||||
</ng-container>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="excludeFileList" i18n>Exclude File List</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder=".ignore;.pg2ignore"
|
|
||||||
id="excludeFileList"
|
|
||||||
[(ngModel)]="excludeFileList"
|
|
||||||
name="excludeFileList" required>
|
|
||||||
<small class="form-text text-muted">
|
|
||||||
<ng-container i18n>Files that mark a folder to be excluded from indexing</ng-container>
|
|
||||||
<br/>
|
|
||||||
<ng-container
|
|
||||||
i18n>';' separated strings. Any folder that contains a file with this name will be excluded from
|
|
||||||
indexing.
|
|
||||||
</ng-container>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -49,21 +49,6 @@ export class IndexingSettingsComponent extends SettingsComponent<ServerConfig.In
|
|||||||
return this.jobsService.progress.value[JobDTO.getHashName(DefaultsJobs[DefaultsJobs.Indexing])];
|
return this.jobsService.progress.value[JobDTO.getHashName(DefaultsJobs[DefaultsJobs.Indexing])];
|
||||||
}
|
}
|
||||||
|
|
||||||
get excludeFolderList(): string {
|
|
||||||
return this.settings.excludeFolderList.join(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
set excludeFolderList(value: string) {
|
|
||||||
this.settings.excludeFolderList = value.split(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
get excludeFileList(): string {
|
|
||||||
return this.settings.excludeFileList.join(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
set excludeFileList(value: string) {
|
|
||||||
this.settings.excludeFileList = value.split(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
super.ngOnDestroy();
|
super.ngOnDestroy();
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<select class="form-control" [(ngModel)]="schedule.trigger.afterScheduleName"
|
<select class="form-control" [(ngModel)]="schedule.trigger.afterScheduleName"
|
||||||
[name]="'triggerAfter'+i" required>
|
[name]="'triggerAfter'+i" required>
|
||||||
<ng-container *ngFor="let sch of settings.scheduled">
|
<ng-container *ngFor="let sch of states.scheduled.value">
|
||||||
<option *ngIf="sch.name !== schedule.name"
|
<option *ngIf="sch.name !== schedule.name"
|
||||||
[ngValue]="sch.name">{{sch.name}}
|
[ngValue]="sch.name">{{sch.name}}
|
||||||
</option>
|
</option>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<app-timestamp-datepicker
|
<app-timestamp-datepicker
|
||||||
[name]="'triggerTime'+i"
|
[name]="'triggerTime'+i"
|
||||||
(timestampChange)="testSettingChanges()"
|
(timestampChange)="onOptionChange()"
|
||||||
[(timestamp)]="schedule.trigger.time"></app-timestamp-datepicker>
|
[(timestamp)]="schedule.trigger.time"></app-timestamp-datepicker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -121,7 +121,7 @@
|
|||||||
</select>
|
</select>
|
||||||
<app-timestamp-timepicker
|
<app-timestamp-timepicker
|
||||||
[name]="'atTime'+i"
|
[name]="'atTime'+i"
|
||||||
(timestampChange)="testSettingChanges()"
|
(timestampChange)="onOptionChange()"
|
||||||
[(timestamp)]="schedule.trigger.atTime"></app-timestamp-timepicker>
|
[(timestamp)]="schedule.trigger.atTime"></app-timestamp-timepicker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -132,12 +132,13 @@
|
|||||||
class="switch"
|
class="switch"
|
||||||
[name]="'allowParallelRun'+'_'+i"
|
[name]="'allowParallelRun'+'_'+i"
|
||||||
[id]="'allowParallelRun'+'_'+i"
|
[id]="'allowParallelRun'+'_'+i"
|
||||||
[switch-on-color]="'primary'"
|
switch-on-color="primary"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
[switch-handle-width]="100"
|
i18n-switch-off-text i18n-switch-on-text
|
||||||
[switch-label-width]="20"
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
[(ngModel)]="schedule.allowParallelRun">
|
[(ngModel)]="schedule.allowParallelRun">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
<small class="form-text text-muted"
|
<small class="form-text text-muted"
|
||||||
@ -164,12 +165,13 @@
|
|||||||
class="switch"
|
class="switch"
|
||||||
[name]="configEntry.id+'_'+i"
|
[name]="configEntry.id+'_'+i"
|
||||||
[id]="configEntry.id+'_'+i"
|
[id]="configEntry.id+'_'+i"
|
||||||
[switch-on-color]="'primary'"
|
switch-on-color="primary"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
[switch-handle-width]="100"
|
i18n-switch-off-text i18n-switch-on-text
|
||||||
[switch-label-width]="20"
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
[(ngModel)]="schedule.config[configEntry.id]">
|
[(ngModel)]="schedule.config[configEntry.id]">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -100,7 +100,7 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove(schedule: JobScheduleDTO) {
|
remove(schedule: JobScheduleDTO) {
|
||||||
this.settings.scheduled.splice(this.settings.scheduled.indexOf(schedule), 1);
|
this.states.scheduled.value.splice(this.states.scheduled.value.indexOf(schedule), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
jobTypeChanged(schedule: JobScheduleDTO) {
|
jobTypeChanged(schedule: JobScheduleDTO) {
|
||||||
@ -163,17 +163,17 @@ export class JobsSettingsComponent extends SettingsComponent<ServerConfig.JobCon
|
|||||||
return curr && curr.trigger.type === JobTriggerType.after && prev && prev.name === curr.trigger.afterScheduleName;
|
return curr && curr.trigger.type === JobTriggerType.after && prev && prev.name === curr.trigger.afterScheduleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sortedSchedules() {
|
public sortedSchedules(): JobScheduleDTO[] {
|
||||||
return this.settings.scheduled.slice().sort((a, b) => {
|
return this.states.scheduled.value.slice().sort((a: JobScheduleDTO, b: JobScheduleDTO) => {
|
||||||
return this.getNextRunningDate(a, this.settings.scheduled) - this.getNextRunningDate(b, this.settings.scheduled);
|
return this.getNextRunningDate(a, this.states.scheduled.value) - this.getNextRunningDate(b, this.states.scheduled.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewJob() {
|
addNewJob() {
|
||||||
const jobName = this.newSchedule.jobName;
|
const jobName = this.newSchedule.jobName;
|
||||||
const count = this.settings.scheduled.filter(s => s.jobName === jobName).length;
|
const count = this.states.scheduled.value.filter((s: JobScheduleDTO) => s.jobName === jobName).length;
|
||||||
this.newSchedule.name = count === 0 ? jobName : this.backendTextService.getJobName(jobName) + ' ' + (count + 1);
|
this.newSchedule.name = count === 0 ? jobName : this.backendTextService.getJobName(jobName) + ' ' + (count + 1);
|
||||||
this.settings.scheduled.push(this.newSchedule);
|
this.states.scheduled.value.push(this.newSchedule);
|
||||||
this.jobModal.hide();
|
this.jobModal.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,56 +6,45 @@
|
|||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
|
i18n-switch-off-text
|
||||||
|
i18n-switch-on-text
|
||||||
[switch-disabled]="inProgress"
|
[switch-disabled]="inProgress"
|
||||||
[switch-handle-width]="100"
|
switch-handle-width="100"
|
||||||
[switch-label-width]="20"
|
switch-label-width="20"
|
||||||
[(ngModel)]="settings.enabled">
|
[(ngModel)]="states.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
<ng-container *ngIf="settings.enabled">
|
<ng-container *ngIf="states.enabled.value">
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
|
||||||
<label class="col-md-2 control-label" for="enableOnScrollRendering" i18n>Use image markers</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
|
||||||
id="enableOnScrollRendering"
|
|
||||||
class="switch"
|
|
||||||
name="enableOnScrollRendering"
|
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.useImageMarkers">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>
|
|
||||||
Map will use thumbnail images as markers instead of the default pin.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="mapProvider" i18n>Map provider</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<select name="mapProvider" id="mapProvider"
|
|
||||||
[disabled]="!settings.enabled"
|
|
||||||
class="form-control" [(ngModel)]="settings.mapProvider" required>
|
|
||||||
<option *ngFor="let type of mapProviders" [ngValue]="type.key">{{type.value}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container custom-layer-container" *ngIf="settings.mapProvider === MapProviders.Custom">
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Use image markers"
|
||||||
|
description="Map will use thumbnail images as markers instead of the default pin."
|
||||||
|
i18n-name i18n-description=""
|
||||||
|
[ngModel]="states.useImageMarkers"
|
||||||
|
[simplifiedMode]="simplifiedMode">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Map provider"
|
||||||
|
i18n-name
|
||||||
|
[ngModel]="states.mapProvider"
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container custom-layer-container" *ngIf="states.mapProvider.value === MapProviders.Custom">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -64,7 +53,7 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let layer of settings.customLayers; let i = index">
|
<tr *ngFor="let layer of states.customLayers.value; let i = index">
|
||||||
<td><input type="text" class="form-control" placeholder="Street"
|
<td><input type="text" class="form-control" placeholder="Street"
|
||||||
[(ngModel)]="layer.name"
|
[(ngModel)]="layer.name"
|
||||||
[name]="'tileName-'+i" [id]="'tileName-'+i" required></td>
|
[name]="'tileName-'+i" [id]="'tileName-'+i" required></td>
|
||||||
@ -74,8 +63,8 @@
|
|||||||
[name]="'tileUrl-'+i" [id]="'tileUrl-'+i" required>
|
[name]="'tileUrl-'+i" [id]="'tileUrl-'+i" required>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button [disabled]="settings.customLayers.length == 1" (click)="removeLayer(layer)"
|
<button [disabled]="states.customLayers.value.length == 1" (click)="removeLayer(layer)"
|
||||||
[ngClass]="settings.customLayers.length > 1? 'btn-danger':'btn-secondary'"
|
[ngClass]="states.customLayers.value.length > 1? 'btn-danger':'btn-secondary'"
|
||||||
class="btn float-right">
|
class="btn float-right">
|
||||||
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
<span class="oi oi-trash" aria-hidden="true" aria-label="Delete"></span>
|
||||||
</button>
|
</button>
|
||||||
@ -95,18 +84,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group row" *ngIf="settings.mapProvider === MapProviders.Mapbox">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="mapboxAccessToken" i18n>Mapbox access token</label>
|
*ngIf="states.mapProvider.value === MapProviders.Mapbox"
|
||||||
<div class="col-md-10">
|
name="Mapbox access token"
|
||||||
<input type="text" class="form-control" placeholder="Mapbox access token"
|
i18n-name
|
||||||
[(ngModel)]="settings.mapboxAccessToken"
|
placeholder="Mapbox access token"
|
||||||
name="mapboxAccessToken" id="mapboxAccessToken" required>
|
[ngModel]="states.mapboxAccessToken"
|
||||||
<small class="form-text text-muted">
|
required="true">
|
||||||
<ng-container i18n>MapBox needs an access token to work, create one at</ng-container>
|
<small class="form-text text-muted">
|
||||||
<a href="https://www.mapbox.com">https://www.mapbox.com</a>.
|
<ng-container i18n>MapBox needs an access token to work, create one at</ng-container>
|
||||||
</small>
|
<a href="https://www.mapbox.com">https://www.mapbox.com</a>.
|
||||||
</div>
|
</small>
|
||||||
</div>
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -33,14 +33,14 @@ export class MapSettingsComponent extends SettingsComponent<ClientConfig.MapConf
|
|||||||
|
|
||||||
|
|
||||||
addNewLayer() {
|
addNewLayer() {
|
||||||
this.settings.customLayers.push({
|
this.states.customLayers.value.push({
|
||||||
name: 'Layer-' + this.settings.customLayers.length,
|
name: 'Layer-' + this.states.customLayers.value.length,
|
||||||
url: ''
|
url: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLayer(layer: ClientConfig.MapLayers) {
|
removeLayer(layer: ClientConfig.MapLayers) {
|
||||||
this.settings.customLayers.splice(this.settings.customLayers.indexOf(layer), 1);
|
this.states.customLayers.value.splice(this.states.customLayers.value.indexOf(layer), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,14 +6,16 @@
|
|||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
|
i18n-switch-off-text
|
||||||
|
i18n-switch-on-text
|
||||||
[switch-disabled]="inProgress"
|
[switch-disabled]="inProgress"
|
||||||
[switch-handle-width]="100"
|
switch-handle-width="100"
|
||||||
[switch-label-width]="20"
|
switch-label-width="20"
|
||||||
[(ngModel)]="settings.enabled">
|
[(ngModel)]="states.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -8,161 +8,89 @@
|
|||||||
|
|
||||||
|
|
||||||
<p class="title" i18n>Threads:</p>
|
<p class="title" i18n>Threads:</p>
|
||||||
<div class="form-group row" >
|
|
||||||
<label class="col-md-2 control-label" for="enableThreading" i18n>Threading</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
|
||||||
id="enableThreading"
|
|
||||||
class="switch"
|
|
||||||
name="enable"
|
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.Server.enabled">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Runs directory scanning and thumbnail generation (only for Jimp) in a
|
|
||||||
different thread
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode || settings.Server.enabled == false">
|
|
||||||
<label class="col-md-2 control-label" for="thumbnailThreads" i18n>Thumbnail threads</label>
|
<app-settings-entry
|
||||||
<div class="col-md-10">
|
name="Threading"
|
||||||
<select id="thumbnailThreads" class="form-control" [(ngModel)]="settings.Server.thumbnailThreads"
|
description="Runs directory scanning and thumbnail generation (only for Jimp) in a different thread."
|
||||||
name="Server[thumbnailThreads]" required>
|
i18n-description i18n-name
|
||||||
<option [ngValue]="0">auto</option>
|
[ngModel]="states.Server.enabled"
|
||||||
<option *ngFor="let i of threads" [ngValue]="i">{{i}}</option>
|
required="true">
|
||||||
</select>
|
</app-settings-entry>
|
||||||
<small class="form-text text-muted" i18n>Number of threads that are used to generate thumbnails. If auto, number of CPU cores -1 threads will be used.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
<app-settings-entry
|
||||||
|
name="Thumbnail threads"
|
||||||
|
description="Number of threads that are used to generate thumbnails. If auto, number of CPU cores -1 threads will be used."
|
||||||
|
i18n-description i18n-name
|
||||||
|
[ngModel]="states.Server.thumbnailThreads"
|
||||||
|
[options]="threads"
|
||||||
|
[simplifiedMode]="simplifiedMode || states.Server.enabled.value == false"
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<p class="title" i18n>Misc:</p>
|
<p class="title" i18n>Misc:</p>
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="enableOnScrollThumbnailPrioritising" i18n>Scroll based thumbnail
|
|
||||||
generation</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
|
||||||
id="enableOnScrollThumbnailPrioritising"
|
|
||||||
class="switch"
|
|
||||||
name="enableOnScrollThumbnailPrioritising"
|
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.Client.enableOnScrollThumbnailPrioritising">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Those thumbnails get higher priority that are visible on the screen
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="enableOnScrollRendering" i18n>Lazy image rendering</label>
|
name="Scroll based thumbnail generation"
|
||||||
<div class="col-md-10">
|
description="Those thumbnails get higher priority that are visible on the screen."
|
||||||
<bSwitch
|
i18n-description i18n-name
|
||||||
id="enableOnScrollRendering"
|
[ngModel]="states.Client.enableOnScrollThumbnailPrioritising"
|
||||||
class="switch"
|
required="true">
|
||||||
name="enableOnScrollRendering"
|
</app-settings-entry>
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.Client.enableOnScrollRendering">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Shows only the required amount of photos at once. Renders more if
|
|
||||||
page bottom is reached
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Lazy image rendering"
|
||||||
|
description="Shows only the required amount of photos at once. Renders more if page bottom is reached."
|
||||||
|
i18n-description i18n-name
|
||||||
|
[ngModel]="states.Client.enableOnScrollRendering"
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="enableCache" i18n>Cache</label>
|
name="Cache"
|
||||||
<div class="col-md-10">
|
description="Caches directory contents and search results for better performance."
|
||||||
<bSwitch
|
i18n-description i18n-name
|
||||||
id="enableCache"
|
[ngModel]="states.Client.enableCache"
|
||||||
class="switch"
|
required="true">
|
||||||
name="enableCache"
|
</app-settings-entry>
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.Client.enableCache">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Caches directory contents and search results for better performance
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<app-settings-entry
|
||||||
|
name="Caption first naming"
|
||||||
|
description="Show the caption (IPTC 120) tags from the EXIF data instead of the filenames."
|
||||||
|
i18n-description i18n-name
|
||||||
|
[ngModel]="states.Client.captionFirstNaming"
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="enableCache" i18n>Caption first naming</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
|
||||||
id="captionFirstNaming"
|
|
||||||
class="switch"
|
|
||||||
name="captionFirstNaming"
|
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.Client.captionFirstNaming">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Show the caption (IPTC 120) tags from the EXIF data instead of the filenames.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<p class="title" i18n>Navigation bar:</p>
|
<p class="title" i18n>Navigation bar:</p>
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="showItemCount" [hidden]="simplifiedMode" i18n>Show item count</label>
|
<app-settings-entry
|
||||||
<div class="col-md-10">
|
name="Show item count"
|
||||||
<bSwitch
|
description="Show the number of items (photos) in the folder."
|
||||||
id="showItemCount"
|
i18n-description i18n-name
|
||||||
class="switch"
|
[ngModel]="states.Client.NavBar.showItemCount"
|
||||||
name="showItemCount"
|
[simplifiedMode]="simplifiedMode"
|
||||||
[switch-on-color]="'primary'"
|
required="true">
|
||||||
[switch-inverse]="true"
|
</app-settings-entry>
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.Client.NavBar.showItemCount">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Show the number of items (photos) in the folder
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
|
||||||
<label class="col-md-2 control-label" for="defaultPhotoSortingMethod" i18n>Default photo sorting method</label>
|
<app-settings-entry
|
||||||
<div class="col-md-10">
|
name="Default photo sorting method"
|
||||||
<select id="defaultPhotoSortingMethod" class="form-control" [(ngModel)]="settings.Client.defaultPhotoSortingMethod"
|
i18n-name
|
||||||
name="defaultPhotoSortingMethod" required>
|
[ngModel]="states.Client.defaultPhotoSortingMethod"
|
||||||
<option *ngFor="let type of types" [ngValue]="type.key">{{type.key | stringifySorting}}
|
[optionMap]="sortingMap"
|
||||||
</option>
|
[simplifiedMode]="simplifiedMode"
|
||||||
</select>
|
required="true">
|
||||||
</div>
|
</app-settings-entry>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -8,6 +8,7 @@ import {OtherConfigDTO} from '../../../../../common/entities/settings/OtherConfi
|
|||||||
import {I18n} from '@ngx-translate/i18n-polyfill';
|
import {I18n} from '@ngx-translate/i18n-polyfill';
|
||||||
import {Utils} from '../../../../../common/Utils';
|
import {Utils} from '../../../../../common/Utils';
|
||||||
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
import {SortingMethods} from '../../../../../common/entities/SortingMethods';
|
||||||
|
import {StringifySortingMethod} from '../../../pipes/StringifySortingMethod';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings-other',
|
selector: 'app-settings-other',
|
||||||
@ -20,17 +21,24 @@ export class OtherSettingsComponent extends SettingsComponent<OtherConfigDTO> im
|
|||||||
|
|
||||||
|
|
||||||
types: { key: number; value: string }[] = [];
|
types: { key: number; value: string }[] = [];
|
||||||
threads: number[] = Utils.createRange(1, 100);
|
threads: { key: number, value: string }[] = [{key: 0, value: 'auto'}].concat(Utils.createRange(1, 100)
|
||||||
|
.map(v => ({key: v, value: '' + v})));
|
||||||
|
sortingMap: any;
|
||||||
|
|
||||||
constructor(_authService: AuthenticationService,
|
constructor(_authService: AuthenticationService,
|
||||||
_navigation: NavigationService,
|
_navigation: NavigationService,
|
||||||
_settingsService: OtherSettingsService,
|
_settingsService: OtherSettingsService,
|
||||||
notification: NotificationService,
|
notification: NotificationService,
|
||||||
i18n: I18n) {
|
i18n: I18n,
|
||||||
|
private formatter: StringifySortingMethod) {
|
||||||
super(i18n('Other'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
super(i18n('Other'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||||
Server: s.Server.Threading,
|
Server: s.Server.Threading,
|
||||||
Client: s.Client.Other
|
Client: s.Client.Other
|
||||||
}));
|
}));
|
||||||
|
this.sortingMap = (v: { key: number; value: string }) => {
|
||||||
|
v.value = this.formatter.transform(v.key);
|
||||||
|
return v;
|
||||||
|
};
|
||||||
this.types = Utils.enumToArray(SortingMethods);
|
this.types = Utils.enumToArray(SortingMethods);
|
||||||
this.hasAvailableSettings = !this.simplifiedMode;
|
this.hasAvailableSettings = !this.simplifiedMode;
|
||||||
}
|
}
|
||||||
|
@ -4,90 +4,64 @@
|
|||||||
{{Name}}</h5>
|
{{Name}}</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
<div [hidden]="settings.photoProcessingLibrary!=PhotoProcessingLib.Jimp"
|
<div [hidden]="states.photoProcessingLibrary.value!=PhotoProcessingLib.Jimp"
|
||||||
class="alert alert-warning"
|
class="alert alert-warning"
|
||||||
role="alert" i18n>It is highly recommended to use hardware accelerated (sharp or gm) lib for thumbnail
|
role="alert" i18n>It is highly recommended to use hardware accelerated (sharp or gm) lib for thumbnail
|
||||||
generation
|
generation
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [hidden]="simplifiedMode">
|
<div [hidden]="simplifiedMode">
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="lib" i18n>Thumbnail generation library</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<select id="lib" class="form-control" [(ngModel)]="settings.photoProcessingLibrary"
|
|
||||||
name="type" required>
|
|
||||||
<option *ngFor="let type of libTypes" [ngValue]="type.key">{{type.value}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<small *ngIf="settings.photoProcessingLibrary==PhotoProcessingLib.sharp"
|
|
||||||
class="form-text text-muted" i18n>Make sure that sharp node module is installed (npm install sharp).
|
|
||||||
</small>
|
|
||||||
<small *ngIf="settings.photoProcessingLibrary==PhotoProcessingLib.gm"
|
|
||||||
class="form-text text-muted">
|
|
||||||
<ng-container i18n>Make sure that gm node module and</ng-container>
|
|
||||||
<a href="http://www.graphicsmagick.org/">GraphicsMagick</a>
|
|
||||||
<ng-container i18n>are installed (npm install sharp).</ng-container>
|
|
||||||
</small>
|
|
||||||
|
|
||||||
</div>
|
<app-settings-entry
|
||||||
</div>
|
name="Thumbnail generation library"
|
||||||
|
i18n-name
|
||||||
|
[ngModel]="states.photoProcessingLibrary"
|
||||||
|
[optionMap]="libTypesMap"
|
||||||
|
[simplifiedMode]="simplifiedMode"
|
||||||
|
required="true">
|
||||||
|
<small *ngIf="states.photoProcessingLibrary.value==PhotoProcessingLib.sharp"
|
||||||
|
class="form-text text-muted" i18n>Make sure that sharp node module is installed (npm install sharp).
|
||||||
|
</small>
|
||||||
|
<small *ngIf="states.photoProcessingLibrary.value==PhotoProcessingLib.gm"
|
||||||
|
class="form-text text-muted">
|
||||||
|
<ng-container i18n>Make sure that gm node module and</ng-container>
|
||||||
|
<a href="http://www.graphicsmagick.org/">GraphicsMagick</a>
|
||||||
|
<ng-container i18n>are installed (npm install sharp).</ng-container>
|
||||||
|
</small>
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
</div>
|
</div>
|
||||||
<p class="title" i18n>Photo converting:</p>
|
<p class="title" i18n>Photo converting:</p>
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="enablePhotoConverting" i18n>Converting</label>
|
name="Converting"
|
||||||
<div class="col-md-10">
|
description="Downsizes photos for faster preview loading. (Zooming in to the photo loads the original)."
|
||||||
<bSwitch
|
i18n-description i18n-name
|
||||||
id="enablePhotoConverting"
|
[ngModel]="states.client.Converting.enabled"
|
||||||
class="switch"
|
required="true">
|
||||||
name="enablePhotoConverting"
|
</app-settings-entry>
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.client.Converting.enabled">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Downsizes photos for faster preview loading. (Zooming in to the photo
|
|
||||||
loads the original)</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="!settings.client.Converting.enabled || simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="onTheFlyConverting" i18n>On the fly converting </label>
|
name="On the fly converting"
|
||||||
<div class="col-md-10">
|
description="Converts photos on the fly, when they are requested."
|
||||||
<bSwitch
|
i18n-description i18n-name
|
||||||
id="onTheFlyConverting"
|
[ngModel]="states.server.Converting.onTheFly"
|
||||||
class="switch"
|
[simplifiedMode]="simplifiedMode"
|
||||||
name="onTheFlyConverting"
|
[disabled]="!states.client.Converting.enabled.value"
|
||||||
[switch-on-color]="'primary'"
|
required="true">
|
||||||
[switch-inverse]="true"
|
</app-settings-entry>
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.server.Converting.onTheFly">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Converts photos on the fly, when they are requested.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="!settings.client.Converting.enabled">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="resolution" i18n>Resolution</label>
|
name="Resolution"
|
||||||
<div class="col-md-10">
|
description="The shorter edge of the converted photo will be scaled down to this, while keeping the aspect ratio."
|
||||||
<select id="resolution" class="form-control" [(ngModel)]="settings.server.Converting.resolution"
|
i18n-description i18n-name
|
||||||
name="resolution" required>
|
[ngModel]="states.server.Converting.resolution"
|
||||||
<option *ngFor="let resolution of resolutions" [ngValue]="resolution">{{resolution}}px
|
[options]="resolutions"
|
||||||
</option>
|
[disabled]="!states.client.Converting.enabled.value"
|
||||||
</select>
|
required="true">
|
||||||
<small class="form-text text-muted" i18n>The shorter edge of the converted photo will be scaled down to this,
|
</app-settings-entry>
|
||||||
while
|
|
||||||
keeping the aspect ratio.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
@ -99,7 +73,7 @@
|
|||||||
(click)="reset()" i18n>Reset
|
(click)="reset()" i18n>Reset
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div [hidden]="!settings.client.Converting.enabled">
|
<div [hidden]="!states.client.Converting.enabled.value">
|
||||||
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
<app-settings-job-button class="mt-2 mt-md-0 float-left"
|
||||||
[soloRun]="true"
|
[soloRun]="true"
|
||||||
(error)="error=$event"
|
(error)="error=$event"
|
||||||
|
@ -6,7 +6,6 @@ import {NavigationService} from '../../../model/navigation.service';
|
|||||||
import {NotificationService} from '../../../model/notification.service';
|
import {NotificationService} from '../../../model/notification.service';
|
||||||
import {I18n} from '@ngx-translate/i18n-polyfill';
|
import {I18n} from '@ngx-translate/i18n-polyfill';
|
||||||
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
import {ScheduledJobsService} from '../scheduled-jobs.service';
|
||||||
import {Utils} from '../../../../../common/Utils';
|
|
||||||
import {DefaultsJobs, JobDTO} from '../../../../../common/entities/job/JobDTO';
|
import {DefaultsJobs, JobDTO} from '../../../../../common/entities/job/JobDTO';
|
||||||
import {JobProgressStates} from '../../../../../common/entities/job/JobProgressDTO';
|
import {JobProgressStates} from '../../../../../common/entities/job/JobProgressDTO';
|
||||||
import {ServerConfig} from '../../../../../common/config/private/PrivateConfig';
|
import {ServerConfig} from '../../../../../common/config/private/PrivateConfig';
|
||||||
@ -25,19 +24,11 @@ export class PhotoSettingsComponent extends SettingsComponent<{
|
|||||||
server: ServerConfig.PhotoConfig,
|
server: ServerConfig.PhotoConfig,
|
||||||
client: ClientConfig.PhotoConfig
|
client: ClientConfig.PhotoConfig
|
||||||
}> {
|
}> {
|
||||||
resolutions = [720, 1080, 1440, 2160, 4320];
|
readonly resolutionTypes = [720, 1080, 1440, 2160, 4320];
|
||||||
|
resolutions: { key: number, value: string }[] = [];
|
||||||
PhotoProcessingLib = ServerConfig.PhotoProcessingLib;
|
PhotoProcessingLib = ServerConfig.PhotoProcessingLib;
|
||||||
JobProgressStates = JobProgressStates;
|
JobProgressStates = JobProgressStates;
|
||||||
|
|
||||||
libTypes = Utils
|
|
||||||
.enumToArray(ServerConfig.PhotoProcessingLib).map((v) => {
|
|
||||||
if (v.value.toLowerCase() === 'sharp') {
|
|
||||||
v.value += ' ' + this.i18n('(recommended)');
|
|
||||||
} else {
|
|
||||||
v.value += ' ' + this.i18n('(deprecated, will be removed)');
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
readonly jobName = DefaultsJobs[DefaultsJobs['Photo Converting']];
|
readonly jobName = DefaultsJobs[DefaultsJobs['Photo Converting']];
|
||||||
|
|
||||||
constructor(_authService: AuthenticationService,
|
constructor(_authService: AuthenticationService,
|
||||||
@ -52,15 +43,24 @@ export class PhotoSettingsComponent extends SettingsComponent<{
|
|||||||
server: s.Server.Media.Photo
|
server: s.Server.Media.Photo
|
||||||
}));
|
}));
|
||||||
const currentRes = _settingsService.Settings.value.Server.Media.Photo.Converting.resolution;
|
const currentRes = _settingsService.Settings.value.Server.Media.Photo.Converting.resolution;
|
||||||
if (this.resolutions.indexOf(currentRes) === -1) {
|
if (this.resolutionTypes.indexOf(currentRes) === -1) {
|
||||||
this.resolutions.push(currentRes);
|
this.resolutionTypes.push(currentRes);
|
||||||
}
|
}
|
||||||
|
this.resolutions = this.resolutionTypes.map(e => ({key: e, value: e + 'px'}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get Progress() {
|
get Progress() {
|
||||||
return this.jobsService.progress.value[JobDTO.getHashName(DefaultsJobs[DefaultsJobs['Photo Converting']])];
|
return this.jobsService.progress.value[JobDTO.getHashName(DefaultsJobs[DefaultsJobs['Photo Converting']])];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
libTypesMap = (v: { key: number, value: string }) => {
|
||||||
|
if (v.value.toLowerCase() === 'sharp') {
|
||||||
|
v.value += ' ' + this.i18n('(recommended)');
|
||||||
|
} else {
|
||||||
|
v.value += ' ' + this.i18n('(deprecated, will be removed)');
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
<form #settingsForm="ngForm">
|
<form #settingsForm="ngForm">
|
||||||
<div class="card mb-4"
|
<div class="card mb-4"
|
||||||
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
|
[ngClass]="states.enabled.value && !_settingsService.isSupported()?'panel-warning':''">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
{{Name}}
|
{{Name}}
|
||||||
<div class="switch-wrapper">
|
<div class="switch-wrapper">
|
||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
[switch-disabled]="inProgress || (!settings.enabled && !_settingsService.isSupported())"
|
i18n-switch-off-text
|
||||||
[switch-handle-width]="100"
|
i18n-switch-on-text
|
||||||
[switch-label-width]="20"
|
[switch-disabled]="inProgress || !_settingsService.isSupported()"
|
||||||
[(ngModel)]="settings.enabled">
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
|
[(ngModel)]="states.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
<ng-container *ngIf="settings.enabled || _settingsService.isSupported()">
|
<ng-container *ngIf="states.enabled.value || _settingsService.isSupported()">
|
||||||
|
|
||||||
<div class="alert alert-secondary" role="alert" i18n>
|
<div class="alert alert-secondary" role="alert" i18n>
|
||||||
This feature enables you to generate 'random photo' urls.
|
This feature enables you to generate 'random photo' urls.
|
||||||
@ -30,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="panel-info" *ngIf="(!settings.enabled && !_settingsService.isSupported())" i18n>
|
<div class="panel-info" *ngIf="(!states.enabled.value && !_settingsService.isSupported())" i18n>
|
||||||
Random Photo is not supported with these settings
|
Random Photo is not supported with these settings
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -1,71 +1,53 @@
|
|||||||
<form #settingsForm="ngForm">
|
<form #settingsForm="ngForm">
|
||||||
<div class="card mb-4"
|
<div class="card mb-4"
|
||||||
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
|
[ngClass]="states.enabled.value && !_settingsService.isSupported()?'panel-warning':''">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
{{Name}}
|
{{Name}}
|
||||||
<div class="switch-wrapper">
|
<div class="switch-wrapper">
|
||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
[switch-disabled]="inProgress || (!settings.enabled && !_settingsService.isSupported())"
|
i18n-switch-off-text
|
||||||
[switch-handle-width]="100"
|
i18n-switch-on-text
|
||||||
[switch-label-width]="20"
|
[switch-disabled]="inProgress || !_settingsService.isSupported()"
|
||||||
[(ngModel)]="settings.enabled">
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
|
[(ngModel)]="states.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
<ng-container *ngIf="settings.enabled || _settingsService.isSupported()">
|
<ng-container *ngIf="states.enabled.value || _settingsService.isSupported()">
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="autocompleteEnabled" i18n>Autocomplete</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
<app-settings-entry
|
||||||
id="autocompleteEnabled"
|
name="Autocomplete"
|
||||||
class="switch"
|
description="Show hints while typing search query."
|
||||||
name="autocompleteEnabled"
|
i18n-description i18n-name
|
||||||
[switch-on-color]="'primary'"
|
[ngModel]="states.AutoComplete.enabled"
|
||||||
[switch-disabled]="!settings.enabled"
|
required="true">
|
||||||
[switch-inverse]="true"
|
</app-settings-entry>
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
<app-settings-entry
|
||||||
[switch-handle-width]="100"
|
name="Instant search"
|
||||||
[switch-label-width]="20"
|
description="Enables showing search results, while typing search query."
|
||||||
[(ngModel)]="settings.AutoComplete.enabled">
|
i18n-description i18n-name
|
||||||
</bSwitch>
|
[ngModel]="states.instantSearchEnabled"
|
||||||
<small class="form-text text-muted" i18n>Show hints while typing search query</small>
|
required="true">
|
||||||
</div>
|
</app-settings-entry>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="instantSearchEnabled" i18n>Instant search</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<bSwitch
|
|
||||||
id="instantSearchEnabled"
|
|
||||||
class="switch"
|
|
||||||
name="instantSearchEnabled"
|
|
||||||
[switch-on-color]="'primary'"
|
|
||||||
[switch-disabled]="!settings.enabled"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.instantSearchEnabled">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Enables showing search results, while typing search query</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="panel-info" *ngIf="(!settings.enabled && !_settingsService.isSupported())" i18n>
|
<div class="panel-info" *ngIf="(!states.enabled.value && !_settingsService.isSupported())" i18n>
|
||||||
Search is not supported with these settings
|
Search is not supported with these settings
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -1,50 +1,41 @@
|
|||||||
<form #settingsForm="ngForm">
|
<form #settingsForm="ngForm">
|
||||||
<div class="card mb-4"
|
<div class="card mb-4"
|
||||||
[ngClass]="settings.enabled && !_settingsService.isSupported()?'panel-warning':''">
|
[ngClass]="states.enabled.value && !_settingsService.isSupported()?'panel-warning':''">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
{{Name}}
|
{{Name}}
|
||||||
<div class="switch-wrapper">
|
<div class="switch-wrapper">
|
||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
[switch-disabled]="inProgress || (!settings.enabled && !_settingsService.isSupported())"
|
i18n-switch-off-text
|
||||||
[switch-handle-width]="100"
|
i18n-switch-on-text
|
||||||
[switch-label-width]="20"
|
[switch-disabled]="inProgress || !_settingsService.isSupported()"
|
||||||
[(ngModel)]="settings.enabled">
|
switch-handle-width="100"
|
||||||
|
switch-label-width="20"
|
||||||
|
[(ngModel)]="states.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
<ng-container *ngIf="settings.enabled || _settingsService.isSupported()">
|
<ng-container *ngIf="states.enabled.value || _settingsService.isSupported()">
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-md-2 control-label" for="passwordProtected" i18n>Password protected</label>
|
<app-settings-entry
|
||||||
<div class="col-md-10">
|
name="Password protected"
|
||||||
<bSwitch
|
description="Enables password protected sharing links."
|
||||||
id="passwordProtected"
|
i18n-description i18n-name
|
||||||
class="switch"
|
[ngModel]="states.passwordProtected"
|
||||||
name="passwordProtected"
|
required="true">
|
||||||
[switch-on-color]="'primary'"
|
</app-settings-entry>
|
||||||
[switch-disabled]="!settings.enabled"
|
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Disabled"
|
|
||||||
[switch-on-text]="text.Enabled"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.passwordProtected">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>Enables password protected sharing links</small>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="panel-info" *ngIf="(!settings.enabled && !_settingsService.isSupported())" i18n>
|
<div class="panel-info" *ngIf="(!states.enabled.value && !_settingsService.isSupported())" i18n>
|
||||||
Sharing is not supported with these settings
|
Sharing is not supported with these settings
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
|
@ -7,62 +7,48 @@
|
|||||||
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
<div [hidden]="!error" class="alert alert-danger" role="alert"><strong>Error: </strong>{{error}}</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="quality" i18n>Thumbnail Quality</label>
|
name="Thumbnail Quality"
|
||||||
<div class="col-md-10">
|
description="High quality may be slow. Especially with Jimp."
|
||||||
<bSwitch
|
i18n-description i18n-name
|
||||||
id="quality"
|
[ngModel]="states.server.qualityPriority"
|
||||||
class="switch"
|
[simplifiedMode]="simplifiedMode"
|
||||||
name="enabled"
|
required="true">
|
||||||
[switch-on-color]="'primary'"
|
</app-settings-entry>
|
||||||
[switch-inverse]="true"
|
|
||||||
[switch-off-text]="text.Low"
|
|
||||||
[switch-on-text]="text.High"
|
|
||||||
[switch-handle-width]="100"
|
|
||||||
[switch-label-width]="20"
|
|
||||||
[(ngModel)]="settings.server.qualityPriority">
|
|
||||||
</bSwitch>
|
|
||||||
<small class="form-text text-muted" i18n>High quality may be slow. Especially with Jimp.</small>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="icon" i18n>Icon size</label>
|
name="Icon size"
|
||||||
<div class="col-md-10">
|
description="Icon size (used on maps)."
|
||||||
<input type="number" class="form-control" placeholder="30"
|
i18n-description i18n-name
|
||||||
id="icon"
|
[ngModel]="states.client.iconSize"
|
||||||
[(ngModel)]="settings.client.iconSize"
|
[simplifiedMode]="simplifiedMode"
|
||||||
min="1"
|
required="true">
|
||||||
max="100"
|
</app-settings-entry>
|
||||||
step="1"
|
|
||||||
name="icon" required>
|
|
||||||
<small class="form-text text-muted" i18n>Icon size (used on maps)</small>
|
<app-settings-entry
|
||||||
|
name="Thumbnail sizes"
|
||||||
|
i18n-name
|
||||||
|
placeholder="240; 480"
|
||||||
|
[ngModel]="states.client.thumbnailSizes"
|
||||||
|
[simplifiedMode]="simplifiedMode"
|
||||||
|
required="true">
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
<ng-container i18n>Size of the thumbnails.</ng-container>
|
||||||
|
<br/>
|
||||||
|
<ng-container i18n>The best matching size will be generated. (More sizes give better quality, but use more
|
||||||
|
storage and CPU to render.)
|
||||||
|
</ng-container>
|
||||||
|
<br/>
|
||||||
|
<ng-container i18n>';' separated integers. If size is 240, that shorter side of the thumbnail will have 160
|
||||||
|
pixels.
|
||||||
|
</ng-container>
|
||||||
|
</small>
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
|
||||||
<label class="col-md-2 control-label" for="thumbnailSizes" i18n>Thumbnail sizes</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input type="text" class="form-control" placeholder="240; 480"
|
|
||||||
id="thumbnailSizes"
|
|
||||||
[(ngModel)]="ThumbnailSizes"
|
|
||||||
name="thumbnailSizes" required>
|
|
||||||
<small class="form-text text-muted">
|
|
||||||
<ng-container i18n>Size of the thumbnails.</ng-container>
|
|
||||||
<br/>
|
|
||||||
<ng-container i18n>The best matching size will be generated. (More sizes give better quality, but use more
|
|
||||||
storage and CPU to render.)
|
|
||||||
</ng-container>
|
|
||||||
<br/>
|
|
||||||
<ng-container i18n>';' separated integers. If size is 240, that shorter side of the thumbnail will have 160
|
|
||||||
pixels.
|
|
||||||
</ng-container>
|
|
||||||
</small>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||||
|
@ -37,20 +37,9 @@ export class ThumbnailSettingsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get Config(): any {
|
get Config(): any {
|
||||||
return {sizes: this.original.client.thumbnailSizes[0]};
|
return {sizes: this.states.client.thumbnailSizes.original[0]};
|
||||||
}
|
}
|
||||||
|
|
||||||
get ThumbnailSizes(): string {
|
|
||||||
return this.settings.client.thumbnailSizes.join('; ');
|
|
||||||
}
|
|
||||||
|
|
||||||
set ThumbnailSizes(value: string) {
|
|
||||||
value = value.replace(new RegExp(',', 'g'), ';');
|
|
||||||
value = value.replace(new RegExp(' ', 'g'), ';');
|
|
||||||
this.settings.client.thumbnailSizes = value.split(';')
|
|
||||||
.map(s => parseInt(s, 10))
|
|
||||||
.filter(i => !isNaN(i) && i > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get Progress() {
|
get Progress() {
|
||||||
return this.jobsService.progress.value[JobDTO.getHashName(this.jobName, this.Config)];
|
return this.jobsService.progress.value[JobDTO.getHashName(this.jobName, this.Config)];
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
[switch-off-text]="text.Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
[switch-on-text]="text.Enabled"
|
||||||
[switch-handle-width]="100"
|
switch-handle-width="100"
|
||||||
[switch-label-width]="20"
|
switch-label-width="20"
|
||||||
[switch-disabled]="inProgress"
|
[switch-disabled]="inProgress"
|
||||||
[(ngModel)]="enabled"
|
[(ngModel)]="enabled"
|
||||||
(changeState)="switched($event)">
|
(changeState)="switched($event)">
|
||||||
|
@ -7,14 +7,16 @@
|
|||||||
<bSwitch
|
<bSwitch
|
||||||
class="switch"
|
class="switch"
|
||||||
name="enabled"
|
name="enabled"
|
||||||
[switch-on-color]="'success'"
|
switch-on-color="success"
|
||||||
[switch-inverse]="true"
|
switch-inverse="true"
|
||||||
[switch-off-text]="text.Disabled"
|
switch-off-text="Disabled"
|
||||||
[switch-on-text]="text.Enabled"
|
switch-on-text="Enabled"
|
||||||
|
i18n-switch-off-text
|
||||||
|
i18n-switch-on-text
|
||||||
[switch-disabled]="inProgress"
|
[switch-disabled]="inProgress"
|
||||||
[switch-handle-width]="100"
|
switch-handle-width="100"
|
||||||
[switch-label-width]="20"
|
switch-label-width="20"
|
||||||
[(ngModel)]="settings.client.enabled">
|
[(ngModel)]="states.client.enabled.value">
|
||||||
</bSwitch>
|
</bSwitch>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</h5>
|
||||||
@ -42,58 +44,50 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="format" i18n>Format</label>
|
name="Format"
|
||||||
<div class="col-md-10">
|
i18n-name
|
||||||
<select id="format" class="form-control" [(ngModel)]="settings.server.transcoding.format"
|
[ngModel]="states.server.transcoding.format"
|
||||||
(ngModelChange)="formatChanged($event)"
|
[simplifiedMode]="simplifiedMode"
|
||||||
name="format" required>
|
(ngModelChange)="formatChanged($event)"
|
||||||
<option *ngFor="let format of formats" [ngValue]="format">{{format}}
|
[options]="formats"
|
||||||
</option>
|
required="true">
|
||||||
</select>
|
</app-settings-entry>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="codec" i18n>Codec</label>
|
name="Codec"
|
||||||
<div class="col-md-10">
|
i18n-name
|
||||||
<select id="codec" class="form-control" [(ngModel)]="settings.server.transcoding.codec"
|
[ngModel]="states.server.transcoding.codec"
|
||||||
name="codec" required>
|
[simplifiedMode]="simplifiedMode"
|
||||||
<option *ngFor="let codec of codecs[settings.server.transcoding.format]" [ngValue]="codec">{{codec}}
|
[options]="codecs[states.server.transcoding.format.value]"
|
||||||
</option>
|
required="true">
|
||||||
</select>
|
</app-settings-entry>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
<label class="col-md-2 control-label" for="resolution" i18n>Resolution</label>
|
name="Resolution"
|
||||||
<div class="col-md-10">
|
description="The height of the output video will be scaled down to this, while keeping the aspect ratio."
|
||||||
<select id="resolution" class="form-control" [(ngModel)]="settings.server.transcoding.resolution"
|
i18n-name i18n-description
|
||||||
(ngModelChange)="updateBitRate()"
|
[ngModel]="states.server.transcoding.resolution"
|
||||||
name="resolution" required>
|
(ngModelChange)="updateBitRate()"
|
||||||
<option *ngFor="let resolution of resolutions" [ngValue]="resolution">{{resolution}}p
|
[options]="resolutions"
|
||||||
</option>
|
required="true">
|
||||||
</select>
|
</app-settings-entry>
|
||||||
<small class="form-text text-muted" i18n>The height of the output video will be scaled down to this, while
|
|
||||||
keeping the aspect ratio.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" [hidden]="simplifiedMode">
|
|
||||||
<label class="col-md-2 control-label" for="fps" i18n>FPS</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<select id="fps" class="form-control" [(ngModel)]="settings.server.transcoding.fps"
|
|
||||||
(ngModelChange)="updateBitRate()"
|
|
||||||
name="fps" required>
|
|
||||||
<option *ngFor="let fps of fps" [ngValue]="fps">{{fps}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<small class="form-text text-muted" i18n>Target frame per second (fps) of the output video will be scaled down
|
|
||||||
this this.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
<app-settings-entry
|
||||||
|
name="FPS"
|
||||||
|
description="Target frame per second (fps) of the output video will be scaled down this this."
|
||||||
|
i18n-name i18n-description
|
||||||
|
[ngModel]="states.server.transcoding.fps"
|
||||||
|
(ngModelChange)="updateBitRate()"
|
||||||
|
[options]="fps"
|
||||||
|
[simplifiedMode]="simplifiedMode"
|
||||||
|
required="true">
|
||||||
|
</app-settings-entry>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group row"
|
||||||
|
[class.changed-settings]="states.server.transcoding.bitRate.value !== states.server.transcoding.bitRate.default">
|
||||||
<label class="col-md-2 control-label" for="bitRate" i18n>Bit rate</label>
|
<label class="col-md-2 control-label" for="bitRate" i18n>Bit rate</label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@ -115,9 +109,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button class="btn btn-success float-right"
|
<button class="btn btn-success float-right"
|
||||||
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
[disabled]="!settingsForm.form.valid || !changed || inProgress"
|
||||||
(click)="save()" i18n>Save
|
(click)="save()" i18n>Save
|
||||||
|
@ -21,10 +21,16 @@ import {ClientConfig} from '../../../../../common/config/public/ClientConfig';
|
|||||||
})
|
})
|
||||||
export class VideoSettingsComponent extends SettingsComponent<{ server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }> {
|
export class VideoSettingsComponent extends SettingsComponent<{ server: ServerConfig.VideoConfig, client: ClientConfig.VideoConfig }> {
|
||||||
|
|
||||||
resolutions: ServerConfig.resolutionType[] = [360, 480, 720, 1080, 1440, 2160, 4320];
|
readonly resolutionTypes: ServerConfig.resolutionType[] = [360, 480, 720, 1080, 1440, 2160, 4320];
|
||||||
codecs: { [key: string]: ServerConfig.codecType[] } = {webm: ['libvpx', 'libvpx-vp9'], mp4: ['libx264', 'libx265']};
|
|
||||||
formats: ServerConfig.formatType[] = ['mp4', 'webm'];
|
resolutions: { key: number, value: string }[] = [];
|
||||||
fps = [24, 25, 30, 48, 50, 60];
|
codecs: { [key: string]: { key: ServerConfig.codecType, value: ServerConfig.codecType }[] } = {
|
||||||
|
webm: ['libvpx', 'libvpx-vp9'].map((e: ServerConfig.codecType) => ({key: e, value: e})),
|
||||||
|
mp4: ['libx264', 'libx265'].map((e: ServerConfig.codecType) => ({key: e, value: e}))
|
||||||
|
};
|
||||||
|
formats: { key: ServerConfig.formatType, value: ServerConfig.formatType }[] = ['mp4', 'webm']
|
||||||
|
.map((e: ServerConfig.formatType) => ({key: e, value: e}));
|
||||||
|
fps = [24, 25, 30, 48, 50, 60].map(e => ({key: e, value: e}));
|
||||||
|
|
||||||
JobProgressStates = JobProgressStates;
|
JobProgressStates = JobProgressStates;
|
||||||
readonly jobName = DefaultsJobs[DefaultsJobs['Video Converting']];
|
readonly jobName = DefaultsJobs[DefaultsJobs['Video Converting']];
|
||||||
@ -41,9 +47,10 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const currentRes = _settingsService.Settings.value.Server.Media.Video.transcoding.resolution;
|
const currentRes = _settingsService.Settings.value.Server.Media.Video.transcoding.resolution;
|
||||||
if (this.resolutions.indexOf(currentRes) === -1) {
|
if (this.resolutionTypes.indexOf(currentRes) === -1) {
|
||||||
this.resolutions.push(currentRes);
|
this.resolutionTypes.push(currentRes);
|
||||||
}
|
}
|
||||||
|
this.resolutions = this.resolutionTypes.map(e => ({key: e, value: e + 'px'}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -52,11 +59,11 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
get bitRate(): number {
|
get bitRate(): number {
|
||||||
return this.settings.server.transcoding.bitRate / 1024 / 1024;
|
return this.states.server.transcoding.bitRate.value / 1024 / 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
set bitRate(value: number) {
|
set bitRate(value: number) {
|
||||||
this.settings.server.transcoding.bitRate = Math.round(value * 1024 * 1024);
|
this.states.server.transcoding.bitRate.value = Math.round(value * 1024 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecommendedBitRate(resolution: number, fps: number) {
|
getRecommendedBitRate(resolution: number, fps: number) {
|
||||||
@ -83,12 +90,12 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateBitRate() {
|
updateBitRate() {
|
||||||
this.settings.server.transcoding.bitRate = this.getRecommendedBitRate(this.settings.server.transcoding.resolution,
|
this.states.server.transcoding.bitRate.value = this.getRecommendedBitRate(this.states.server.transcoding.resolution.value,
|
||||||
this.settings.server.transcoding.fps);
|
this.states.server.transcoding.fps.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatChanged(format: ServerConfig.formatType) {
|
formatChanged(format: ServerConfig.formatType) {
|
||||||
this.settings.server.transcoding.codec = this.codecs[format][0];
|
this.states.server.transcoding.codec.value = this.codecs[format][0].key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.changed-settings .bootstrap-switch{
|
||||||
|
border-color: #007bff;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
#toast-container > div {
|
#toast-container > div {
|
||||||
opacity:1;
|
opacity:1;
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,7 @@ describe('SettingsRouter', () => {
|
|||||||
result.res.should.have.status(200);
|
result.res.should.have.status(200);
|
||||||
result.body.should.be.a('object');
|
result.body.should.be.a('object');
|
||||||
should.equal(result.body.error, null);
|
should.equal(result.body.error, null);
|
||||||
result.body.result.should.deep.equal(JSON.parse(JSON.stringify(originalSettings.toJSON({attachDefaults: true}))));
|
result.body.result.should.deep.equal(JSON.parse(JSON.stringify(originalSettings.toJSON({attachState: true, attachVolatile: true}))));
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user