Skip to content

Commit 27ef890

Browse files
committed
Adds script editing and refines message handling
Enables customized script metadata (ID, start/end modes) and unifies message editing for steps and activities. Introduces a dedicated script selection state, collapsible layouts, and a modular editor for improved clarity and maintainability.
1 parent 9bb4e58 commit 27ef890

File tree

10 files changed

+340
-103
lines changed

10 files changed

+340
-103
lines changed

editor/src/App.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { TopBar } from './components/TopBar';
44
import { TabBar } from './components/TabBar';
55
import { Sidebar } from './components/Sidebar';
66
import { NodeDetailsPanel } from './components/NodeDetailsPanel';
7+
import { ScriptEditor } from './components/ScriptEditor';
78
import { DialogManager } from './components/DialogManager';
89
import { DocumentationViewer } from './components/DocumentationViewer';
910
import { Activity, Step, ScriptData, isActivity, isStep, LogicNode } from './types';
@@ -304,6 +305,10 @@ export default function App() {
304305
return undefined;
305306
}
306307

308+
if (state.selectedRef.kind === 'script') {
309+
return undefined;
310+
}
311+
307312
const activityId = state.selectedRef.activityId;
308313
return script.activities.find(a => a.id === activityId);
309314
}, [state.selectedRef, script.activities]);
@@ -313,6 +318,11 @@ export default function App() {
313318
dispatch({ type: 'UPDATE_STEP', stepId, patch });
314319
};
315320

