Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e10238fe0 | ||
|
|
b6e882add2 | ||
|
|
7a87d979bf |
@@ -106,7 +106,7 @@ export function CreatePlayerModal({
|
|||||||
return (
|
return (
|
||||||
<dialog
|
<dialog
|
||||||
ref={dialogRef}
|
ref={dialogRef}
|
||||||
className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl backdrop:bg-black/50"
|
className="m-auto w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl backdrop:bg-black/50"
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h2 className="font-semibold text-foreground text-lg">
|
<h2 className="font-semibold text-foreground text-lg">
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function PlayerManagement({
|
|||||||
return (
|
return (
|
||||||
<dialog
|
<dialog
|
||||||
ref={dialogRef}
|
ref={dialogRef}
|
||||||
className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl backdrop:bg-black/50"
|
className="m-auto w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl backdrop:bg-black/50"
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h2 className="font-semibold text-foreground text-lg">
|
<h2 className="font-semibold text-foreground text-lg">
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { Database, Trash2 } from "lucide-react";
|
import { Database, Search, Trash2 } from "lucide-react";
|
||||||
import { useCallback, useEffect, useOptimistic, useState } from "react";
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useOptimistic,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import type { CachedSourceInfo } from "../adapters/bestiary-cache.js";
|
import type { CachedSourceInfo } from "../adapters/bestiary-cache.js";
|
||||||
import * as bestiaryCache from "../adapters/bestiary-cache.js";
|
import * as bestiaryCache from "../adapters/bestiary-cache.js";
|
||||||
import { Button } from "./ui/button.js";
|
import { Button } from "./ui/button.js";
|
||||||
|
import { Input } from "./ui/input.js";
|
||||||
|
|
||||||
interface SourceManagerProps {
|
interface SourceManagerProps {
|
||||||
onCacheCleared: () => void;
|
onCacheCleared: () => void;
|
||||||
@@ -12,6 +19,7 @@ export function SourceManager({
|
|||||||
onCacheCleared,
|
onCacheCleared,
|
||||||
}: Readonly<SourceManagerProps>) {
|
}: Readonly<SourceManagerProps>) {
|
||||||
const [sources, setSources] = useState<CachedSourceInfo[]>([]);
|
const [sources, setSources] = useState<CachedSourceInfo[]>([]);
|
||||||
|
const [filter, setFilter] = useState("");
|
||||||
const [optimisticSources, applyOptimistic] = useOptimistic(
|
const [optimisticSources, applyOptimistic] = useOptimistic(
|
||||||
sources,
|
sources,
|
||||||
(
|
(
|
||||||
@@ -46,6 +54,15 @@ export function SourceManager({
|
|||||||
onCacheCleared();
|
onCacheCleared();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filteredSources = useMemo(() => {
|
||||||
|
const term = filter.toLowerCase();
|
||||||
|
return term
|
||||||
|
? optimisticSources.filter((s) =>
|
||||||
|
s.displayName.toLowerCase().includes(term),
|
||||||
|
)
|
||||||
|
: optimisticSources;
|
||||||
|
}, [optimisticSources, filter]);
|
||||||
|
|
||||||
if (optimisticSources.length === 0) {
|
if (optimisticSources.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-2 py-8 text-center">
|
<div className="flex flex-col items-center gap-2 py-8 text-center">
|
||||||
@@ -70,8 +87,17 @@ export function SourceManager({
|
|||||||
Clear All
|
Clear All
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="pointer-events-none absolute top-1/2 left-3 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Filter sources…"
|
||||||
|
value={filter}
|
||||||
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<ul className="flex flex-col gap-1">
|
<ul className="flex flex-col gap-1">
|
||||||
{optimisticSources.map((source) => (
|
{filteredSources.map((source) => (
|
||||||
<li
|
<li
|
||||||
key={source.sourceCode}
|
key={source.sourceCode}
|
||||||
className="flex items-center justify-between rounded-md border border-border px-3 py-2"
|
className="flex items-center justify-between rounded-md border border-border px-3 py-2"
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ const buttonVariants = cva(
|
|||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
outline:
|
outline:
|
||||||
"border border-border bg-transparent hover:bg-hover-neutral-bg hover:text-hover-neutral",
|
"border border-border bg-transparent text-foreground hover:bg-hover-neutral-bg hover:text-hover-neutral",
|
||||||
ghost: "hover:bg-hover-neutral-bg hover:text-hover-neutral",
|
ghost:
|
||||||
|
"text-foreground hover:bg-hover-neutral-bg hover:text-hover-neutral",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-8 px-3 text-xs",
|
default: "h-8 px-3 text-xs",
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ While the bulk import is in progress, the user sees a text counter ("Loading sou
|
|||||||
If the user closes the side panel while a bulk import is still in progress, a persistent toast notification appears at the bottom-center of the screen showing the same progress text and progress bar.
|
If the user closes the side panel while a bulk import is still in progress, a persistent toast notification appears at the bottom-center of the screen showing the same progress text and progress bar.
|
||||||
|
|
||||||
**US-M6 — Manage Cached Sources (P4)**
|
**US-M6 — Manage Cached Sources (P4)**
|
||||||
A DM wants to see which sources are cached, clear a specific source's cache, or clear all cached data. A management UI provides this visibility and control.
|
A DM wants to see which sources are cached, find a specific source, clear a specific source's cache, or clear all cached data. A management UI provides this visibility and control, including a filter input to quickly locate sources by name when many are cached.
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ A DM wants to see which sources are cached, clear a specific source's cache, or
|
|||||||
- **FR-044**: The bulk import MUST run asynchronously and not block the rest of the app.
|
- **FR-044**: The bulk import MUST run asynchronously and not block the rest of the app.
|
||||||
- **FR-045**: The user MUST explicitly provide/confirm the URL before any fetches occur — the app never auto-fetches content.
|
- **FR-045**: The user MUST explicitly provide/confirm the URL before any fetches occur — the app never auto-fetches content.
|
||||||
- **FR-046**: The "Load All" button MUST be disabled when the URL field is empty or while a bulk import is already in progress.
|
- **FR-046**: The "Load All" button MUST be disabled when the URL field is empty or while a bulk import is already in progress.
|
||||||
- **FR-047**: The app MUST provide a management UI showing cached sources with options to clear individual sources or all cached data.
|
- **FR-047**: The app MUST provide a management UI showing cached sources with a filter input for searching by display name and options to clear individual sources or all cached data.
|
||||||
- **FR-048**: The normalization adapter and tag-stripping utility MUST remain the canonical pipeline for all fetched and uploaded data.
|
- **FR-048**: The normalization adapter and tag-stripping utility MUST remain the canonical pipeline for all fetched and uploaded data.
|
||||||
- **FR-049**: The distributed app bundle MUST contain zero copyrighted prose content — only mechanical facts and creature names in the search index.
|
- **FR-049**: The distributed app bundle MUST contain zero copyrighted prose content — only mechanical facts and creature names in the search index.
|
||||||
|
|
||||||
@@ -198,6 +198,7 @@ A DM wants to see which sources are cached, clear a specific source's cache, or
|
|||||||
16. **Given** two sources have been cached, **When** the DM opens the source management UI, **Then** both sources are listed with their display names.
|
16. **Given** two sources have been cached, **When** the DM opens the source management UI, **Then** both sources are listed with their display names.
|
||||||
17. **Given** the source management UI is open, **When** the DM clears a single source, **Then** that source's data is removed; stat blocks for its creatures require re-fetching, while other cached sources remain.
|
17. **Given** the source management UI is open, **When** the DM clears a single source, **Then** that source's data is removed; stat blocks for its creatures require re-fetching, while other cached sources remain.
|
||||||
18. **Given** the source management UI is open, **When** the DM clears all cached data, **Then** all source data is removed and all stat blocks require re-fetching.
|
18. **Given** the source management UI is open, **When** the DM clears all cached data, **Then** all source data is removed and all stat blocks require re-fetching.
|
||||||
|
19. **Given** many sources are cached, **When** the DM types a partial name in the filter input, **Then** only sources whose display name matches (case-insensitive) are shown.
|
||||||
|
|
||||||
### Edge Cases
|
### Edge Cases
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user