From 83c7608e999c6eee23514dbb4a4fe6f363879b47 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Sun, 22 Mar 2026 04:09:18 +0400 Subject: [PATCH] Presets: Added flag for making it "exclusive" This will make it so only one tag will be active from marked preset. This can be useful for some tags that cannot be together in the editor, for example, rating tags. --- src/components/features/PresetView.svelte | 6 ++ .../extension/presets/PresetTableRow.ts | 59 ++++++++++++++++++- src/lib/extension/entities/TagEditorPreset.ts | 2 + src/lib/extension/transporting/exporters.ts | 1 + .../features/presets/[id]/edit/+page.svelte | 9 +++ src/styles/booru-vars.scss | 1 + src/styles/content/tag-presets.scss | 7 +++ 7 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/components/features/PresetView.svelte b/src/components/features/PresetView.svelte index 18c24ca..4dcc1b4 100644 --- a/src/components/features/PresetView.svelte +++ b/src/components/features/PresetView.svelte @@ -18,3 +18,9 @@ +{#if preset.settings.exclusive} + + Only one tag in this preset should be active at a time. If you will click on other non-active tag, other tags will + be automatically removed from the editor. + +{/if} diff --git a/src/content/components/extension/presets/PresetTableRow.ts b/src/content/components/extension/presets/PresetTableRow.ts index 61bcafd..025e3da 100644 --- a/src/content/components/extension/presets/PresetTableRow.ts +++ b/src/content/components/extension/presets/PresetTableRow.ts @@ -9,6 +9,7 @@ export default class PresetTableRow extends BaseComponent { #tagsList: HTMLElement[] = []; #applyAllButton = document.createElement('button'); #removeAllButton = document.createElement('button'); + #exclusiveWarning = document.createElement('div'); constructor(container: HTMLElement, preset: TagEditorPreset) { super(container); @@ -32,6 +33,7 @@ export default class PresetTableRow extends BaseComponent { nameCell.textContent = this.#preset.settings.name; const tagsCell = document.createElement('td'); + tagsCell.style.width = '70%'; const tagsListContainer = document.createElement('div'); tagsListContainer.classList.add('tag-list'); @@ -52,6 +54,18 @@ export default class PresetTableRow extends BaseComponent { this.#removeAllButton.append(createFontAwesomeIcon('circle-minus')); this.#removeAllButton.title = 'Remove all tags from this preset from the editor'; + if (this.#preset.settings.exclusive) { + this.#applyAllButton.disabled = true; + this.#applyAllButton.title = "You can't add all tags from this preset since it only allows one tag to be active"; + + this.#exclusiveWarning.classList.add('block', 'block--fixed', 'block--warning'); + this.#exclusiveWarning.textContent = ' Multiple tags from this preset present in the editor! If you will click one of the tags here, other tags will be cleared automatically.' + this.#exclusiveWarning.prepend(createFontAwesomeIcon('triangle-exclamation')); + this.#exclusiveWarning.style.display = 'none'; + + tagsCell.append(this.#exclusiveWarning); + } + actionsContainer.append( this.#applyAllButton, this.#removeAllButton, @@ -85,6 +99,30 @@ export default class PresetTableRow extends BaseComponent { const tagName = targetElement.dataset.tagName; const isMissing = targetElement.classList.contains(PresetTableRow.#tagMissingClassName); + if (!tagName) { + return; + } + + // If a user clicks on the tag which was missing, then we have to remove all other active tags that are in this + // preset. But only when clicking on a tag which is missing, just so they will be able to remove any cases where + // multiple tags from exclusive present are active. + if (this.#preset.settings.exclusive && isMissing) { + const tagNamesToRemove = this.#tagsList + .filter( + tagElement => tagElement !== targetElement + && !tagElement.classList.contains(PresetTableRow.#tagMissingClassName) + ) + .map(tagElement => tagElement.dataset.tagName) + .filter(tagName => typeof tagName === 'string'); + + emit(this, EVENT_PRESET_TAG_CHANGE_APPLIED, { + addedTags: new Set([tagName]), + removedTags: new Set(tagNamesToRemove) + }); + + return; + } + emit(this, EVENT_PRESET_TAG_CHANGE_APPLIED, { [isMissing ? 'addedTags' : 'removedTags']: new Set([tagName]) }); @@ -109,11 +147,29 @@ export default class PresetTableRow extends BaseComponent { } updateTags(tags: Set) { + let presentTagsAmount = 0; + for (const tagElement of this.#tagsList) { - tagElement.classList.toggle( + const isTagMissing = tagElement.classList.toggle( PresetTableRow.#tagMissingClassName, !tags.has(tagElement.dataset.tagName || ''), ); + + if (!isTagMissing) { + presentTagsAmount++; + } + } + + if (this.#preset.settings.exclusive) { + const multipleTagsInExclusivePreset = presentTagsAmount > 1; + + this.container.classList.toggle(PresetTableRow.#presetWarningClassName, multipleTagsInExclusivePreset); + + if (multipleTagsInExclusivePreset) { + this.#exclusiveWarning.style.removeProperty('display'); + } else { + this.#exclusiveWarning.style.display = 'none'; + } } } @@ -126,4 +182,5 @@ export default class PresetTableRow extends BaseComponent { } static #tagMissingClassName = 'is-missing'; + static #presetWarningClassName = 'has-warning'; } diff --git a/src/lib/extension/entities/TagEditorPreset.ts b/src/lib/extension/entities/TagEditorPreset.ts index d904d5f..3932862 100644 --- a/src/lib/extension/entities/TagEditorPreset.ts +++ b/src/lib/extension/entities/TagEditorPreset.ts @@ -3,6 +3,7 @@ import StorageEntity from "$lib/extension/base/StorageEntity"; interface TagEditorPresetSettings { name: string; tags: string[]; + exclusive: boolean; } export default class TagEditorPreset extends StorageEntity { @@ -10,6 +11,7 @@ export default class TagEditorPreset extends StorageEntity([]); + let isExclusive = $state(false); $effect(() => { if (presetId === 'new') { @@ -39,6 +41,7 @@ presetName = targetPreset.settings.name; tagsList = [...targetPreset.settings.tags].sort((a, b) => a.localeCompare(b)); + isExclusive = targetPreset.settings.exclusive; }); async function savePreset() { @@ -49,6 +52,7 @@ targetPreset.settings.name = presetName; targetPreset.settings.tags = [...tagsList]; + targetPreset.settings.exclusive = isExclusive; await targetPreset.save(); await goto(`/features/presets/${targetPreset.id}`); @@ -67,6 +71,11 @@ + + + Keep only one tag from this preset active at a time. + +
diff --git a/src/styles/booru-vars.scss b/src/styles/booru-vars.scss index 462d9c5..1f9d5f6 100644 --- a/src/styles/booru-vars.scss +++ b/src/styles/booru-vars.scss @@ -4,6 +4,7 @@ $media-box-color: var(--media-box-color); $padding-small: var(--padding-small); $padding-normal: var(--padding-normal); $padding-large: var(--padding-large); +$block-spacing: var(--block-spacing); // These variables are defined dynamically based on the category of the tag $resolved-tag-background: var(--tag-background); diff --git a/src/styles/content/tag-presets.scss b/src/styles/content/tag-presets.scss index cf24ef6..30450c3 100644 --- a/src/styles/content/tag-presets.scss +++ b/src/styles/content/tag-presets.scss @@ -13,4 +13,11 @@ background: booru-vars.$resolved-tag-color; } } + + .block.block--fixed.block--warning { + margin: { + top: booru-vars.$block-spacing; + bottom: 0; + } + } }