321+
// Helper to update the script data
322+
const updateScript = (patch: Partial<ScriptData>) => {
323+
dispatch({ type: 'LOAD_SCRIPT', script: { ...script, ...patch } });
324+
};
325+
316326
// Ensure step logic is properly initialized when a step is selected
317327
useEffect(() => {
318328
const isStepSelected = !!selectedNode && isStep(selectedNode);
@@ -374,17 +384,28 @@ export default function App() {
374384
onCloneSelected={() => dispatch({ type: 'CLONE_SELECTED' })}
375385
onShowActivityDialog={() => setShowActivityDialog(true)}
376386
onShowStepDialog={() => setShowStepDialog(true)}
387+
onSelectScript={() => dispatch({ type: 'SELECT_SCRIPT' })}
377388
onSelectActivity={(activityId: string) => dispatch({ type: 'SELECT_ACTIVITY', activityId })}
378389
onSelectStep={(activityId: string, stepId: string) => dispatch({ type: 'SELECT_STEP', activityId, stepId })}
379390
onDeleteActivity={deleteActivity}
380391
onDeleteStep={deleteStep}
381392
parentActivityForAdd={parentActivityForAdd}
382393
/>
383-
<NodeDetailsPanel
384-
selectedNode={selectedNode}
385-
onUpdateActivity={updateActivity}
386-
onUpdateStep={updateStep}
387-
/>
394+
{state.selectedRef?.kind === 'script' ? (
395+
<div className="tree-main">
396+
<h2>Script Configuration</h2>
397+
<ScriptEditor
398+
script={script}
399+
onChange={updateScript}
400+
/>
401+
</div>
402+
) : (
403+
<NodeDetailsPanel
404+
selectedNode={selectedNode}
405+
onUpdateActivity={updateActivity}
406+
onUpdateStep={updateStep}
407+
/>
408+
)}
388409
</div>
389410
) : (
390411
<div className="documentation-flex">

editor/src/components/EditPanel.tsx

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { Activity, Step, isActivity } from '../types';
33
import { CollapsibleSection } from './CollapsibleSection';
4+
import { MessageEditor } from './MessageEditor';
45

56
interface EditPanelProps {
67
node: Activity | Step;
@@ -9,8 +10,6 @@ interface EditPanelProps {
910

1011
export const EditPanel: React.FC<EditPanelProps> = ({ node, onChange }) => {
1112
const isActivityNode = isActivity(node);
12-
const intro = node.introMessage;
13-
const endOrSuccess = isActivityNode ? (node as Activity).endMessage : (node as Step).successMessage;
1413

1514
return (
1615
<CollapsibleSection
@@ -24,21 +23,34 @@ export const EditPanel: React.FC<EditPanelProps> = ({ node, onChange }) => {
2423
<div className="edit-field">
2524
<label>ID <input value={node.id} onChange={e => onChange({ id: e.target.value })} /></label>
2625
</div>
27-
<div className="edit-field">
28-
<label>Header <input value={intro.header} onChange={e => onChange({ introMessage: { ...intro, header: e.target.value } as any })} /></label>
29-
</div>
30-
<div className="edit-field">
31-
<label>Description <input value={intro.description} onChange={e => onChange({ introMessage: { ...intro, description: e.target.value } as any })} /></label>
32-
</div>
33-
{!isActivityNode && (
34-
<div className="edit-field">
35-
<label>Success Header <input value={endOrSuccess.header} onChange={e => onChange({ successMessage: { ...endOrSuccess, header: e.target.value } as any })} /></label>
36-
</div>
37-
)}
26+
27+
<MessageEditor
28+
title="Intro Message"
29+
message={node.introMessage}
30+
onChange={introMessage => onChange({ introMessage } as any)}
31+
/>
32+
3833
{isActivityNode && (
39-
<div className="edit-field">
40-
<label>End Header <input value={endOrSuccess.header} onChange={e => onChange({ endMessage: { ...endOrSuccess, header: e.target.value } as any })} /></label>
41-
</div>
34+
<MessageEditor
35+
title="End Message"
36+
message={(node as Activity).endMessage}
37+
onChange={endMessage => onChange({ endMessage } as any)}
38+
/>
39+
)}
40+
41+
{!isActivityNode && (
42+
<>
43+
<MessageEditor
44+
title="Success Message"
45+
message={(node as Step).successMessage}
46+
onChange={successMessage => onChange({ successMessage } as any)}
47+
/>
48+
<MessageEditor
49+
title="Fail Message"
50+
message={(node as Step).failMessage}
51+
onChange={failMessage => onChange({ failMessage } as any)}
52+
/>
53+
</>
4254
)}
4355
</CollapsibleSection>
4456
);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react';
2+
import { Message } from '../types';
3+
4+
interface MessageEditorProps {
5+
title: string;
6+
message: Message;
7+
onChange: (message: Message) => void;
8+
}
9+
10+
export const MessageEditor: React.FC<MessageEditorProps> = ({ title, message, onChange }) => {
11+
const update = (patch: Partial<Message>) => {
12+
onChange({ ...message, ...patch });
13+
};
14+
15+
return (
16+
<div className="message-editor">
17+
<h5 className="message-title">{title}</h5>
18+
19+
<div className="edit-field">
20+
<label>
21+
Header
22+
<input
23+
type="text"
24+
value={message.header}
25+
onChange={e => update({ header: e.target.value })}
26+
placeholder="Enter message header..."
27+
/>
28+
</label>
29+
</div>
30+
31+
<div className="edit-field">
32+
<label>
33+
Description
34+
<input
35+
type="text"
36+
value={message.description || ''}
37+
onChange={e => update({ description: e.target.value })}
38+
placeholder="Optional supporting text..."
39+
/>
40+
</label>
41+
</div>
42+
43+
<div className="edit-field">
44+
<label>
45+
Display Duration
46+
<select
47+
className="cond-input"
48+
value={message.seconds || 0}
49+
onChange={e => update({ seconds: Number(e.target.value) })}
50+
>
51+
<option value={0}>Hidden</option>
52+
{Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
53+
<option key={num} value={num}>{num} second{num > 1 ? 's' : ''}</option>
54+
))}
55+
</select>
56+
</label>
57+
</div>
58+
</div>
59+
);
60+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
import { ScriptData, StartMode, EndMode } from '../types';
3+
import { CollapsibleSection } from './CollapsibleSection';
4+
5+
interface ScriptEditorProps {
6+
script: ScriptData;
7+
onChange: (script: ScriptData) => void;
8+
}
9+
10+
export const ScriptEditor: React.FC<ScriptEditorProps> = ({ script, onChange }) => {
11+
const update = (patch: Partial<ScriptData>) => {
12+
onChange({ ...script, ...patch });
13+
};
14+
15+
return (
16+
<CollapsibleSection
17+
title="Edit Script Configuration"
18+
className="edit-panel"
19+
bodyClassName="edit-panel-body"
20+
defaultOpen
21+
persistKey="script-config"
22+
>
23+
<div className="edit-field">
24+
<label>
25+
ID
26+
<input
27+
type="text"
28+
value={script.id || ''}
29+
onChange={e => update({ id: e.target.value || undefined })}
30+
placeholder="Enter script identifier..."
31+
/>
32+
</label>
33+
</div>
34+
35+
<div className="edit-field">
36+
<label>
37+
Start Mode
38+
<select
39+
className="cond-input"
40+
value={script.startMode || 'Overwrite'}
41+
onChange={e => update({ startMode: e.target.value as StartMode })}
42+
>
43+
<option value="Overwrite">Overwrite</option>
44+
<option value="Append">Append</option>
45+
</select>
46+
</label>
47+
</div>
48+
49+
<div className="edit-field">
50+
<label>
51+
End Mode
52+
<select
53+
className="cond-input"
54+
value={script.endMode || 'Exit'}
55+
onChange={e => update({ endMode: e.target.value as EndMode })}
56+
>
57+
<option value="Exit">Exit</option>
58+
<option value="Wait">Wait</option>
59+
</select>
60+
</label>
61+
</div>
62+
</CollapsibleSection>
63+
);
64+
};

0 commit comments

Comments
 (0)