mirror of
https://github.com/koloml/furbooru-tagging-assistant.git
synced 2025-12-23 23:02:58 +00:00
Implemented the bulk import interface
This commit is contained in:
@@ -7,4 +7,5 @@
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/transporting/export">Export</MenuItem>
|
||||
<MenuItem href="/transporting/import">Import</MenuItem>
|
||||
</Menu>
|
||||
|
||||
248
src/routes/transporting/import/+page.svelte
Normal file
248
src/routes/transporting/import/+page.svelte
Normal file
@@ -0,0 +1,248 @@
|
||||
<script lang="ts">
|
||||
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 MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import BulkEntitiesTransporter from "$lib/extension/BulkEntitiesTransporter";
|
||||
import type StorageEntity from "$lib/extension/base/StorageEntity";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
import ProfileView from "$components/features/ProfileView.svelte";
|
||||
import GroupView from "$components/features/GroupView.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
let importedString = $state('');
|
||||
let errorMessage = $state('');
|
||||
|
||||
let importedProfiles = $state<MaintenanceProfile[]>([]);
|
||||
let importedGroups = $state<TagGroup[]>([]);
|
||||
|
||||
let saveAllProfiles = $state(false);
|
||||
let saveAllGroups = $state(false);
|
||||
|
||||
let selectedEntities: Record<keyof App.EntityNamesMap, Record<string, boolean>> = $state({
|
||||
profiles: {},
|
||||
groups: {},
|
||||
});
|
||||
|
||||
let previewedEntity = $state<StorageEntity | null>(null);
|
||||
|
||||
const existingProfilesMap = $derived(
|
||||
$maintenanceProfiles.reduce((map, profile) => {
|
||||
map.set(profile.id, profile);
|
||||
return map;
|
||||
}, new Map<string, MaintenanceProfile>())
|
||||
);
|
||||
|
||||
const existingGroupsMap = $derived(
|
||||
$tagGroups.reduce((map, group) => {
|
||||
map.set(group.id, group);
|
||||
return map;
|
||||
}, new Map<string, TagGroup>())
|
||||
);
|
||||
|
||||
const hasImportedEntities = $derived(
|
||||
Boolean(importedProfiles.length || importedGroups.length)
|
||||
);
|
||||
|
||||
const transporter = new BulkEntitiesTransporter();
|
||||
|
||||
function tryBulkImport() {
|
||||
importedProfiles = [];
|
||||
importedGroups = [];
|
||||
errorMessage = '';
|
||||
|
||||
importedString = importedString.trim();
|
||||
|
||||
if (!importedString) {
|
||||
errorMessage = 'Nothing to import.';
|
||||
return;
|
||||
}
|
||||
|
||||
let importedEntities: StorageEntity[] = [];
|
||||
|
||||
try {
|
||||
if (importedString.startsWith('{')) {
|
||||
importedEntities = transporter.parseAndImportFromJSON(importedString);
|
||||
} else {
|
||||
importedEntities = transporter.parseAndImportFromCompressedJSON(importedString);
|
||||
}
|
||||
} catch (importError) {
|
||||
errorMessage = importError instanceof Error ? importError.message : 'Unknown error!';
|
||||
return;
|
||||
}
|
||||
|
||||
if (importedEntities.length) {
|
||||
for (const targetImportedEntity of importedEntities) {
|
||||
switch (targetImportedEntity.type) {
|
||||
case "profiles":
|
||||
importedProfiles.push(targetImportedEntity as MaintenanceProfile);
|
||||
break;
|
||||
case "groups":
|
||||
importedGroups.push(targetImportedEntity as TagGroup);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unprocessed entity type detected: ${targetImportedEntity.type}`, targetImportedEntity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorMessage = "Import string contains nothing!";
|
||||
}
|
||||
}
|
||||
|
||||
function cancelImport() {
|
||||
importedProfiles = [];
|
||||
importedGroups = [];
|
||||
}
|
||||
|
||||
function refreshAreAllEntitiesChecked() {
|
||||
requestAnimationFrame(() => {
|
||||
saveAllProfiles = importedProfiles.every(profile => selectedEntities.profiles[profile.id]);
|
||||
saveAllGroups = importedGroups.every(group => selectedEntities.groups[group.id]);
|
||||
});
|
||||
}
|
||||
|
||||
function createToggleAllOnUserInput(entityType: keyof App.EntityNamesMap) {
|
||||
return () => {
|
||||
requestAnimationFrame(() => {
|
||||
switch (entityType) {
|
||||
case "profiles":
|
||||
importedProfiles.forEach(profile => selectedEntities.profiles[profile.id] = saveAllProfiles);
|
||||
break;
|
||||
case "groups":
|
||||
importedGroups.forEach(group => selectedEntities.groups[group.id] = saveAllGroups);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Trying to toggle unsupported entity type: ${entityType}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function createShowPreviewForEntity(entity: StorageEntity) {
|
||||
return (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
previewedEntity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSelectedEntities() {
|
||||
Promise.allSettled([
|
||||
Promise.allSettled(
|
||||
importedProfiles
|
||||
.filter(profile => selectedEntities.profiles[profile.id])
|
||||
.map(profile => profile.save())
|
||||
),
|
||||
Promise.allSettled(
|
||||
importedGroups
|
||||
.filter(group => selectedEntities.groups[group.id])
|
||||
.map(group => group.save())
|
||||
),
|
||||
]).then(() => {
|
||||
goto("/transporting");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !hasImportedEntities}
|
||||
<Menu>
|
||||
<MenuItem href="/transporting" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if errorMessage}
|
||||
<p class="error">{errorMessage}</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
</Menu>
|
||||
{/if}
|
||||
<FormContainer>
|
||||
<FormControl label="Import string">
|
||||
<textarea bind:value={importedString} rows="6"></textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem onclick={tryBulkImport}>Import & Preview</MenuItem>
|
||||
</Menu>
|
||||
{:else if previewedEntity}
|
||||
<Menu>
|
||||
<MenuItem onclick={() => previewedEntity = null} icon="arrow-left">Back to Selection</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if previewedEntity instanceof MaintenanceProfile}
|
||||
<ProfileView profile={previewedEntity}></ProfileView>
|
||||
{:else if previewedEntity instanceof TagGroup}
|
||||
<GroupView group={previewedEntity}></GroupView>
|
||||
{/if}
|
||||
{:else}
|
||||
<Menu>
|
||||
<MenuItem onclick={cancelImport} icon="arrow-left">Cancel Import</MenuItem>
|
||||
<hr>
|
||||
{#if importedProfiles.length}
|
||||
<hr>
|
||||
<MenuCheckboxItem bind:checked={saveAllProfiles} oninput={createToggleAllOnUserInput('profiles')}>
|
||||
Import All Profiles
|
||||
</MenuCheckboxItem>
|
||||
{#each importedProfiles as candidateProfile}
|
||||
<MenuCheckboxItem
|
||||
bind:checked={selectedEntities.profiles[candidateProfile.id]}
|
||||
oninput={refreshAreAllEntitiesChecked}
|
||||
onitemclick={createShowPreviewForEntity(candidateProfile)}
|
||||
>
|
||||
{#if existingProfilesMap.has(candidateProfile.id)}
|
||||
Update:
|
||||
{:else}
|
||||
New:
|
||||
{/if}
|
||||
{candidateProfile.settings.name || 'Unnamed Profile'}
|
||||
</MenuCheckboxItem>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if importedGroups.length}
|
||||
<hr>
|
||||
<MenuCheckboxItem
|
||||
bind:checked={saveAllGroups}
|
||||
oninput={createToggleAllOnUserInput('groups')}
|
||||
>
|
||||
Import All Groups
|
||||
</MenuCheckboxItem>
|
||||
{#each importedGroups as candidateGroup}
|
||||
<MenuCheckboxItem
|
||||
bind:checked={selectedEntities.groups[candidateGroup.id]}
|
||||
oninput={refreshAreAllEntitiesChecked}
|
||||
onitemclick={createShowPreviewForEntity(candidateGroup)}
|
||||
>
|
||||
{#if existingGroupsMap.has(candidateGroup.id)}
|
||||
Update:
|
||||
{:else}
|
||||
New:
|
||||
{/if}
|
||||
{candidateGroup.settings.name || 'Unnamed Group'}
|
||||
</MenuCheckboxItem>
|
||||
{/each}
|
||||
{/if}
|
||||
<hr>
|
||||
<MenuItem onclick={saveSelectedEntities}>
|
||||
Imported Selected
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@use '$styles/colors';
|
||||
|
||||
.error {
|
||||
padding: 5px 24px;
|
||||
margin: {
|
||||
left: -24px;
|
||||
right: -24px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background: colors.$error-background;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user