1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-02-05 13:25:08 +02:00

Improving config UI #569

This commit is contained in:
Patrik J. Braun 2022-12-30 23:44:53 +01:00
parent 36d4641e9d
commit 875d120df8
14 changed files with 265 additions and 194 deletions

View File

@ -1,14 +1,10 @@
import { import {ConfigTemplateEntry, DefaultsJobs,} from '../../../../common/entities/job/JobDTO';
ConfigTemplateEntry,
DefaultsJobs,
} from '../../../../common/entities/job/JobDTO';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import {Job} from './Job'; import {Job} from './Job';
import {ProjectPath} from '../../../ProjectPath'; import {ProjectPath} from '../../../ProjectPath';
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing'; import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
import {VideoProcessing} from '../../fileprocessing/VideoProcessing'; import {VideoProcessing} from '../../fileprocessing/VideoProcessing';
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
import {GPXProcessing} from '../../fileprocessing/GPXProcessing'; import {GPXProcessing} from '../../fileprocessing/GPXProcessing';
export class TempFolderCleaningJob extends Job { export class TempFolderCleaningJob extends Job {

View File

@ -331,8 +331,9 @@ export class ServerGPXCompressingConfig extends ClientGPXCompressingConfig {
{ {
name: $localize`Min distance`, name: $localize`Min distance`,
priority: ConfigPriority.underTheHood, priority: ConfigPriority.underTheHood,
unit: 'm',
uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx uiDisabled: (sc: ServerGPXCompressingConfig, c: ServerConfig) => !c.Map.enabled || !sc.enabled || !c.MetaFile.gpx
}, } as TAGS,
description: $localize`Filters out entry that are closer than this in meters.` description: $localize`Filters out entry that are closer than this in meters.`
}) })
minDistance: number = 5; minDistance: number = 5;
@ -356,8 +357,12 @@ export class ServerMetaFileConfig extends ClientMetaFileConfig {
tags: tags:
{ {
name: $localize`GPX compression`, name: $localize`GPX compression`,
priority: ConfigPriority.advanced priority: ConfigPriority.advanced,
} uiJob: [{
job: DefaultsJobs[DefaultsJobs['GPX Compression']],
relevant: (c) => c.MetaFile.GPXCompressing.enabled
}]
} as TAGS
}) })
GPXCompressing: ServerGPXCompressingConfig = new ServerGPXCompressingConfig(); GPXCompressing: ServerGPXCompressingConfig = new ServerGPXCompressingConfig();
} }
@ -419,8 +424,8 @@ export class ServerIndexingConfig {
{ {
name: $localize`Exclude File List`, name: $localize`Exclude File List`,
priority: ConfigPriority.advanced, priority: ConfigPriority.advanced,
uiOptional: true uiOptional: true,
hint: $localize`.ignore;.pg2ignore`
} as TAGS, } as TAGS,
description: $localize`Files that mark a folder to be excluded from indexing. Any folder that contains a file with this name will be excluded from indexing.`, description: $localize`Files that mark a folder to be excluded from indexing. Any folder that contains a file with this name will be excluded from indexing.`,
}) })
@ -610,7 +615,7 @@ export class ServerJobConfig {
DefaultsJobs[DefaultsJobs.Indexing], DefaultsJobs[DefaultsJobs.Indexing],
DefaultsJobs[DefaultsJobs.Indexing], DefaultsJobs[DefaultsJobs.Indexing],
new NeverJobTriggerConfig(), new NeverJobTriggerConfig(),
{indexChangesOnly: true} // set config explicitly so it not undefined on the UI {indexChangesOnly: true} // set config explicitly so it is not undefined on the UI
), ),
new JobScheduleConfig( new JobScheduleConfig(
DefaultsJobs[DefaultsJobs['Preview Filling']], DefaultsJobs[DefaultsJobs['Preview Filling']],
@ -681,7 +686,7 @@ export class VideoTranscodingConfig {
tags: tags:
{ {
name: $localize`FPS`, name: $localize`FPS`,
priority: ConfigPriority.advanced, priority: ConfigPriority.underTheHood,
uiOptions: [24, 25, 30, 48, 50, 60] uiOptions: [24, 25, 30, 48, 50, 60]
}, },
description: $localize`Target frame per second (fps) of the output video will be scaled down this this.` description: $localize`Target frame per second (fps) of the output video will be scaled down this this.`
@ -700,7 +705,7 @@ export class VideoTranscodingConfig {
tags: tags:
{ {
name: $localize`MP4 codec`, name: $localize`MP4 codec`,
priority: ConfigPriority.advanced, priority: ConfigPriority.underTheHood,
uiOptions: ['libx264', 'libx265'], uiOptions: ['libx264', 'libx265'],
relevant: (c: any) => c.format === 'mp4' relevant: (c: any) => c.format === 'mp4'
} }
@ -710,7 +715,7 @@ export class VideoTranscodingConfig {
tags: tags:
{ {
name: $localize`Webm Codec`, name: $localize`Webm Codec`,
priority: ConfigPriority.advanced, priority: ConfigPriority.underTheHood,
uiOptions: ['libvpx', 'libvpx-vp9'], uiOptions: ['libvpx', 'libvpx-vp9'],
relevant: (c: any) => c.format === 'webm' relevant: (c: any) => c.format === 'webm'
} }
@ -782,6 +787,8 @@ export class PhotoConvertingConfig extends ClientPhotoConvertingConfig {
tags: { tags: {
name: $localize`Resolution`, name: $localize`Resolution`,
priority: ConfigPriority.advanced, priority: ConfigPriority.advanced,
uiOptions: [720, 1080, 1440, 2160, 4320],
unit: 'px',
uiDisabled: (sc: PhotoConvertingConfig) => uiDisabled: (sc: PhotoConvertingConfig) =>
!sc.enabled !sc.enabled
}, },
@ -867,23 +874,34 @@ export class ServerMediaConfig extends ClientMediaConfig {
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Video`, name: $localize`Video`,
priority: ConfigPriority.advanced priority: ConfigPriority.advanced,
}, uiJob: [
{
job: DefaultsJobs[DefaultsJobs['Video Converting']],
relevant: (c) => c.Media.Video.enabled
}]
} as TAGS,
description: $localize`Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or the @ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.` description: $localize`Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or the @ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.`
}) })
Video: ServerVideoConfig = new ServerVideoConfig(); Video: ServerVideoConfig = new ServerVideoConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Photo`, name: $localize`Photo`,
priority: ConfigPriority.advanced priority: ConfigPriority.advanced,
} uiJob: [
{
job: DefaultsJobs[DefaultsJobs['Photo Converting']],
relevant: (c) => c.Media.Photo.Converting.enabled
}]
} as TAGS
}) })
Photo: ServerPhotoConfig = new ServerPhotoConfig(); Photo: ServerPhotoConfig = new ServerPhotoConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Thumbnail`, name: $localize`Thumbnail`,
priority: ConfigPriority.advanced priority: ConfigPriority.advanced,
} uiJob: [{job: DefaultsJobs[DefaultsJobs['Thumbnail Generation']]}]
} as TAGS
}) })
Thumbnail: ServerThumbnailConfig = new ServerThumbnailConfig(); Thumbnail: ServerThumbnailConfig = new ServerThumbnailConfig();
} }
@ -965,34 +983,99 @@ export class ServerConfig extends ClientConfig {
@ConfigProperty({volatile: true}) @ConfigProperty({volatile: true})
Environment: ServerEnvironmentConfig = new ServerEnvironmentConfig(); Environment: ServerEnvironmentConfig = new ServerEnvironmentConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Server`,
uiIcon: 'cog'
} as TAGS,
})
Server: ServerServiceConfig = new ServerServiceConfig(); Server: ServerServiceConfig = new ServerServiceConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Database`,
uiIcon: 'list'
} as TAGS
})
Database: ServerDataBaseConfig = new ServerDataBaseConfig(); Database: ServerDataBaseConfig = new ServerDataBaseConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Users`,
uiIcon: 'person'
} as TAGS,
})
Users: ServerUserConfig = new ServerUserConfig(); Users: ServerUserConfig = new ServerUserConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Indexing`,
uiIcon: 'pie-chart',
uiJob: [
{
job: DefaultsJobs[DefaultsJobs.Indexing],
description: $localize`If you add a new folder to your gallery, the site indexes it automatically. If you would like to trigger indexing manually, click index button. (Note: search only works among the indexed directories.)`
}, {
job: DefaultsJobs[DefaultsJobs['Database Reset']],
hideProgress: true
}]
} as TAGS
})
Indexing: ServerIndexingConfig = new ServerIndexingConfig(); Indexing: ServerIndexingConfig = new ServerIndexingConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Media`,
uiIcon: 'camera-slr'
} as TAGS,
})
Media: ServerMediaConfig = new ServerMediaConfig(); Media: ServerMediaConfig = new ServerMediaConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Meta file`,
uiIcon: 'file'
} as TAGS,
})
MetaFile: ServerMetaFileConfig = new ServerMetaFileConfig(); MetaFile: ServerMetaFileConfig = new ServerMetaFileConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Preview`,
uiIcon: 'image',
uiJob: [
{
job: DefaultsJobs[DefaultsJobs['Preview Filling']],
}, {
job: DefaultsJobs[DefaultsJobs['Preview Reset']],
hideProgress: true
}]
} as TAGS
})
Preview: ServerPreviewConfig = new ServerPreviewConfig(); Preview: ServerPreviewConfig = new ServerPreviewConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Sharing`,
uiIcon: 'share'
} as TAGS,
})
Sharing: ServerSharingConfig = new ServerSharingConfig(); Sharing: ServerSharingConfig = new ServerSharingConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Duplicates`,
uiIcon: 'layers'
} as TAGS
})
Duplicates: ServerDuplicatesConfig = new ServerDuplicatesConfig(); Duplicates: ServerDuplicatesConfig = new ServerDuplicatesConfig();
@ConfigProperty() @ConfigProperty({
tags: {
name: $localize`Jobs`,
uiIcon: 'project'
} as TAGS
})
Jobs: ServerJobConfig = new ServerJobConfig(); Jobs: ServerJobConfig = new ServerJobConfig();
} }

