Skip to content

Commit 41a12cf

Browse files
committed
Refactors node editing with sub-tabs, toggles message fields
Implements a multi-tab node editor, consolidating metadata, logic, UI, and script views for clearer workflow Comments out debug display to reduce clutter Shows or hides message fields based on duration, improving clarity
1 parent f1a8241 commit 41a12cf

File tree

9 files changed

+318
-81
lines changed

9 files changed

+318
-81
lines changed

editor/src/App.tsx

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import './treeview.css';
33
import { TopBar } from './components/TopBar';
44
import { TabBar } from './components/TabBar';
55
import { Sidebar } from './components/Sidebar';
6-
import { NodeDetailsPanel } from './components/NodeDetailsPanel';
6+
// ...existing code...
77
import { ScriptEditor } from './components/ScriptEditor';
8+
import { NodeEditor } from './components/NodeEditor';
89
import { DialogManager } from './components/DialogManager';
910
import { DocViewer } from './components/DocViewer';
1011
import { EnvironmentDebug } from './components/EnvironmentDebug';
@@ -638,7 +639,7 @@ export default function App() {
638639
activeTab={activeTab}
639640
onTabChange={setActiveTab}
640641
/>
641-
<EnvironmentDebug />
642+
{/* <EnvironmentDebug /> */}
642643
{activeTab === 'edit' ? (
643644
<div className="tree-flex">
644645
<DialogManager
@@ -676,22 +677,26 @@ export default function App() {
676677
onDeleteStep={deleteStep}
677678
parentActivityForAdd={parentActivityForAdd}
678679
/>
679-
{state.selectedRef?.kind === 'script' ? (
680-
<div className="tree-main">
681-
<h2>Script Configuration</h2>
682-
<ScriptEditor
683-
script={script}
684-
onChange={updateScript}
680+
<div className="tree-main">
681+
{state.selectedRef?.kind === 'script' ? (
682+
<>
683+
<h2>Script Configuration</h2>
684+
<ScriptEditor
685+
script={script}
686+
onChange={updateScript}
687+
/>
688+
<pre>{JSON.stringify(script, null, 2)}</pre>
689+
</>
690+
) : selectedNode ? (
691+
<NodeEditor
692+
node={selectedNode}
693+
onUpdateActivity={updateActivity}
694+
onUpdateStep={updateStep}
685695
/>
686-
<pre>{JSON.stringify(script, null, 2)}</pre>
687-
</div>
688-
) : (
689-
<NodeDetailsPanel
690-
selectedNode={selectedNode}
691-
onUpdateActivity={updateActivity}
692-
onUpdateStep={updateStep}
693-
/>
694-
)}
696+
) : (
697+
<div className="empty-node-editor">Select a node to edit its details.</div>
698+
)}
699+
</div>
695700
</div>
696701
) : (
697702
<div className="documentation-flex">

editor/src/components/MessageEditor.tsx

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,12 @@ export const MessageEditor: React.FC<MessageEditorProps> = ({ title, message, on
1515
onChange({ ...currentMessage, ...patch });
1616
};
1717

18+
const isHidden = currentMessage.seconds === 0;
19+
1820
return (
1921
<div className="message-editor">
2022
<h5 className="message-title">{title}</h5>
2123

22-
<div className="edit-field">
23-
<label>
24-
Header
25-
<input
26-
type="text"
27-
value={currentMessage.header}
28-
onChange={e => update({ header: e.target.value })}
29-
placeholder="Enter message header..."
30-
/>
31-
</label>
32-
</div>
33-
34-
<div className="edit-field">
35-
<label>
36-
Description
37-
<input
38-
type="text"
39-
value={currentMessage.description || ''}
40-
onChange={e => update({ description: e.target.value })}
41-
placeholder="Optional supporting text..."
42-
/>
43-
</label>
44-
</div>
45-
4624
<div className="edit-field">
4725
<label>
4826
Display Duration
@@ -52,12 +30,41 @@ export const MessageEditor: React.FC<MessageEditorProps> = ({ title, message, on
5230
onChange={e => update({ seconds: Number(e.target.value) })}
5331
>
5432
<option value={0}>Hidden</option>
33+
<option value={-1}>Need to accept</option>
5534
{Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
5635
<option key={num} value={num}>{num} second{num > 1 ? 's' : ''}</option>
5736
))}
5837
</select>
5938
</label>
6039
</div>
40+
41+
{!isHidden && (
42+
<>
43+
<div className="edit-field">
44+
<label>
45+
Header
46+
<input
47+
type="text"
48+
value={currentMessage.header}
49+
onChange={e => update({ header: e.target.value })}
50+
placeholder="Enter message header..."
51+
/>
52+
</label>
53+
</div>
54+
55+
<div className="edit-field">
56+
<label>
57+
Description
58+
<input
59+
type="text"
60+
value={currentMessage.description || ''}
61+
onChange={e => update({ description: e.target.value })}
62+
placeholder="Optional supporting text..."
63+
/>
64+
</label>
65+
</div>
66+
</>
67+
)}
6168
</div>
6269
);
6370
};

editor/src/components/NodeDetailsPanel.tsx

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { SubTabBar } from './SubTabBar';
23
import { Activity, Step, isActivity, isStep, ConditionGroup } from '../types';
34
import { ConditionEditor } from './ConditionEditor';
45
import { EditPanel } from './EditPanel';
@@ -28,25 +29,37 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
2829
onUpdateActivity(activityId, patch);
2930
};
3031

32+
33+
const [activeTab, setActiveTab] = React.useState('Meta Data');
34+
const tabList = ['Meta Data', 'Logic', 'UI Configuration', 'Script'];
35+
36+
// Helper for safe logic update
37+
function safeUpdateStepLogic(s: Step, patch: Partial<Step['logic']>) {
38+
updateStep(s.id!, { logic: { ...s.logic, ...patch } } as Partial<Step>);
39+
}
40+
3141
return (
3242
<div className="tree-main">
3343
<h2>Node Details</h2>
3444
{selectedNode ? (
3545
<>
36-
{/* For both activities and steps, show metadata first */}
37-
<EditPanel
38-
node={selectedNode}
39-
onChange={(patch: Partial<Activity> | Partial<Step>) => {
40-
if (isActivitySelected) {
41-
updateActivity(selectedNode.id!, patch as Partial<Activity>);
42-
} else if (isStepSelected) {
43-
updateStep(selectedNode.id!, patch as Partial<Step>);
44-
}
45-
}}
46-
/>
47-
{/* Step-only logic section follows metadata */}
48-
{isStepSelected && (selectedNode as Step).logic && (
49-
<CollapsibleSection title="Logic" className="logic-block" bodyClassName="logic-inner" defaultOpen persistKey={`${(selectedNode as Step).id}-logic`}>
46+
<SubTabBar tabs={tabList} activeTab={activeTab} onTabChange={setActiveTab} />
47+
48+
{activeTab === 'Meta Data' && (
49+
<EditPanel
50+
node={selectedNode}
51+
onChange={(patch: Partial<Activity> | Partial<Step>) => {
52+
if (isActivitySelected) {
53+
updateActivity(selectedNode.id!, patch as Partial<Activity>);
54+
} else if (isStepSelected) {
55+
updateStep(selectedNode.id!, patch as Partial<Step>);
56+
}
57+
}}
58+
/>
59+
)}
60+
61+
{activeTab === 'Logic' && isStepSelected && (selectedNode as Step).logic && (
62+
<div className="logic-block logic-inner">
5063
<SetupEditor
5164
step={selectedNode as Step}
5265
onUpdateStep={updateStep}
@@ -57,7 +70,7 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
5770
showConditionType={true}
5871
onChange={(c) => {
5972
const s = selectedNode as Step;
60-
updateStep(s.id!, { logic: { ...s.logic, successCondition: c } });
73+
safeUpdateStepLogic(s, { successCondition: c });
6174
}}
6275
/>
6376
<ConditionEditor
@@ -66,25 +79,28 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
6679
showConditionType={true}
6780
onChange={(c) => {
6881
const s = selectedNode as Step;
69-
updateStep(s.id!, { logic: { ...s.logic, failCondition: c } });
82+
safeUpdateStepLogic(s, { failCondition: c });
7083
}}
7184
/>
7285
<div className="logic-flags">
73-
<label>Can Retry: <input type="checkbox" checked={!!(selectedNode as Step).logic.canRetry} onChange={e => { const s = selectedNode as Step; updateStep(s.id!, { logic: { ...s.logic, canRetry: e.target.checked } }); }} /></label>
74-
<label>Skip On Success: <input type="checkbox" checked={!!(selectedNode as Step).logic.skipOnSuccess} onChange={e => { const s = selectedNode as Step; updateStep(s.id!, { logic: { ...s.logic, skipOnSuccess: e.target.checked } }); }} /></label>
86+
<label>Can Retry: <input type="checkbox" checked={!!(selectedNode as Step).logic.canRetry} onChange={e => { const s = selectedNode as Step; safeUpdateStepLogic(s, { canRetry: e.target.checked }); }} /></label>
87+
<label>Skip On Success: <input type="checkbox" checked={!!(selectedNode as Step).logic.skipOnSuccess} onChange={e => { const s = selectedNode as Step; safeUpdateStepLogic(s, { skipOnSuccess: e.target.checked }); }} /></label>
7588
</div>
76-
</CollapsibleSection>
89+
</div>
7790
)}
78-
{/* UI Editor section for steps that support UI configuration */}
79-
{isStepSelected && ((selectedNode as Step).nodeType === 'RangeAnalysisScriptedStep' || (selectedNode as Step).nodeType === 'PerformanceCenterScriptedStep') && (
80-
<CollapsibleSection title="UI Configuration" className="ui-block" bodyClassName="ui-inner" defaultOpen persistKey={`${(selectedNode as Step).id}-ui`}>
91+
92+
{activeTab === 'UI Configuration' && isStepSelected && ((selectedNode as Step).nodeType === 'RangeAnalysisScriptedStep' || (selectedNode as Step).nodeType === 'PerformanceCenterScriptedStep') && (
93+
<div className="ui-block ui-inner">
8194
<UIEditor
8295
step={selectedNode as Step}
8396
onUpdateStep={updateStep}
8497
/>
85-
</CollapsibleSection>
98+
</div>
99+
)}
100+
101+
{activeTab === 'Script' && (
102+
<pre>{JSON.stringify(selectedNode, null, 2)}</pre>
86103
)}
87-
<pre>{JSON.stringify(selectedNode, null, 2)}</pre>
88104
</>
89105
) : (
90106
<p>Select an activity or step from the tree.</p>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React, { useState } from 'react';
2+
import { Activity, Step, isActivity, isStep } from '../types';
3+
import { SubTabBar } from './SubTabBar';
4+
import { SetupEditor } from './SetupEditor';
5+
import { UIEditor } from './UIEditor';
6+
import { MessageEditor } from './MessageEditor';
7+
8+
interface NodeEditorProps {
9+
node: Activity | Step;
10+
onUpdateActivity?: (activityId: string, patch: Partial<Activity>) => void;
11+
onUpdateStep?: (stepId: string, patch: Partial<Step>) => void;
12+
}
13+
14+
const ACTIVITY_TABS = ['Meta Data', 'Script'];
15+
const STEP_TABS = ['Meta Data', 'Logic', 'UI Configuration', 'Script'];
16+
17+
export const NodeEditor: React.FC<NodeEditorProps> = ({ node, onUpdateActivity, onUpdateStep }) => {
18+
const [activeTab, setActiveTab] = useState('Meta Data');
19+
20+
if (!node) return null;
21+
22+
// Activity editing
23+
if (isActivity(node)) {
24+
return (
25+
<div className="node-editor">
26+
<div className="tab-bar-wrapper">
27+
<SubTabBar tabs={ACTIVITY_TABS} activeTab={activeTab} onTabChange={setActiveTab} />
28+
</div>
29+
<div className="tab-panel">
30+
{activeTab === 'Meta Data' && (
31+
<MessageEditor
32+
title="Intro Message"
33+
message={node.introMessage}
34+
onChange={msg => onUpdateActivity?.(node.id!, { introMessage: msg })}
35+
/>
36+
)}
37+
{activeTab === 'Script' && (
38+
<pre>{JSON.stringify(node, null, 2)}</pre>
39+
)}
40+
</div>
41+
</div>
42+
);
43+
}
44+
45+
// Step editing
46+
if (isStep(node)) {
47+
return (
48+
<div className="node-editor" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
49+
<div className="tab-bar-wrapper">
50+
<SubTabBar tabs={STEP_TABS} activeTab={activeTab} onTabChange={setActiveTab} />
51+
</div>
52+
<div className="tab-panel" style={{ flex: 1, overflowY: 'auto' }}>
53+
{activeTab === 'Meta Data' && (
54+
<>
55+
<MessageEditor
56+
title="Intro Message"
57+
message={node.introMessage}
58+
onChange={msg => onUpdateStep?.(node.id!, { introMessage: msg })}
59+
/>
60+
<MessageEditor
61+
title="Success Message"
62+
message={node.successMessage}
63+
onChange={msg => onUpdateStep?.(node.id!, { successMessage: msg })}
64+
/>
65+
<MessageEditor
66+
title="Fail Message"
67+
message={node.failMessage}
68+
onChange={msg => onUpdateStep?.(node.id!, { failMessage: msg })}
69+
/>
70+
</>
71+
)}
72+
{activeTab === 'Logic' && (
73+
<SetupEditor step={node} onUpdateStep={onUpdateStep!} />
74+
)}
75+
{activeTab === 'UI Configuration' && (
76+
<UIEditor step={node} onUpdateStep={onUpdateStep!} />
77+
)}
78+
{activeTab === 'Script' && (
79+
<pre>{JSON.stringify(node, null, 2)}</pre>
80+
)}
81+
</div>
82+
</div>
83+
);
84+
}
85+
86+
return null;
87+
};

editor/src/components/SetupEditor.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface SetupEditorProps {
88
}
99

1010
export const SetupEditor: React.FC<SetupEditorProps> = ({ step, onUpdateStep }) => {
11-
const setup = step.logic?.setup || {};
11+
const setup = step.logic?.setup as any || {};
1212
const nodeType = setup.nodeType || '';
1313

1414
// Check if this is a Performance Center step that can have different setup types
@@ -25,7 +25,7 @@ export const SetupEditor: React.FC<SetupEditorProps> = ({ step, onUpdateStep })
2525
...setupPatch
2626
}
2727
};
28-
onUpdateStep(step.id, { logic: newLogic });
28+
onUpdateStep(step.id!, { logic: newLogic } as any);
2929
};
3030

3131
const switchSetupType = (newNodeType: string) => {
@@ -153,14 +153,27 @@ export const SetupEditor: React.FC<SetupEditorProps> = ({ step, onUpdateStep })
153153
</div>
154154
</div>
155155

156-
{/* Common Fields */}
156+
{/* Hole Field - Different UI for Approach vs Tee shots */}
157157
<div className="edit-field">
158158
<label>Hole</label>
159-
<HoleSelector
160-
selectedHole={setup.hole || 1}
161-
onHoleSelect={(holeNumber) => updateSetup({ hole: holeNumber })}
162-
setupType={isTeeShotsSetup ? 'tee' : 'approach'}
163-
/>
159+
{isApproachSetup ? (
160+
// Simple dropdown for Approach shots
161+
<select
162+
className="cond-input"
163+
value={setup.hole || 1}
164+
onChange={e => updateSetup({ hole: parseInt(e.target.value) || 1 })}
165+
>
166+
{Array.from({ length: 9 }, (_, i) => (
167+
<option key={i + 1} value={i + 1}>Hole {i + 1}</option>
168+
))}
169+
</select>
170+
) : (
171+
// Full hole layout selector for Tee shots
172+
<HoleSelector
173+
selectedHole={setup.hole || 1}
174+
onHoleSelect={(holeNumber) => updateSetup({ hole: holeNumber })}
175+
/>
176+
)}
164177
</div>
165178

166179
<div className="edit-field">

0 commit comments

Comments
 (0)