Skip to content

Commit 0b88ba3

Browse files
Merge pull request #685 from cruxcode/state-orchestration
Implemented select and hover states in state machine
2 parents 4deabdc + 22e3ecd commit 0b88ba3

File tree

6 files changed

+245
-10
lines changed

6 files changed

+245
-10
lines changed

‎packages/atri-app-core/src/api/canvasApi.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,39 @@ if (typeof window !== "undefined") {
218218
},
219219
true
220220
);
221+
window.addEventListener(
222+
"mousedown",
223+
(ev) => {
224+
canvasMachineInterpreter.send({
225+
type: "MOUSE_DOWN",
226+
event: { pageX: ev.pageX, pageY: ev.pageY, target: ev.target },
227+
});
228+
},
229+
true
230+
);
231+
window.addEventListener(
232+
"mouseover",
233+
(ev) => {
234+
canvasMachineInterpreter.send({
235+
type: "MOUSE_OVER",
236+
event: { pageX: ev.pageX, pageY: ev.pageY, target: ev.target },
237+
});
238+
},
239+
true
240+
);
221241
window.addEventListener("scroll", () => {
222242
canvasMachineInterpreter.send({ type: "SCROLL" });
223243
});
244+
window.addEventListener(
245+
"blur",
246+
() => {
247+
canvasMachineInterpreter.send({
248+
type: "BLUR",
249+
});
250+
},
251+
true
252+
);
253+
224254
if (window.location !== window.parent.location) {
225255
canvasMachineInterpreter.send({ type: "IFRAME_DETECTED" });
226256
} else {

‎packages/atri-app-core/src/canvasMachine.ts‎

Lines changed: 177 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ const INSIDE_CANVAS = "INSIDE_CANVAS" as const;
1616
const OUTSIDE_CANVAS = "OUTSIDE_CANVAS" as const;
1717
const MOUSE_MOVE = "MOUSE_MOVE" as const;
1818
const MOUSE_UP = "MOUSE_UP" as const;
19+
const MOUSE_DOWN = "MOUSE_DOWN" as const;
20+
const MOUSE_OVER = "MOUSE_OVER" as const;
1921
const COMPONENT_CREATED = "COMPONENT_CREATED" as const; // emitted only after drag-drop
2022
const SCROLL = "SCROLL" as const;
23+
const BLUR = "BLUR" as const;
24+
const COMPONENT_RENDERED = "COMPONENT_RENDERED" as const;
2125

2226
type IFRAME_DETECTED_EVENT = { type: typeof IFRAME_DETECTED };
2327
type TOP_WINDOW_DETECTED_EVENT = { type: typeof TOP_WINDOW_DETECTED };
@@ -46,6 +50,14 @@ type MOUSE_UP_EVENT = {
4650
type: typeof MOUSE_UP;
4751
event: { pageX: number; pageY: number; target: MouseEvent["target"] };
4852
};
53+
type MOUSE_DOWN_EVENT = {
54+
type: typeof MOUSE_DOWN;
55+
event: { pageX: number; pageY: number; target: MouseEvent["target"] };
56+
};
57+
type MOUSE_OVER_EVENT = {
58+
type: typeof MOUSE_OVER;
59+
event: { pageX: number; pageY: number; target: MouseEvent["target"] };
60+
};
4961
type COMPONENT_CREATED_EVENT = {
5062
type: typeof COMPONENT_CREATED;
5163
compId: string;
@@ -55,6 +67,13 @@ type COMPONENT_CREATED_EVENT = {
5567
type SCROLL_EVENT = {
5668
type: typeof SCROLL;
5769
};
70+
type BLUR_EVENT = {
71+
type: typeof BLUR;
72+
};
73+
type COMPONENT_RENDERED_EVENT = {
74+
type: typeof COMPONENT_RENDERED;
75+
compId: string;
76+
};
5877

5978
type CanvasMachineEvent =
6079
| IFRAME_DETECTED_EVENT
@@ -66,8 +85,12 @@ type CanvasMachineEvent =
6685
| OUTSIDE_CANVAS_EVENT
6786
| MOUSE_MOVE_EVENT
6887
| MOUSE_UP_EVENT
88+
| MOUSE_DOWN_EVENT
89+
| MOUSE_OVER_EVENT
6990
| COMPONENT_CREATED_EVENT
70-
| SCROLL_EVENT;
91+
| SCROLL_EVENT
92+
| BLUR_EVENT
93+
| COMPONENT_RENDERED_EVENT;
7194

7295
// states
7396
const initial = "initial" as const;
@@ -80,6 +103,12 @@ const drag_in_progress_active = "drag_in_progress_active" as const;
80103
// inside ready
81104
const idle = "idle" as const;
82105
const hover = "hover" as const;
106+
const pressed = "pressed" as const;
107+
const selected = "selected" as const;
108+
const focused = "focused" as const;
109+
const unfocused = "unfocused" as const;
110+
const selectIdle = "selectIdle" as const;
111+
const hoverWhileSelected = "hoverWhileSelected" as const;
83112

84113
// context
85114

@@ -94,6 +123,8 @@ type CanvasMachineContext = {
94123
target: MouseEvent["target"];
95124
} | null;
96125
hovered: string | null;
126+
selected: string | null;
127+
lastDropped: string | null; // string until COMPONENT_RENDERED received, otherwise null
97128
};
98129

99130
// actions
@@ -108,7 +139,7 @@ function setDragData(
108139

109140
function setMousePosition(
110141
context: CanvasMachineContext,
111-
event: MOUSE_MOVE_EVENT | MOUSE_UP_EVENT
142+
event: MOUSE_MOVE_EVENT | MOUSE_UP_EVENT | MOUSE_DOWN_EVENT
112143
) {
113144
context.mousePosition = event.event;
114145
}
@@ -127,11 +158,37 @@ function setHoverComponent(
127158
}
128159
}
129160

161+
function setSelectedComponent(
162+
context: CanvasMachineContext,
163+
event: MOUSE_DOWN_EVENT
164+
) {
165+
const { target } = event.event;
166+
if (target !== null && "closest" in target) {
167+
const comp = (target as any).closest("[data-atri-comp-id]");
168+
if (comp !== null) {
169+
const compId = comp.getAttribute("data-atri-comp-id");
170+
context.selected = compId;
171+
}
172+
}
173+
}
174+
175+
function setLastDropped(
176+
context: CanvasMachineContext,
177+
event: COMPONENT_CREATED_EVENT
178+
) {
179+
context.lastDropped = event.compId;
180+
}
181+
182+
function handleComponentRendered(context: CanvasMachineContext) {
183+
context.selected = context.lastDropped;
184+
context.lastDropped = null;
185+
}
186+
130187
// conds
131188

132189
function insideComponent(
133190
_context: CanvasMachineContext,
134-
event: MOUSE_MOVE_EVENT
191+
event: MOUSE_MOVE_EVENT | MOUSE_DOWN_EVENT
135192
) {
136193
const { target } = event.event;
137194
if (target !== null && "closest" in target) {
@@ -158,6 +215,28 @@ function hoveringOverDifferentComponent(
158215
return false;
159216
}
160217

218+
function selectedDifferentComponent(
219+
context: CanvasMachineContext,
220+
event: MOUSE_DOWN_EVENT | MOUSE_OVER_EVENT
221+
) {
222+
const { target } = event.event;
223+
if (target !== null && "closest" in target) {
224+
const comp = (target as any).closest("[data-atri-comp-id]");
225+
if (comp !== null) {
226+
const compId = comp.getAttribute("data-atri-comp-id");
227+
return compId !== context.selected;
228+
}
229+
}
230+
return false;
231+
}
232+
233+
function isLastDroppedComponent(
234+
context: CanvasMachineContext,
235+
event: COMPONENT_RENDERED_EVENT
236+
) {
237+
return context.lastDropped === event.compId;
238+
}
239+
161240
type Callback = (
162241
context: CanvasMachineContext,
163242
event: CanvasMachineEvent
@@ -170,7 +249,10 @@ type SubscribeStates =
170249
| typeof OUTSIDE_CANVAS
171250
| typeof COMPONENT_CREATED
172251
| "hover"
173-
| "hoverEnd";
252+
| "hoverEnd"
253+
| "focus"
254+
| "focusEnd"
255+
| typeof COMPONENT_RENDERED;
174256

175257
export function createCanvasMachine(id: string) {
176258
const subscribers: { [key in SubscribeStates]: Callback[] } = {
@@ -182,6 +264,9 @@ export function createCanvasMachine(id: string) {
182264
[COMPONENT_CREATED]: [],
183265
hover: [],
184266
hoverEnd: [],
267+
focus: [],
268+
focusEnd: [],
269+
COMPONENT_RENDERED: [],
185270
};
186271
function subscribeCanvasMachine(state: SubscribeStates, cb: Callback) {
187272
subscribers[state].push(cb);
@@ -192,6 +277,7 @@ export function createCanvasMachine(id: string) {
192277
}
193278
};
194279
}
280+
195281
function callSubscribers(
196282
state: SubscribeStates,
197283
context: CanvasMachineContext,
@@ -215,6 +301,7 @@ export function createCanvasMachine(id: string) {
215301
callSubscribers(state, context, event);
216302
};
217303
}
304+
218305
const canvasMachine = createMachine<CanvasMachineContext, CanvasMachineEvent>(
219306
{
220307
id,
@@ -227,6 +314,8 @@ export function createCanvasMachine(id: string) {
227314
dragData: null,
228315
mousePosition: null,
229316
hovered: null,
317+
selected: null,
318+
lastDropped: null,
230319
},
231320
states: {
232321
[initial]: {
@@ -250,28 +339,102 @@ export function createCanvasMachine(id: string) {
250339
cond: insideComponent,
251340
actions: ["setHoverComponent"],
252341
},
342+
[COMPONENT_RENDERED]: {
343+
target: selected,
344+
cond: isLastDroppedComponent,
345+
actions: ["handleComponentRendered", "emitComponentRendered"],
346+
},
253347
},
254348
},
255349
[hover]: {
350+
entry: (context, event) => {
351+
callSubscribers("hover", context, event);
352+
},
353+
exit: (context, event) => {
354+
context.hovered = null;
355+
callSubscribers("hoverEnd", context, event);
356+
},
256357
on: {
257358
[MOUSE_MOVE]: {
258359
target: hover,
259360
cond: hoveringOverDifferentComponent,
260361
actions: ["setHoverComponent"],
261362
},
363+
[MOUSE_DOWN]: {
364+
target: pressed,
365+
},
262366
[SCROLL]: {
263367
target: idle,
264368
},
265369
[OUTSIDE_CANVAS]: {
266370
target: idle,
267371
},
268372
},
269-
entry: (context, event) => {
270-
callSubscribers("hover", context, event);
373+
},
374+
[pressed]: {
375+
on: {
376+
[MOUSE_UP]: {
377+
target: selected,
378+
actions: ["setSelectedComponent"],
379+
},
271380
},
272-
exit: (context, event) => {
273-
context.hovered = null;
274-
callSubscribers("hoverEnd", context, event);
381+
},
382+
[selected]: {
383+
exit: (context) => {
384+
context.selected = null;
385+
},
386+
on: {
387+
[MOUSE_DOWN]: {
388+
target: pressed,
389+
cond: selectedDifferentComponent,
390+
},
391+
},
392+
type: "parallel",
393+
states: {
394+
focusstates: {
395+
initial: focused,
396+
states: {
397+
[focused]: {
398+
entry: (context, event) => {
399+
callSubscribers("focus", context, event);
400+
},
401+
exit: (context, event) => {
402+
callSubscribers("focusEnd", context, event);
403+
},
404+
on: {
405+
[BLUR]: {
406+
target: unfocused,
407+
},
408+
},
409+
},
410+
[unfocused]: {
411+
type: "final",
412+
},
413+
},
414+
},
415+
hoverstates: {
416+
initial: selectIdle,
417+
states: {
418+
[selectIdle]: {
419+
on: {
420+
[MOUSE_OVER]: {
421+
target: hoverWhileSelected,
422+
cond: selectedDifferentComponent,
423+
},
424+
},
425+
},
426+
[hoverWhileSelected]: {
427+
on: {
428+
[MOUSE_OVER]: {
429+
target: hoverWhileSelected,
430+
cond: selectedDifferentComponent,
431+
},
432+
[SCROLL]: { target: selectIdle },
433+
[OUTSIDE_CANVAS]: { target: selectIdle },
434+
},
435+
},
436+
},
437+
},
275438
},
276439
},
277440
},
@@ -281,7 +444,7 @@ export function createCanvasMachine(id: string) {
281444
actions: ["setDragData"],
282445
},
283446
[COMPONENT_CREATED]: {
284-
actions: ["emitComponentCreated"],
447+
actions: ["emitComponentCreated", "setLastDropped"],
285448
},
286449
},
287450
},
@@ -327,7 +490,11 @@ export function createCanvasMachine(id: string) {
327490
emitInsideCanvas: callSubscribersFromAction("INSIDE_CANVAS"),
328491
emitReady: callSubscribersFromAction("ready"),
329492
emitComponentCreated: callSubscribersFromAction("COMPONENT_CREATED"),
493+
emitComponentRendered: callSubscribersFromAction("COMPONENT_RENDERED"),
330494
setHoverComponent,
495+
setSelectedComponent,
496+
setLastDropped,
497+
handleComponentRendered,
331498
},
332499
}
333500
);

‎packages/atri-app-core/src/editor-components/NormalComponentRenderer/NormalComponentRenderer.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { NormalComponentRendererProps } from "../../types";
22
import { componentStoreApi } from "../../api";
33
import { useAssignComponentId } from "../hooks/useAssignComponentId";
4+
import { useFocusComponent } from "../hooks/useFocusComponent";
5+
import { useHasComponentRendered } from "../hooks/useHasComponentRendered";
46

57
export function NormalComponentRenderer(props: NormalComponentRendererProps) {
68
const {
@@ -10,5 +12,7 @@ export function NormalComponentRenderer(props: NormalComponentRendererProps) {
1012
callbacks,
1113
} = componentStoreApi.getComponent(props.id)!;
1214
useAssignComponentId({ id: props.id });
15+
useFocusComponent({ id: props.id });
16+
useHasComponentRendered({ id: props.id });
1317
return <Comp {...compProps} ref={ref} {...callbacks} />;
1418
}

0 commit comments

Comments
 (0)