View File

@ -33,11 +33,18 @@ export type TAGS = {
secret?: boolean, // these config properties should never travel out of the server secret?: boolean, // these config properties should never travel out of the server
experimental?: boolean, //is it a beta feature experimental?: boolean, //is it a beta feature
unit?: string, // Unit info to display on UI unit?: string, // Unit info to display on UI
uiIcon?: string,
uiType?: 'SearchQuery', // Hint for the UI about the type uiType?: 'SearchQuery', // Hint for the UI about the type
uiOptions?: (string | number)[], //Hint for the UI about the recommended options uiOptions?: (string | number)[], //Hint for the UI about the recommended options
uiAllowSpaces?: boolean uiAllowSpaces?: boolean
uiOptional?: boolean; //makes the tag not "required" uiOptional?: boolean; //makes the tag not "required"
uiDisabled?: (subConfig: any, config: ClientConfig) => boolean uiDisabled?: (subConfig: any, config: ClientConfig) => boolean
uiJob?: {
job: string,
hideProgress: boolean,
relevant?: (c: ClientConfig) => boolean,
description: string
}[]
}; };
@SubConfigClass<TAGS>({tags: {client: true}}) @SubConfigClass<TAGS>({tags: {client: true}})
@ -278,7 +285,7 @@ export class ClientMapConfig {
tags: { tags: {
name: $localize`Max Preview Markers`, name: $localize`Max Preview Markers`,
priority: ConfigPriority.underTheHood priority: ConfigPriority.underTheHood
}, } as TAGS,
description: $localize`Maximum number of markers to be shown on the map preview on the gallery page.`, description: $localize`Maximum number of markers to be shown on the map preview on the gallery page.`,
}) })
maxPreviewMarkers: number = 50; maxPreviewMarkers: number = 50;
@ -289,7 +296,8 @@ export class ClientThumbnailConfig {
@ConfigProperty({ @ConfigProperty({
type: 'unsignedInt', max: 100, type: 'unsignedInt', max: 100,
tags: { tags: {
name: $localize`Max Preview Markers`, name: $localize`Map Icon size`,
unit: 'px',
priority: ConfigPriority.underTheHood priority: ConfigPriority.underTheHood
}, },
description: $localize`Icon size (used on maps).`, description: $localize`Icon size (used on maps).`,
@ -298,6 +306,7 @@ export class ClientThumbnailConfig {
@ConfigProperty({ @ConfigProperty({
type: 'unsignedInt', tags: { type: 'unsignedInt', tags: {
name: $localize`Person thumbnail size`, name: $localize`Person thumbnail size`,
unit: 'px',
priority: ConfigPriority.underTheHood priority: ConfigPriority.underTheHood
}, },
description: $localize`Person (face) thumbnail size.`, description: $localize`Person (face) thumbnail size.`,
@ -732,8 +741,9 @@ export class ClientServiceConfig {
description: $localize`If you access the page form local network its good to know the public url for creating sharing link.`, description: $localize`If you access the page form local network its good to know the public url for creating sharing link.`,
tags: { tags: {
name: $localize`Page public url`, name: $localize`Page public url`,
hint: typeof window !== 'undefined' ? window?.origin : '',
uiOptional: true uiOptional: true
} } as TAGS
}) })
publicUrl: string = ''; publicUrl: string = '';
@ -804,72 +814,57 @@ export class ClientUserConfig {
@SubConfigClass<TAGS>({tags: {client: true}}) @SubConfigClass<TAGS>({tags: {client: true}})
export class ClientConfig { export class ClientConfig {
@ConfigProperty({ @ConfigProperty()
tags: {
name: $localize`Server`
} as TAGS,
})
Server: ClientServiceConfig = new ClientServiceConfig(); Server: ClientServiceConfig = new ClientServiceConfig();
@ConfigProperty({ @ConfigProperty()
tags: {
name: $localize`Users`
} as TAGS,
})
Users: ClientUserConfig = new ClientUserConfig(); Users: ClientUserConfig = new ClientUserConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Gallery` name: $localize`Gallery`,
uiIcon: 'browser'
} as TAGS, } as TAGS,
}) })
Gallery: ClientGalleryConfig = new ClientGalleryConfig(); Gallery: ClientGalleryConfig = new ClientGalleryConfig();
@ConfigProperty({ @ConfigProperty()
tags: {
name: $localize`Media`
} as TAGS,
})
Media: ClientMediaConfig = new ClientMediaConfig(); Media: ClientMediaConfig = new ClientMediaConfig();
@ConfigProperty({ @ConfigProperty()
tags: {
name: $localize`Meta file`
} as TAGS,
})
MetaFile: ClientMetaFileConfig = new ClientMetaFileConfig(); MetaFile: ClientMetaFileConfig = new ClientMetaFileConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Album` name: $localize`Album`,
uiIcon: 'grid-two-up'
} as TAGS, } as TAGS,
}) })
Album: ClientAlbumConfig = new ClientAlbumConfig(); Album: ClientAlbumConfig = new ClientAlbumConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Search` name: $localize`Search`,
uiIcon: 'magnifying-glass'
} as TAGS, } as TAGS,
}) })
Search: ClientSearchConfig = new ClientSearchConfig(); Search: ClientSearchConfig = new ClientSearchConfig();
@ConfigProperty({ @ConfigProperty()
tags: {
name: $localize`Sharing`
} as TAGS,
})
Sharing: ClientSharingConfig = new ClientSharingConfig(); Sharing: ClientSharingConfig = new ClientSharingConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Map` name: $localize`Map`,
uiIcon: 'map-marker'
} as TAGS, } as TAGS,
}) })
Map: ClientMapConfig = new ClientMapConfig(); Map: ClientMapConfig = new ClientMapConfig();
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Faces` name: $localize`Faces`,
uiIcon: 'people'
} as TAGS, } as TAGS,
}) })
Faces: ClientFacesConfig = new ClientFacesConfig(); Faces: ClientFacesConfig = new ClientFacesConfig();
@ -877,6 +872,7 @@ export class ClientConfig {
@ConfigProperty({ @ConfigProperty({
tags: { tags: {
name: $localize`Random photo`, name: $localize`Random photo`,
uiIcon: 'random',
githubIssue: 392 githubIssue: 392
} as TAGS, } as TAGS,
description: $localize`This feature enables you to generate 'random photo' urls. That URL returns a photo random selected from your gallery. You can use the url with 3rd party application like random changing desktop background. Note: With the current implementation, random link also requires login.` description: $localize`This feature enables you to generate 'random photo' urls. That URL returns a photo random selected from your gallery. You can use the url with 3rd party application like random changing desktop background. Note: With the current implementation, random link also requires login.`

View File

@ -49,8 +49,7 @@ export interface RecursiveState extends ConfigState {
@Directive() @Directive()
export abstract class SettingsComponentDirective< export abstract class SettingsComponentDirective<
T extends RecursiveState> implements OnInit, OnDestroy, ISettingsComponent { T extends RecursiveState> implements OnInit, OnDestroy, ISettingsComponent {
public icon: string;
@Input() icon: string;
@Input() ConfigPath: string; @Input() ConfigPath: string;
@ViewChild('settingsForm', {static: true}) @ViewChild('settingsForm', {static: true})
@ -72,7 +71,7 @@ export abstract class SettingsComponentDirective<
private navigation: NavigationService, private navigation: NavigationService,
public settingsService: AbstractSettingsService, public settingsService: AbstractSettingsService,
protected notification: NotificationService, protected notification: NotificationService,
protected globalSettingsService: SettingsService, public globalSettingsService: SettingsService,
sliceFN?: (s: IWebConfigClassPrivate<TAGS> & WebConfig) => T sliceFN?: (s: IWebConfigClassPrivate<TAGS> & WebConfig) => T
) { ) {
this.setSliceFN(sliceFN); this.setSliceFN(sliceFN);
@ -108,6 +107,7 @@ export abstract class SettingsComponentDirective<
if (state.volatile) { if (state.volatile) {
return true; return true;
} }
if (state.tags && if (state.tags &&
((state.tags.relevant && !state.tags.relevant(parent.value)) ((state.tags.relevant && !state.tags.relevant(parent.value))
|| state.tags.secret)) { || state.tags.secret)) {
@ -139,8 +139,7 @@ export abstract class SettingsComponentDirective<
Utils.equalsFilter(state.value, state.default, Utils.equalsFilter(state.value, state.default,
['__propPath', '__created', '__prototype', '__rootConfig']) && ['__propPath', '__created', '__prototype', '__rootConfig']) &&
Utils.equalsFilter(state.original, state.default, Utils.equalsFilter(state.original, state.default,
['__propPath', '__created', '__prototype', '__rootConfig']) ['__propPath', '__created', '__prototype', '__rootConfig']));
);
}; };
}; };
@ -164,6 +163,7 @@ export abstract class SettingsComponentDirective<
} }
}; };
instrument(this.states, null); instrument(this.states, null);
this.icon = this.states.tags?.uiIcon;
}; };
onOptionChange = () => { onOptionChange = () => {

View File

@ -13,8 +13,6 @@
<div class="col-md-10"> <div class="col-md-10">
<div class="input-group"> <div class="input-group">
<app-gallery-search-field <app-gallery-search-field
*ngIf="Type === 'SearchQuery'" *ngIf="Type === 'SearchQuery'"
[(ngModel)]="state.value" [(ngModel)]="state.value"
@ -296,7 +294,9 @@
</div> </div>
<small class="form-text text-muted" *ngIf="description">{{description}} <small class="form-text text-muted" *ngIf="description">{{description}}
<span *ngIf="Type==='array' && (state.arrayType === 'string' || isNumberArray)" i18n>';' separated list.</span> <span *ngIf="Type==='array' && (state.arrayType === 'string' || isNumberArray)" i18n>';' separated list.</span>
<a *ngIf="link" [href]="link">{{linkText || link}}</a> <a *ngIf="state.tags?.githubIssue"
[href]="'https://github.com/bpatrik/pigallery2/issues/'+state.tags?.githubIssue">See
#{{state.tags?.githubIssue}}.</a>
</small> </small>
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>

View File

@ -1,22 +1,9 @@
import {Component, forwardRef, Input, OnChanges} from '@angular/core'; import {Component, forwardRef, Input, OnChanges} from '@angular/core';
import { import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator,} from '@angular/forms';
ControlValueAccessor,
FormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
import {Utils} from '../../../../../../common/Utils'; import {Utils} from '../../../../../../common/Utils';
import {IConfigClass, propertyTypes} from 'typeconfig/common'; import {propertyTypes} from 'typeconfig/common';
import {SearchQueryParserService} from '../../../gallery/search/search-query-parser.service'; import {SearchQueryParserService} from '../../../gallery/search/search-query-parser.service';
import { import {MapLayers, NavigationLinkConfig, NavigationLinkTypes, TAGS} from '../../../../../../common/config/public/ClientConfig';
ClientThumbnailConfig,
ConfigPriority,
MapLayers,
NavigationLinkConfig, NavigationLinkTypes,
TAGS
} from '../../../../../../common/config/public/ClientConfig';
import {SettingsService} from '../../settings.service'; import {SettingsService} from '../../settings.service';
import {WebConfig} from '../../../../../../common/config/private/WebConfig'; import {WebConfig} from '../../../../../../common/config/private/WebConfig';
import {JobScheduleConfig, UserConfig} from '../../../../../../common/config/private/PrivateConfig'; import {JobScheduleConfig, UserConfig} from '../../../../../../common/config/private/PrivateConfig';
@ -61,16 +48,12 @@ interface IState {
}) })
export class SettingsEntryComponent export class SettingsEntryComponent
implements ControlValueAccessor, Validator, OnChanges { implements ControlValueAccessor, Validator, OnChanges {
@Input() defaultName: string;
name: string; name: string;
@Input() required: boolean; required: boolean;
@Input() dockerWarning: boolean; dockerWarning: boolean;
@Input() placeholder: string; placeholder: string;
@Input() allowSpaces = false; allowSpaces = false;
@Input() description: string; description: string;
@Input() link: string;
@Input() linkText: string;
@Input() typeOverride: 'SearchQuery';
state: IState; state: IState;
isNumberArray = false; isNumberArray = false;
isNumber = false; isNumber = false;
@ -89,13 +72,14 @@ export class SettingsEntryComponent
if (this.Disabled) { if (this.Disabled) {
return false; return false;
} }
if (this.state.isConfigArrayType) { if (this.state.isConfigArrayType) {
for (let i = 0; i < this.state.value?.length; ++i) { for (let i = 0; i < this.state.value?.length; ++i) {
for (const k of Object.keys(this.state.value[i].__state)) { for (const k of Object.keys(this.state.value[i].__state)) {
if (!Utils.equalsFilter( if (!Utils.equalsFilter(
this.state.value[i]?.__state[k]?.value, this.state.value[i]?.__state[k]?.value,
this.state.default[i]?.__state[k]?.value, this.state.default[i]?.__state[k]?.value,
['__propPath', '__created', '__prototype', '__rootConfig'])) { ['default', '__propPath', '__created', '__prototype', '__rootConfig'])) {
return true; return true;
} }
} }
@ -128,7 +112,7 @@ export class SettingsEntryComponent
} }
get Type(): string | object { get Type(): string | object {
return this.typeOverride || this.state.tags?.uiType || this.state.type; return this.state.tags?.uiType || this.state.type;
} }
get ArrayType(): string { get ArrayType(): string {
@ -216,7 +200,7 @@ export class SettingsEntryComponent
this.title = $localize`readonly` + ', '; this.title = $localize`readonly` + ', ';
} }
this.title += $localize`default value` + ': ' + this.defaultStr; this.title += $localize`default value` + ': ' + this.defaultStr;
this.name = this.state?.tags?.name || this.defaultName; this.name = this.state?.tags?.name;
if (this.name) { if (this.name) {
this.idName = this.idName =
this.GUID + this.name.toLowerCase().replace(new RegExp(' ', 'gm'), '-'); this.GUID + this.name.toLowerCase().replace(new RegExp(' ', 'gm'), '-');
@ -245,10 +229,6 @@ export class SettingsEntryComponent
if (typeof this.dockerWarning === 'undefined') { if (typeof this.dockerWarning === 'undefined') {
this.dockerWarning = this.state.tags.dockerSensitive && this.settingsService.settings.value.Environment.isDocker; this.dockerWarning = this.state.tags.dockerSensitive && this.settingsService.settings.value.Environment.isDocker;
} }
if (this.state.tags.githubIssue) {
this.link = `https://github.com/bpatrik/pigallery2/issues/` + this.state.tags.githubIssue;
this.linkText = $localize`See` + ' ' + this.state.tags.githubIssue;
}
this.name = this.name || this.state.tags.name; this.name = this.name || this.state.tags.name;
this.allowSpaces = this.allowSpaces || this.state.tags.uiAllowSpaces; this.allowSpaces = this.allowSpaces || this.state.tags.uiAllowSpaces;
this.required = this.required || !this.state.tags.uiOptional; this.required = this.required || !this.state.tags.uiOptional;

