diff --git a/deno.lock b/deno.lock index e55205d4..dac71082 100644 --- a/deno.lock +++ b/deno.lock @@ -79,6 +79,7 @@ "npm:path-to-regexp@6.2.1": "6.2.1", "npm:sv@*": "0.12.5", "npm:sv@latest": "0.12.5", + "npm:svelte-check@*": "4.3.2_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3", "npm:svelte-check@4.3.2": "4.3.2_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3", "npm:svelte@5.39.8": "5.39.8_acorn@8.15.0", "npm:svelte@~5.39.8": "5.39.8_acorn@8.15.0", diff --git a/packages/web/src/lib/base_controller.ts b/packages/web/src/lib/base_controller.ts index b221163e..b1fd89f0 100644 --- a/packages/web/src/lib/base_controller.ts +++ b/packages/web/src/lib/base_controller.ts @@ -2,7 +2,17 @@ import * as svelte from 'svelte' import type {ApiSpec} from '$lib/api.ts' import * as rpc from '@andykais/ts-rpc/client.ts' import type { Config } from '$lib/server/config.ts' -import {create_focuser, SettingsRune} from '$lib/runes/index.ts' +import { + create_focuser, + SettingsRune, + MediaListRune, + MediaSelectionsRune, + type DimensionsRune, +} from '$lib/runes/index.ts' +import type { + BrowsableQueryParams, + BrowsableSearchParams, +} from '$lib/runes/browsable_queryparams.svelte.ts' import {Keybinds} from '$lib/keybinds.ts' @@ -30,4 +40,25 @@ abstract class BaseController { abstract handlers: {} } -export { BaseController } + +/** + * Shared controller shape used by the browsable routes (`/browse`, + * `/series/`). Route-specific queryparams runes extend + * {@linkcode BrowsableSearchParams} with their own fields, but the core runes + * listed here are required so that the shared browsable components can operate + * uniformly across routes. + */ +abstract class BrowsableController extends BaseController { + abstract runes: { + media_list: MediaListRune + focus: ReturnType + dimensions: DimensionsRune + settings: SettingsRune + media_selections: MediaSelectionsRune + queryparams: BrowsableQueryParams + } + + handlers = {} +} + +export { BaseController, BrowsableController } diff --git a/packages/web/src/lib/components/Header.svelte b/packages/web/src/lib/components/Header.svelte index 4ed3a0e7..46c2351c 100644 --- a/packages/web/src/lib/components/Header.svelte +++ b/packages/web/src/lib/components/Header.svelte @@ -7,6 +7,7 @@ height = $bindable(), ...props }: { + height?: number title: string children: SvelteHTMLElements['div']['children'] } = $props() diff --git a/packages/web/src/lib/components/SelectInput.svelte b/packages/web/src/lib/components/SelectInput.svelte index f8b7e702..264666ca 100644 --- a/packages/web/src/lib/components/SelectInput.svelte +++ b/packages/web/src/lib/components/SelectInput.svelte @@ -5,9 +5,9 @@ } interface Props { label: string - options: SelectOption + options: SelectOption[] value: string - onchange: () => void | undefined + onchange: (event?: Event) => unknown } let { label, diff --git a/packages/web/src/lib/components/browsable/BrowsableShell.svelte b/packages/web/src/lib/components/browsable/BrowsableShell.svelte new file mode 100644 index 00000000..3850768d --- /dev/null +++ b/packages/web/src/lib/components/browsable/BrowsableShell.svelte @@ -0,0 +1,44 @@ + + +
+
+ +
+
+ +
+ + +
+
+
+
+ + diff --git a/packages/web/src/routes/browse/components/Footer.svelte b/packages/web/src/lib/components/browsable/Footer.svelte similarity index 91% rename from packages/web/src/routes/browse/components/Footer.svelte rename to packages/web/src/lib/components/browsable/Footer.svelte index a8a63399..3302da5e 100644 --- a/packages/web/src/routes/browse/components/Footer.svelte +++ b/packages/web/src/lib/components/browsable/Footer.svelte @@ -1,8 +1,8 @@ @@ -62,27 +62,27 @@ >
{ + onsubmit={async (e) => { e.preventDefault() let media_info = undefined - let tags: {add: string[]} | undefined + let tags: { add: string[] } | undefined if (new_tag_str) { - tags = {add: [new_tag_str]} + tags = { add: [new_tag_str] } } media_selections.current_selection.media_response!.update(media_info, tags) new_tag_str = '' }} - > + > {#if media_selections.current_selection.media_response} + content={media_selections.current_selection.media_response.media_reference.view_count} /> + content={media_selections.current_selection.media_response.media_reference.stars} /> {#each sorted_tags as tag_group_entry, tag_entry_index (tag_group_entry[0])} @@ -98,7 +98,7 @@ title="Remove from media" type="button" onclick={async () => { - await media_selections.current_selection.media_response!.update(undefined, {remove: [`${tag.group}:${tag.name}`]}) + await media_selections.current_selection.media_response!.update(undefined, { remove: [`${tag.group}:${tag.name}`] }) }}> @@ -106,14 +106,14 @@ class="hover:cursor-pointer" title="Apply to search" {controller} - params={queryparams.merge({tags: parsers.Tag.encode(tag)})}> + params={queryparams.merge({ tags: parsers.Tag.encode(tag) })}> + params={{ ...queryparams.DEFAULTS, search_string: parsers.Tag.encode(tag) }}> @@ -134,14 +134,14 @@ editable hide_if_null label="Title" - content={media_selections.current_selection.media_response.media_reference.title}/> + content={media_selections.current_selection.media_response.media_reference.title} /> + content={media_selections.current_selection.media_response.media_reference.description} /> + content={media_selections.current_selection.media_response.media_reference.source_created_at} /> + content={media_selections.current_selection.media_response.media_reference.created_at} /> + content={media_selections.current_selection.media_response.media_reference.source_url} /> + content={media_selections.current_selection.media_response.media_reference.metadata} /> {#if media_selections.current_selection.media_response.media_type === 'media_file'} + content={media_selections.current_selection.media_response.media_file.filepath} /> + content={path.basename(media_selections.current_selection.media_response.media_file.filepath)} /> {/if} {:else} diff --git a/packages/web/src/routes/browse/components/MediaList.svelte b/packages/web/src/lib/components/browsable/MediaList.svelte similarity index 83% rename from packages/web/src/routes/browse/components/MediaList.svelte rename to packages/web/src/lib/components/browsable/MediaList.svelte index f2a08670..0de63cf7 100644 --- a/packages/web/src/routes/browse/components/MediaList.svelte +++ b/packages/web/src/lib/components/browsable/MediaList.svelte @@ -1,9 +1,17 @@ { + onclick={async (e) => { if (e.ctrlKey || e.shiftKey) return e.preventDefault() await queryparams.goto(params) diff --git a/packages/web/src/routes/browse/components/SearchParams.svelte b/packages/web/src/lib/components/browsable/SearchParams.svelte similarity index 69% rename from packages/web/src/routes/browse/components/SearchParams.svelte rename to packages/web/src/lib/components/browsable/SearchParams.svelte index b1158c88..571553be 100644 --- a/packages/web/src/routes/browse/components/SearchParams.svelte +++ b/packages/web/src/lib/components/browsable/SearchParams.svelte @@ -2,14 +2,28 @@ import SelectInput from '$lib/components/SelectInput.svelte' import * as theme from '$lib/theme.ts' import Icon from '$lib/components/Icon.svelte' - import { Filter, ChevronUp, ChevronDown, ArrowDown, ArrowUp } from '$lib/icons/mod.ts' - import TagAutoCompleteInput from "$lib/components/TagAutoCompleteInput.svelte"; - import type { BrowseController } from "../controller.ts"; + import { Filter, ArrowDown, ArrowUp } from '$lib/icons/mod.ts' + import TagAutoCompleteInput from '$lib/components/TagAutoCompleteInput.svelte' + import type { BrowsableController } from '$lib/base_controller.ts' import StarInput from '$lib/components/StarInput.svelte' + import type { Snippet } from 'svelte' - let {controller}: {controller: BrowseController} = $props() + interface Props { + controller: BrowsableController + /** + * Renders route-specific