diff options
Diffstat (limited to 'frontend/src/lib/filter')
-rw-r--r-- | frontend/src/lib/filter/ComicFilterForm.svelte | 48 | ||||
-rw-r--r-- | frontend/src/lib/filter/TagFilterForm.svelte | 31 | ||||
-rw-r--r-- | frontend/src/lib/filter/components/ComicFilterGroup.svelte | 27 | ||||
-rw-r--r-- | frontend/src/lib/filter/components/Filter.svelte | 77 | ||||
-rw-r--r-- | frontend/src/lib/filter/components/FilterForm.svelte | 47 | ||||
-rw-r--r-- | frontend/src/lib/filter/components/TagFilterGroup.svelte | 14 |
6 files changed, 244 insertions, 0 deletions
diff --git a/frontend/src/lib/filter/ComicFilterForm.svelte b/frontend/src/lib/filter/ComicFilterForm.svelte new file mode 100644 index 0000000..13b5320 --- /dev/null +++ b/frontend/src/lib/filter/ComicFilterForm.svelte @@ -0,0 +1,48 @@ +<script lang="ts"> + import { page } from '$app/stores'; + import { artistList, characterList, circleList, comicTagList, worldList } from '$gql/Queries'; + import { ComicFilterContext, getFilterContext } from '$lib/Filter'; + import { getContextClient } from '@urql/svelte'; + import ComicFilterGroup from './components/ComicFilterGroup.svelte'; + import FilterForm from './components/FilterForm.svelte'; + + const client = getContextClient(); + + $: tagsQuery = comicTagList(client, { forFilter: true }); + $: 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 filter = getFilterContext<ComicFilterContext>(); + const apply = () => $filter.apply($page.url.searchParams); +</script> + +<FilterForm type="grid" on:submit={apply}> + <ComicFilterGroup + slot="include" + type="include" + bind:controls={$filter.include.controls} + {tags} + {artists} + {characters} + {circles} + {worlds} + /> + <ComicFilterGroup + slot="exclude" + type="exclude" + bind:controls={$filter.exclude.controls} + {tags} + {artists} + {characters} + {circles} + {worlds} + /> +</FilterForm> diff --git a/frontend/src/lib/filter/TagFilterForm.svelte b/frontend/src/lib/filter/TagFilterForm.svelte new file mode 100644 index 0000000..be5996e --- /dev/null +++ b/frontend/src/lib/filter/TagFilterForm.svelte @@ -0,0 +1,31 @@ +<script lang="ts"> + import { page } from '$app/stores'; + import { namespaceList } from '$gql/Queries'; + import { TagFilterContext, getFilterContext } from '$lib/Filter'; + import { getContextClient } from '@urql/svelte'; + import FilterForm from './components/FilterForm.svelte'; + import TagFilterGroup from './components/TagFilterGroup.svelte'; + + const client = getContextClient(); + + $: namespaceQuery = namespaceList(client); + $: namespaces = $namespaceQuery.data?.namespaces.edges; + + const filter = getFilterContext<TagFilterContext>(); + const apply = () => $filter.apply($page.url.searchParams); +</script> + +<FilterForm on:submit={apply}> + <TagFilterGroup + slot="include" + type="include" + bind:controls={$filter.include.controls} + {namespaces} + /> + <TagFilterGroup + slot="exclude" + type="exclude" + bind:controls={$filter.exclude.controls} + {namespaces} + /> +</FilterForm> diff --git a/frontend/src/lib/filter/components/ComicFilterGroup.svelte b/frontend/src/lib/filter/components/ComicFilterGroup.svelte new file mode 100644 index 0000000..d302de4 --- /dev/null +++ b/frontend/src/lib/filter/components/ComicFilterGroup.svelte @@ -0,0 +1,27 @@ +<script lang="ts"> + import { categories, censorships, languages, ratings } from '$lib/Enums'; + import { ComicFilterControls } from '$lib/Filter'; + import type { ListItem } from '$lib/Utils'; + import { setContext } from 'svelte'; + import Filter from './Filter.svelte'; + + export let tags: ListItem[] | undefined; + export let artists: ListItem[] | undefined; + export let circles: ListItem[] | undefined; + export let characters: ListItem[] | undefined; + export let worlds: ListItem[] | undefined; + export let controls: ComicFilterControls; + export let type: 'include' | 'exclude'; + + setContext('filter-type', type); +</script> + +<Filter title="Tags" options={tags} bind:filter={controls.tags} --grid-column="span 2" /> +<Filter title="Artists" options={artists} bind:filter={controls.artists} /> +<Filter title="Circles" options={circles} bind:filter={controls.circles} /> +<Filter title="Characters" options={characters} bind:filter={controls.characters} /> +<Filter title="Worlds" options={worlds} bind:filter={controls.worlds} /> +<Filter title="Categories" options={categories} bind:filter={controls.categories} /> +<Filter title="Ratings" options={ratings} bind:filter={controls.ratings} /> +<Filter title="Censorship" options={censorships} bind:filter={controls.censorships} /> +<Filter title="Languages" options={languages} bind:filter={controls.languages} /> diff --git a/frontend/src/lib/filter/components/Filter.svelte b/frontend/src/lib/filter/components/Filter.svelte new file mode 100644 index 0000000..ead5c4d --- /dev/null +++ b/frontend/src/lib/filter/components/Filter.svelte @@ -0,0 +1,77 @@ +<script lang="ts"> + import { Association, Enum } from '$lib/Filter'; + import type { ListItem } from '$lib/Utils'; + import Select from '$lib/components/Select.svelte'; + import { getContext } from 'svelte'; + + export let title: string; + const context: 'include' | 'exclude' = getContext('filter-type'); + $: exclude = context === 'exclude'; + + const id = `${context}-${title.toLowerCase()}`; + + export let options: ListItem[] | undefined; + export let filter: Association<string> | Enum<string>; +</script> + +<div class:exclude class="filter-container"> + <div class="flex gap-2"> + <label for={id}>{title}</label> + <div class="ml-auto flex items-center gap-1 self-center text-xs"> + {#if filter instanceof Association} + <button + type="button" + title="matches all" + class:active={filter.mode === 'all'} + class="btn btn-xs" + on:click={() => (filter.mode = 'all')} + > + ∀ + </button> + <button + type="button" + title="matches any of" + class:active={filter.mode === 'any'} + class="btn btn-xs" + on:click={() => (filter.mode = 'any')} + > + ∃ + </button> + <button + type="button" + title="matches exactly" + class:active={filter.mode === 'exact'} + class="btn btn-xs" + on:click={() => (filter.mode = 'exact')} + > + = + </button> + <hr class="border-px border-slate-600" /> + {/if} + <button + type="button" + title="empty" + class:active={filter.empty} + class="btn btn-xs" + on:click={() => (filter.empty = !filter.empty)} + > + ∅ + </button> + </div> + </div> + <Select multi clearable {options} {id} bind:value={filter.values} /> +</div> + +<style lang="postcss"> + button:hover { + @apply bg-slate-700; + } + + button.active { + @apply bg-indigo-800; + } + + .filter-container { + grid-column: var(--grid-column); + } +</style> diff --git a/frontend/src/lib/filter/components/FilterForm.svelte b/frontend/src/lib/filter/components/FilterForm.svelte new file mode 100644 index 0000000..6fc4c90 --- /dev/null +++ b/frontend/src/lib/filter/components/FilterForm.svelte @@ -0,0 +1,47 @@ +<script lang="ts"> + import Expander from '$lib/components/Expander.svelte'; + import { getFilterContext } from '$lib/Filter'; + + const filter = getFilterContext(); + export let type: 'grid' | 'row' = 'row'; + + let exclude = false; + + $: if ($filter.exclude.size > 0) { + exclude = true; + } +</script> + +<form on:submit|preventDefault class="gap-0"> + {#if type === 'grid'} + <div class="flex flex-col gap-4 px-2 md:grid md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6"> + <slot name="include" /> + </div> + <div class="my-2 flex justify-start"> + <Expander title="Exclude" bind:expanded={exclude} /> + </div> + {#if exclude} + <div + class="flex flex-col gap-4 bg-rose-950/50 p-2 md:grid md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6" + > + <slot name="exclude" /> + </div> + {/if} + {:else} + <div + class="flex flex-wrap justify-center gap-2 [&>*]:basis-full xl:[&>*]:basis-1/3 2xl:[&>*]:basis-1/5" + > + <div class="p-2"> + <slot name="include" /> + </div> + <div class="bg-rose-950/50 p-2"> + <slot name="exclude" /> + </div> + </div> + {/if} + <div class=" mt-4 flex items-center"> + <hr class="flex-1 border-slate-700/70" /> + <button type="submit" class="btn-blue mx-2">Apply</button> + <hr class="flex-1 border-slate-700/70" /> + </div> +</form> diff --git a/frontend/src/lib/filter/components/TagFilterGroup.svelte b/frontend/src/lib/filter/components/TagFilterGroup.svelte new file mode 100644 index 0000000..83b6997 --- /dev/null +++ b/frontend/src/lib/filter/components/TagFilterGroup.svelte @@ -0,0 +1,14 @@ +<script lang="ts"> + import { TagFilterControls } from '$lib/Filter'; + import type { ListItem } from '$lib/Utils'; + import { setContext } from 'svelte'; + import Filter from './Filter.svelte'; + + export let namespaces: ListItem[] | undefined; + export let controls: TagFilterControls; + export let type: 'include' | 'exclude'; + + setContext('filter-type', type); +</script> + +<Filter title="Namespaces" options={namespaces} bind:filter={controls.namespaces} /> |