View File

@ -1,3 +0,0 @@
:host {
display: inline-block;
}

View File

@ -1,3 +0,0 @@
.panel-info {
text-align: center;
}

View File

@ -24,9 +24,7 @@
<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="states.value.enabled !== false"> <ng-container *ngIf="states.value.enabled !== false">
<div class="alert alert-secondary" role="alert" *ngIf="states.description">
{{states.description}}
</div>
<ng-container <ng-container
*ngTemplateOutlet="Recursion; context:{ rStates: states,skipEnabled:true,confPath:ConfigPath }" *ngTemplateOutlet="Recursion; context:{ rStates: states,skipEnabled:true,confPath:ConfigPath }"
></ng-container> ></ng-container>
@ -48,16 +46,18 @@
</div> </div>
<ng-template #Recursion let-rStates="rStates" let-skipEnabled="skipEnabled" let-confPath="confPath"> <ng-template #Recursion let-rStates="rStates" let-skipEnabled="skipEnabled" let-confPath="confPath">
<div class="alert alert-secondary" role="alert" *ngIf="rStates.description">
{{rStates.description}}
</div>
<ng-container *ngFor="let ck of getKeys(rStates)"> <ng-container *ngFor="let ck of getKeys(rStates)">
<ng-container *ngIf="!(rStates.value.__state[ck].shouldHide && rStates.value.__state[ck].shouldHide())"> <ng-container *ngIf="!(rStates.value.__state[ck].shouldHide && rStates.value.__state[ck].shouldHide())">
<app-settings-entry <app-settings-entry
*ngIf="(ck!=='enabled' || !skipEnabled) && !rStates.value.__state[ck].isConfigType" *ngIf="(ck!=='enabled' || !skipEnabled) && !rStates.value.__state[ck].isConfigType"
[name]="confPath+'_'+ck" [name]="confPath+'_'+ck"
[defaultName]="ck"
[ngModel]="rStates?.value.__state[ck]"> [ngModel]="rStates?.value.__state[ck]">
</app-settings-entry> </app-settings-entry>
<ng-container *ngIf="rStates.value.__state[ck].isConfigType"> <ng-container *ngIf="rStates.value.__state[ck].isConfigType">
<div class="row"> <div class="row mt-2">
<div class="col-auto"> <div class="col-auto">
<h5>{{rStates?.value.__state[ck].tags?.name || ck}}</h5> <h5>{{rStates?.value.__state[ck].tags?.name || ck}}</h5>
</div> </div>
@ -66,9 +66,6 @@
</div> </div>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<div class="alert alert-secondary" role="alert" *ngIf="rStates.value.__state[ck]?.description">
{{rStates.value.__state[ck]?.description}}
</div>
<ng-container <ng-container
*ngTemplateOutlet="Recursion; context:{ rStates: rStates.value.__state[ck], confPath:confPath+'.'+ck }" *ngTemplateOutlet="Recursion; context:{ rStates: rStates.value.__state[ck], confPath:confPath+'.'+ck }"
></ng-container> ></ng-container>
@ -76,6 +73,31 @@
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>
<div *ngIf="rStates.tags?.uiJob">
<ng-container *ngFor="let job of rStates.tags?.uiJob; let i = index">
<div class="alert alert-secondary" role="alert" *ngIf="job.description">
{{job.description}}
</div>
<app-settings-job-button
*ngIf="!job.relevant || job.relevant(globalSettingsService.settings | async)"
class="mt-2 mb-1 mb-md-0 mt-md-0 float-left me-2"
[soloRun]="true"
(jobError)="error=$event"
[allowParallelRun]="false"
[danger]="i>0"
[jobName]="job.job"></app-settings-job-button>
</ng-container>
<ng-container *ngFor="let job of rStates.tags?.uiJob">
<ng-container
*ngIf="getProgress(job.job) && !job.hideProgress && (!job.relevant || job.relevant(globalSettingsService.settings | async))">
<hr class="mt-1"/>
<app-settings-job-progress
class="d-block mb-2"
[progress]="getProgress(job.job)"></app-settings-job-progress>
</ng-container>
</ng-container>
</div>
</ng-template> </ng-template>
</form> </form>

