Skip to content

Commit 8336d12

Browse files
authored
Specify button event button (#166)
1 parent 8fd48ea commit 8336d12

File tree

16 files changed

+487
-116
lines changed

16 files changed

+487
-116
lines changed

backend/app/codegen/app_loader_nim.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ def _as_float(v: Scalar, default: float) -> float:
4141
pass
4242
return default
4343

44-
def _default_literal(field_type: str, default: Scalar, required: bool) -> str:
44+
def _default_literal(field_name: str, field_type: str, default: Scalar, required: bool) -> str:
4545
"""Return a Nim literal/expression usable as the fallback/default element value."""
46-
if field_type in ("string", "text", "select", "font"):
46+
if field_type in ("string", "text", "select", "font", "date"):
4747
return _nim_quote(default if isinstance(default, str) else "")
4848
if field_type == "integer":
4949
return str(_as_int(default, 0))
@@ -65,15 +65,15 @@ def _default_literal(field_type: str, default: Scalar, required: bool) -> str:
6565
# fallback to black if not provided
6666
s = default if isinstance(default, str) and default else "#000000"
6767
return f'parseHtmlColor({_nim_quote(s)})'
68-
raise ValueError(f"Unsupported field type: {field_type}")
68+
raise ValueError(f"Unsupported field type: {field_type}, field name: {field_name}")
6969

7070
def _scalar_getter(field_name: str, field_type: str, default_expr: str, required: bool) -> str:
7171
"""
7272
Build Nim expression to read a scalar from params with default, robust to numbers-as-strings, etc.
7373
Returns a Nim *expression* (often a 'block:' expression).
7474
"""
7575
k = f'params{{"{field_name}"}}'
76-
if field_type in ("string", "text", "select", "font"):
76+
if field_type in ("string", "text", "select", "font", "date"):
7777
return f'{k}.getStr({default_expr})'
7878

7979
if field_type == "integer":
@@ -150,7 +150,7 @@ def _scalar_getter(field_name: str, field_type: str, default_expr: str, required
150150
except CatchableError:
151151
discard
152152
v"""
153-
raise ValueError(f"Unsupported field type: {field_type}")
153+
raise ValueError(f"Unsupported field type: {field_type}, fieldName: {field_name}")
154154

155155
def _field_elem_nim_type(field_type: str, required: bool) -> str:
156156
"""Element type (for seqs)."""
@@ -218,7 +218,7 @@ def _seq_init_expr(field: Dict[str, Any], all_fields: Dict[str, Dict[str, Any]])
218218
ftype = field["type"]
219219
required = bool(field.get("required", False))
220220
elem_type = _field_elem_nim_type(ftype, required)
221-
elem_default = _default_literal(ftype, field.get("value"), required)
221+
elem_default = _default_literal(name, ftype, field.get("value"), required)
222222

223223
seq_spec: List[List[Union[str, int]]] = field.get("seq", [])
224224
dims = len(seq_spec)
@@ -567,7 +567,7 @@ def write_app_loader_nim(app_dir, config: Optional[dict] = None) -> str:
567567
continue
568568

569569
# Scalars
570-
default_expr = _default_literal(field_type, field_default, field_required)
570+
default_expr = _default_literal(field_name, field_type, field_default, field_required)
571571
getter_expr = _scalar_getter(field_name, field_type, default_expr, field_required)
572572
if getter_expr.startswith("block:"):
573573
formatted = _format_block_value_after_colon(getter_expr)

backend/app/codegen/scene_nim.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -698,11 +698,29 @@ def read_nodes(self):
698698

699699
for node in nodes:
700700
next_node = self.next_nodes.get(node["id"], "-1")
701-
self.run_event_lines += [
702-
f" try: discard self.runNode({self.node_id_to_integer(next_node)}.NodeId, context)",
703-
f' except Exception as e: self.logger.log(%*{{"event": "{sanitize_nim_string(event)}:error", ' +
704-
f'"node": {self.node_id_to_integer(next_node)}, "error": $e.msg, "stacktrace": e.getStackTrace()}})'
705-
]
701+
button_label = ""
702+
if event == "button":
703+
button_label = (node.get("data", {}).get("label") or "").strip()
704+
705+
if event == "button" and button_label:
706+
filter_value = sanitize_nim_string(button_label)
707+
condition = (
708+
' if not context.payload.isNil and context.payload.kind == JObject '
709+
'and context.payload.hasKey("label") '
710+
f'and context.payload["label"].getStr() == "{filter_value}":'
711+
)
712+
self.run_event_lines += [
713+
condition,
714+
f" try: discard self.runNode({self.node_id_to_integer(next_node)}.NodeId, context)",
715+
f' except Exception as e: self.logger.log(%*{{"event": "{sanitize_nim_string(event)}:error", ' +
716+
f'"node": {self.node_id_to_integer(next_node)}, "error": $e.msg, "stacktrace": e.getStackTrace()}})'
717+
]
718+
else:
719+
self.run_event_lines += [
720+
f" try: discard self.runNode({self.node_id_to_integer(next_node)}.NodeId, context)",
721+
f' except Exception as e: self.logger.log(%*{{"event": "{sanitize_nim_string(event)}:error", ' +
722+
f'"node": {self.node_id_to_integer(next_node)}, "error": $e.msg, "stacktrace": e.getStackTrace()}})'
723+
]
706724
if not self.event_nodes.get("render", None):
707725
self.run_event_lines += [
708726
'of "render":',

frameos/src/frameos/interpreter.nim

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,16 @@ proc runEvent*(self: FrameScene, context: ExecutionContext) =
897897
for nodeId in scene.eventListeners[context.event]:
898898
let nextNode = if scene.nextNodeIds.hasKey(nodeId): scene.nextNodeIds[nodeId] else: -1.NodeId
899899
if nextNode != 0.NodeId and nextNode != -1.NodeId:
900+
if context.event == "button":
901+
if scene.nodes.hasKey(nodeId):
902+
let node = scene.nodes[nodeId]
903+
if not node.data.isNil and node.data.hasKey("label"):
904+
let buttonFilter = strip(node.data["label"].getStr())
905+
if buttonFilter.len > 0:
906+
if context.payload.isNil or context.payload.kind != JObject:
907+
continue
908+
if not context.payload.hasKey("label") or context.payload["label"].getStr() != buttonFilter:
909+
continue
900910
try:
901911
discard scene.runNode(nextNode, context)
902912
except Exception as e:

frontend/schema/config_json.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,20 @@
229229
"type": "string"
230230
},
231231
"FieldType": {
232-
"enum": ["string", "text", "float", "integer", "boolean", "color", "json", "node", "scene", "image", "font"],
232+
"enum": [
233+
"string",
234+
"text",
235+
"float",
236+
"integer",
237+
"boolean",
238+
"color",
239+
"date",
240+
"json",
241+
"node",
242+
"scene",
243+
"image",
244+
"font"
245+
],
233246
"type": "string"
234247
},
235248
"MarkdownField": {

frontend/src/components/FieldTypeTag.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const typeColors: Record<FieldType, TagProps['color']> = {
88
integer: 'teal',
99
boolean: 'gray',
1010
color: 'orange',
11+
date: 'secondary',
1112
json: 'secondary',
1213
node: 'secondary',
1314
scene: 'secondary',

frontend/src/scenes/frame/panels/Diagram/AppNode.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,16 @@ export function AppNode({ id, isConnectable }: NodeProps<AppNodeData | DispatchN
392392
onChange={(value) => updateNodeConfig(id, field.name, value)}
393393
className="!min-w-[50px]"
394394
/>
395+
) : field.type === 'date' ? (
396+
<TextInput
397+
theme="node"
398+
type="date"
399+
placeholder={field.placeholder}
400+
value={String(
401+
(field.name in data.config ? data.config[field.name] : field.value) ?? ''
402+
)}
403+
onChange={(value) => updateNodeConfig(id, field.name, value)}
404+
/>
395405
) : (
396406
<TextInput
397407
theme="node"

frontend/src/scenes/frame/panels/Diagram/AppNodeEdge.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ export function AppNodeEdge({
3131
style={
3232
isNodeConnection
3333
? selected
34-
? { strokeWidth: 8, stroke: '#ffffff' }
34+
? { strokeWidth: 8, stroke: '#f29cf6' }
3535
: { strokeWidth: 6, stroke: 'hsl(56 60% 70% / 1)' }
3636
: selected
37-
? { strokeWidth: 4, stroke: '#ffffff' }
37+
? { strokeWidth: 4, stroke: '#f29cf6' }
3838
: { strokeWidth: 2, stroke: '#c5c5c5' }
3939
}
4040
/>

frontend/src/scenes/frame/panels/Diagram/CodeNode.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ export function CodeNode({ id, isConnectable }: NodeProps<CodeNodeData>): JSX.El
2828
className={clsx(
2929
'shadow-lg border-2 h-full flex flex-col',
3030
isSelected
31-
? 'bg-black bg-opacity-70 border-indigo-900 shadow-indigo-700/50'
31+
? 'bg-black bg-opacity-70 border-fuchsia-900 shadow-fuchsia-700/50'
3232
: 'bg-black bg-opacity-70 border-green-900 shadow-green-700/50 '
3333
)}
3434
>
3535
<NodeResizer minWidth={200} minHeight={119} />
3636
<div
37-
className={clsx('flex w-full items-center justify-between', isSelected ? 'bg-indigo-900' : 'bg-green-900')}
37+
className={clsx('flex w-full items-center justify-between', isSelected ? 'bg-fuchsia-900' : 'bg-green-900')}
3838
>
3939
<div className={clsx('frameos-node-title text-xl px-1 gap-2', 'flex w-full items-center')}>
4040
{[...(data.codeArgs ?? []), '+'].map((codeField, i) => (
@@ -160,7 +160,7 @@ export function CodeNode({ id, isConnectable }: NodeProps<CodeNodeData>): JSX.El
160160
<div
161161
className={clsx(
162162
'frameos-node-title text-xl px-1 gap-1',
163-
isSelected ? 'bg-indigo-900' : 'bg-green-900',
163+
isSelected ? 'bg-fuchsia-900' : 'bg-green-900',
164164
'flex w-full justify-between items-center'
165165
)}
166166
>

frontend/src/scenes/frame/panels/Diagram/CodeNodeEdge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function CodeNodeEdge({
2828
<BaseEdge
2929
id={id}
3030
path={edgePath}
31-
style={selected ? { strokeWidth: 6, stroke: '#ffffff' } : { strokeWidth: 4, stroke: 'hsl(220 100% 91%)' }}
31+
style={selected ? { strokeWidth: 6, stroke: '#f29cf6' } : { strokeWidth: 4, stroke: 'hsl(220 100% 91%)' }}
3232
/>
3333
<EdgeLabelRenderer>
3434
{selected ? (

frontend/src/scenes/frame/panels/Diagram/Diagram.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ReactFlow, {
1010
EdgeProps,
1111
useUpdateNodeInternals,
1212
ReactFlowProvider,
13+
SelectionMode,
1314
} from 'reactflow'
1415
import { frameLogic } from '../../frameLogic'
1516
import {
@@ -37,7 +38,7 @@ import { CodeNodeEdge } from './CodeNodeEdge'
3738
import { SceneDropDown } from '../Scenes/SceneDropDown'
3839
import { AppNodeEdge } from './AppNodeEdge'
3940
import { NewNodePicker } from './NewNodePicker'
40-
import { getNewFieldName, newNodePickerLogic } from './newNodePickerLogic'
41+
import { CANVAS_NODE_ID, getNewFieldName, newNodePickerLogic } from './newNodePickerLogic'
4142

4243
const nodeTypes: Record<NodeType, (props: NodeProps) => JSX.Element> = {
4344
app: AppNode,
@@ -170,6 +171,27 @@ function Diagram_({ sceneId }: DiagramProps) {
170171
}
171172
}, [])
172173

174+
const onContextMenu = useCallback(
175+
(event: ReactMouseEvent) => {
176+
const target = event.target as HTMLElement | null
177+
const pane = target?.closest('.react-flow__pane') as HTMLElement | null
178+
if (!pane) {
179+
return
180+
}
181+
event.preventDefault()
182+
if (!reactFlowInstance) {
183+
return
184+
}
185+
const reactFlowBounds = reactFlowWrapper.current?.getBoundingClientRect()
186+
const position = reactFlowInstance.project({
187+
x: event.clientX - (reactFlowBounds?.left ?? 0),
188+
y: event.clientY - (reactFlowBounds?.top ?? 0),
189+
})
190+
openNewNodePicker(event.clientX, event.clientY, position.x, position.y, CANVAS_NODE_ID, 'canvas', 'canvas')
191+
},
192+
[openNewNodePicker, reactFlowInstance]
193+
)
194+
173195
useEffect(() => {
174196
if (fitViewCounter > 0) {
175197
reactFlowInstance?.fitView({ maxZoom: 1, padding: 0.2 })
@@ -178,7 +200,7 @@ function Diagram_({ sceneId }: DiagramProps) {
178200

179201
return (
180202
<BindLogic logic={diagramLogic} props={diagramLogicProps}>
181-
<div className="w-full h-full dndflow" ref={reactFlowWrapper}>
203+
<div className="w-full h-full dndflow" ref={reactFlowWrapper} onContextMenu={onContextMenu}>
182204
<ReactFlow
183205
nodes={nodesWithStyle}
184206
edges={edges}
@@ -197,6 +219,7 @@ function Diagram_({ sceneId }: DiagramProps) {
197219
deleteKeyCode={['Backspace', 'Delete']}
198220
nodeTypes={nodeTypes}
199221
edgeTypes={edgeTypes}
222+
selectionMode={SelectionMode.Partial}
200223
>
201224
<Background id="1" gap={24} color="#cccccc" variant={BackgroundVariant.Dots} />
202225
<div className="absolute top-1 right-1 z-10 flex gap-2">

0 commit comments

Comments
 (0)