refactor: move duplicates into library settings
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
import { useSidebar } from '$lib/components/ui/sidebar/index.js';
|
import { useSidebar } from '$lib/components/ui/sidebar/index.js';
|
||||||
import { getBookshelfState } from '$lib/state/bookshelf.svelte';
|
import { getBookshelfState } from '$lib/state/bookshelf.svelte';
|
||||||
import { getLibraryState } from '$lib/state/library.svelte';
|
import { getLibraryState } from '$lib/state/library.svelte';
|
||||||
import { CopyCheck, House, LibraryBig, Rows3, ChevronRightIcon } from '@lucide/svelte';
|
import { House, LibraryBig, Rows3, ChevronRightIcon } from '@lucide/svelte';
|
||||||
|
|
||||||
const libraryState = getLibraryState();
|
const libraryState = getLibraryState();
|
||||||
const bookshelfState = getBookshelfState();
|
const bookshelfState = getBookshelfState();
|
||||||
@@ -43,13 +43,6 @@
|
|||||||
path: (id?: number) =>
|
path: (id?: number) =>
|
||||||
resolve('/(root)/(library)/library/[libraryId]/view', { libraryId: String(id ?? '') })
|
resolve('/(root)/(library)/library/[libraryId]/view', { libraryId: String(id ?? '') })
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Duplicates',
|
|
||||||
icon: CopyCheck,
|
|
||||||
routeId: '/(root)/(library)/library/[libraryId]/duplicates',
|
|
||||||
path: (id?: number) =>
|
|
||||||
resolve('/(root)/(library)/library/[libraryId]/duplicates', { libraryId: String(id ?? '') })
|
|
||||||
},
|
|
||||||
{ title: 'Shelves', icon: Rows3, routeId: null, path: () => '#', shelves: [] }
|
{ title: 'Shelves', icon: Rows3, routeId: null, path: () => '#', shelves: [] }
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
import { getLibraryState } from '$lib/state/library.svelte';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
|
// Set in (root)/+layout.svelte, above this group, so listing the libraries in
|
||||||
|
// the nav needs no load function.
|
||||||
|
const libraryState = getLibraryState();
|
||||||
|
|
||||||
// Route ids rather than paths: resolve() is called in the markup so it stays a
|
// Route ids rather than paths: resolve() is called in the markup so it stays a
|
||||||
// direct call the lint rule can see, and the active check compares route ids.
|
// direct call the lint rule can see, and the active check compares route ids.
|
||||||
// A pathname comparison would miss during SSR, where resolve() returns a
|
// A pathname comparison would miss during SSR, where resolve() returns a
|
||||||
@@ -14,12 +19,28 @@
|
|||||||
{ title: 'Libraries', routeId: '/(root)/settings/libraries' },
|
{ title: 'Libraries', routeId: '/(root)/settings/libraries' },
|
||||||
{ title: 'Devices', routeId: '/(root)/settings/devices' }
|
{ title: 'Devices', routeId: '/(root)/settings/devices' }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a nested library item is the one being looked at.
|
||||||
|
*
|
||||||
|
* Both halves are needed: the route id alone is shared by every library, so
|
||||||
|
* matching on it would light all of them up at once.
|
||||||
|
*/
|
||||||
|
function isActiveLibrary(id: number) {
|
||||||
|
return (
|
||||||
|
page.route.id?.startsWith('/(root)/settings/libraries/[libraryId]') === true &&
|
||||||
|
page.params.libraryId === String(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-[calc(100vh-var(--header-height)-2rem)] flex-col">
|
<div class="flex h-[calc(100vh-var(--header-height)-2rem)] flex-col">
|
||||||
<h1 class="font-serif text-xl font-medium tracking-tight">Settings</h1>
|
<h1 class="font-serif text-xl font-medium tracking-tight">Settings</h1>
|
||||||
<div class="mt-6 flex flex-1 gap-8 overflow-hidden">
|
<div class="mt-6 flex flex-1 gap-8 overflow-hidden">
|
||||||
<nav class="flex w-48 shrink-0 flex-col gap-1">
|
<!-- Scrolls on its own now that it holds a list that grows with the number
|
||||||
|
of libraries; the row above it is overflow-hidden, so without this a
|
||||||
|
long list is clipped rather than reachable. -->
|
||||||
|
<nav class="flex w-48 shrink-0 flex-col gap-1 overflow-y-auto">
|
||||||
{#each items as item}
|
{#each items as item}
|
||||||
<a
|
<a
|
||||||
href={resolve(item.routeId)}
|
href={resolve(item.routeId)}
|
||||||
@@ -30,6 +51,26 @@
|
|||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<!-- Libraries is the one item with children: a library's own settings
|
||||||
|
hang off it, so every library and every section it has stay one
|
||||||
|
click from here. -->
|
||||||
|
{#if item.routeId === '/(root)/settings/libraries'}
|
||||||
|
{#each libraryState.libraries as library (library.id)}
|
||||||
|
<a
|
||||||
|
href={resolve('/(root)/settings/libraries/[libraryId]/duplicates', {
|
||||||
|
libraryId: String(library.id)
|
||||||
|
})}
|
||||||
|
class="ml-3 truncate rounded-md border-l px-3 py-1.5 text-sm transition-colors hover:bg-muted {isActiveLibrary(
|
||||||
|
library.id
|
||||||
|
)
|
||||||
|
? 'bg-muted font-medium'
|
||||||
|
: 'text-muted-foreground'}"
|
||||||
|
>
|
||||||
|
{library.name}
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex-1 overflow-auto">{@render children()}</div>
|
<div class="flex-1 overflow-auto">{@render children()}</div>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { getLibraryState } from '$lib/state/library.svelte';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
|
|
||||||
|
// Set in (root)/+layout.svelte, above the settings group, so the libraries are
|
||||||
|
// already here — this pane needs no load function of its own.
|
||||||
|
const libraryState = getLibraryState();
|
||||||
|
|
||||||
|
const library = $derived(
|
||||||
|
libraryState.libraries.find((lib) => String(lib.id) === page.params.libraryId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Duplicates is the only section today. The strip exists so General and a
|
||||||
|
// danger zone have an obvious place to land; neither is built yet, and an
|
||||||
|
// empty tab is worse than no tab.
|
||||||
|
//
|
||||||
|
// Active state is matched on route id, not pathname, for the reason spelled
|
||||||
|
// out in settings/+layout.svelte.
|
||||||
|
const sections = [
|
||||||
|
{ title: 'Duplicates', routeId: '/(root)/settings/libraries/[libraryId]/duplicates' }
|
||||||
|
] as const;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-semibold">{library?.name ?? 'Library'}</h2>
|
||||||
|
<p class="text-sm text-muted-foreground">Settings for this library</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="flex gap-1 border-b">
|
||||||
|
{#each sections as section (section.routeId)}
|
||||||
|
<a
|
||||||
|
href={resolve(section.routeId, { libraryId: page.params.libraryId! })}
|
||||||
|
class="-mb-px border-b-2 px-3 py-2 text-sm font-medium transition-colors hover:text-foreground {page
|
||||||
|
.route.id === section.routeId
|
||||||
|
? 'border-foreground'
|
||||||
|
: 'border-transparent text-muted-foreground'}"
|
||||||
|
>
|
||||||
|
{section.title}
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function load({ params }) {
|
||||||
|
// The library pane is its sections; land on the first one.
|
||||||
|
redirect(
|
||||||
|
303,
|
||||||
|
resolve('/(root)/settings/libraries/[libraryId]/duplicates', { libraryId: params.libraryId })
|
||||||
|
);
|
||||||
|
}
|
||||||
+9
-9
@@ -70,15 +70,15 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto flex w-full max-w-5xl flex-col gap-6 p-4">
|
<!-- No max width and no padding of its own: the settings pane sets the width and
|
||||||
<div>
|
is the thing that scrolls, so a wrapper that centres inside it only makes the
|
||||||
<h2 class="text-lg font-semibold">Possible duplicates</h2>
|
cards narrower than they need to be. -->
|
||||||
<p class="text-sm text-muted-foreground">
|
<div class="flex flex-col gap-6 pb-4">
|
||||||
Books whose metadata matches. A second edition, a translation and a different scan of one book
|
<p class="text-sm text-muted-foreground">
|
||||||
all look like this, so nothing here has been changed or removed — this is a list to read, not
|
Books whose metadata matches. A second edition, a translation and a different scan of one book
|
||||||
a problem to fix.
|
all look like this, so nothing here has been changed or removed — this is a list to read, not a
|
||||||
</p>
|
problem to fix.
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
{#if data.groups.length === 0}
|
{#if data.groups.length === 0}
|
||||||
<Empty.Root>
|
<Empty.Root>
|
||||||
Reference in New Issue
Block a user