1
0
Fork 0

Refactor inputs

This commit is contained in:
Pabloader 2026-04-12 18:11:07 +00:00
parent 7f18a66a8b
commit f81d8674c0
7 changed files with 54 additions and 50 deletions

View File

@ -4,7 +4,7 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "bun build/server.ts",
"start": "bun --hot build/server.ts",
"bake": "bun build/build.ts",
"test": "bun test"
},

View File

@ -1,16 +1,6 @@
import { extractString } from "@common/utils";
import { useCallback } from "preact/hooks";
export function useInputCallback<T>(callback: (value: string) => T, deps: any[]): ((value: string | Event) => T) {
return useCallback((e: Event | string) => {
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);
}
export const useInputCallback = <T>(callback: (value: string) => T, deps: any[]): ((value: string | Event) => T) => (
useCallback((e: Event | string) => callback(extractString(e)), deps)
);

View File

@ -1,19 +1,11 @@
import { extractString } from "@common/utils";
import { useCallback, useState } from "preact/hooks";
export const useInputState = (defaultValue = ''): [string, (e: Event | string) => void] => {
const [value, setValue] = useState(defaultValue);
const handleInput = useCallback((e: Event | string) => {
if (typeof e === 'string') {
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);
}
}
setValue(extractString(e));
}, []);
return [value, handleInput];

View File

@ -160,3 +160,17 @@ export const formatTime = (seconds: number): string => {
parts.push(timeStr);
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 '';
}

View File