View File

@ -1,4 +1,4 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {AuthenticationService} from '../../../model/network/authentication.service'; import {AuthenticationService} from '../../../model/network/authentication.service';
import {NavigationService} from '../../../model/navigation.service'; import {NavigationService} from '../../../model/navigation.service';
import {NotificationService} from '../../../model/notification.service'; import {NotificationService} from '../../../model/notification.service';
@ -6,6 +6,9 @@ import {SettingsComponentDirective} from '../_abstract/abstract.settings.compone
import {SettingsService} from '../settings.service'; import {SettingsService} from '../settings.service';
import {WebConfig} from '../../../../../common/config/private/WebConfig'; import {WebConfig} from '../../../../../common/config/private/WebConfig';
import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; import {AbstractSettingsService} from '../_abstract/abstract.settings.service';
import {JobProgressDTO} from '../../../../../common/entities/job/JobProgressDTO';
import {JobDTOUtils} from '../../../../../common/entities/job/JobDTO';
import {ScheduledJobsService} from '../scheduled-jobs.service';
@Component({ @Component({
@ -22,7 +25,8 @@ export class TemplateComponent extends SettingsComponentDirective<any> implement
navigation: NavigationService, navigation: NavigationService,
notification: NotificationService, notification: NotificationService,
settingsService: AbstractSettingsService, settingsService: AbstractSettingsService,
globalSettingsService: SettingsService globalSettingsService: SettingsService,
public jobsService: ScheduledJobsService,
) { ) {
super( super(
authService, authService,
@ -66,4 +70,7 @@ export class TemplateComponent extends SettingsComponentDirective<any> implement
}); });
} }
getProgress(jobName: string): JobProgressDTO {
return this.jobsService.progress.value[JobDTOUtils.getHashName(jobName)];
}
} }

