Add story duplication and fix switching
This commit is contained in:
parent
97f88a24b9
commit
951c13022c
|
|
@ -20,27 +20,26 @@ const TABS: { id: Tab; label: string }[] = [
|
||||||
export const Editor = () => {
|
export const Editor = () => {
|
||||||
const { currentStory, dispatch } = useAppState();
|
const { currentStory, dispatch } = useAppState();
|
||||||
|
|
||||||
if (!currentStory) {
|
|
||||||
return <div class={styles.editor} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInput = useInputCallback((text: string) => {
|
const handleInput = useInputCallback((text: string) => {
|
||||||
|
if (!currentStory) return;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EDIT_STORY',
|
type: 'EDIT_STORY',
|
||||||
id: currentStory.id,
|
id: currentStory.id,
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
}, []);
|
}, [currentStory?.id]);
|
||||||
|
|
||||||
const handleLoreInput = useInputCallback((lore: string) => {
|
const handleLoreInput = useInputCallback((lore: string) => {
|
||||||
|
if (!currentStory) return;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EDIT_LORE',
|
type: 'EDIT_LORE',
|
||||||
id: currentStory.id,
|
id: currentStory.id,
|
||||||
lore,
|
lore,
|
||||||
});
|
});
|
||||||
}, []);
|
}, [currentStory?.id]);
|
||||||
|
|
||||||
const handleTabChange = (tab: Tab) => {
|
const handleTabChange = (tab: Tab) => {
|
||||||
|
if (!currentStory) return;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_CURRENT_TAB',
|
type: 'SET_CURRENT_TAB',
|
||||||
id: currentStory.id,
|
id: currentStory.id,
|
||||||
|
|
@ -48,8 +47,12 @@ export const Editor = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const storyValue = useMemo(() => highlight(currentStory.text), [currentStory.text]);
|
const storyValue = useMemo(() => currentStory ? highlight(currentStory.text) : '', [currentStory?.text]);
|
||||||
const loreValue = useMemo(() => highlight(currentStory.lore), [currentStory.lore]);
|
const loreValue = useMemo(() => currentStory ? highlight(currentStory.lore) : '', [currentStory?.lore]);
|
||||||
|
|
||||||
|
if (!currentStory) {
|
||||||
|
return <div class={styles.editor} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.editor}>
|
<div class={styles.editor}>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useBool } from "@common/hooks/useBool";
|
||||||
import type { Story } from "../contexts/state";
|
import type { Story } from "../contexts/state";
|
||||||
import styles from '../assets/menu-sidebar.module.css';
|
import styles from '../assets/menu-sidebar.module.css';
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { Pencil, X, Plus, Plug, Settings } from "lucide-preact";
|
import { Pencil, X, Plus, Plug, Settings, Copy } from "lucide-preact";
|
||||||
|
|
||||||
// ─── Story Item ───────────────────────────────────────────────────────────────
|
// ─── Story Item ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -17,9 +17,10 @@ interface StoryItemProps {
|
||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
onRename: (newTitle: string) => void;
|
onRename: (newTitle: string) => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
|
onDuplicate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StoryItem = ({ story, active, onSelect, onRename, onDelete }: StoryItemProps) => {
|
const StoryItem = ({ story, active, onSelect, onRename, onDelete, onDuplicate }: StoryItemProps) => {
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [editTitle, setEditTitle] = useState(story.title);
|
const [editTitle, setEditTitle] = useState(story.title);
|
||||||
|
|
||||||
|
|
@ -70,6 +71,9 @@ const StoryItem = ({ story, active, onSelect, onRename, onDelete }: StoryItemPro
|
||||||
<button class={styles.actionButton} onClick={() => setIsEditing(true)} title="Rename">
|
<button class={styles.actionButton} onClick={() => setIsEditing(true)} title="Rename">
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
<button class={styles.actionButton} onClick={onDuplicate} title="Duplicate">
|
||||||
|
<Copy size={14} />
|
||||||
|
</button>
|
||||||
<button class={styles.actionButton} onClick={onDelete} title="Delete">
|
<button class={styles.actionButton} onClick={onDelete} title="Delete">
|
||||||
<X size={14} />
|
<X size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -105,6 +109,10 @@ export const MenuSidebar = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDuplicate = (id: string) => {
|
||||||
|
dispatch({ type: 'DUPLICATE_STORY', id });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar side="left">
|
<Sidebar side="left">
|
||||||
<div class={styles.menu}>
|
<div class={styles.menu}>
|
||||||
|
|
@ -120,6 +128,7 @@ export const MenuSidebar = () => {
|
||||||
onSelect={() => handleSelect(story.id)}
|
onSelect={() => handleSelect(story.id)}
|
||||||
onRename={(newTitle) => handleRename(story.id, newTitle)}
|
onRename={(newTitle) => handleRename(story.id, newTitle)}
|
||||||
onDelete={() => handleDelete(story.id)}
|
onDelete={() => handleDelete(story.id)}
|
||||||
|
onDuplicate={() => handleDuplicate(story.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ type Action =
|
||||||
| { type: 'SET_CURRENT_TAB'; id: string; tab: Tab }
|
| { type: 'SET_CURRENT_TAB'; id: string; tab: Tab }
|
||||||
| { type: 'DELETE_STORY'; id: string }
|
| { type: 'DELETE_STORY'; id: string }
|
||||||
| { type: 'SELECT_STORY'; id: string }
|
| { type: 'SELECT_STORY'; id: string }
|
||||||
|
| { type: 'DUPLICATE_STORY'; id: string }
|
||||||
| { type: 'ADD_CHAT_MESSAGE'; storyId: string; message: ChatMessage }
|
| { type: 'ADD_CHAT_MESSAGE'; storyId: string; message: ChatMessage }
|
||||||
| { type: 'CLEAR_CHAT'; storyId: string }
|
| { type: 'CLEAR_CHAT'; storyId: string }
|
||||||
| { type: 'SET_CONNECTION'; connection: LLM.Connection | null }
|
| { type: 'SET_CONNECTION'; connection: LLM.Connection | null }
|
||||||
|
|
@ -180,6 +181,26 @@ function reducer(state: IState, action: Action): IState {
|
||||||
currentStoryId: deletingCurrent ? null : state.currentStoryId,
|
currentStoryId: deletingCurrent ? null : state.currentStoryId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'DUPLICATE_STORY': {
|
||||||
|
const original = state.stories.find(s => s.id === action.id);
|
||||||
|
if (!original) return state;
|
||||||
|
const newStory: Story = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
title: `${original.title} (Copy)`,
|
||||||
|
text: '',
|
||||||
|
lore: original.lore,
|
||||||
|
characters: original.characters,
|
||||||
|
locations: original.locations,
|
||||||
|
currentTab: 'story',
|
||||||
|
chatMessages: [],
|
||||||
|
chapters: [],
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
stories: [...state.stories, newStory],
|
||||||
|
currentStoryId: newStory.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
case 'SELECT_STORY': {
|
case 'SELECT_STORY': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue