Refactor inputs
This commit is contained in:
parent
7f18a66a8b
commit
f81d8674c0
|
|
@ -4,7 +4,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun build/server.ts",
|
"start": "bun --hot build/server.ts",
|
||||||
"bake": "bun build/build.ts",
|
"bake": "bun build/build.ts",
|
||||||
"test": "bun test"
|
"test": "bun test"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
|
import { extractString } from "@common/utils";
|
||||||
import { useCallback } from "preact/hooks";
|
import { useCallback } from "preact/hooks";
|
||||||
|
|
||||||
export function useInputCallback<T>(callback: (value: string) => T, deps: any[]): ((value: string | Event) => T) {
|
export const useInputCallback = <T>(callback: (value: string) => T, deps: any[]): ((value: string | Event) => T) => (
|
||||||
return useCallback((e: Event | string) => {
|
useCallback((e: Event | string) => callback(extractString(e)), deps)
|
||||||
if (typeof e === 'string') {
|
);
|
||||||
return callback(e);
|
|
||||||
} else {
|
|
||||||
const { target } = e;
|
|
||||||
if (target && 'value' in target && typeof target.value === 'string') {
|
|
||||||
return callback(target.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback('');
|
|
||||||
}, deps);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,11 @@
|
||||||
|
import { extractString } from "@common/utils";
|
||||||
import { useCallback, useState } from "preact/hooks";
|
import { useCallback, useState } from "preact/hooks";
|
||||||
|
|
||||||
export const useInputState = (defaultValue = ''): [string, (e: Event | string) => void] => {
|
export const useInputState = (defaultValue = ''): [string, (e: Event | string) => void] => {
|
||||||
const [value, setValue] = useState(defaultValue);
|
const [value, setValue] = useState(defaultValue);
|
||||||
|
|
||||||
const handleInput = useCallback((e: Event | string) => {
|
const handleInput = useCallback((e: Event | string) => {
|
||||||
if (typeof e === 'string') {
|
setValue(extractString(e));
|
||||||
setValue(e);
|
|
||||||
} else {
|
|
||||||
const { target } = e;
|
|
||||||
if (target && 'value' in target && typeof target.value === 'string') {
|
|
||||||
setValue(target.value);
|
|
||||||
} else if (target instanceof HTMLElement) {
|
|
||||||
setValue(target.innerHTML);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [value, handleInput];
|
return [value, handleInput];
|
||||||
|
|
|
||||||
|
|
@ -160,3 +160,17 @@ export const formatTime = (seconds: number): string => {
|
||||||
parts.push(timeStr);
|
parts.push(timeStr);
|
||||||
return parts.join(' ');
|
return parts.join(' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extractString = (e: Event | string): string => {
|
||||||
|
if (typeof e === 'string') {
|
||||||
|
return e;
|
||||||
|
} else {
|
||||||
|
const { target } = e;
|
||||||
|
if (target && 'value' in target && typeof target.value === 'string') {
|
||||||
|
return target.value;
|
||||||
|
} else if (target instanceof HTMLElement) {
|
||||||
|
return target.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import { useState } from "preact/hooks";
|
||||||
import styles from '../../assets/character-editor.module.css';
|
import styles from '../../assets/character-editor.module.css';
|
||||||
import { CharacterRole, useAppState, type Character } from "../../contexts/state";
|
import { CharacterRole, useAppState, type Character } from "../../contexts/state";
|
||||||
import LLM from "../../utils/llm";
|
import LLM from "../../utils/llm";
|
||||||
|
import { extractString } from "@common/utils";
|
||||||
|
|
||||||
export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
const { currentWorld, currentStory, mergedCharacters, dispatch, connection, model } = useAppState();
|
const { currentWorld, currentStory, mergedCharacters, dispatch, connection, model } = useAppState();
|
||||||
|
|
@ -38,7 +39,10 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditCharacter = (characterId: string, field: keyof Character, value: any) => {
|
const handleEditCharacter = (characterId: string, field: keyof Character, value: string[] | string | Event) => {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
value = extractString(value);
|
||||||
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EDIT_CHARACTER',
|
type: 'EDIT_CHARACTER',
|
||||||
worldId,
|
worldId,
|
||||||
|
|
@ -71,14 +75,14 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
setNewRelation({ ...newRelation, [characterId]: { name: '', relation: '' } });
|
setNewRelation({ ...newRelation, [characterId]: { name: '', relation: '' } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditRelation = (characterId: string, targetName: string, field: 'name' | 'relation', value: string) => {
|
const handleEditRelation = (characterId: string, targetName: string, field: 'name' | 'relation', value: string | Event) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EDIT_CHARACTER_RELATION',
|
type: 'EDIT_CHARACTER_RELATION',
|
||||||
worldId,
|
worldId,
|
||||||
storyId,
|
storyId,
|
||||||
characterId,
|
characterId,
|
||||||
targetName,
|
targetName,
|
||||||
updates: { [field]: value },
|
updates: { [field]: extractString(value) },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -110,7 +114,8 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewRelationChange = (characterId: string, field: 'name' | 'relation', value: string) => {
|
const handleNewRelationChange = (characterId: string, field: 'name' | 'relation', value: string | Event) => {
|
||||||
|
value = extractString(value);
|
||||||
const current = newRelation[characterId] || { name: '', relation: '' };
|
const current = newRelation[characterId] || { name: '', relation: '' };
|
||||||
setNewRelation({ ...newRelation, [characterId]: { ...current, [field]: value } });
|
setNewRelation({ ...newRelation, [characterId]: { ...current, [field]: value } });
|
||||||
};
|
};
|
||||||
|
|
@ -153,7 +158,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.nameInput}
|
class={styles.nameInput}
|
||||||
value={character.name}
|
value={character.name}
|
||||||
onInput={(e) => handleEditCharacter(character.id, 'name', e.currentTarget.value)}
|
onInput={(e) => handleEditCharacter(character.id, 'name', e)}
|
||||||
onFocus={(e) => e.currentTarget.select()}
|
onFocus={(e) => e.currentTarget.select()}
|
||||||
placeholder="Character name"
|
placeholder="Character name"
|
||||||
/>
|
/>
|
||||||
|
|
@ -191,7 +196,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
<select
|
<select
|
||||||
class={styles.select}
|
class={styles.select}
|
||||||
value={character.role}
|
value={character.role}
|
||||||
onInput={(e) => handleEditCharacter(character.id, 'role', e.currentTarget.value as CharacterRole)}
|
onInput={(e) => handleEditCharacter(character.id, 'role', e)}
|
||||||
>
|
>
|
||||||
{Object.entries(CharacterRole)
|
{Object.entries(CharacterRole)
|
||||||
.filter(([, value]) => typeof value === 'string')
|
.filter(([, value]) => typeof value === 'string')
|
||||||
|
|
@ -209,7 +214,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
autoLines
|
autoLines
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={character.description}
|
value={character.description}
|
||||||
onInput={(e) => handleEditCharacter(character.id, 'description', e.currentTarget.textContent)}
|
onInput={(e) => handleEditCharacter(character.id, 'description', e)}
|
||||||
placeholder="Full character description..."
|
placeholder="Full character description..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -228,7 +233,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
<textarea
|
<textarea
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={character.shortDescription}
|
value={character.shortDescription}
|
||||||
onInput={(e) => handleEditCharacter(character.id, 'shortDescription', e.currentTarget.value)}
|
onInput={(e) => handleEditCharacter(character.id, 'shortDescription', e)}
|
||||||
placeholder="Brief description (one line)..."
|
placeholder="Brief description (one line)..."
|
||||||
rows={1}
|
rows={1}
|
||||||
/>
|
/>
|
||||||
|
|
@ -242,7 +247,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.addInputField}
|
class={styles.addInputField}
|
||||||
value={newNickname[character.id] || ''}
|
value={newNickname[character.id] || ''}
|
||||||
onInput={(e) => setNewNickname({ ...newNickname, [character.id]: e.currentTarget.value })}
|
onInput={(e) => setNewNickname({ ...newNickname, [character.id]: extractString(e) })}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -284,7 +289,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
<select
|
<select
|
||||||
class={styles.addInputField}
|
class={styles.addInputField}
|
||||||
value={(newRelation[character.id]?.name) || ''}
|
value={(newRelation[character.id]?.name) || ''}
|
||||||
onInput={(e) => handleNewRelationChange(character.id, 'name', e.currentTarget.value)}
|
onInput={(e) => handleNewRelationChange(character.id, 'name', e)}
|
||||||
>
|
>
|
||||||
<option value="" disabled>Select character</option>
|
<option value="" disabled>Select character</option>
|
||||||
{mergedCharacters
|
{mergedCharacters
|
||||||
|
|
@ -297,7 +302,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.addInputField}
|
class={styles.addInputField}
|
||||||
value={(newRelation[character.id]?.relation) || ''}
|
value={(newRelation[character.id]?.relation) || ''}
|
||||||
onInput={(e) => handleNewRelationChange(character.id, 'relation', e.currentTarget.value)}
|
onInput={(e) => handleNewRelationChange(character.id, 'relation', e)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -323,7 +328,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
<select
|
<select
|
||||||
class={styles.relationInput}
|
class={styles.relationInput}
|
||||||
value={rel.name}
|
value={rel.name}
|
||||||
onInput={(e) => handleEditRelation(character.id, rel.name, 'name', e.currentTarget.value)}
|
onInput={(e) => handleEditRelation(character.id, rel.name, 'name', e)}
|
||||||
>
|
>
|
||||||
{mergedCharacters
|
{mergedCharacters
|
||||||
.filter(c => c.id !== character.id)
|
.filter(c => c.id !== character.id)
|
||||||
|
|
@ -335,7 +340,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.relationInput}
|
class={styles.relationInput}
|
||||||
value={rel.relation}
|
value={rel.relation}
|
||||||
onInput={(e) => handleEditRelation(character.id, rel.name, 'relation', e.currentTarget.value)}
|
onInput={(e) => handleEditRelation(character.id, rel.name, 'relation', e)}
|
||||||
placeholder="Relationship"
|
placeholder="Relationship"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { ContentEditable } from "@common/components/ContentEditable";
|
import { ContentEditable } from "@common/components/ContentEditable";
|
||||||
|
import { extractString } from "@common/utils";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import styles from '../../assets/location-editor.module.css';
|
import styles from '../../assets/location-editor.module.css';
|
||||||
import { LocationScale, useAppState, type Location } from "../../contexts/state";
|
import { LocationScale, useAppState, type Location } from "../../contexts/state";
|
||||||
|
|
@ -33,13 +34,13 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditLocation = (locationId: string, field: keyof Location, value: any) => {
|
const handleEditLocation = (locationId: string, field: keyof Location, value: string | Event) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EDIT_LOCATION',
|
type: 'EDIT_LOCATION',
|
||||||
worldId,
|
worldId,
|
||||||
storyId,
|
storyId,
|
||||||
locationId,
|
locationId,
|
||||||
updates: { [field]: value },
|
updates: { [field]: extractString(value) },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -90,7 +91,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.nameInput}
|
class={styles.nameInput}
|
||||||
value={location.name}
|
value={location.name}
|
||||||
onInput={(e) => handleEditLocation(location.id, 'name', e.currentTarget.value)}
|
onInput={(e) => handleEditLocation(location.id, 'name', e)}
|
||||||
onFocus={(e) => e.currentTarget.select()}
|
onFocus={(e) => e.currentTarget.select()}
|
||||||
placeholder="Location name"
|
placeholder="Location name"
|
||||||
/>
|
/>
|
||||||
|
|
@ -128,7 +129,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
|
||||||
<select
|
<select
|
||||||
class={styles.select}
|
class={styles.select}
|
||||||
value={location.scale}
|
value={location.scale}
|
||||||
onInput={(e) => handleEditLocation(location.id, 'scale', e.currentTarget.value as LocationScale)}
|
onInput={(e) => handleEditLocation(location.id, 'scale', e)}
|
||||||
>
|
>
|
||||||
{Object.values(LocationScale).map((option) => (
|
{Object.values(LocationScale).map((option) => (
|
||||||
<option key={option} value={option}>
|
<option key={option} value={option}>
|
||||||
|
|
@ -144,7 +145,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
|
||||||
autoLines
|
autoLines
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={location.description}
|
value={location.description}
|
||||||
onInput={(e) => handleEditLocation(location.id, 'description', e.currentTarget.textContent)}
|
onInput={(e) => handleEditLocation(location.id, 'description', e)}
|
||||||
placeholder="Full location description..."
|
placeholder="Full location description..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -163,7 +164,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
|
||||||
<textarea
|
<textarea
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={location.shortDescription}
|
value={location.shortDescription}
|
||||||
onInput={(e) => handleEditLocation(location.id, 'shortDescription', e.currentTarget.value)}
|
onInput={(e) => handleEditLocation(location.id, 'shortDescription', e)}
|
||||||
placeholder="Brief description (one line)..."
|
placeholder="Brief description (one line)..."
|
||||||
rows={1}
|
rows={1}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { ContentEditable } from "@common/components/ContentEditable";
|
import { ContentEditable } from "@common/components/ContentEditable";
|
||||||
|
import { useInputState } from "@common/hooks/useInputState";
|
||||||
|
import { extractString } from "@common/utils";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import styles from '../../assets/lore-editor.module.css';
|
import styles from '../../assets/lore-editor.module.css';
|
||||||
import { useAppState, type LoreEntry } from "../../contexts/state";
|
import { useAppState, type LoreEntry } from "../../contexts/state";
|
||||||
|
|
@ -6,7 +8,7 @@ import { useAppState, type LoreEntry } from "../../contexts/state";
|
||||||
export const LoreEditor = ({ visible }: { visible: boolean }) => {
|
export const LoreEditor = ({ visible }: { visible: boolean }) => {
|
||||||
const { currentWorld, currentStory, dispatch } = useAppState();
|
const { currentWorld, currentStory, dispatch } = useAppState();
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
const [newTitle, setNewTitle] = useState('');
|
const [newTitle, setNewTitle] = useInputState('');
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
|
||||||
|
|
||||||
if (!currentWorld || !visible) {
|
if (!currentWorld || !visible) {
|
||||||
|
|
@ -35,13 +37,13 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditEntry = (entryId: string, field: keyof LoreEntry, value: string) => {
|
const handleEditEntry = (entryId: string, field: keyof LoreEntry, value: string | Event) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'EDIT_LORE_ENTRY',
|
type: 'EDIT_LORE_ENTRY',
|
||||||
worldId,
|
worldId,
|
||||||
storyId,
|
storyId,
|
||||||
entryId,
|
entryId,
|
||||||
updates: { [field]: value },
|
updates: { [field]: extractString(value) },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -87,7 +89,7 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.titleInput}
|
class={styles.titleInput}
|
||||||
value={newTitle}
|
value={newTitle}
|
||||||
onInput={(e) => setNewTitle(e.currentTarget.value)}
|
onInput={setNewTitle}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -116,7 +118,7 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
|
||||||
type="text"
|
type="text"
|
||||||
class={styles.titleEditInput}
|
class={styles.titleEditInput}
|
||||||
value={entry.title}
|
value={entry.title}
|
||||||
onInput={(e) => handleEditEntry(entry.id, 'title', e.currentTarget.value)}
|
onInput={(e) => handleEditEntry(entry.id, 'title', e)}
|
||||||
onBlur={() => setEditingId(null)}
|
onBlur={() => setEditingId(null)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
|
|
@ -191,7 +193,7 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
|
||||||
autoLines
|
autoLines
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={entry.text}
|
value={entry.text}
|
||||||
onInput={(e) => handleEditEntry(entry.id, 'text', e.currentTarget.textContent || '')}
|
onInput={(e) => handleEditEntry(entry.id, 'text', e)}
|
||||||
placeholder="Enter lore content..."
|
placeholder="Enter lore content..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue