153 lines
5.5 KiB
TypeScript
153 lines
5.5 KiB
TypeScript
import clsx from "clsx";
|
|
import { Sidebar } from "./sidebar";
|
|
import { ConnectionSettingsModal } from "./connection-settings-modal";
|
|
import { SettingsModal } from "./settings-modal";
|
|
import { useAppState } from "../contexts/state";
|
|
import { useBool } from "@common/hooks/useBool";
|
|
import type { Story } from "../contexts/state";
|
|
import styles from '../assets/menu-sidebar.module.css';
|
|
import { useState } from "preact/hooks";
|
|
import { Pencil, X, Plus, Plug, Settings, Copy } from "lucide-preact";
|
|
|
|
// ─── 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, setIsEditing] = useState(false);
|
|
const [editTitle, setEditTitle] = useState(story.title);
|
|
|
|
const handleSubmit = () => {
|
|
if (editTitle.trim()) {
|
|
onRename(editTitle.trim());
|
|
}
|
|
setIsEditing(false);
|
|
};
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === 'Enter') {
|
|
handleSubmit();
|
|
} else if (e.key === 'Escape') {
|
|
setEditTitle(story.title);
|
|
setIsEditing(false);
|
|
}
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
handleSubmit();
|
|
};
|
|
|
|
if (isEditing) {
|
|
return (
|
|
<div class={clsx(styles.itemWrapper, active && styles.active)}>
|
|
<input
|
|
class={styles.input}
|
|
value={editTitle}
|
|
onInput={(e) => setEditTitle((e.target as HTMLInputElement).value)}
|
|
onKeyDown={handleKeyDown}
|
|
onBlur={handleBlur}
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div class={clsx(styles.itemWrapper, active && styles.active)}>
|
|
<button
|
|
class={clsx(styles.item, active && styles.active)}
|
|
onClick={onSelect}
|
|
>
|
|
{story.title}
|
|
</button>
|
|
<div class={styles.actions}>
|
|
<button class={styles.actionButton} onClick={() => setIsEditing(true)} title="Rename">
|
|
<Pencil size={14} />
|
|
</button>
|
|
<button class={styles.actionButton} onClick={onDuplicate} title="Duplicate">
|
|
<Copy size={14} />
|
|
</button>
|
|
<button class={styles.actionButton} onClick={onDelete} title="Delete">
|
|
<X size={14} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// ─── Menu Sidebar ─────────────────────────────────────────────────────────────
|
|
|
|
export const MenuSidebar = () => {
|
|
const { stories, currentStory, dispatch } = useAppState();
|
|
const isConnectionSettingsOpen = useBool(false);
|
|
const isSettingsOpen = useBool(false);
|
|
|
|
const handleCreate = () => {
|
|
dispatch({ type: 'CREATE_STORY', title: 'New Story' });
|
|
};
|
|
|
|
const handleSelect = (id: string) => {
|
|
dispatch({ type: 'SELECT_STORY', id });
|
|
};
|
|
|
|
const handleRename = (id: string, newTitle: string) => {
|
|
dispatch({ type: 'RENAME_STORY', id, title: newTitle });
|
|
};
|
|
|
|
const handleDelete = (id: string) => {
|
|
const story = stories.find(s => s.id === id);
|
|
if (!story) return;
|
|
if (confirm(`Delete "${story.title}"?`)) {
|
|
dispatch({ type: 'DELETE_STORY', id });
|
|
}
|
|
};
|
|
|
|
const handleDuplicate = (id: string) => {
|
|
dispatch({ type: 'DUPLICATE_STORY', id });
|
|
};
|
|
|
|
return (
|
|
<Sidebar side="left">
|
|
<div class={styles.menu}>
|
|
<button class={styles.newButton} onClick={handleCreate}>
|
|
<Plus size={16} /> New Story
|
|
</button>
|
|
<div class={styles.list}>
|
|
{stories.map(story => (
|
|
<StoryItem
|
|
key={story.id}
|
|
story={story}
|
|
active={story.id === currentStory?.id}
|
|
onSelect={() => handleSelect(story.id)}
|
|
onRename={(newTitle) => handleRename(story.id, newTitle)}
|
|
onDelete={() => handleDelete(story.id)}
|
|
onDuplicate={() => handleDuplicate(story.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
<div class={styles.bottomButtons}>
|
|
<button class={styles.settingsButton} onClick={isSettingsOpen.toggle}>
|
|
<Settings size={16} /> Settings
|
|
</button>
|
|
<button class={styles.settingsButton} onClick={isConnectionSettingsOpen.toggle}>
|
|
<Plug size={16} /> Connection Settings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{isSettingsOpen.value && (
|
|
<SettingsModal onClose={isSettingsOpen.toggle} />
|
|
)}
|
|
{isConnectionSettingsOpen.value && (
|
|
<ConnectionSettingsModal onClose={isConnectionSettingsOpen.toggle} />
|
|
)}
|
|
</Sidebar>
|
|
);
|
|
};
|