Skip to content

Commit 1d3c24b

Browse files
committed
Add UIEditor component for RangeAnalysisScriptedUI and PerformanceCenterScriptedUI
Features: - Complete UI configuration editor with multi-select dropdowns for parameters - Visual tag system for selected values with remove functionality - UI Frame Action editors for beforeShot, duringShot, afterShot phases - Real-time validation with proper TypeScript typing - Integrated into NodeDetailsPanel as collapsible 'UI Configuration' section - Responsive CSS design with hover states and accessibility Technical: - Added UIEditor.tsx component with comprehensive parameter/frame management - Added UIEditor.css with modern dropdown and tag styling - Updated NodeDetailsPanel.tsx to include UI editor for supported step types - Created ui-demo.json example demonstrating UI configuration options - Updated authoring-guide.md with detailed UI configuration documentation UI Editor supports: - Target availability toggle - Active data tiles selection (max 8, with visual feedback) - Shot list parameters configuration with default selection - Frame actions for add/remove/disable operations across shot phases - Multi-select dropdowns with search-friendly interface and remove tags
1 parent 83034cd commit 1d3c24b

File tree

6 files changed

+627
-286
lines changed

6 files changed

+627
-286
lines changed

docs/authoring-guide.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,53 @@ Control UI visibility during different shot phases using `beforeShot`, `duringSh
143143
}
144144
```
145145
146+
### UI Configuration
147+
148+
Steps can include a `ui` configuration object to customize the user interface. This is available for both Range Analysis and Performance Center steps.
149+
150+
**Basic Properties**:
151+
- `targetAvailable` (boolean, default: true): Whether targeting functionality is available
152+
- `activeDataTiles` (ParameterName[], max 8): Custom data tiles displayed on screen
153+
- `shotListParameters` (ParameterName[]): Available parameters for shot list selection
154+
- `defaultShotListParameter` (ParameterName, default: "Carry"): Default parameter shown in shot list
155+
156+
**UI Frame Actions**: Control frame visibility during different shot phases
157+
- `beforeShot`: Actions before taking a shot
158+
- `duringShot`: Actions during shot playback
159+
- `afterShot`: Actions after ball stops
160+
161+
**Complete Example**:
162+
```json
163+
{
164+
"ui": {
165+
"nodeType": "RangeAnalysisScriptedUI",
166+
"targetAvailable": true,
167+
"activeDataTiles": ["Total", "Carry", "Curve", "BallSpeed"],
168+
"shotListParameters": ["Total", "Carry", "Curve", "BallSpeed", "LaunchAngle"],
169+
"defaultShotListParameter": "Total",
170+
"beforeShot": {
171+
"addFrames": ["Player", "Tiles"],
172+
"removeFrames": ["Minimap"]
173+
},
174+
"duringShot": {
175+
"removeFrames": ["ShotList"],
176+
"disableFrames": ["GoToSetup"]
177+
},
178+
"afterShot": {
179+
"addFrames": ["ShotList", "AllTiles"]
180+
}
181+
}
182+
}
183+
```
184+
185+
**Available Parameters**: ClubSpeed, AttackAngle, ClubPath, DynamicLoft, FaceAngle, DynamicLie, ImpactHeight, SpinLoft, FaceToPath, SwingPlane, SwingDirection, LowPoint, ImpactOffset, Curve, Height, Carry, Total, Side, SideTotal, LandingAngle, FromPin, BallSpeed, SmashFactor, LaunchAngle, LaunchDirection, SpinRate, SpinAxis, StrokesGained
186+
187+
**Editor Integration**: The built-in editor provides a user-friendly interface for configuring UI settings:
188+
- Toggle target availability
189+
- Multi-select dropdowns for data tiles and parameters (with visual tags)
190+
- Frame action configuration for each shot phase
191+
- Real-time validation and preview
192+
146193
---
147194
148195
## File Anatomy

editor/src/components/NodeDetailsPanel.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ConditionEditor } from './ConditionEditor';
44
import { EditPanel } from './EditPanel';
55
import { CollapsibleSection } from './CollapsibleSection';
66
import { SetupEditor } from './SetupEditor';
7+
import { UIEditor } from './UIEditor';
78