@ -4,6 +4,7 @@ import { useState } from "preact/hooks";
import styles from '../../assets/character-editor.module.css';
import { CharacterRole, useAppState, type Character } from "../../contexts/state";
import LLM from "../../utils/llm";
import { extractString } from "@common/utils";
export const CharacterEditor = ({ visible }: { visible: boolean }) => {
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({
type: 'EDIT_CHARACTER',
worldId,
@ -71,14 +75,14 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
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({
type: 'EDIT_CHARACTER_RELATION',
worldId,
storyId,
characterId,
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: '' };
setNewRelation({ ...newRelation, [characterId]: { ...current, [field]: value } });
};
@ -153,7 +158,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.nameInput}
value={character.name}
onInput={(e) => handleEditCharacter(character.id, 'name', e.currentTarget.value)}
onInput={(e) => handleEditCharacter(character.id, 'name', e)}
onFocus={(e) => e.currentTarget.select()}
placeholder="Character name"
/>
@ -191,7 +196,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
<select
class={styles.select}
value={character.role}
onInput={(e) => handleEditCharacter(character.id, 'role', e.currentTarget.value as CharacterRole)}
onInput={(e) => handleEditCharacter(character.id, 'role', e)}
>
{Object.entries(CharacterRole)
.filter(([, value]) => typeof value === 'string')
@ -209,7 +214,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
autoLines
class={styles.textarea}
value={character.description}
onInput={(e) => handleEditCharacter(character.id, 'description', e.currentTarget.textContent)}
onInput={(e) => handleEditCharacter(character.id, 'description', e)}
placeholder="Full character description..."
/>
</div>
@ -228,7 +233,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
<textarea
class={styles.textarea}
value={character.shortDescription}
onInput={(e) => handleEditCharacter(character.id, 'shortDescription', e.currentTarget.value)}
onInput={(e) => handleEditCharacter(character.id, 'shortDescription', e)}
placeholder="Brief description (one line)..."
rows={1}
/>
@ -242,7 +247,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.addInputField}
value={newNickname[character.id] || ''}
onInput={(e) => setNewNickname({ ...newNickname, [character.id]: e.currentTarget.value })}
onInput={(e) => setNewNickname({ ...newNickname, [character.id]: extractString(e) })}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -284,7 +289,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
<select
class={styles.addInputField}
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>
{mergedCharacters
@ -297,7 +302,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.addInputField}
value={(newRelation[character.id]?.relation) || ''}
onInput={(e) => handleNewRelationChange(character.id, 'relation', e.currentTarget.value)}
onInput={(e) => handleNewRelationChange(character.id, 'relation', e)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -323,7 +328,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
<select
class={styles.relationInput}
value={rel.name}
onInput={(e) => handleEditRelation(character.id, rel.name, 'name', e.currentTarget.value)}
onInput={(e) => handleEditRelation(character.id, rel.name, 'name', e)}
>
{mergedCharacters
.filter(c => c.id !== character.id)
@ -335,7 +340,7 @@ export const CharacterEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.relationInput}
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"
/>
<button

View File

@ -1,4 +1,5 @@
import { ContentEditable } from "@common/components/ContentEditable";
import { extractString } from "@common/utils";
import { useState } from "preact/hooks";
import styles from '../../assets/location-editor.module.css';
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({
type: 'EDIT_LOCATION',
worldId,
storyId,
locationId,
updates: { [field]: value },
updates: { [field]: extractString(value) },
});
};
@ -90,7 +91,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.nameInput}
value={location.name}
onInput={(e) => handleEditLocation(location.id, 'name', e.currentTarget.value)}
onInput={(e) => handleEditLocation(location.id, 'name', e)}
onFocus={(e) => e.currentTarget.select()}
placeholder="Location name"
/>
@ -128,7 +129,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
<select
class={styles.select}
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) => (
<option key={option} value={option}>
@ -144,7 +145,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
autoLines
class={styles.textarea}
value={location.description}
onInput={(e) => handleEditLocation(location.id, 'description', e.currentTarget.textContent)}
onInput={(e) => handleEditLocation(location.id, 'description', e)}
placeholder="Full location description..."
/>
</div>
@ -163,7 +164,7 @@ export const LocationEditor = ({ visible }: { visible: boolean }) => {
<textarea
class={styles.textarea}
value={location.shortDescription}
onInput={(e) => handleEditLocation(location.id, 'shortDescription', e.currentTarget.value)}
onInput={(e) => handleEditLocation(location.id, 'shortDescription', e)}
placeholder="Brief description (one line)..."
rows={1}
/>

View File

@ -1,4 +1,6 @@
import { ContentEditable } from "@common/components/ContentEditable";
import { useInputState } from "@common/hooks/useInputState";
import { extractString } from "@common/utils";
import { useState } from "preact/hooks";
import styles from '../../assets/lore-editor.module.css';
import { useAppState, type LoreEntry } from "../../contexts/state";
@ -6,7 +8,7 @@ import { useAppState, type LoreEntry } from "../../contexts/state";
export const LoreEditor = ({ visible }: { visible: boolean }) => {
const { currentWorld, currentStory, dispatch } = useAppState();
const [editingId, setEditingId] = useState<string | null>(null);
const [newTitle, setNewTitle] = useState('');
const [newTitle, setNewTitle] = useInputState('');
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
if (!currentWorld || !visible) {
@ -35,13 +37,13 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
setEditingId(null);
};
const handleEditEntry = (entryId: string, field: keyof LoreEntry, value: string) => {
const handleEditEntry = (entryId: string, field: keyof LoreEntry, value: string | Event) => {
dispatch({
type: 'EDIT_LORE_ENTRY',
worldId,
storyId,
entryId,
updates: { [field]: value },
updates: { [field]: extractString(value) },
});
};
@ -87,7 +89,7 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.titleInput}
value={newTitle}
onInput={(e) => setNewTitle(e.currentTarget.value)}
onInput={setNewTitle}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -116,7 +118,7 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
type="text"
class={styles.titleEditInput}
value={entry.title}
onInput={(e) => handleEditEntry(entry.id, 'title', e.currentTarget.value)}
onInput={(e) => handleEditEntry(entry.id, 'title', e)}
onBlur={() => setEditingId(null)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
@ -191,7 +193,7 @@ export const LoreEditor = ({ visible }: { visible: boolean }) => {
autoLines
class={styles.textarea}
value={entry.text}
onInput={(e) => handleEditEntry(entry.id, 'text', e.currentTarget.textContent || '')}
onInput={(e) => handleEditEntry(entry.id, 'text', e)}
placeholder="Enter lore content..."
/>
</div>