diff --git a/DESIGN.md b/DESIGN.md index 71de2923..6991001c 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -152,18 +152,19 @@ Solid Imager is a workbench, not a marketing site. Its visual character is calm, compact, and media-first: the image collection receives the available area while navigation and metadata remain immediately reachable without competing with it. -Three sources define the system, in this order: +This file defines the system in two layers: 1. The tokens in this file are the normative visual values. 2. The guidance below defines why and when those values are used. -3. `/design-lab` is the executable reference for layout and interaction behavior. + +The `/v2/*` routes are the executable reference for layout and interaction +behavior. Use the shared components in `packages/ui` and continue to source primitives from -Solid UI. The design lab may compose those primitives, but must not create a -parallel component library. The lab is a migration reference; it is not evidence -that a mocked concept such as accounts or a dedicated route must exist in the -product. Controls that only demonstrate a visual state in the lab are not an API or -feature contract until the corresponding product behavior is implemented. +Solid UI. Keep the V2 screens on those shared primitives instead of creating a +parallel component library. A visual state or control shown in design references +is not an API or feature contract until the corresponding product behavior is +implemented. The governing principles are: @@ -204,10 +205,9 @@ black-to-transparent gradient is allowed only as a thumbnail text scrim. Do not a gray frame behind a full-size media preview; the preview itself should use the available surface. -The current production theme and design-lab prototype predate this canonical -palette: the theme still contains a zinc/black primary while parts of the lab use -nearby colors inline. During migration, map the tokens above to shared CSS variables -first. Do not copy the lab's one-off hex values into production components. +The legacy production theme predates this canonical palette: it still contains a +zinc/black primary. During migration, map the tokens above to shared CSS variables +first. Do not introduce one-off hex values into production components. ## Typography @@ -221,12 +221,13 @@ weight and spacing, not dramatic size jumps. - Form labels and secondary table data use `label` (12px). - Badges, counts, file extensions, and compact status text use `metadata` (11px). -Prefer sentence case. Use all caps only for very small design-lab taxonomy labels, +Prefer sentence case. Use all caps only for very small taxonomy labels, never for ordinary navigation or actions. Filenames and identifiers may wrap or truncate according to context, but they must expose the full value through the detail view or an accessible name. A production control group uses one active locale; do not mix Japanese and English labels inside the same workflow. Explanatory -copy in the design lab may remain Japanese while product localization is undecided. +copy in design documentation may remain Japanese while product localization is +undecided. ## Layout @@ -423,8 +424,8 @@ browser Back must render the same item or source. ### Do -- Consult this file before changing shared UI, then compare the result in - `/design-lab` at wide and narrow viewports. +- Consult this file before changing shared UI, then compare the implemented + `/v2/*` routes at wide and narrow viewports. - Reuse Solid UI primitives and promote recurring compositions into `packages/ui`. - Give each region one scroll owner and verify direct navigation, reload, and Back. - Use the shared spacing, radius, type, and color values instead of introducing a diff --git a/REPORT.md b/REPORT.md index 4ab62576..45194122 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,6 +1,6 @@ # V2 UI migration report -`/v2/*` は `DESIGN.md` と Design Lab を基準にした移行用フロントエンドです。検索、ソース操作、メディア管理、AI操作、設定、インポート、データ転送は実データへ接続し、未対応機能は推測データを表示せず無効化しています。 +`/v2/*` は `DESIGN.md` を基準にした移行用フロントエンドです。検索、ソース操作、メディア管理、AI操作、設定、インポート、データ転送は実データへ接続し、未対応機能は推測データを表示せず無効化しています。 ## Current implementation status diff --git a/apps/server/src/components/nav.tsx b/apps/server/src/components/nav.tsx index 490ee515..cc4f7b0a 100644 --- a/apps/server/src/components/nav.tsx +++ b/apps/server/src/components/nav.tsx @@ -2,10 +2,5 @@ import { AppNav } from "@solid-imager/ui/layouts/app-nav"; import { PendingDownloadsIndicator } from "./imports/pending-downloads-indicator"; export default function Nav() { - return ( - } - showDesignLab - /> - ); + return } />; } diff --git a/apps/server/src/routes/__root.tsx b/apps/server/src/routes/__root.tsx index 7bccdb48..f4b1d4f8 100644 --- a/apps/server/src/routes/__root.tsx +++ b/apps/server/src/routes/__root.tsx @@ -57,14 +57,7 @@ function RootComponent() { - } - when={ - location().pathname !== "/design-lab" && - !location().pathname.startsWith("/design-lab/") && - !isV2Route() - } - > + } when={!isV2Route()}> } statusIndicator={} diff --git a/apps/server/src/routes/design-lab.tsx b/apps/server/src/routes/design-lab.tsx deleted file mode 100644 index dcc4f67d..00000000 --- a/apps/server/src/routes/design-lab.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { DesignConceptScreen } from "@solid-imager/ui/screens/design-concept-screen"; -import { createFileRoute } from "@tanstack/solid-router"; - -export const Route = createFileRoute("/design-lab")({ - ssr: false, - pendingComponent: () => null, - component: DesignLabRoute, -}); - -function DesignLabRoute() { - return ; -} diff --git a/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md b/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md index 73bd2bc0..d3f20185 100644 --- a/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md +++ b/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md @@ -373,8 +373,6 @@ graph LR N368[npm:@solid-imager/ui/screens/sources-screen] N369[npm:@solid-imager/ui/source-card] N370[npm:@solid-imager/ui/source-delete-modal] - N371[routes/design-lab.tsx] - N372[npm:@solid-imager/ui/screens/design-concept-screen] N0 --> N1 N0 --> N2 N0 --> N3 @@ -887,6 +885,4 @@ graph LR N364 --> N282 N364 --> N341 N364 --> N303 - N371 --> N372 - N371 --> N282 ``` diff --git a/docs/architecture/indexion-dependency-graph-04-server-application-components.md b/docs/architecture/indexion-dependency-graph-04-server-application-components.md index e7768066..e94922a9 100644 --- a/docs/architecture/indexion-dependency-graph-04-server-application-components.md +++ b/docs/architecture/indexion-dependency-graph-04-server-application-components.md @@ -80,7 +80,6 @@ graph LR N75[apps/server/src/routes/__root.tsx] N76[apps/server/src/routes/search.tsx] N77[apps/server/src/routes/manager.tsx] - N78[apps/server/src/routes/design-lab.tsx] N79[apps/server/src/routes/config.tsx] N80[apps/server/src/routes/about.tsx] N81[apps/server/src/routes/$.tsx] @@ -198,7 +197,6 @@ graph LR N74 --> N75 N74 --> N76 N74 --> N77 - N74 --> N78 N74 --> N79 N74 --> N80 N74 --> N81 diff --git a/docs/architecture/indexion-dependency-graph-05-ui.md b/docs/architecture/indexion-dependency-graph-05-ui.md index 73e579b5..5a681adb 100644 --- a/docs/architecture/indexion-dependency-graph-05-ui.md +++ b/docs/architecture/indexion-dependency-graph-05-ui.md @@ -101,7 +101,6 @@ graph LR N96[screens/source-media-screen.tsx] N97[screens/config-state-screen.types.ts] N98[query-state.ts] - N99[screens/design-concept-screen.tsx] N100[npm:lucide-solid/icons/arrow-down-up] N101[npm:lucide-solid/icons/arrow-left] N102[npm:lucide-solid/icons/ban] @@ -367,41 +366,6 @@ graph LR N96 --> N9 N97 --> N74 N97 --> N98 - N99 --> N100 - N99 --> N101 - N99 --> N102 - N99 --> N103 - N99 --> N104 - N99 --> N105 - N99 --> N106 - N99 --> N107 - N99 --> N108 - N99 --> N109 - N99 --> N110 - N99 --> N111 - N99 --> N112 - N99 --> N113 - N99 --> N114 - N99 --> N115 - N99 --> N116 - N99 --> N117 - N99 --> N118 - N99 --> N119 - N99 --> N120 - N99 --> N121 - N99 --> N122 - N99 --> N123 - N99 --> N124 - N99 --> N125 - N99 --> N126 - N99 --> N127 - N99 --> N128 - N99 --> N129 - N99 --> N130 - N99 --> N131 - N99 --> N132 - N99 --> N133 - N99 --> N134 N135 --> N2 N135 --> N90 N135 --> N93 diff --git a/docs/architecture/indexion-dependency-graph-full.md b/docs/architecture/indexion-dependency-graph-full.md index 8198e140..dfa26994 100644 --- a/docs/architecture/indexion-dependency-graph-full.md +++ b/docs/architecture/indexion-dependency-graph-full.md @@ -724,8 +724,6 @@ graph LR N719[npm:~/routes/v2/media-context] N720[apps/server/src/routes/sources/$mediaSourceId/index.tsx] N721[apps/server/src/routes/sources/index.tsx] - N722[apps/server/src/routes/design-lab.tsx] - N723[npm:@solid-imager/ui/screens/design-concept-screen] N724[apps/server/src/components/imports/pending-downloads-indicator.tsx] N725[apps/server/src/components/imports/pending-downloads-indicator-data.ts] N726[apps/server/src/components/imports/v2-pending-downloads-indicator.tsx] @@ -861,7 +859,6 @@ graph LR N856[packages/ui/src/screens/source-media-screen.tsx] N857[packages/ui/src/screens/config-state-screen.types.ts] N858[packages/ui/src/query-state.ts] - N859[packages/ui/src/screens/design-concept-screen.tsx] N860[npm:lucide-solid/icons/arrow-down-up] N861[npm:lucide-solid/icons/arrow-left] N862[npm:lucide-solid/icons/ban] @@ -2251,8 +2248,6 @@ graph LR N721 --> N666 N721 --> N118 N721 --> N678 - N722 --> N723 - N722 --> N666 N724 --> N49 N724 --> N725 N725 --> N48 @@ -2346,7 +2341,6 @@ graph LR N771 --> N667 N771 --> N710 N771 --> N708 - N771 --> N722 N771 --> N677 N771 --> N668 N771 --> N665 @@ -2520,41 +2514,6 @@ graph LR N856 --> N780 N857 --> N426 N857 --> N858 - N859 --> N860 - N859 --> N861 - N859 --> N862 - N859 --> N863 - N859 --> N864 - N859 --> N865 - N859 --> N866 - N859 --> N867 - N859 --> N868 - N859 --> N869 - N859 --> N870 - N859 --> N871 - N859 --> N872 - N859 --> N873 - N859 --> N874 - N859 --> N875 - N859 --> N876 - N859 --> N877 - N859 --> N878 - N859 --> N879 - N859 --> N880 - N859 --> N881 - N859 --> N882 - N859 --> N883 - N859 --> N884 - N859 --> N885 - N859 --> N886 - N859 --> N887 - N859 --> N888 - N859 --> N889 - N859 --> N890 - N859 --> N891 - N859 --> N892 - N859 --> N893 - N859 --> N894 N895 --> N774 N895 --> N850 N895 --> N853 diff --git a/docs/architecture/indexion-dependency-graph.md b/docs/architecture/indexion-dependency-graph.md index d5d00e66..47342d1b 100644 --- a/docs/architecture/indexion-dependency-graph.md +++ b/docs/architecture/indexion-dependency-graph.md @@ -1,14 +1,14 @@ # solid-imager source dependencies (indexion) -The complete indexion graph contains 1,810 edges, while the default Mermaid renderer allows 500 edges. Use the split diagrams below for VS Code preview. +The complete indexion graph contains 1,772 edges, while the default Mermaid renderer allows 500 edges. Use the split diagrams below for VS Code preview. | Part | Scope | Edges | | --- | --- | ---: | | [01 apps and core](indexion-dependency-graph-01-apps-and-core.md) | CLI, Tauri, xtracter, and shared core packages | 431 | | [02 server tests](indexion-dependency-graph-02-server-tests.md) | `apps/server/src/tests` | 333 | -| [03 server infrastructure and routes](indexion-dependency-graph-03-server-infrastructure-routes.md) | `apps/server/src/infrastructure` and `routes` | 514 | -| [04 server components and root modules](indexion-dependency-graph-04-server-application-components.md) | server components, config, root modules, and route dependencies | 117 | -| [05 UI](indexion-dependency-graph-05-ui.md) | `packages/ui/src` | 415 | +| [03 server infrastructure and routes](indexion-dependency-graph-03-server-infrastructure-routes.md) | `apps/server/src/infrastructure` and `routes` | 512 | +| [04 server components and root modules](indexion-dependency-graph-04-server-application-components.md) | server components, config, root modules, and route dependencies | 116 | +| [05 UI](indexion-dependency-graph-05-ui.md) | `packages/ui/src` | 380 | The un-split graph is preserved in [indexion-dependency-graph-full.md](indexion-dependency-graph-full.md) for tools that allow a higher `maxEdges` value. diff --git a/packages/ui/src/layouts/app-nav.tsx b/packages/ui/src/layouts/app-nav.tsx index 4b14fbaa..1656369c 100644 --- a/packages/ui/src/layouts/app-nav.tsx +++ b/packages/ui/src/layouts/app-nav.tsx @@ -14,7 +14,6 @@ import { isServer } from "solid-js/web"; type AppNavProps = { pendingDownloadsIndicator?: JSX.Element; - showDesignLab?: boolean; }; const navigationItems = [ @@ -120,17 +119,6 @@ export function AppNav(props: AppNavProps) { ))} - -
  • - - Design Lab - -
  • -
  • ))} - -
  • - - Design Lab - -
  • -
  • ; - -type FilterToken = { - id: string; - key: keyof DesignFilterState; - prefix: string; - value: string; - destructive?: boolean; -}; - -type DesignLabView = - | "detail" - | "interactions" - | "jobs" - | "layouts" - | "library" - | "manager" - | "overlays" - | "settings"; - -type MockSource = { - count: number; - id: string; - name: string; - status: "online" | "syncing"; - type: string; -}; - -type MockJob = { - completed: number; - error?: string; - id: string; - name: string; - source: string; - started: string; - status: "completed" | "failed" | "queued" | "running"; - total: number; - type: string; -}; - -type ManagerArea = - | "characters" - | "duplicates" - | "ips" - | "projects" - | "tagging" - | "transfer" - | "vectors"; - -type MockManagerEntity = { - description: string; - id: string; - mediaCount: number; - modified: string; - name: string; - relations: string[]; -}; - -type SettingsCategory = - | "ai" - | "downloads" - | "jobs" - | "logging" - | "media" - | "storage"; - -type SettingsDraft = { - aiTimeout: string; - aiUrl: string; - generalConcurrency: string; - aiConcurrency: string; - imageExtensions: string; - logLevel: string; - requestInterval: string; - thumbnailDirectory: string; - thumbnailQuality: string; - thumbnailSize: string; - videoExtensions: string; -}; - -type ScreenLayoutKind = "collection" | "detail" | "management" | "settings"; - -type DrawerDraft = { - description: string; - tags: string; - characters: string; - ips: string; - projects: string; -}; - -type DrawerFieldKey = Exclude; - -const EMPTY_DRAWER_DRAFT: DrawerDraft = { - description: "", - tags: "", - characters: "", - ips: "", - projects: "", -}; - -const MOCK_SOURCES: MockSource[] = [ - { - count: 864, - id: "local-assets", - name: "Local assets", - status: "online", - type: "Local", - }, - { - count: 312, - id: "production-s3", - name: "Production S3", - status: "online", - type: "S3", - }, - { - count: 72, - id: "reference-sftp", - name: "References", - status: "syncing", - type: "SFTP", - }, -]; - -const MOCK_JOBS: MockJob[] = [ - { - completed: 184, - id: "job-batch-tags", - name: "Extract tags", - source: "Local assets", - started: "2 min ago", - status: "running", - total: 240, - type: "AI batch", - }, - { - completed: 38, - id: "job-sync-s3", - name: "Sync source", - source: "Production S3", - started: "Just now", - status: "running", - total: 112, - type: "Source sync", - }, - { - completed: 0, - id: "job-vectors", - name: "Extract CCIP vectors", - source: "References", - started: "Queued 1 min ago", - status: "queued", - total: 72, - type: "AI batch", - }, - { - completed: 61, - error: "AI service stopped responding after 30 seconds.", - id: "job-crop", - name: "Detect characters", - source: "Local assets", - started: "12 min ago", - status: "failed", - total: 96, - type: "AI batch", - }, - { - completed: 312, - id: "job-import", - name: "Import metadata", - source: "Production S3", - started: "Today, 10:42", - status: "completed", - total: 312, - type: "Import", - }, -]; - -const MANAGER_AREAS: Array<{ - description: string; - group: "Entities" | "Tools"; - label: string; - value: ManagerArea; -}> = [ - { - description: "制作単位と用途", - group: "Entities", - label: "Projects", - value: "projects", - }, - { - description: "作品・シリーズ", - group: "Entities", - label: "IPs", - value: "ips", - }, - { - description: "人物と所属IP", - group: "Entities", - label: "Characters", - value: "characters", - }, - { - description: "AIタグを一括付与", - group: "Tools", - label: "Batch tagging", - value: "tagging", - }, - { - description: "CCIP特徴量を生成", - group: "Tools", - label: "Vector extraction", - value: "vectors", - }, - { - description: "重複候補を整理", - group: "Tools", - label: "Duplicates", - value: "duplicates", - }, - { - description: "書き出しと復元", - group: "Tools", - label: "Data transfer", - value: "transfer", - }, -]; - -const MOCK_MANAGER_ENTITIES: Record< - "characters" | "ips" | "projects", - MockManagerEntity[] -> = { - projects: [ - { - description: "夏季キャンペーン向けのキービジュアルと資料", - id: "project-summer", - mediaCount: 184, - modified: "2 min ago", - name: "Summer Visuals", - relations: ["学園アイドルマスター", "花海咲季"], - }, - { - description: "商品ページ用の切り抜き・物撮り素材", - id: "project-catalog", - mediaCount: 96, - modified: "Yesterday", - name: "Product Catalog", - relations: ["Production S3"], - }, - { - description: "背景と構図のリファレンス集", - id: "project-reference", - mediaCount: 312, - modified: "Jul 28", - name: "Reference Board", - relations: ["References", "scenery"], - }, - { - description: "公開前のキャラクター案と差分", - id: "project-character", - mediaCount: 72, - modified: "Jul 24", - name: "Character Studies", - relations: ["Local assets"], - }, - ], - ips: [ - { - description: "学園を舞台にしたアイドルプロジェクト", - id: "ip-gakumas", - mediaCount: 428, - modified: "Today", - name: "学園アイドルマスター", - relations: ["花海咲季", "月村手毬", "藤田ことね"], - }, - { - description: "アイドルマスターシリーズ共通分類", - id: "ip-idolmaster", - mediaCount: 684, - modified: "Jul 26", - name: "THE IDOLM@STER", - relations: ["5 Characters"], - }, - ], - characters: [ - { - description: "初星学園 普通科1年", - id: "character-saki", - mediaCount: 108, - modified: "Today", - name: "花海咲季", - relations: ["学園アイドルマスター"], - }, - { - description: "初星学園 普通科1年", - id: "character-temari", - mediaCount: 92, - modified: "Yesterday", - name: "月村手毬", - relations: ["学園アイドルマスター"], - }, - { - description: "初星学園 普通科1年", - id: "character-kotone", - mediaCount: 86, - modified: "Jul 27", - name: "藤田ことね", - relations: ["学園アイドルマスター"], - }, - ], -}; - -const INITIAL_SETTINGS_DRAFT: SettingsDraft = { - aiConcurrency: "2", - aiTimeout: "30000", - aiUrl: "http://ai-worker.local:3000", - generalConcurrency: "4", - imageExtensions: ".jpg, .jpeg, .png, .webp, .gif", - logLevel: "Info", - requestInterval: "1200", - thumbnailDirectory: "./data/thumbnails", - thumbnailQuality: "82", - thumbnailSize: "480", - videoExtensions: ".mp4, .webm, .mov", -}; - -const DRAWER_FIELDS: Array<{ key: DrawerFieldKey; label: string }> = [ - { key: "tags", label: "Tags" }, - { key: "characters", label: "Characters" }, - { key: "ips", label: "IPs" }, - { key: "projects", label: "Projects" }, -]; - -const FILTER_FIELDS: Array<{ - key: FilterArrayKey; - label: string; - placeholder: string; - prefix: string; - primary: boolean; -}> = [ - { - key: "selectedTags", - label: "タグ(すべて含む)", - placeholder: "scenery, summer", - prefix: "tag", - primary: true, - }, - { - key: "excludeTags", - label: "除外タグ", - placeholder: "R18, spoiler", - prefix: "-tag", - primary: true, - }, - { - key: "selectedCharacters", - label: "キャラクター", - placeholder: "花海咲季", - prefix: "character", - primary: true, - }, - { - key: "selectedIps", - label: "IP", - placeholder: "学園アイドルマスター", - prefix: "ip", - primary: false, - }, - { - key: "selectedAuthors", - label: "作者", - placeholder: "作者名・ID", - prefix: "author", - primary: false, - }, - { - key: "selectedProjects", - label: "プロジェクト", - placeholder: "Summer Visuals", - prefix: "project", - primary: false, - }, -]; - -const MOCK_MEDIA: MockMedia[] = [ - { - id: "field", - name: "scenery_field_summer.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-field/960/720", - resolution: "3840 × 2160", - size: "4.2 MB", - tags: ["scenery", "summer", "sky", "field"], - }, - { - id: "night", - name: "city_after_rain.png", - extension: "PNG", - src: "https://picsum.photos/seed/solid-night/720/720", - resolution: "2048 × 2048", - size: "3.8 MB", - tags: ["city", "night", "rain"], - }, - { - id: "coast", - name: "coastal_drive.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-coast/960/720", - resolution: "4096 × 3072", - size: "5.1 MB", - tags: ["coast", "travel", "vehicle"], - }, - { - id: "objects", - name: "soft_shapes.png", - extension: "PNG", - src: "https://picsum.photos/seed/solid-objects/720/720", - resolution: "2048 × 2048", - size: "2.7 MB", - tags: ["objects", "pastel", "reference"], - }, - { - id: "mountain", - name: "alpine_lake.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-mountain/960/720", - resolution: "5120 × 2880", - size: "6.4 MB", - tags: ["mountain", "lake", "nature"], - }, - { - id: "portrait", - name: "portrait_study.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-portrait/720/720", - resolution: "2400 × 2400", - size: "3.2 MB", - tags: ["portrait", "study", "warm"], - }, - { - id: "machine", - name: "machine_companion.png", - extension: "PNG", - src: "https://picsum.photos/seed/solid-machine/720/720", - resolution: "2048 × 2048", - size: "3.0 MB", - tags: ["machine", "character", "concept"], - }, - { - id: "interior", - name: "quiet_interior.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-interior/960/720", - resolution: "4200 × 2800", - size: "5.8 MB", - tags: ["interior", "light", "architecture"], - }, - { - id: "food", - name: "late_lunch.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-food/720/720", - resolution: "3000 × 3000", - size: "4.0 MB", - tags: ["food", "reference", "warm"], - }, - { - id: "illustration", - name: "character_profile.png", - extension: "PNG", - src: "https://picsum.photos/seed/solid-illustration/720/720", - resolution: "2048 × 2048", - size: "2.9 MB", - tags: ["character", "profile", "illustration"], - }, - { - id: "space", - name: "deep_space.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-space/960/720", - resolution: "3840 × 2160", - size: "4.7 MB", - tags: ["space", "dark", "concept"], - }, - { - id: "chair", - name: "chair_catalog.png", - extension: "PNG", - src: "https://picsum.photos/seed/solid-chair/720/720", - resolution: "1800 × 1800", - size: "2.1 MB", - tags: ["product", "chair", "catalog"], - }, - { - id: "texture", - name: "painted_texture.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-texture/720/720", - resolution: "3200 × 3200", - size: "6.1 MB", - tags: ["texture", "paint", "reference"], - }, - { - id: "bicycle", - name: "green_bicycle.jpg", - extension: "JPG", - src: "https://picsum.photos/seed/solid-bicycle/960/720", - resolution: "3600 × 2400", - size: "4.4 MB", - tags: ["bicycle", "green", "object"], - }, - { - id: "architecture", - name: "white_facade.png", - extension: "PNG", - src: "https://picsum.photos/seed/solid-building/720/720", - resolution: "2160 × 2160", - size: "3.6 MB", - tags: ["architecture", "minimal", "white"], - }, -]; - -function SidebarItem(props: { - active?: boolean; - badge?: string; - children: JSX.Element; - expanded: boolean; - label: string; - onClick?: () => void; -}) { - return ( - - ); -} - -function DesignSourcesNavigation(props: { - expanded: boolean; - onExpandedChange: (expanded: boolean) => void; - onSelect: (sourceId: string) => void; - selectedId: string; -}) { - const [open, setOpen] = createSignal(true); - - return ( - - { - if (!props.expanded) props.onExpandedChange(true); - }} - > - - - - ); -} - -function DesignSidebar(props: { - activeView: DesignLabView; - expanded: boolean; - inboxCount: number; - onExpandedChange: (expanded: boolean) => void; - onInboxOpen: () => void; - onSourceChange: (sourceId: string) => void; - onViewChange: (view: DesignLabView) => void; - selectedSourceId: string; -}) { - return ( - - ); -} - -function MediaTile(props: { - media: MockMedia; - onSelect: () => void; - selected: boolean; -}) { - return ( - - ); -} - -type SearchMode = "simple" | "pro"; - -function parseFilterValues(value: string) { - return [ - ...new Set( - value - .split(",") - .map((part) => part.trim()) - .filter(Boolean), - ), - ]; -} - -function DesignFilterField(props: { - field: (typeof FILTER_FIELDS)[number]; - state: DesignFilterState; - onChange: (key: FilterArrayKey, values: string[]) => void; -}) { - const inputId = () => `design-filter-${props.field.key}`; - const [draft, setDraft] = createSignal( - props.state[props.field.key].join(", "), - ); - let inputElement: HTMLInputElement | undefined; - - createEffect(() => { - const canonicalValue = props.state[props.field.key].join(", "); - if (!inputElement?.matches(":focus")) setDraft(canonicalValue); - }); - - return ( -
    - - setDraft(props.state[props.field.key].join(", "))} - onInput={(event) => { - const value = event.currentTarget.value; - setDraft(value); - props.onChange(props.field.key, parseFilterValues(value)); - }} - placeholder={props.field.placeholder} - ref={(element) => { - inputElement = element; - }} - value={draft()} - /> -
    - ); -} - -function DesignFilterPopover(props: { - filterCount: number; - mode: SearchMode; - onClear: () => void; - onModeChange: (mode: SearchMode) => void; - onValuesChange: (key: FilterArrayKey, values: string[]) => void; - state: DesignFilterState; -}) { - return ( - - - - -
    -
    -

    検索フィルター

    -

    - 検索バーと同じ条件を編集します -

    -
    - -
    - -
    -
    - - 検索モード - -
    - - {([value, label]) => ( - - )} - -
    -
    - - - {props.mode === "pro" - ? "詳細検索は条件ビルダーを別パネルで開き、この一覧レイアウトは維持します。" - : "検索条件をここで確認し、一覧の並べ替えは維持します。"} -
    - } - when={props.mode === "simple"} - > -
    -
    -

    よく使う条件

    -
    - field.primary)}> - {(field) => ( - - )} - -
    -
    - -
    -

    - 追加条件 -

    -
    - !field.primary)}> - {(field) => ( - - )} - -
    -
    -
    - - -
    -
    - ); -} - -function DesignToolbar(props: { - draft: string; - filterTokens: FilterToken[]; - filters: DesignFilterState; - mode: SearchMode; - onClearFilters: () => void; - onDraftChange: (value: string) => void; - onDraftSubmit: () => void; - onModeChange: (mode: SearchMode) => void; - onRemoveToken: (token: FilterToken) => void; - onValuesChange: (key: FilterArrayKey, values: string[]) => void; - sourceName: string; -}) { - return ( -
    -
    - Library - / - - {props.sourceName} - -
    -
    -
    { - event.preventDefault(); - props.onDraftSubmit(); - }} - > - -
    -
    - ); -} - -function InspectorAction(props: { - children: JSX.Element; - destructive?: boolean; - icon: JSX.Element; -}) { - return ( - - ); -} - -function DesignInspector(props: { - media: MockMedia; - onOpenDetail: () => void; -}) { - return ( - - ); -} - -function DetailMetadataSection(props: { - children: JSX.Element; - onAdd?: () => void; - title: string; -}) { - return ( -
    -
    -

    {props.title}

    - - - -
    - {props.children} -
    - ); -} - -function DesignMediaDetailScreen(props: { - media: MockMedia; - onBack: () => void; - onNext: () => void; - onPrevious: () => void; -}) { - return ( -
    -
    -
    - -
    -

    - {props.media.name} -

    -

    - File System / Assets -

    -
    - -
    - - -
    - - -
    - -
    - - -
    -
    - -
    -
    - {props.media.name} -
    - {props.media.name} のプレビュー -
    -
    - - -
    -
    - ); -} - -const MOCK_IMPORT_POSTS = [ - { - author: "@visual_archive", - id: "import-field", - media: MOCK_MEDIA[0], - source: "X / Twitter", - }, - { - author: "@night_reference", - id: "import-night", - media: MOCK_MEDIA[1], - source: "X / Twitter", - }, - { - author: "coastal-sketch", - id: "import-coast", - media: MOCK_MEDIA[2], - source: "Pixiv", - }, - { - author: "product-notes", - id: "import-objects", - media: MOCK_MEDIA[3], - source: "Web page", - }, -]; - -function ImportInboxDialog(props: { - onOpenChange: (open: boolean) => void; - open: boolean; -}) { - const allIds = () => new Set(MOCK_IMPORT_POSTS.map((post) => post.id)); - const [selectedIds, setSelectedIds] = createSignal(allIds()); - - createEffect(() => { - if (props.open) setSelectedIds(allIds()); - }); - - const togglePost = (id: string) => { - const next = new Set(selectedIds()); - if (next.has(id)) next.delete(id); - else next.add(id); - setSelectedIds(next); - }; - const allSelected = () => selectedIds().size === MOCK_IMPORT_POSTS.length; - - return ( - - - - Import inbox - - ブラウザ拡張から届いた投稿を確認し、取り込むメディアを選択します。 - - - -
    -
    - Import to - -
    -
    -

    - {selectedIds().size} of {MOCK_IMPORT_POSTS.length} selected -

    - -
    -
    - -
    -
      - - {(post) => ( -
    • - -
    • - )} -
      -
    -
    - - - - - - -
    -
    - ); -} - -function PatternCard(props: { - children: JSX.Element; - description: string; - label: string; - title: string; -}) { - return ( -
    -
    - - {props.label} - -

    {props.title}

    -

    {props.description}

    -
    -
    {props.children}
    -
    - ); -} - -const SCREEN_LAYOUT_OPTIONS: Array<{ - description: string; - label: string; - value: ScreenLayoutKind; -}> = [ - { - description: "検索・選択・比較", - label: "Collection", - value: "collection", - }, - { description: "単体を大きく見る", label: "Detail", value: "detail" }, - { - description: "行単位で管理する", - label: "Management", - value: "management", - }, - { description: "設定を順に編集する", label: "Settings", value: "settings" }, -]; - -const PREVIEW_ITEMS = Array.from({ length: 12 }, (_, index) => index); -const PREVIEW_ROWS = Array.from({ length: 6 }, (_, index) => index); - -function CollectionLayoutPreview() { - return ( -