89
interface NodeDetailsPanelProps {
910
selectedNode: Activity | Step | null;
@@ -37,9 +38,9 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
3738
node={selectedNode}
3839
onChange={(patch: Partial<Activity> | Partial<Step>) => {
3940
if (isActivitySelected) {
40-
updateActivity(selectedNode.id, patch as Partial<Activity>);
41+
updateActivity(selectedNode.id!, patch as Partial<Activity>);
4142
} else if (isStepSelected) {
42-
updateStep(selectedNode.id, patch as Partial<Step>);
43+
updateStep(selectedNode.id!, patch as Partial<Step>);
4344
}
4445
}}
4546
/>
@@ -56,7 +57,7 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
5657
showConditionType={true}
5758
onChange={(c) => {
5859
const s = selectedNode as Step;
59-
updateStep(s.id, { logic: { ...s.logic, successCondition: c } });
60+
updateStep(s.id!, { logic: { ...s.logic, successCondition: c } });
6061
}}
6162
/>
6263
<ConditionEditor
@@ -65,15 +66,24 @@ export const NodeDetailsPanel: React.FC<NodeDetailsPanelProps> = ({
6566
showConditionType={true}
6667
onChange={(c) => {
6768
const s = selectedNode as Step;
68-
updateStep(s.id, { logic: { ...s.logic, failCondition: c } });
69+
updateStep(s.id!, { logic: { ...s.logic, failCondition: c } });
6970
}}
7071
/>
7172
<div className="logic-flags">
72-
<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>
73-
<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>
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>
7475
</div>
7576
</CollapsibleSection>
7677
)}
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`}>
81+
<UIEditor
82+
step={selectedNode as Step}
83+
onUpdateStep={updateStep}
84+
/>
85+
</CollapsibleSection>
86+
)}
7787
<pre>{JSON.stringify(selectedNode, null, 2)}</pre>
7888
</>
7989
) : (

editor/src/components/UIEditor.css

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/* UI Editor Styles */
2+
.ui-editor {
3+
padding: 1rem;
4+
border: 1px solid #e0e0e0;
5+
border-radius: 4px;
6+
margin-bottom: 1rem;
7+
}
8+
9+
.ui-basic-settings {
10+
margin-bottom: 1.5rem;
11+
}
12+
13+
.ui-basic-settings label {
14+
display: flex;
15+
align-items: center;
16+
gap: 0.5rem;
17+
font-weight: 500;
18+
}
19+
20+
.ui-data-settings {
21+
margin-bottom: 1.5rem;
22+
}
23+
24+
.ui-data-settings > div {
25+
margin-bottom: 1rem;
26+
}
27+
28+
.single-select label {
29+
display: block;
30+
font-weight: 500;
31+
margin-bottom: 0.25rem;
32+
}
33+
34+
.single-select select {
35+
width: 100%;
36+
padding: 0.5rem;
37+
border: 1px solid #ccc;
38+
border-radius: 4px;
39+
font-size: 0.9rem;
40+
}
41+
42+
.help-text {
43+
display: block;
44+
color: #666;
45+
font-size: 0.8rem;
46+
margin-top: 0.25rem;
47+
}
48+
49+
.ui-frame-actions {
50+
border-top: 1px solid #e0e0e0;
51+
padding-top: 1rem;
52+
}
53+
54+
.ui-frame-actions h3 {
55+
margin: 0 0 1rem 0;
56+
font-size: 1.1rem;
57+
color: #333;
58+
}
59+
60+
.ui-frame-action {
61+
margin-bottom: 1.5rem;
62+
padding: 1rem;
63+
background-color: #f8f9fa;
64+
border-radius: 4px;
65+
}
66+
67+
.ui-frame-action h4 {
68+
margin: 0 0 1rem 0;
69+
font-size: 1rem;
70+
color: #495057;
71+
}
72+
73+
/* Multi-select dropdown styles */
74+
.multi-select-dropdown {
75+
margin-bottom: 0.75rem;
76+
}
77+
78+
.multi-select-dropdown label {
79+
display: block;
80+
font-weight: 500;
81+
margin-bottom: 0.25rem;
82+
color: #333;
83+
}
84+
85+
.dropdown-container {
86+
position: relative;
87+
}
88+
89+
.dropdown-toggle {
90+
width: 100%;
91+
padding: 0.5rem;
92+
border: 1px solid #ccc;
93+
border-radius: 4px;
94+
background-color: white;
95+
text-align: left;
96+
cursor: pointer;
97+
display: flex;
98+
justify-content: space-between;
99+
align-items: center;
100+
font-size: 0.9rem;
101+
}
102+
103+
.dropdown-toggle:hover {
104+
border-color: #999;
105+
}
106+
107+
.dropdown-arrow {
108+
font-size: 0.7rem;
109+
color: #666;
110+
}
111+
112+
.dropdown-menu {
113+
position: absolute;
114+
top: 100%;
115+
left: 0;
116+
right: 0;
117+
background-color: white;
118+
border: 1px solid #ccc;
119+
border-top: none;
120+
border-radius: 0 0 4px 4px;
121+
max-height: 200px;
122+
overflow-y: auto;
123+
z-index: 1000;
124+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
125+
}
126+
127+
.dropdown-option {
128+
display: flex;
129+
align-items: center;
130+
padding: 0.5rem;
131+
cursor: pointer;
132+
font-size: 0.9rem;
133+
gap: 0.5rem;
134+
}
135+
136+
.dropdown-option:hover {
137+
background-color: #f0f0f0;
138+
}
139+
140+
.dropdown-option input[type="checkbox"] {
141+
margin: 0;
142+
}
143+
144+
.dropdown-option:has(input:disabled) {
145+
opacity: 0.5;
146+
cursor: not-allowed;
147+
}
148+
149+
.selected-values {
150+
margin-top: 0.5rem;
151+
display: flex;
152+
flex-wrap: wrap;
153+
gap: 0.25rem;
154+
}
155+
156+
.selected-tag {
157+
display: inline-flex;
158+
align-items: center;
159+
background-color: #007bff;
160+
color: white;
161+
padding: 0.2rem 0.5rem;
162+
border-radius: 3px;
163+
font-size: 0.8rem;
164+
gap: 0.25rem;
165+
}
166+
167+
.remove-tag {
168+
background: none;
169+
border: none;
170+
color: white;
171+
cursor: pointer;
172+
font-size: 1rem;
173+
line-height: 1;
174+
padding: 0;
175+
margin-left: 0.25rem;
176+
}
177+
178+
.remove-tag:hover {
179+
background-color: rgba(255,255,255,0.2);
180+
border-radius: 2px;
181+
}
182+
183+
/* Responsive adjustments */
184+
@media (max-width: 768px) {
185+
.ui-editor {
186+
padding: 0.75rem;
187+
}
188+
189+
.ui-frame-action {
190+
padding: 0.75rem;
191+
}
192+
193+
.dropdown-menu {
194+
max-height: 150px;
195+
}
196+
}

0 commit comments

Comments
 (0)