mirror of
https://github.com/koloml/philomena-tagging-assistant.git
synced 2026-05-09 07:12:19 +00:00
Merge pull request #169 from koloml/feature/conditional-presets
Presets: Added option to make presets conditional
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
let { preset }: PresetViewProps = $props();
|
||||
|
||||
const sortedTagsList = $derived(preset.settings.tags.toSorted((a, b) => a.localeCompare(b)));
|
||||
const requiredTagsList = $derived(preset.settings.requiredTags.toSorted((a, b) => a.localeCompare(b)));
|
||||
</script>
|
||||
|
||||
<DetailsBlock title="Preset Name">
|
||||
@@ -24,3 +25,11 @@
|
||||
be automatically removed from the editor.
|
||||
</DetailsBlock>
|
||||
{/if}
|
||||
{#if preset.settings.conditional}
|
||||
<DetailsBlock title="Conditional">
|
||||
This preset will only appear when one of the tags below are present on image.
|
||||
</DetailsBlock>
|
||||
<DetailsBlock title="Conditional Tags">
|
||||
<TagsList tags={requiredTagsList}></TagsList>
|
||||
</DetailsBlock>
|
||||
{/if}
|
||||
|
||||
@@ -5,11 +5,13 @@ import { EVENT_PRESET_TAG_CHANGE_APPLIED } from "$content/components/events/pres
|
||||
import { createFontAwesomeIcon } from "$lib/dom-utils";
|
||||
|
||||
export default class PresetTableRow extends BaseComponent {
|
||||
#preset: TagEditorPreset;
|
||||
readonly #preset: TagEditorPreset;
|
||||
readonly #applyAllButton = document.createElement('button');
|
||||
readonly #removeAllButton = document.createElement('button');
|
||||
readonly #exclusiveWarning = document.createElement('div');
|
||||
readonly #alternateColorDummy = document.createElement('span');
|
||||
|
||||
#tagsList: HTMLElement[] = [];
|
||||
#applyAllButton = document.createElement('button');
|
||||
#removeAllButton = document.createElement('button');
|
||||
#exclusiveWarning = document.createElement('div');
|
||||
|
||||
constructor(container: HTMLElement, preset: TagEditorPreset) {
|
||||
super(container);
|
||||
@@ -78,6 +80,8 @@ export default class PresetTableRow extends BaseComponent {
|
||||
tagsCell,
|
||||
actionsCell,
|
||||
);
|
||||
|
||||
this.#alternateColorDummy.style.display = 'none';
|
||||
}
|
||||
|
||||
protected init() {
|
||||
@@ -146,6 +150,26 @@ export default class PresetTableRow extends BaseComponent {
|
||||
});
|
||||
}
|
||||
|
||||
#maybeRefreshVisibilityFromTags(sourceTags: Set<string>) {
|
||||
if (!this.#preset.settings.conditional || this.#isMatchesConditional(sourceTags)) {
|
||||
this.container.style.display = '';
|
||||
this.#alternateColorDummy.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
this.container.style.display = 'none';
|
||||
this.container.after(this.#alternateColorDummy);
|
||||
}
|
||||
|
||||
#isMatchesConditional(sourceTags: Set<string>): boolean {
|
||||
const listOfRequiredTags = this.#preset.settings.requiredTags;
|
||||
|
||||
return Boolean(
|
||||
listOfRequiredTags.length
|
||||
&& listOfRequiredTags.some(tagName => sourceTags.has(tagName))
|
||||
);
|
||||
}
|
||||
|
||||
updateTags(tags: Set<string>) {
|
||||
let presentTagsAmount = 0;
|
||||
|
||||
@@ -171,6 +195,8 @@ export default class PresetTableRow extends BaseComponent {
|
||||
this.#exclusiveWarning.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
this.#maybeRefreshVisibilityFromTags(tags);
|
||||
}
|
||||
|
||||
remove() {
|
||||
|
||||
@@ -4,6 +4,8 @@ interface TagEditorPresetSettings {
|
||||
name: string;
|
||||
tags: string[];
|
||||
exclusive: boolean;
|
||||
conditional: boolean;
|
||||
requiredTags: string[];
|
||||
}
|
||||
|
||||
export default class TagEditorPreset extends StorageEntity<TagEditorPresetSettings> {
|
||||
@@ -11,7 +13,9 @@ export default class TagEditorPreset extends StorageEntity<TagEditorPresetSettin
|
||||
super(id, {
|
||||
name: settings.name || '',
|
||||
tags: settings.tags || [],
|
||||
exclusive: settings.exclusive ?? false
|
||||
exclusive: settings.exclusive ?? false,
|
||||
conditional: settings.conditional || false,
|
||||
requiredTags: settings.requiredTags || [],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ const entitiesExporters: ExportersMap = {
|
||||
name: entity.settings.name,
|
||||
tags: entity.settings.tags,
|
||||
exclusive: entity.settings.exclusive,
|
||||
conditional: entity.settings.conditional,
|
||||
requiredTags: entity.settings.requiredTags,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,7 +29,15 @@ function validateRequiredString(value: unknown): boolean {
|
||||
* @param value Value to be checked.
|
||||
*/
|
||||
function validateOptionalArray(value: unknown): boolean {
|
||||
return typeof value === 'undefined' || value === null || Array.isArray(value);
|
||||
return value === undefined || value === null || Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the following value is not set or is a valid boolean.
|
||||
* @param value Value to be checked.
|
||||
*/
|
||||
function validateOptionalBoolean(value: unknown): boolean {
|
||||
return value === undefined || typeof value === 'boolean';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,6 +81,9 @@ const entitiesValidators: EntitiesValidationMap = {
|
||||
!validateRequiredString(importedObject?.id)
|
||||
|| !validateRequiredString(importedObject?.name)
|
||||
|| !validateOptionalArray(importedObject?.tags)
|
||||
|| !validateOptionalBoolean(importedObject?.exclusive)
|
||||
|| !validateOptionalBoolean(importedObject?.conditional)
|
||||
|| !validateOptionalArray(importedObject?.requiredTags)
|
||||
) {
|
||||
throw new Error('Invalid preset format detected!');
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
let presetName = $state('');
|
||||
let tagsList = $state<string[]>([]);
|
||||
let isExclusive = $state(false);
|
||||
let isConditional = $state<boolean>(false);
|
||||
let requiredTags = $state<string[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
if (presetId === 'new') {
|
||||
@@ -42,6 +44,8 @@
|
||||
presetName = targetPreset.settings.name;
|
||||
tagsList = [...targetPreset.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
isExclusive = targetPreset.settings.exclusive;
|
||||
isConditional = targetPreset.settings.conditional;
|
||||
requiredTags = [...targetPreset.settings.requiredTags].sort((a, b) => a.localeCompare(b));
|
||||
});
|
||||
|
||||
async function savePreset() {
|
||||
@@ -53,6 +57,8 @@
|
||||
targetPreset.settings.name = presetName;
|
||||
targetPreset.settings.tags = [...tagsList];
|
||||
targetPreset.settings.exclusive = isExclusive;
|
||||
targetPreset.settings.conditional = isConditional;
|
||||
targetPreset.settings.requiredTags = [...requiredTags];
|
||||
|
||||
await targetPreset.save();
|
||||
await goto(`/features/presets/${targetPreset.id}`);
|
||||
@@ -76,6 +82,16 @@
|
||||
Keep only one tag from this preset active at a time.
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={isConditional}>
|
||||
Show this preset only when any of specified tags are provided.
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
{#if isConditional}
|
||||
<FormControl label="Required Tags">
|
||||
<TagsEditor bind:tags={requiredTags}></TagsEditor>
|
||||
</FormControl>
|
||||
{/if}
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
.block.tag-presets {
|
||||
.tag {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&.is-missing {
|
||||
opacity: 0.5;
|
||||
|
||||
Reference in New Issue
Block a user