1
0
mirror of https://github.com/koloml/philomena-tagging-assistant.git synced 2026-05-09 15:12:21 +00:00

Added Tag Presets, popup editor for them, implemented presets image edit

This commit is contained in:
2026-03-12 00:17:45 +04:00
parent 6c2ef795b3
commit 74866949bb
24 changed files with 720 additions and 8 deletions

View File

@@ -26,6 +26,7 @@
{/if}
<MenuItem href="/features/profiles">Tagging Profiles</MenuItem>
<MenuItem href="/features/groups">Tag Groups</MenuItem>
<MenuItem href="/features/presets">Tag Presets</MenuItem>
<hr>
<MenuItem href="/transporting">Import/Export</MenuItem>
<MenuItem href="/preferences">Preferences</MenuItem>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { tagEditorPresets } from "$stores/entities/tag-editor-presets";
import { sortEntitiesByField } from "$lib/utils";
let presets = $derived(sortEntitiesByField($tagEditorPresets, "name"))
</script>
<Menu>
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
<MenuItem href="/features/presets/new/edit" icon="plus">Create New</MenuItem>
{#if presets.length}
<hr>
{#each presets as preset}
<MenuItem href="/features/presets/{preset.id}">{preset.settings.name}</MenuItem>
{/each}
{/if}
</Menu>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { page } from "$app/state";
import TagEditorPreset from "$entities/TagEditorPreset";
import { tagEditorPresets } from "$stores/entities/tag-editor-presets";
import { goto } from "$app/navigation";
import { popupTitle } from "$stores/popup";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import PresetView from "$components/features/PresetView.svelte";
let presetId = $derived(page.params.id);
let preset = $derived<TagEditorPreset|null>(
$tagEditorPresets.find(preset => preset.id === presetId) || null
);
$effect(() => {
if (presetId === 'new') {
goto(`/features/presets/new/edit`);
return;
}
if (!preset) {
console.warn(`Preset ${presetId} not found.`);
goto('/features/presets');
} else {
$popupTitle = `Preset: ${preset.settings.name}`;
}
});
</script>
<Menu>
<MenuItem href="/features/presets" icon="arrow-left">Back</MenuItem>
<hr>
</Menu>
{#if preset}
<PresetView {preset}></PresetView>
{/if}
<Menu>
<hr>
<MenuItem href="/features/presets/{presetId}/edit" icon="wrench">Edit Preset</MenuItem>
<MenuItem href="/features/presets/{presetId}/delete">Delete Preset</MenuItem>
</Menu>

View File

@@ -0,0 +1,74 @@
<script lang="ts">
import { page } from "$app/state";
import TagEditorPreset from "$entities/TagEditorPreset";
import { tagEditorPresets } from "$stores/entities/tag-editor-presets";
import { popupTitle } from "$stores/popup";
import { goto } from "$app/navigation";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import FormControl from "$components/ui/forms/FormControl.svelte";
import TextField from "$components/ui/forms/TextField.svelte";
import TagsEditor from "$components/tags/TagsEditor.svelte";
let presetId = $derived(page.params.id);
let targetPreset = $derived.by<TagEditorPreset | null>(() => {
if (presetId === 'new') {
return new TagEditorPreset(crypto.randomUUID(), {});
}
return $tagEditorPresets.find(preset => preset.id === presetId) || null;
});
let presetName = $state('');
let tagsList = $state<string[]>([]);
$effect(() => {
if (presetId === 'new') {
$popupTitle = 'Create New Preset';
return;
}
if (!targetPreset) {
goto('/features/presets');
return;
}
$popupTitle = `Edit Tagging Preset: ${targetPreset.settings.name}}`;
presetName = targetPreset.settings.name;
tagsList = [...targetPreset.settings.tags].sort((a, b) => a.localeCompare(b));
});
async function savePreset() {
if (!targetPreset) {
console.warn('Attempting to save the preset, but the preset is not loaded yet.');
return;
}
targetPreset.settings.name = presetName;
targetPreset.settings.tags = [...tagsList];
await targetPreset.save();
await goto(`/features/presets/${targetPreset.id}`);
}
</script>
<Menu>
<MenuItem href="/features/presets{presetId === 'new' ? '' : '/' + presetId}" icon="arrow-left">
Back
</MenuItem>
</Menu>
<FormContainer>
<FormControl label="Preset Name">
<TextField bind:value={presetName} placeholder="Preset Name"></TextField>
</FormControl>
<FormControl label="Tags">
<TagsEditor bind:tags={tagsList}></TagsEditor>
</FormControl>
</FormContainer>
<Menu>
<hr>
<MenuItem href="#" onclick={savePreset}>Save Preset</MenuItem>
</Menu>

View File

@@ -9,11 +9,13 @@
import FormControl from "$components/ui/forms/FormControl.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import { popupTitle } from "$stores/popup";
import { tagEditorPresets } from "$stores/entities/tag-editor-presets";
const bulkTransporter = new BulkEntitiesTransporter();
let exportAllProfiles = $state(false);
let exportAllGroups = $state(false);
let exportAllPresets = $state(false);
let displayExportedString = $state(false);
let shouldUseCompressed = $state(true);
@@ -24,6 +26,7 @@
const exportedEntities: Record<keyof App.EntityNamesMap, Record<string, boolean>> = $state({
profiles: {},
groups: {},
presets: {},
});
$effect(() => {
@@ -42,6 +45,12 @@
}
});
$tagEditorPresets.forEach(preset => {
if (exportedEntities.presets[preset.id]) {
elementsToExport.push(preset);
}
});
plainExport = bulkTransporter.exportToJSON(elementsToExport);
compressedExport = bulkTransporter.exportToCompressedJSON(elementsToExport);
}
@@ -57,6 +66,7 @@
requestAnimationFrame(() => {
exportAllProfiles = $taggingProfiles.every(profile => exportedEntities.profiles[profile.id]);
exportAllGroups = $tagGroups.every(group => exportedEntities.groups[group.id]);
exportAllPresets = $tagEditorPresets.every(preset => exportedEntities.presets[preset.id]);
});
}
@@ -74,6 +84,9 @@
case "groups":
$tagGroups.forEach(group => exportedEntities.groups[group.id] = exportAllGroups);
break;
case "presets":
$tagEditorPresets.forEach(preset => exportedEntities.presets[preset.id] = exportAllPresets);
break;
default:
console.warn(`Trying to toggle unsupported entity type: ${targetEntity}`);
}
@@ -116,6 +129,17 @@
{/each}
<hr>
{/if}
{#if $tagEditorPresets.length}
<MenuCheckboxItem bind:checked={exportAllPresets} oninput={createToggleAllOnUserInput('presets')}>
Export All Presets
</MenuCheckboxItem>
{#each $tagEditorPresets as preset}
<MenuCheckboxItem bind:checked={exportedEntities.presets[preset.id]} oninput={refreshAreAllEntitiesChecked}>
Preset: {preset.settings.name}
</MenuCheckboxItem>
{/each}
<hr>
{/if}
<MenuItem icon="file-export" onclick={toggleExportedStringDisplay}>Export Selected</MenuItem>
</Menu>
{:else}

View File

@@ -16,21 +16,26 @@
import type { SameSiteStatus } from "$lib/extension/EntitiesTransporter";
import { popupTitle } from "$stores/popup";
import Notice from "$components/ui/Notice.svelte";
import TagEditorPreset from "$entities/TagEditorPreset";
import { tagEditorPresets } from "$stores/entities/tag-editor-presets";
let importedString = $state('');
let errorMessage = $state('');
let importedProfiles = $state<TaggingProfile[]>([]);
let importedGroups = $state<TagGroup[]>([]);
let importedPresets = $state<TagEditorPreset[]>([]);
let saveAllProfiles = $state(false);
let saveAllGroups = $state(false);
let saveAllPresets = $state(false);
let isSaving = $state(false);
let selectedEntities: Record<keyof App.EntityNamesMap, Record<string, boolean>> = $state({
profiles: {},
groups: {},
presets: {},
});
let previewedEntity = $state<StorageEntity | null>(null);
@@ -49,8 +54,15 @@
}, new Map<string, TagGroup>())
);
const existingPresetsMap = $derived(
$tagEditorPresets.reduce((map, preset) => {
map.set(preset.id, preset);
return map;
}, new Map<string, TagEditorPreset>())
);
const hasImportedEntities = $derived(
Boolean(importedProfiles.length || importedGroups.length)
Boolean(importedProfiles.length || importedGroups.length || importedPresets.length)
);
$effect(() => {
@@ -70,6 +82,7 @@
function tryBulkImport() {
importedProfiles = [];
importedGroups = [];
importedPresets = [];
errorMessage = '';
importedString = importedString.trim();
@@ -103,6 +116,9 @@
case "groups":
importedGroups.push(targetImportedEntity as TagGroup);
break;
case "presets":
importedPresets.push(targetImportedEntity as TagEditorPreset);
break;
default:
console.warn(`Unprocessed entity type detected: ${targetImportedEntity.type}`, targetImportedEntity);
}
@@ -115,12 +131,14 @@
function cancelImport() {
importedProfiles = [];
importedGroups = [];
importedPresets = [];
}
function refreshAreAllEntitiesChecked() {
requestAnimationFrame(() => {
saveAllProfiles = importedProfiles.every(profile => selectedEntities.profiles[profile.id]);
saveAllGroups = importedGroups.every(group => selectedEntities.groups[group.id]);
saveAllPresets = importedPresets.every(preset => selectedEntities.presets[preset.id]);
});
}
@@ -134,6 +152,9 @@
case "groups":
importedGroups.forEach(group => selectedEntities.groups[group.id] = saveAllGroups);
break;
case "presets":
importedPresets.forEach(preset => selectedEntities.presets[preset.id] = saveAllPresets);
break;
default:
console.warn(`Trying to toggle unsupported entity type: ${entityType}`);
}
@@ -171,6 +192,14 @@
await group.save();
}
for (const preset of importedPresets) {
if (!selectedEntities.presets[preset.id]) {
continue;
}
await preset.save();
}
await goto("/transporting");
}
</script>
@@ -251,10 +280,7 @@
{/if}
{#if importedGroups.length}
<hr>
<MenuCheckboxItem
bind:checked={saveAllGroups}
oninput={createToggleAllOnUserInput('groups')}
>
<MenuCheckboxItem bind:checked={saveAllGroups} oninput={createToggleAllOnUserInput('groups')}>
Import All Groups
</MenuCheckboxItem>
{#each importedGroups as candidateGroup}
@@ -272,6 +298,26 @@
</MenuCheckboxItem>
{/each}
{/if}
{#if importedPresets.length}
<hr>
<MenuCheckboxItem bind:checked={saveAllPresets} oninput={createToggleAllOnUserInput('presets')}>
Import All Presets
</MenuCheckboxItem>
{#each importedPresets as candidatePreset}
<MenuCheckboxItem
bind:checked={selectedEntities.presets[candidatePreset.id]}
oninput={refreshAreAllEntitiesChecked}
onitemclick={createShowPreviewForEntity(candidatePreset)}
>
{#if existingPresetsMap.has(candidatePreset.id)}
Update:
{:else}
New:
{/if}
{candidatePreset.settings.name || 'Unnamed Preset'}
</MenuCheckboxItem>
{/each}
{/if}
<hr>
<MenuItem onclick={saveSelectedEntities}>
Imported Selected