1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Add info panel to jobs #587

This commit is contained in:
Patrik J. Braun 2023-09-19 21:10:33 +02:00
parent 71c9df2d26
commit 07328a7ae2
6 changed files with 123 additions and 60 deletions

View File

@ -142,6 +142,7 @@ import {
ionHammerOutline, ionHammerOutline,
ionImageOutline, ionImageOutline,
ionImagesOutline, ionImagesOutline,
ionInformationCircleOutline,
ionInformationOutline, ionInformationOutline,
ionLinkOutline, ionLinkOutline,
ionLocationOutline, ionLocationOutline,
@ -204,7 +205,7 @@ export class MyHammerConfig extends HammerGestureConfig {
export class CustomUrlSerializer implements UrlSerializer { export class CustomUrlSerializer implements UrlSerializer {
private defaultUrlSerializer: DefaultUrlSerializer = private defaultUrlSerializer: DefaultUrlSerializer =
new DefaultUrlSerializer(); new DefaultUrlSerializer();
parse(url: string): UrlTree { parse(url: string): UrlTree {
// Encode parentheses // Encode parentheses
@ -215,9 +216,9 @@ export class CustomUrlSerializer implements UrlSerializer {
serialize(tree: UrlTree): string { serialize(tree: UrlTree): string {
return this.defaultUrlSerializer return this.defaultUrlSerializer
.serialize(tree) .serialize(tree)
.replace(/%28/g, '(') .replace(/%28/g, '(')
.replace(/%29/g, ')'); .replace(/%29/g, ')');
} }
} }
@ -245,6 +246,7 @@ Marker.prototype.options.icon = MarkerFactory.defIcon;
ionTextOutline, ionFolderOutline, ionDocumentOutline, ionImageOutline, ionTextOutline, ionFolderOutline, ionDocumentOutline, ionImageOutline,
ionPricetagOutline, ionLocationOutline, ionPricetagOutline, ionLocationOutline,
ionSunnyOutline, ionMoonOutline, ionVideocamOutline, ionSunnyOutline, ionMoonOutline, ionVideocamOutline,
ionInformationCircleOutline,
ionInformationOutline, ionContractOutline, ionExpandOutline, ionCloseOutline, ionInformationOutline, ionContractOutline, ionExpandOutline, ionCloseOutline,
ionTimerOutline, ionTimerOutline,
ionPlayOutline, ionPauseOutline, ionVolumeMediumOutline, ionVolumeMuteOutline, ionPlayOutline, ionPauseOutline, ionVolumeMediumOutline, ionVolumeMuteOutline,

View File

@ -1,6 +1,8 @@
import {inject, TestBed} from '@angular/core/testing'; import {inject, TestBed} from '@angular/core/testing';
import {BackendtextService} from './backendtext.service'; import {BackendtextService} from './backendtext.service';
import {backendTexts} from '../../../common/BackendTexts'; import {backendTexts} from '../../../common/BackendTexts';
import {Utils} from '../../../common/Utils';
import {DefaultsJobs} from '../../../common/entities/job/JobDTO';
describe('BackendTextService', () => { describe('BackendTextService', () => {
beforeEach(() => { beforeEach(() => {
@ -10,18 +12,31 @@ describe('BackendTextService', () => {
}); });
it('should have valid text for all keys', inject( it('should have valid text for all keys', inject(
[BackendtextService], [BackendtextService],
(backendTextService: BackendtextService) => { (backendTextService: BackendtextService) => {
const getTexts = (obj: any) => { const getTexts = (obj: any) => {
for (const key of Object.keys(obj)) { for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object') { if (typeof obj[key] === 'object') {
getTexts(obj[key]); getTexts(obj[key]);
continue; continue;
}
expect(backendTextService.get(obj[key])).not.toEqual(null, 'Error for key: ' + obj[key] + ', ' + key);
} }
}; expect(backendTextService.get(obj[key])).not.toEqual(null, 'Error for key: ' + obj[key] + ', ' + key);
getTexts(backendTexts); }
};
getTexts(backendTexts);
}
));
it('should have valid text for all jobs', inject(
[BackendtextService],
(backendTextService: BackendtextService) => {
const allJobs = Utils.enumToArray(DefaultsJobs);
for (let i = 0; i < allJobs.length; ++i){
expect(backendTextService.getJobName(allJobs[i].value)).not.toEqual(null, 'Cant find job name: ' + allJobs[i].value);
expect(backendTextService.getJobDescription(allJobs[i].value)).not.toEqual(null, 'Cant find job name: ' + allJobs[i].value);
} }
}
)); ));
}); });

View File

@ -60,13 +60,51 @@ export class BackendtextService {
case DefaultsJobs['Temp Folder Cleaning']: case DefaultsJobs['Temp Folder Cleaning']:
return $localize`Temp folder cleaning`; return $localize`Temp folder cleaning`;
case DefaultsJobs['Album Cover Filling']: case DefaultsJobs['Album Cover Filling']:
return $localize`Album cover filling`; return $localize`Cover filling`;
case DefaultsJobs['Album Cover Reset']: case DefaultsJobs['Album Cover Reset']:
return $localize`Album cover reset`; return $localize`Cover reset`;
case DefaultsJobs['GPX Compression']: case DefaultsJobs['GPX Compression']:
return $localize`GPX compression`; return $localize`GPX compression`;
case DefaultsJobs['Delete Compressed GPX']:
return $localize`Delete Compressed GPX`;
case DefaultsJobs['Top Pick Sending']:
return $localize`Top Pick Sending`;
default: default:
return DefaultsJobs[job as DefaultsJobs]; return null;
}
}
public getJobDescription(job: DefaultsJobs | string): string {
if (typeof job === 'string') {
job = DefaultsJobs[job as any];
}
switch (job as DefaultsJobs) {
case DefaultsJobs.Indexing:
return $localize`Scans the whole gallery from disk and indexes it to the DB.`;
case DefaultsJobs['Gallery Reset']:
return $localize`Deletes all directories, photos and videos from the DB.`;
case DefaultsJobs['Album Reset']:
return $localize`Removes all albums from the DB`;
case DefaultsJobs['Thumbnail Generation']:
return $localize`Generates thumbnails from all media files and stores them in the tmp folder.`;
case DefaultsJobs['Photo Converting']:
return $localize`Generates high res photos from all media files and stores them in the tmp folder.`;
case DefaultsJobs['Video Converting']:
return $localize`Transcodes all videos and stores them in the tmp folder.`;
case DefaultsJobs['Temp Folder Cleaning']:
return $localize`Removes unnecessary files from the tmp folder.`;
case DefaultsJobs['Album Cover Filling']:
return $localize`Updates the cover photo of all albums (both directories and saved searches) and and faces.`;
case DefaultsJobs['Album Cover Reset']:
return $localize`Deletes the cover photo of all albums and faces`;
case DefaultsJobs['GPX Compression']:
return $localize`Compresses all gpx files`;
case DefaultsJobs['Delete Compressed GPX']:
return $localize`Deletes all compressed GPX files`;
case DefaultsJobs['Top Pick Sending']:
return $localize`Gets the top photos of the selected search queries and sends them over email. You need to set up the SMTP server connection send e-mails.`;
default:
return null;
} }
} }
} }

View File

@ -72,7 +72,7 @@
let-confPath="confPath"> let-confPath="confPath">
<div class="alert alert-secondary" role="alert" <div class="alert alert-secondary" role="alert"
*ngIf="rStates.description && settingsService.configStyle == ConfigStyle.full"> *ngIf="rStates.description && settingsService.configStyle == ConfigStyle.full">
{{rStates.description}} <ng-icon size="1.3em" name="ionInformationCircleOutline"></ng-icon> {{rStates.description}}
<a *ngIf="rStates.tags?.githubIssue" <a *ngIf="rStates.tags?.githubIssue"
[href]="'https://github.com/bpatrik/pigallery2/issues/'+rStates.tags?.githubIssue"> [href]="'https://github.com/bpatrik/pigallery2/issues/'+rStates.tags?.githubIssue">
<ng-container i18n>See</ng-container> <ng-container i18n>See</ng-container>
@ -130,7 +130,7 @@
<ng-container *ngFor="let job of uiJob; let i = index"> <ng-container *ngFor="let job of uiJob; let i = index">
<div class="alert alert-secondary" role="alert" <div class="alert alert-secondary" role="alert"
*ngIf="job.description && settingsService.configStyle == ConfigStyle.full"> *ngIf="job.description && settingsService.configStyle == ConfigStyle.full">
{{job.description}} <ng-icon size="1.3em" name="ionInformationCircleOutline"></ng-icon> {{job.description}}
</div> </div>
<app-settings-job-button <app-settings-job-button
*ngIf="!job.relevant || job.relevant(settingsService.settings | async)" *ngIf="!job.relevant || job.relevant(settingsService.settings | async)"

View File

@ -40,6 +40,10 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="alert alert-secondary" role="alert"
*ngIf="settingsService.configStyle == ConfigStyle.full">
<ng-icon size="1.3em" name="ionInformationCircleOutline"></ng-icon> {{getJobDescription(schedule.jobName)}}
</div>
<div class="mb-1 row"> <div class="mb-1 row">
<label class="col-md-2 control-label" i18n>Job:</label> <label class="col-md-2 control-label" i18n>Job:</label>
<div class="col-md-4"> <div class="col-md-4">

View File

@ -70,9 +70,9 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
error: string; error: string;
constructor( constructor(
public settingsService: SettingsService, public settingsService: SettingsService,
public jobsService: ScheduledJobsService, public jobsService: ScheduledJobsService,
public backendTextService: BackendtextService, public backendTextService: BackendtextService,
) { ) {
this.JobTriggerTypeMap = [ this.JobTriggerTypeMap = [
{key: JobTriggerType.after, value: $localize`after`}, {key: JobTriggerType.after, value: $localize`after`},
@ -116,27 +116,27 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
remove(schedule: JobScheduleDTO): void { remove(schedule: JobScheduleDTO): void {
this.schedules.splice( this.schedules.splice(
this.schedules.indexOf(schedule), this.schedules.indexOf(schedule),
1 1
); );
} }
jobTypeChanged(schedule: JobScheduleDTO): void { jobTypeChanged(schedule: JobScheduleDTO): void {
const job = this.jobsService.availableJobs.value.find( const job = this.jobsService.availableJobs.value.find(
(t) => t.Name === schedule.jobName (t) => t.Name === schedule.jobName
); );
schedule.config = schedule.config || {}; schedule.config = schedule.config || {};
if (job.ConfigTemplate) { if (job.ConfigTemplate) {
job.ConfigTemplate.forEach( job.ConfigTemplate.forEach(
(ct) => (schedule.config[ct.id] = ct.defaultValue) (ct) => (schedule.config[ct.id] = ct.defaultValue)
); );
} }
} }
jobTriggerTypeChanged( jobTriggerTypeChanged(
triggerType: JobTriggerType, triggerType: JobTriggerType,
schedule: JobScheduleDTO schedule: JobScheduleDTO
): void { ): void {
switch (triggerType) { switch (triggerType) {
case JobTriggerType.never: case JobTriggerType.never:
@ -166,7 +166,7 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
value = value.replace(new RegExp(',', 'g'), ';'); value = value.replace(new RegExp(',', 'g'), ';');
value = value.replace(new RegExp(' ', 'g'), ';'); value = value.replace(new RegExp(' ', 'g'), ';');
configElement[id] = value configElement[id] = value
.split(';').filter((i: string) => i != ''); .split(';').filter((i: string) => i != '');
} }
getArray(configElement: Record<string, number[]>, id: string): string { getArray(configElement: Record<string, number[]>, id: string): string {
@ -177,46 +177,46 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
value = value.replace(new RegExp(',', 'g'), ';'); value = value.replace(new RegExp(',', 'g'), ';');
value = value.replace(new RegExp(' ', 'g'), ';'); value = value.replace(new RegExp(' ', 'g'), ';');
configElement[id] = value configElement[id] = value
.split(';') .split(';')
.map((s: string) => parseInt(s, 10)) .map((s: string) => parseInt(s, 10))
.filter((i: number) => !isNaN(i) && i > 0); .filter((i: number) => !isNaN(i) && i > 0);
} }
public shouldIdent(curr: JobScheduleDTO, prev: JobScheduleDTO): boolean { public shouldIdent(curr: JobScheduleDTO, prev: JobScheduleDTO): boolean {
return ( return (
curr && curr &&
curr.trigger.type === JobTriggerType.after && curr.trigger.type === JobTriggerType.after &&
prev && prev &&
prev.name === curr.trigger.afterScheduleName prev.name === curr.trigger.afterScheduleName
); );
} }
public sortedSchedules(): JobScheduleDTO[] { public sortedSchedules(): JobScheduleDTO[] {
return (this.schedules || []) return (this.schedules || [])
.slice() .slice()
.sort((a: JobScheduleDTO, b: JobScheduleDTO) => { .sort((a: JobScheduleDTO, b: JobScheduleDTO) => {
return ( return (
this.getNextRunningDate(a, this.schedules) - this.getNextRunningDate(a, this.schedules) -
this.getNextRunningDate(b, this.schedules) this.getNextRunningDate(b, this.schedules)
); );
}); });
} }
prepareNewJob(): void { prepareNewJob(): void {
const jobName = this.jobsService.availableJobs.value[0].Name; const jobName = this.jobsService.availableJobs.value[0].Name;
this.newSchedule = new JobScheduleConfig('new job', this.newSchedule = new JobScheduleConfig('new job',
jobName, jobName,
new NeverJobTriggerConfig()); new NeverJobTriggerConfig());
// setup job specific config // setup job specific config
const job = this.jobsService.availableJobs.value.find( const job = this.jobsService.availableJobs.value.find(
(t) => t.Name === jobName (t) => t.Name === jobName
); );
this.newSchedule.config = this.newSchedule.config || {}; this.newSchedule.config = this.newSchedule.config || {};
if (job.ConfigTemplate) { if (job.ConfigTemplate) {
job.ConfigTemplate.forEach( job.ConfigTemplate.forEach(
(ct) => (this.newSchedule.config[ct.id] = ct.defaultValue) (ct) => (this.newSchedule.config[ct.id] = ct.defaultValue)
); );
} }
this.jobModalQL.first.show(); this.jobModalQL.first.show();
@ -226,12 +226,12 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
// make unique job name // make unique job name
const jobName = this.newSchedule.jobName; const jobName = this.newSchedule.jobName;
const count = this.schedules.filter( const count = this.schedules.filter(
(s: JobScheduleDTO) => s.jobName === jobName (s: JobScheduleDTO) => s.jobName === jobName
).length; ).length;
this.newSchedule.name = this.newSchedule.name =
count === 0 count === 0
? jobName ? jobName
: this.backendTextService.getJobName(jobName) + ' ' + (count + 1); : this.backendTextService.getJobName(jobName) + ' ' + (count + 1);
this.schedules.push(this.newSchedule); this.schedules.push(this.newSchedule);
this.jobModalQL.first.hide(); this.jobModalQL.first.hide();
@ -243,24 +243,24 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
} }
private getNextRunningDate( private getNextRunningDate(
sch: JobScheduleDTO, sch: JobScheduleDTO,
list: JobScheduleDTO[], list: JobScheduleDTO[],
depth = 0 depth = 0
): number { ): number {
if (depth > list.length) { if (depth > list.length) {
return 0; return 0;
} }
if (sch.trigger.type === JobTriggerType.never) { if (sch.trigger.type === JobTriggerType.never) {
return ( return (
list list
.map((s) => s.name) .map((s) => s.name)
.sort() .sort()
.indexOf(sch.name) * -1 .indexOf(sch.name) * -1
); );
} }
if (sch.trigger.type === JobTriggerType.after) { if (sch.trigger.type === JobTriggerType.after) {
const parent = list.find( const parent = list.find(
(s) => s.name === (sch.trigger as AfterJobTrigger).afterScheduleName (s) => s.name === (sch.trigger as AfterJobTrigger).afterScheduleName
); );
if (parent) { if (parent) {
return this.getNextRunningDate(parent, list, depth + 1) + 0.001; return this.getNextRunningDate(parent, list, depth + 1) + 0.001;
@ -282,6 +282,10 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
// empty // empty
}; };
getJobDescription(jobName: string): string {
return this.backendTextService.getJobDescription(jobName);
}
public writeValue(obj: JobScheduleConfig[]): void { public writeValue(obj: JobScheduleConfig[]): void {
this.schedules = obj; this.schedules = obj;
} }