View File

@ -1,7 +0,0 @@
.form-control {
margin: 5px 0;
}
.panel-info {
text-align: center;
}

View File

@ -1,4 +0,0 @@
.buttons-row {
margin-top: 10px;
margin-bottom: 20px;
}

View File

@ -6,7 +6,6 @@ import {
JobScheduleDTO, JobScheduleDTO,
JobScheduleDTOUtils, JobScheduleDTOUtils,
JobTriggerType, JobTriggerType,
PeriodicJobTrigger,
ScheduledJobTrigger ScheduledJobTrigger
} from '../../../../../common/entities/job/JobScheduleDTO'; } from '../../../../../common/entities/job/JobScheduleDTO';
import {ScheduledJobsService} from '../scheduled-jobs.service'; import {ScheduledJobsService} from '../scheduled-jobs.service';
@ -96,6 +95,9 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
} }
atTimeLocal(atTime: number): Date { atTimeLocal(atTime: number): Date {
if (!atTime) {
return null;
}
const d = new Date(); const d = new Date();
d.setUTCHours(Math.floor(atTime / 60)); d.setUTCHours(Math.floor(atTime / 60));
d.setUTCMinutes(Math.floor(atTime % 60)); d.setUTCMinutes(Math.floor(atTime % 60));
@ -156,6 +158,8 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
case JobTriggerType.periodic: case JobTriggerType.periodic:
schedule.trigger = new PeriodicJobTriggerConfig(); schedule.trigger = new PeriodicJobTriggerConfig();
schedule.trigger.periodicity = 7;
schedule.trigger.atTime = 0;
break; break;
case JobTriggerType.after: case JobTriggerType.after: