From 8334e4388685cbf2ffb44e2b6ee102a88f020ea5 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Wed, 17 Jun 2026 21:37:21 +0700 Subject: [PATCH] Moodboard: webapp parity, lightbox redesign, editable notes & selection polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align the shared @storyteller/ui-moodboard surface with the webapp's design language across both hosts (Tauri desktop + webapp), plus several UX fixes. Theming & layout - Use `var(--st-bg, hsl(0 0% 9%))` for the workspace/grid background so desktop follows the active theme while the webapp falls back to its --background token (replaces the desktop-only bg-ui-background class). - Fix desktop overflow: the fixed TopBar is 56px, so the moodboard tab now uses h-screen + pt-[56px] + overflow-hidden, killing the stray vertical/horizontal scrollbars on an empty board. Grid view root clips too. Empty state & toolbars - Empty-state Upload / From-library CTAs now use the shared Button, matching the webapp create pages (h-12, pill, semibold; primary + secondary). - Restyle SelectionBar to match the library multiselect bar (solid pill, filled bg-ui-controls buttons, red Delete, round clear); align the "Move to" menu/items/Kbd to the same idiom. - Restyle SmartSearchBar (bg-ui-controls, rounded-xl) and tighten ToolbarShell padding. - Add a tooltip to the toolbar's Add Color button to match the sibling buttons. Notes - Adding a note creates an empty, immediately-editable note (autofocusing inline textarea; commit on blur/Enter, Shift+Enter newline, Esc reverts); double-click re-enters edit mode — no more stuck "New note". - Pad the note's top so the selection check no longer overlaps the first line. Selection - Clicks accumulate: clicking an item adds it to the selection (clicking again removes it), so multi-select builds up without a modifier; background click clears. "Use as reference" only shows when the selection has a reference-capable image. Item inspector - Redesign ItemInspector to mirror the shared LightboxModal: a bounded, centered, draggable/resizable Modal with media on a dark plate, hover nav arrows, and a fixed info/actions column using shared Buttons. Webapp homepage - Add a Moodboard card next to Edit Video on the webapp home page. --- .../artcraft-webapp/src/pages/home/home.tsx | 11 ++ .../apps/artcraft/app/src/pages/MainApp.tsx | 95 +++++++-- .../moodboard/src/lib/MoodboardWorkspace.tsx | 14 +- .../src/lib/boards/BoardLibraryStore.ts | 15 +- .../src/lib/boards/useBoardItemEntry.tsx | 9 +- .../moodboard/src/lib/grid/BoardCard.tsx | 99 +++++++++- .../src/lib/grid/BoardEmptyState.tsx | 31 ++- .../moodboard/src/lib/grid/BoardGrid.tsx | 10 + .../src/lib/grid/BoardGridToolbar.tsx | 66 ++++++- .../moodboard/src/lib/grid/BoardGridView.tsx | 42 +++- .../src/lib/grid/ColorPickerPopover.tsx | 44 +++-- .../moodboard/src/lib/grid/ItemInspector.tsx | 180 ++++++++---------- .../moodboard/src/lib/grid/SelectionBar.tsx | 59 +++--- .../moodboard/src/lib/grid/SmartSearchBar.tsx | 7 +- .../moodboard/src/lib/ui/Toolbar.tsx | 2 +- 15 files changed, 477 insertions(+), 207 deletions(-) diff --git a/frontend/apps/artcraft-webapp/src/pages/home/home.tsx b/frontend/apps/artcraft-webapp/src/pages/home/home.tsx index f920e71642..7b6f453137 100644 --- a/frontend/apps/artcraft-webapp/src/pages/home/home.tsx +++ b/frontend/apps/artcraft-webapp/src/pages/home/home.tsx @@ -6,6 +6,7 @@ import { faWandMagicSparkles, faCube, faFilm, + faObjectGroup, faArrowRight, } from "@fortawesome/pro-solid-svg-icons"; import type { IconDefinition } from "@fortawesome/fontawesome-svg-core"; @@ -69,6 +70,16 @@ const APPS: AppCard[] = [ iconColor: "text-rose-300", badge: "BETA", }, + { + label: "Moodboard", + description: "Collect references and ideas to steer a generation.", + href: "/moodboard", + icon: faObjectGroup, + accent: "from-indigo-500/20 to-indigo-500/0", + iconBg: "bg-indigo-500/25 border-indigo-400/30", + iconColor: "text-indigo-300", + badge: "BETA", + }, ]; export function Home() { diff --git a/frontend/apps/artcraft/app/src/pages/MainApp.tsx b/frontend/apps/artcraft/app/src/pages/MainApp.tsx index 5f7e824aa5..79d810f6fc 100644 --- a/frontend/apps/artcraft/app/src/pages/MainApp.tsx +++ b/frontend/apps/artcraft/app/src/pages/MainApp.tsx @@ -46,10 +46,7 @@ import { } from "@storyteller/tauri-events"; import { SoundManager } from "@storyteller/soundboard"; import { useStoryboardPageEnabled } from "@storyteller/ui-settings-modal"; -import { - DomLevels, - usePageSceneStore, -} from "@storyteller/ui-pagescene"; +import { DomLevels, usePageSceneStore } from "@storyteller/ui-pagescene"; import { useActiveJobs } from "~/hooks/useActiveJobs"; import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia"; @@ -224,9 +221,17 @@ const TabBody = ({ sceneToken }: { sceneToken?: string }) => { case "3D": return ; case "APPS": - return
; + return ( +
+ +
+ ); case "2D": - return
; + return ( +
+ +
+ ); case "IMAGE": return (
@@ -237,29 +242,83 @@ const TabBody = ({ sceneToken }: { sceneToken?: string }) => {
); case "VIDEO": - return
; + return ( +
+ +
+ ); case "VIDEO_FRAME_EXTRACTOR": - return
; + return ( +
+ +
+ ); case "VIDEO_WATERMARK_REMOVAL": - return
; + return ( +
+ +
+ ); case "IMAGE_WATERMARK_REMOVAL": - return
; + return ( +
+ +
+ ); case "IMAGE_TO_3D_OBJECT": - return
; + return ( +
+ +
+ ); case "IMAGE_TO_3D_WORLD": - return
; + return ( +
+ +
+ ); case "REMOVE_BACKGROUND": - return
; + return ( +
+ +
+ ); case "ANGLES": - return
; + return ( +
+ +
+ ); case "STORYBOARD": - return storyboardPageEnabled ?
: null; + return storyboardPageEnabled ? ( +
+ +
+ ) : null; case "BACKGROUND_CHANGE": - return
; + return ( +
+ +
+ ); case "VIDEO_EDITOR": - return
; + return ( +
+ +
+ ); case "MOODBOARD": - return
; + // The TopBar is fixed and 56px tall, so the board sits below it and fills + // the rest of the viewport. h-screen + pt-[56px] (border-box) makes the + // board area exactly viewport-minus-topbar, and overflow-hidden keeps + // sub-pixel 100vh rounding from spawning page scrollbars (which w-screen + // ancestors would turn into a horizontal scrollbar via the Windows + // scrollbar gutter). + return ( +
+ +
+ ); default: return null; } diff --git a/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx b/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx index 6797a1f3aa..d3ac63c8dc 100644 --- a/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx +++ b/frontend/libs/components/moodboard/src/lib/MoodboardWorkspace.tsx @@ -42,7 +42,11 @@ export const MoodboardWorkspace = ({ adapter, topBarEndSlot }: Props) => { return (
{viewMode === "grid" ? ( @@ -77,7 +81,7 @@ const ViewSwitch = ({ mode: ViewMode; onChange: (m: ViewMode) => void; }) => ( -
+
{OPTIONS.map((opt) => { const active = opt.mode === mode; return ( @@ -86,7 +90,7 @@ const ViewSwitch = ({ type="button" onClick={() => onChange(opt.mode)} className={[ - "flex items-center gap-2 rounded-full px-3.5 py-1.5 text-sm font-medium", + "flex items-center gap-2 rounded-xl px-3.5 py-1.5 text-sm font-medium", "transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", active ? "bg-base-fg/15 text-base-fg" @@ -112,13 +116,13 @@ const TopRightCluster = ({ endSlot?: ReactNode; }) => (
-
+
); -const CardContent = ({ item }: { item: BoardItem }) => { +const CardContent = ({ + item, + editing, + onCommitText, +}: { + item: BoardItem; + editing?: boolean; + onCommitText?: (id: string, text: string) => void; +}) => { switch (item.kind) { case "image": return ( @@ -243,9 +268,22 @@ const CardContent = ({ item }: { item: BoardItem }) => {
); case "text": - return ( -
-

+ return editing && onCommitText ? ( + onCommitText(item.id, text)} + /> + ) : ( + // Extra top padding clears the selection check (top-left) so it never + // overlaps the first line of the note. +

+

e.stopPropagation()} + className="whitespace-pre-wrap text-sm leading-relaxed text-base-fg/90" + > {item.text || "Note"}

@@ -287,4 +325,53 @@ const CardContent = ({ item }: { item: BoardItem }) => { } }; +// Inline note editor. Autofocuses, commits on blur or Enter (Shift+Enter inserts +// a newline), reverts on Escape. Pointer events are stopped so typing/selecting +// inside the textarea never triggers card selection, drag, or marquee. +const NoteEditor = ({ + initial, + onCommit, +}: { + initial: string; + onCommit: (text: string) => void; +}) => { + const ref = useRef(null); + const [draft, setDraft] = useState(initial); + + useEffect(() => { + const el = ref.current; + if (!el) return; + el.focus(); + el.select(); + }, []); + + return ( +
e.stopPropagation()} + onDoubleClick={(e) => e.stopPropagation()} + > +