diff --git a/client/src/components/fableloom/LoomNodeEditor.jsx b/client/src/components/fableloom/LoomNodeEditor.jsx index 37fdd2d293..9cc406a58a 100644 --- a/client/src/components/fableloom/LoomNodeEditor.jsx +++ b/client/src/components/fableloom/LoomNodeEditor.jsx @@ -44,7 +44,7 @@ const rowToPatch = ({ targetNodeId, intent, triggersText, description }) => ({ }); export default function LoomNodeEditor({ - loom, episode, node, onLoomUpdate, onClearSelection, onMakeStart, + loom, episode, node, universe, onLoomUpdate, onClearSelection, onMakeStart, mediaJobs = {}, onGenerateImage, onGenerateVideo, generationDisabled = false, generationDisabledReason = '', }) { @@ -70,6 +70,11 @@ export default function LoomNodeEditor({ imagePrompt: node.imagePrompt || '', videoPrompt: node.videoPrompt || '', cameraMovement: node.cameraMovement || '', + visualCanon: node.visualCanon ? { + ...node.visualCanon, + characterAppearances: [...(node.visualCanon.characterAppearances || [])], + objectIds: [...(node.visualCanon.objectIds || [])], + } : null, playbackMode: node.playbackMode || 'decision', audienceConnection: node.audienceConnection || 'disconnected', playbackAssets: node.playbackAssets || null, @@ -107,6 +112,13 @@ export default function LoomNodeEditor({ return updated; }; + const saveVisualCanon = (next) => { + setForm((current) => ({ ...current, visualCanon: next })); + patchNode({ visualCanon: next }); + }; + + const updateVisualCanon = (patch) => saveVisualCanon({ ...form.visualCanon, ...patch }); + // Blur-save helper: skip the round-trip when the value matches the record // (tabbing through the panel shouldn't rewrite the loom). const saveField = (key, value) => { @@ -474,6 +486,187 @@ export default function LoomNodeEditor({ /> +
+ + {form.visualCanon && ( +
+ + +
+ Characters + {(universe?.characters || []).map((character) => { + const appearance = form.visualCanon.characterAppearances + .find((item) => item.characterId === character.id); + return ( +
+ + {appearance && (character.wardrobes || []).length > 0 && ( + + )} + {appearance && ( +
+ setForm((current) => ({ + ...current, + visualCanon: { + ...current.visualCanon, + characterAppearances: current.visualCanon.characterAppearances.map((item) => ( + item.characterId === character.id ? { ...item, expression: event.target.value } : item + )), + }, + }))} + onBlur={() => patchNode({ visualCanon: form.visualCanon })} + /> + setForm((current) => ({ + ...current, + visualCanon: { + ...current.visualCanon, + characterAppearances: current.visualCanon.characterAppearances.map((item) => ( + item.characterId === character.id ? { ...item, continuityNotes: event.target.value } : item + )), + }, + }))} + onBlur={() => patchNode({ visualCanon: form.visualCanon })} + /> +
+ )} +
+ ); + })} +
+ + + + + + {(universe?.objects || []).length > 0 && ( +
+ Props and objects + {universe.objects.map((object) => ( + + ))} +
+ )} + + + + + + +