summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/dialogs
diff options
context:
space:
mode:
authorWolfgang Müller2024-03-05 18:08:09 +0100
committerWolfgang Müller2024-03-05 19:25:59 +0100
commitd1d654ebac2d51e3841675faeb56480e440f622f (patch)
tree56ef123c1a15a10dfd90836e4038e27efde950c6 /frontend/src/lib/dialogs
downloadhircine-d1d654ebac2d51e3841675faeb56480e440f622f.tar.gz
Initial commit0.1.0
Diffstat (limited to 'frontend/src/lib/dialogs')
-rw-r--r--frontend/src/lib/dialogs/AddArtist.svelte30
-rw-r--r--frontend/src/lib/dialogs/AddCharacter.svelte30
-rw-r--r--frontend/src/lib/dialogs/AddCircle.svelte30
-rw-r--r--frontend/src/lib/dialogs/AddNamespace.svelte30
-rw-r--r--frontend/src/lib/dialogs/AddTag.svelte30
-rw-r--r--frontend/src/lib/dialogs/AddWorld.svelte30
-rw-r--r--frontend/src/lib/dialogs/ConfirmDeletion.svelte51
-rw-r--r--frontend/src/lib/dialogs/EditArtist.svelte46
-rw-r--r--frontend/src/lib/dialogs/EditCharacter.svelte46
-rw-r--r--frontend/src/lib/dialogs/EditCircle.svelte46
-rw-r--r--frontend/src/lib/dialogs/EditNamespace.svelte46
-rw-r--r--frontend/src/lib/dialogs/EditTag.svelte44
-rw-r--r--frontend/src/lib/dialogs/EditWorld.svelte46
-rw-r--r--frontend/src/lib/dialogs/UpdateComics.svelte96
-rw-r--r--frontend/src/lib/dialogs/UpdateTags.svelte45
-rw-r--r--frontend/src/lib/dialogs/components/UpdateModeSelector.svelte24
16 files changed, 670 insertions, 0 deletions
diff --git a/frontend/src/lib/dialogs/AddArtist.svelte b/frontend/src/lib/dialogs/AddArtist.svelte
new file mode 100644
index 0000000..6ec93c5
--- /dev/null
+++ b/frontend/src/lib/dialogs/AddArtist.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { addArtist, type ArtistInput } from '$gql/Mutations';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import ArtistForm from '$lib/forms/ArtistForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ let artist = { name: '' };
+
+ function add(event: CustomEvent<ArtistInput>) {
+ addArtist(client, { input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Add Artist</h2>
+ </svelte:fragment>
+ <ArtistForm bind:artist on:submit={add}>
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={artist.name.length > 0} />
+ </div>
+ </ArtistForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/AddCharacter.svelte b/frontend/src/lib/dialogs/AddCharacter.svelte
new file mode 100644
index 0000000..23fea08
--- /dev/null
+++ b/frontend/src/lib/dialogs/AddCharacter.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { addCharacter, type CharacterInput } from '$gql/Mutations';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import CharacterForm from '$lib/forms/CharacterForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ let character = { name: '' };
+
+ function add(event: CustomEvent<CharacterInput>) {
+ addCharacter(client, { input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Add Character</h2>
+ </svelte:fragment>
+ <CharacterForm bind:character on:submit={add}>
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={character.name.length > 0} />
+ </div>
+ </CharacterForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/AddCircle.svelte b/frontend/src/lib/dialogs/AddCircle.svelte
new file mode 100644
index 0000000..f0ef014
--- /dev/null
+++ b/frontend/src/lib/dialogs/AddCircle.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { addCircle, type CircleInput } from '$gql/Mutations';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import CircleForm from '$lib/forms/CircleForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ let circle = { name: '' };
+
+ function add(event: CustomEvent<CircleInput>) {
+ addCircle(client, { input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Add Circle</h2>
+ </svelte:fragment>
+ <CircleForm bind:circle on:submit={add}>
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={circle.name.length > 0} />
+ </div>
+ </CircleForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/AddNamespace.svelte b/frontend/src/lib/dialogs/AddNamespace.svelte
new file mode 100644
index 0000000..e81b22a
--- /dev/null
+++ b/frontend/src/lib/dialogs/AddNamespace.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { addNamespace, type NamespaceInput } from '$gql/Mutations';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import NamespaceForm from '$lib/forms/NamespaceForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ let namespace = { name: '' };
+
+ function add(event: CustomEvent<NamespaceInput>) {
+ addNamespace(client, { input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Add Namespace</h2>
+ </svelte:fragment>
+ <NamespaceForm bind:namespace on:submit={add}>
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={namespace.name.length > 0} />
+ </div>
+ </NamespaceForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/AddTag.svelte b/frontend/src/lib/dialogs/AddTag.svelte
new file mode 100644
index 0000000..00d3a03
--- /dev/null
+++ b/frontend/src/lib/dialogs/AddTag.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { addTag, type TagInput } from '$gql/Mutations';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import TagForm from '$lib/forms/TagForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ let tag = { name: '', namespaces: [] };
+
+ function add(event: CustomEvent<TagInput>) {
+ addTag(client, { input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Add Tag</h2>
+ </svelte:fragment>
+ <TagForm bind:tag on:submit={add}>
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={tag.name.length > 0} />
+ </div>
+ </TagForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/AddWorld.svelte b/frontend/src/lib/dialogs/AddWorld.svelte
new file mode 100644
index 0000000..ceb946e
--- /dev/null
+++ b/frontend/src/lib/dialogs/AddWorld.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { addWorld, type WorldInput } from '$gql/Mutations';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import WorldForm from '$lib/forms/WorldForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ let world = { name: '' };
+
+ function add(event: CustomEvent<WorldInput>) {
+ addWorld(client, { input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Add World</h2>
+ </svelte:fragment>
+ <WorldForm bind:world on:submit={add}>
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={world.name.length > 0} />
+ </div>
+ </WorldForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/ConfirmDeletion.svelte b/frontend/src/lib/dialogs/ConfirmDeletion.svelte
new file mode 100644
index 0000000..6b0cbf8
--- /dev/null
+++ b/frontend/src/lib/dialogs/ConfirmDeletion.svelte
@@ -0,0 +1,51 @@
+<script lang="ts">
+ import { accelerator } from '$lib/Shortcuts';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import { closeModal } from 'svelte-modals';
+
+ export let isOpen: boolean;
+ export let callback: () => void;
+
+ export let names: string[];
+ export let typename: string;
+ export let warning: string | undefined = undefined;
+ const multiple = names.length > 1;
+ const formattedTypename = multiple ? `${typename}s` : typename;
+ const formattedNames = multiple ? `${names.length} ${formattedTypename}` : names[0];
+
+ function confirm() {
+ callback();
+ closeModal();
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Delete {formattedTypename}</h2>
+ </svelte:fragment>
+ <form on:submit|preventDefault={confirm}>
+ <div class="flex flex-col">
+ <p class="mb-3">
+ Are you sure you want to delete <span class="font-semibold">{formattedNames}</span>?
+ </p>
+ {#if multiple}
+ <ul class="mb-3 ml-8 list-disc">
+ {#each names.slice(0, 10) as name}
+ <li>{name}</li>
+ {/each}
+ </ul>
+ {#if names.length - 10 > 0}
+ <p>... and {names.length - 10} more.</p>
+ {/if}
+ {/if}
+ {#if warning}
+ <p class="font-medium text-red-600">Warning: {warning}</p>
+ {/if}
+ </div>
+
+ <div class="flex justify-end gap-4">
+ <button type="submit" class="btn-rose" use:accelerator={'Enter'}>Delete</button>
+ <button type="button" on:click={closeModal} class="btn-slate">Cancel</button>
+ </div>
+ </form>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/EditArtist.svelte b/frontend/src/lib/dialogs/EditArtist.svelte
new file mode 100644
index 0000000..dd08bc6
--- /dev/null
+++ b/frontend/src/lib/dialogs/EditArtist.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { deleteArtists, updateArtists, type ArtistInput } from '$gql/Mutations';
+ import { itemEquals } from '$gql/Utils';
+ import { type Artist } from '$gql/graphql';
+ import { toastFinally } from '$lib/Toasts';
+ import { confirmDeletion } from '$lib/Utils';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import ArtistForm from '$lib/forms/ArtistForm.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ export let artist: Artist;
+ const original = structuredClone(artist);
+ $: pending = !itemEquals(artist, original);
+
+ function save(event: CustomEvent<ArtistInput>) {
+ updateArtists(client, { ids: artist.id, input: event.detail })
+ .then(closeModal)
+ .catch(toastFinally);
+ }
+
+ function deleteArtist() {
+ confirmDeletion('Artist', artist.name, () => {
+ deleteArtists(client, { ids: artist.id }).then(closeModal).catch(toastFinally);
+ });
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Artist</h2>
+ </svelte:fragment>
+ <ArtistForm bind:artist on:submit={save}>
+ <div class="flex gap-4">
+ <DeleteButton on:click={deleteArtist} />
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </ArtistForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/EditCharacter.svelte b/frontend/src/lib/dialogs/EditCharacter.svelte
new file mode 100644
index 0000000..3b45e78
--- /dev/null
+++ b/frontend/src/lib/dialogs/EditCharacter.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { deleteCharacters, updateCharacters, type CharacterInput } from '$gql/Mutations';
+ import { itemEquals } from '$gql/Utils';
+ import { type Character } from '$gql/graphql';
+ import { toastFinally } from '$lib/Toasts';
+ import { confirmDeletion } from '$lib/Utils';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import CharacterForm from '$lib/forms/CharacterForm.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ export let character: Character;
+ const original = structuredClone(character);
+ $: pending = !itemEquals(original, character);
+
+ function save(event: CustomEvent<CharacterInput>) {
+ updateCharacters(client, { ids: character.id, input: event.detail })
+ .then(closeModal)
+ .catch(toastFinally);
+ }
+
+ function deleteCharacter() {
+ confirmDeletion('Character', character.name, () => {
+ deleteCharacters(client, { ids: character.id }).then(closeModal).catch(toastFinally);
+ });
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Character</h2>
+ </svelte:fragment>
+ <CharacterForm bind:character on:submit={save}>
+ <div class="flex gap-4">
+ <DeleteButton on:click={deleteCharacter} />
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </CharacterForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/EditCircle.svelte b/frontend/src/lib/dialogs/EditCircle.svelte
new file mode 100644
index 0000000..bdc1217
--- /dev/null
+++ b/frontend/src/lib/dialogs/EditCircle.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { deleteCircles, updateCircles, type CircleInput } from '$gql/Mutations';
+ import { itemEquals } from '$gql/Utils';
+ import { type Circle } from '$gql/graphql';
+ import { toastFinally } from '$lib/Toasts';
+ import { confirmDeletion } from '$lib/Utils';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import CircleForm from '$lib/forms/CircleForm.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ export let circle: Circle;
+ const original = structuredClone(circle);
+ $: pending = !itemEquals(original, circle);
+
+ function save(event: CustomEvent<CircleInput>) {
+ updateCircles(client, { ids: circle.id, input: event.detail })
+ .then(closeModal)
+ .catch(toastFinally);
+ }
+
+ function deleteCircle() {
+ confirmDeletion('Circle', circle.name, () => {
+ deleteCircles(client, { ids: circle.id }).then(closeModal).catch(toastFinally);
+ });
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Circle</h2>
+ </svelte:fragment>
+ <CircleForm bind:circle on:submit={save}>
+ <div class="flex gap-4">
+ <DeleteButton on:click={deleteCircle} />
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </CircleForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/EditNamespace.svelte b/frontend/src/lib/dialogs/EditNamespace.svelte
new file mode 100644
index 0000000..f398b21
--- /dev/null
+++ b/frontend/src/lib/dialogs/EditNamespace.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { deleteNamespaces, updateNamespaces, type NamespaceInput } from '$gql/Mutations';
+ import { itemEquals } from '$gql/Utils';
+ import { type Namespace } from '$gql/graphql';
+ import { toastFinally } from '$lib/Toasts';
+ import { confirmDeletion } from '$lib/Utils';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import NamespaceForm from '$lib/forms/NamespaceForm.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ export let namespace: Namespace;
+ const original = structuredClone(namespace);
+ $: pending = !itemEquals(original, namespace);
+
+ function save(event: CustomEvent<NamespaceInput>) {
+ updateNamespaces(client, { ids: namespace.id, input: event.detail })
+ .then(closeModal)
+ .catch(toastFinally);
+ }
+
+ function deleteNamespace() {
+ confirmDeletion('Namespace', namespace.name, () => {
+ deleteNamespaces(client, { ids: namespace.id }).then(closeModal).catch(toastFinally);
+ });
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Namespace</h2>
+ </svelte:fragment>
+ <NamespaceForm bind:namespace on:submit={save}>
+ <div class="flex gap-4">
+ <DeleteButton on:click={deleteNamespace} />
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </NamespaceForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/EditTag.svelte b/frontend/src/lib/dialogs/EditTag.svelte
new file mode 100644
index 0000000..d2d0013
--- /dev/null
+++ b/frontend/src/lib/dialogs/EditTag.svelte
@@ -0,0 +1,44 @@
+<script lang="ts">
+ import { deleteTags, updateTags, type TagInput } from '$gql/Mutations';
+ import { tagEquals } from '$gql/Utils';
+ import { type FullTag } from '$gql/graphql';
+ import { toastFinally } from '$lib/Toasts';
+ import { confirmDeletion } from '$lib/Utils';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import TagForm from '$lib/forms/TagForm.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ export let tag: FullTag;
+ const original = structuredClone(tag);
+ $: pending = !tagEquals(original, tag);
+
+ function save(event: CustomEvent<TagInput>) {
+ updateTags(client, { ids: tag.id, input: event.detail }).then(closeModal).catch(toastFinally);
+ }
+
+ function deleteTag() {
+ confirmDeletion('Tag', tag.name, () => {
+ deleteTags(client, { ids: tag.id }).then(closeModal).catch(toastFinally);
+ });
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Tag</h2>
+ </svelte:fragment>
+ <TagForm bind:tag on:submit={save}>
+ <div class="flex gap-4">
+ <DeleteButton on:click={deleteTag} />
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </TagForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/EditWorld.svelte b/frontend/src/lib/dialogs/EditWorld.svelte
new file mode 100644
index 0000000..82afe6a
--- /dev/null
+++ b/frontend/src/lib/dialogs/EditWorld.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { type World } from '$gql/graphql';
+ import { deleteWorlds, updateWorlds, type WorldInput } from '$gql/Mutations';
+ import { itemEquals } from '$gql/Utils';
+ import DeleteButton from '$lib/components/DeleteButton.svelte';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import WorldForm from '$lib/forms/WorldForm.svelte';
+ import { toastFinally } from '$lib/Toasts';
+ import { confirmDeletion } from '$lib/Utils';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+
+ export let world: World;
+ const original = structuredClone(world);
+ $: pending = !itemEquals(original, world);
+
+ function save(event: CustomEvent<WorldInput>) {
+ updateWorlds(client, { ids: world.id, input: event.detail })
+ .then(closeModal)
+ .catch(toastFinally);
+ }
+
+ function deleteWorld() {
+ confirmDeletion('World', world.name, () => {
+ deleteWorlds(client, { ids: world.id }).then(closeModal).catch(toastFinally);
+ });
+ }
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit World</h2>
+ </svelte:fragment>
+ <WorldForm bind:world on:submit={save}>
+ <div class="flex gap-4">
+ <DeleteButton on:click={deleteWorld} />
+ <div class="grow" />
+ <SubmitButton active={pending} />
+ </div>
+ </WorldForm>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/UpdateComics.svelte b/frontend/src/lib/dialogs/UpdateComics.svelte
new file mode 100644
index 0000000..8de9622
--- /dev/null
+++ b/frontend/src/lib/dialogs/UpdateComics.svelte
@@ -0,0 +1,96 @@
+<script lang="ts">
+ import { updateComics } from '$gql/Mutations';
+ import { artistList, characterList, circleList, comicTagList, worldList } from '$gql/Queries';
+ import { categories, censorships, directions, languages, layouts, ratings } from '$lib/Enums';
+ import { toastFinally } from '$lib/Toasts';
+ import { UpdateComicsControls } from '$lib/Update';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import Labelled from '$lib/components/Labelled.svelte';
+ import LabelledBlock from '$lib/components/LabelledBlock.svelte';
+ import Select from '$lib/components/Select.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+ import UpdateModeSelector from './components/UpdateModeSelector.svelte';
+
+ const client = getContextClient();
+
+ export let isOpen: boolean;
+ export let ids: number[];
+
+ $: tagsQuery = comicTagList(client);
+ $: artistsQuery = artistList(client);
+ $: charactersQuery = characterList(client);
+ $: circlesQuery = circleList(client);
+ $: worldsQuery = worldList(client);
+
+ $: tags = $tagsQuery.data?.comicTags.edges;
+ $: artists = $artistsQuery.data?.artists.edges;
+ $: characters = $charactersQuery.data?.characters.edges;
+ $: circles = $circlesQuery.data?.circles.edges;
+ $: worlds = $worldsQuery.data?.worlds.edges;
+
+ const controls = new UpdateComicsControls();
+
+ const update = () => {
+ updateComics(client, {
+ ids: ids,
+ input: controls.toInput()
+ })
+ .then(closeModal)
+ .catch(toastFinally);
+ };
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Comics</h2>
+ </svelte:fragment>
+ <form on:submit|preventDefault={update}>
+ <div class="grid-labels">
+ <Labelled label="Category" let:id>
+ <Select clearable {id} options={categories} bind:value={controls.category.value} />
+ </Labelled>
+ <Labelled label="Rating" let:id>
+ <Select clearable {id} options={ratings} bind:value={controls.rating.value} />
+ </Labelled>
+ <Labelled label="Censorship" let:id>
+ <Select clearable {id} options={censorships} bind:value={controls.censorship.value} />
+ </Labelled>
+ <Labelled label="Language" let:id>
+ <Select clearable {id} options={languages} bind:value={controls.language.value} />
+ </Labelled>
+ <Labelled label="Direction" let:id>
+ <Select clearable {id} options={directions} bind:value={controls.direction.value} />
+ </Labelled>
+ <Labelled label="Layout" let:id>
+ <Select clearable {id} options={layouts} bind:value={controls.layout.value} />
+ </Labelled>
+ </div>
+
+ <LabelledBlock label="Artists" let:id>
+ <Select multi {id} options={artists} bind:value={controls.artists.ids} />
+ <UpdateModeSelector bind:mode={controls.artists.options.mode} slot="controls" />
+ </LabelledBlock>
+ <LabelledBlock label="Circles" let:id>
+ <Select multi {id} options={circles} bind:value={controls.circles.ids} />
+ <UpdateModeSelector bind:mode={controls.circles.options.mode} slot="controls" />
+ </LabelledBlock>
+ <LabelledBlock label="Characters" let:id>
+ <Select multi {id} options={characters} bind:value={controls.characters.ids} />
+ <UpdateModeSelector bind:mode={controls.characters.options.mode} slot="controls" />
+ </LabelledBlock>
+ <LabelledBlock label="Worlds" let:id>
+ <Select multi {id} options={worlds} bind:value={controls.worlds.ids} />
+ <UpdateModeSelector bind:mode={controls.worlds.options.mode} slot="controls" />
+ </LabelledBlock>
+ <LabelledBlock label="Tags" let:id>
+ <Select multi {id} options={tags} bind:value={controls.tags.ids} />
+ <UpdateModeSelector bind:mode={controls.tags.options.mode} slot="controls" />
+ </LabelledBlock>
+
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={controls.hasInput()} />
+ </div>
+ </form>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/UpdateTags.svelte b/frontend/src/lib/dialogs/UpdateTags.svelte
new file mode 100644
index 0000000..f753c7f
--- /dev/null
+++ b/frontend/src/lib/dialogs/UpdateTags.svelte
@@ -0,0 +1,45 @@
+<script lang="ts">
+ import { updateTags } from '$gql/Mutations';
+ import { namespaceList } from '$gql/Queries';
+ import { toastFinally } from '$lib/Toasts';
+ import { UpdateTagsControls } from '$lib/Update';
+ import Dialog from '$lib/components/Dialog.svelte';
+ import LabelledBlock from '$lib/components/LabelledBlock.svelte';
+ import Select from '$lib/components/Select.svelte';
+ import SubmitButton from '$lib/components/SubmitButton.svelte';
+ import { getContextClient } from '@urql/svelte';
+ import { closeModal } from 'svelte-modals';
+ import UpdateModeSelector from './components/UpdateModeSelector.svelte';
+
+ const client = getContextClient();
+
+ $: namespaceQuery = namespaceList(client);
+ $: namespaces = $namespaceQuery.data?.namespaces.edges;
+
+ export let isOpen: boolean;
+ export let ids: number[];
+
+ const controls = new UpdateTagsControls();
+
+ const update = () => {
+ updateTags(client, { ids: ids, input: controls.toInput() })
+ .then(closeModal)
+ .catch(toastFinally);
+ };
+</script>
+
+<Dialog {isOpen}>
+ <svelte:fragment slot="header">
+ <h2>Edit Tags</h2>
+ </svelte:fragment>
+ <form on:submit|preventDefault={update}>
+ <LabelledBlock label="Namespaces" let:id>
+ <Select multi {id} options={namespaces} bind:value={controls.namespaces.ids} />
+ <UpdateModeSelector bind:mode={controls.namespaces.options.mode} slot="controls" />
+ </LabelledBlock>
+
+ <div class="flex justify-end gap-4">
+ <SubmitButton active={controls.hasInput()} />
+ </div>
+ </form>
+</Dialog>
diff --git a/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte b/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte
new file mode 100644
index 0000000..e4b4479
--- /dev/null
+++ b/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte
@@ -0,0 +1,24 @@
+<script lang="ts">
+ import { UpdateMode } from '$gql/graphql';
+ import { UpdateModeLabel } from '$lib/Enums';
+
+ export let mode: UpdateMode;
+
+ function select(e: string) {
+ mode = e as UpdateMode;
+ }
+</script>
+
+<div class="flex gap-1 pb-1 text-xs">
+ {#each Object.entries(UpdateModeLabel) as [e, label]}
+ <button
+ type="button"
+ class:active={mode === e}
+ class:dangerous={mode !== UpdateMode.Add}
+ class="btn btn-xs hover:bg-slate-700 [&.active.dangerous]:bg-rose-800 [&.active]:bg-indigo-700"
+ on:click={() => select(e)}
+ >
+ {label}
+ </button>
+ {/each}
+</div>