import clsx from "clsx"; import { ConnectionSettingsModal } from "./connection-settings-modal"; import { SettingsModal } from "./settings-modal"; import { useAppState } from "../contexts/state"; import { useBool } from "@common/hooks/useBool"; import { useInputState } from "@common/hooks/useInputState"; import type { World, Story } from "../contexts/state"; import styles from '../assets/menu.module.css'; import { Pencil, X, Plus, Plug, Settings, Copy, ChevronRight, ChevronDown, Globe } from "lucide-preact"; // ─── Inline Rename Input ────────────────────────────────────────────────────── interface RenameInputProps { value: string; onSubmit: (title: string) => void; onCancel: () => void; className?: string; } const RenameInput = ({ value, onSubmit, onCancel, className }: RenameInputProps) => { const [editTitle, setEditTitle] = useInputState(value); const handleSubmit = () => { if (editTitle.trim()) onSubmit(editTitle.trim()); else onCancel(); }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter') handleSubmit(); else if (e.key === 'Escape') onCancel(); }; return ( ); }; // ─── Story Item ─────────────────────────────────────────────────────────────── interface StoryItemProps { story: Story; active: boolean; onSelect: () => void; onRename: (newTitle: string) => void; onDelete: () => void; onDuplicate: () => void; } const StoryItem = ({ story, active, onSelect, onRename, onDelete, onDuplicate }: StoryItemProps) => { const isEditing = useBool(false); if (isEditing.value) { return (
{ onRename(t); isEditing.setFalse(); }} onCancel={isEditing.setFalse} />
); } return (
); }; // ─── World Item ─────────────────────────────────────────────────────────────── interface WorldItemProps { world: World; activeWorldId: string | null; activeStoryId: string | null; onSelectWorld: () => void; onRenameWorld: (title: string) => void; onDeleteWorld: () => void; onCreateStory: () => void; onSelectStory: (storyId: string) => void; onRenameStory: (storyId: string, title: string) => void; onDeleteStory: (storyId: string) => void; onDuplicateStory: (storyId: string) => void; } const WorldItem = ({ world, activeWorldId, activeStoryId, onSelectWorld, onRenameWorld, onDeleteWorld, onCreateStory, onSelectStory, onRenameStory, onDeleteStory, onDuplicateStory, }: WorldItemProps) => { const isRenaming = useBool(false); const isExpanded = useBool(activeWorldId === world.id); const isWorldActive = activeWorldId === world.id && !activeStoryId; const toggleExpand = (e: MouseEvent) => { e.stopPropagation(); isExpanded.toggle(); }; return (
{isRenaming.value ? (
{ onRenameWorld(t); isRenaming.setFalse(); }} onCancel={isRenaming.setFalse} />
) : (
)} {isExpanded.value && (
{world.stories.map(story => ( onSelectStory(story.id)} onRename={(title) => onRenameStory(story.id, title)} onDelete={() => onDeleteStory(story.id)} onDuplicate={() => onDuplicateStory(story.id)} /> ))} {world.stories.length === 0 && (
No stories yet
)}
)}
); }; // ─── Menu Sidebar ───────────────────────────────────────────────────────────── export const Menu = () => { const { worlds, currentWorld, currentStory, dispatch } = useAppState(); const isConnectionSettingsOpen = useBool(false); const isSettingsOpen = useBool(false); const handleCreateWorld = () => { dispatch({ type: 'CREATE_WORLD', title: 'New World' }); }; const handleSelectWorld = (worldId: string) => { dispatch({ type: 'SELECT_WORLD', worldId }); }; const handleRenameWorld = (worldId: string, title: string) => { dispatch({ type: 'RENAME_WORLD', worldId, title }); }; const handleDeleteWorld = (worldId: string) => { const world = worlds.find(w => w.id === worldId); if (!world) return; if (confirm(`Delete world "${world.title}" and all its stories?`)) { dispatch({ type: 'DELETE_WORLD', worldId }); } }; const handleCreateStory = (worldId: string) => { dispatch({ type: 'CREATE_STORY', worldId, title: 'New Story' }); }; const handleSelectStory = (worldId: string, storyId: string) => { dispatch({ type: 'SELECT_STORY', worldId, id: storyId }); }; const handleRenameStory = (worldId: string, storyId: string, title: string) => { dispatch({ type: 'RENAME_STORY', worldId, id: storyId, title }); }; const handleDeleteStory = (worldId: string, storyId: string) => { const world = worlds.find(w => w.id === worldId); const story = world?.stories.find(s => s.id === storyId); if (!story) return; if (confirm(`Delete "${story.title}"?`)) { dispatch({ type: 'DELETE_STORY', worldId, id: storyId }); } }; const handleDuplicateStory = (worldId: string, storyId: string) => { dispatch({ type: 'DUPLICATE_STORY', worldId, id: storyId }); }; return (
{worlds.map(world => ( handleSelectWorld(world.id)} onRenameWorld={(title) => handleRenameWorld(world.id, title)} onDeleteWorld={() => handleDeleteWorld(world.id)} onCreateStory={() => handleCreateStory(world.id)} onSelectStory={(storyId) => handleSelectStory(world.id, storyId)} onRenameStory={(storyId, title) => handleRenameStory(world.id, storyId, title)} onDeleteStory={(storyId) => handleDeleteStory(world.id, storyId)} onDuplicateStory={(storyId) => handleDuplicateStory(world.id, storyId)} /> ))}
{isSettingsOpen.value && ( )} {isConnectionSettingsOpen.value && ( )}
); };