diff --git a/editor-packages/editor-canvas/README.md b/editor-packages/editor-canvas/README.md index aa198bad..095f93ff 100644 --- a/editor-packages/editor-canvas/README.md +++ b/editor-packages/editor-canvas/README.md @@ -1 +1,29 @@ -# A Interactive canvas for runtime frames. +# A Html5 backend Interactive canvas for runtime frames. + +## The system + +- Canvas +- Hud +- Event +- Math +- Iframe +- Node +- Host + +## General architecture + +- `ScaffoldCanvas` - A single component canvas that holds both renderer and eventsystem +- `RenderOnlyCanvas + EventSystem` - A Customizable system for complex and heavy rendering. (use saperate render host with iframe) + +## Events + +gesture events + +- move (pan) +- zoom (pinch) + +- create node +- remove node +- move node +- resize node +- rename node diff --git a/editor-packages/editor-canvas/canvas-event-target/canvas-event-target.tsx b/editor-packages/editor-canvas/canvas-event-target/canvas-event-target.tsx index ed277943..97c21e56 100644 --- a/editor-packages/editor-canvas/canvas-event-target/canvas-event-target.tsx +++ b/editor-packages/editor-canvas/canvas-event-target/canvas-event-target.tsx @@ -96,6 +96,9 @@ export function CanvasEventTarget({ const [first_wheel_event, set_first_wheel_event] = useState>(); + // this is a hack to prevent from onDragStart being called even when no movement is detected. + const [drag_start_emitted, set_drag_start_emitted] = useState(false); + useGesture( { onPinch: onZooming, @@ -150,7 +153,10 @@ export function CanvasEventTarget({ return; } - onDragStart(s); + if (s.delta[0] || s.delta[1]) { + onDragStart(s); + set_drag_start_emitted(true); + } }, onDrag: (s) => { if (isSpacebarPressed) { @@ -161,6 +167,10 @@ export function CanvasEventTarget({ return; } + if ((s.delta[0] || s.delta[1]) && !drag_start_emitted) { + set_drag_start_emitted(true); + onDragStart(s); + } onDrag(s); }, onDragEnd: (s) => { @@ -169,6 +179,7 @@ export function CanvasEventTarget({ return; } + set_drag_start_emitted(false); onDragEnd(s); }, onMouseDown: onPointerDown, diff --git a/editor-packages/editor-canvas/canvas/canvas.tsx b/editor-packages/editor-canvas/canvas/canvas.tsx index 595f8cad..671d6e14 100644 --- a/editor-packages/editor-canvas/canvas/canvas.tsx +++ b/editor-packages/editor-canvas/canvas/canvas.tsx @@ -9,10 +9,15 @@ import { OnPointerDownHandler, OnDragHandler, } from "../canvas-event-target"; -import { get_hovering_target, centerOf } from "../math"; +import { + target_of_point, + centerOf, + edge_scrolling, + target_of_area, +} from "../math"; import { utils } from "@design-sdk/core"; import { LazyFrame } from "@code-editor/canvas/lazy-frame"; -import { HudCustomRenderers, HudSurface } from "./hud-surface"; +import { HudCustomRenderers, HudSurface } from "../hud"; import type { Box, XY, CanvasTransform, XYWH } from "../types"; import type { FrameOptimizationFactors } from "../frame"; const designq = utils.query; @@ -89,7 +94,7 @@ export function Canvas({ ...props }: { viewbound: Box; - onSelectNode?: (node?: ReflectSceneNode) => void; + onSelectNode?: (...node: ReflectSceneNode[]) => void; onClearSelection?: () => void; } & CanvasCustomRenderers & CanvasState & { @@ -131,6 +136,7 @@ export function Canvas({ ? [offset[0] / zoom, offset[1] / zoom] : [0, 0]; const [isPanning, setIsPanning] = useState(false); + const [isDraggomg, setIsDragging] = useState(false); const [marquee, setMarquee] = useState(null); const cvtransform: CanvasTransform = { @@ -151,17 +157,46 @@ export function Canvas({ setHoveringLayer(wshighlight); }, [highlightedLayer]); + // area selection hook + useEffect(() => { + if (marquee) { + const area: XYWH = [ + marquee[0] / zoom, + marquee[1] / zoom, + marquee[2] / zoom, + marquee[3] / zoom, + ]; + + const selections = target_of_area({ + area, + tree: nodes, + contain: false, + }); + + // https://stackoverflow.com/a/19746771 + const same = + selectedNodes.length === selections?.length && + selectedNodes.every((value, index) => value === selections[index].id); + + if (!same) { + onSelectNode(...selections); + } + } + // + }, [marquee]); + const onPointerMove: OnPointerMoveHandler = (state) => { - if (isPanning || isZooming) { + if (isPanning || isZooming || isDraggomg) { // don't perform hover calculation while transforming. return; } - const hovering = get_hovering_target({ + const hovering = target_of_point({ point: state.xy, tree: nodes, zoom: zoom, offset: nonscaled_offset, margin: LAYER_HOVER_HIT_MARGIN, + reverse: true, }); if (!hovering) { @@ -223,27 +258,48 @@ export function Canvas({ setOffset([newx, newy]); }; + const onDragStart: OnDragHandler = (s) => { + onClearSelection(); + setIsDragging(true); + setHoveringLayer(null); + + // set the marquee start point + const [x, y] = s.initial; + const [ox, oy] = offset; + const [x1, y1] = [x - ox, y - oy]; + setMarquee([x1, y1, 0, 0]); + }; + const onDrag: OnDragHandler = (s) => { - const [x1, y1] = s.initial; - const [x2, y2] = [ + const [ox, oy] = offset; + const [x, y] = [ // @ts-ignore s.event.clientX, // @ts-ignore s.event.clientY, ]; - const [ox, oy] = offset; - const [x, y, w, h] = [ - x1 - ox, - y1 - oy, - x2 - x1, // w - y2 - y1, // h - ]; - setMarquee([x, y, w, h]); + const [x1, y1] = [x - ox, y - oy]; + + if (marquee) { + const [w, h] = [ + x1 - marquee[0], // w + y1 - marquee[1], // h + ]; + setMarquee([marquee[0], marquee[1], w, h]); + } + + // edge scrolling + const [cx, cy] = [x, y]; + const [dx, dy] = edge_scrolling(cx, cy, viewbound); + if (dx || dy) { + setOffset([ox + dx, oy + dy]); + } }; const onDragEnd: OnDragHandler = (s) => { setMarquee(null); + setIsDragging(false); }; const is_canvas_transforming = isPanning || isZooming; @@ -299,8 +355,8 @@ export function Canvas({ onPointerMoveStart={() => {}} onPointerMoveEnd={() => {}} onPointerDown={onPointerDown} + onDragStart={onDragStart} onDrag={onDrag} - onDragStart={() => {}} // TODO: onDragEnd={onDragEnd} > diff --git a/editor-packages/editor-canvas/docs/commands.md b/editor-packages/editor-canvas/docs/commands.md new file mode 100644 index 00000000..cd6b6dca --- /dev/null +++ b/editor-packages/editor-canvas/docs/commands.md @@ -0,0 +1,7 @@ +- move back +- move front + +- copy +- paste +- move under parent +- lock / unlock diff --git a/editor-packages/editor-canvas/docs/feature-area-selection.md b/editor-packages/editor-canvas/docs/feature-area-selection.md new file mode 100644 index 00000000..1c87074e --- /dev/null +++ b/editor-packages/editor-canvas/docs/feature-area-selection.md @@ -0,0 +1,25 @@ +# Area selection (Marquee selection) + +> This feature yet does not consider vector networks. (boolean operation) + +Keymode + +- default +- cmd + +Parameters + +- raycast mode: + - hit - if the target has an intersection with the area. + - contain - if the target is contained in the area. +- type + - frame + - group + - frame + - element (others) +- is root - this only effects to the frame node. + +Final output for painting + +- selections (each selection will get a highlight) +- bounding box (abstract group) diff --git a/editor-packages/editor-canvas/docs/feature-drag-edge-scrolling.md b/editor-packages/editor-canvas/docs/feature-drag-edge-scrolling.md new file mode 100644 index 00000000..a60e46a2 --- /dev/null +++ b/editor-packages/editor-canvas/docs/feature-drag-edge-scrolling.md @@ -0,0 +1,3 @@ +# Canvas Drag Scroll (Scroll / Pan while dragging) + +scroll (translate) a canvas while dragging (marquee, move, resize element). if hit the edge of the canvas, the canvas will scroll. diff --git a/editor-packages/editor-canvas/docs/hud.md b/editor-packages/editor-canvas/docs/hud.md new file mode 100644 index 00000000..ba46ef22 --- /dev/null +++ b/editor-packages/editor-canvas/docs/hud.md @@ -0,0 +1,42 @@ +# Hud system + +node + +- hover outline +- resize knob +- rotate knob +- border radius knob +- frame title +- draft editor (text editing) +- multiplayer cursor +- resizing size indicator +- rotating rotation indicator + +ruler & guide + +- ruler +- guide (user defined guide line) +- highlight guide (hovered, selected guide line) +- snap line (snapping guide line) +- layout grids (grid-template-columns) +- spacing guide (size between 2 nodes) + +layout + +- layout placement guide (preview the place of an moving item after placement inside a certain lyout - row / col) + +- interaction knob +- interaction line + +popover + +- popovers + +feedback (comments) + +- pin +- create pin + +vector + +- TODO: diff --git a/editor-packages/editor-canvas/docs/index.md b/editor-packages/editor-canvas/docs/index.md new file mode 100644 index 00000000..f8f0694c --- /dev/null +++ b/editor-packages/editor-canvas/docs/index.md @@ -0,0 +1 @@ +# Html5 Backend Canvas docs diff --git a/editor-packages/editor-canvas/flow-connections/arrow.tsx b/editor-packages/editor-canvas/flow-connections/arrow.tsx new file mode 100644 index 00000000..617b2f8f --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/arrow.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import type { XY } from "../types"; + +export function Arrow({ + b, + color, + size, + width, + direction, +}: { + b: XY; + color: React.CSSProperties["color"]; + size: number; + width: number; + direction: "n" | "s" | "e" | "w"; +}) { + return ( + + ); +} + +/** + * + * the result will have 3 modifiers, + * if the arrow is facing right, the modifiers will be: + * - M - starting point [edge_x - height, edge_y + width / 2] + * - L - edge [edge_x, edge_y] + * - L - ending point [edge_x - height, edge_y - width / 2] + * + * @param edge the edge of a arrow (triangle) + * @param width + */ +function make_arrow_svg_path_data( + edge: XY, + direction: "n" | "s" | "e" | "w", + { width, height }: { width: number; height: number } +) { + const [x, y] = edge; + const w = width / 2; + switch (direction) { + case "e": { + return `M${x - height},${y + w} L${x},${y} L${x - height},${y - w}`; + } + case "w": { + return `M${x + height},${y + w} L${x},${y} L${x + height},${y - w}`; + } + case "n": { + return `M${x - w},${y + height} L${x},${y} L${x + w},${y + height}`; + } + case "s": { + return `M${x - w},${y - height} L${x},${y} L${x + w},${y - height}`; + } + default: { + throw new Error(`invalid direction: ${direction}`); + } + } +} diff --git a/editor-packages/editor-canvas/flow-connections/connection-line-bezier-curved.tsx b/editor-packages/editor-canvas/flow-connections/connection-line-bezier-curved.tsx new file mode 100644 index 00000000..c3392199 --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/connection-line-bezier-curved.tsx @@ -0,0 +1,84 @@ +import { color_connection_line } from "../theme"; +import type { XY } from "../types"; +import { Arrow } from "./arrow"; +import { get_direction } from "./math"; +import type { ConnectionLineStyleProps } from "./props"; + +export function BezierCurvedLine({ + a, + b, + width = 2, + color = color_connection_line, +}: { a: XY; b: XY } & ConnectionLineStyleProps) { + const direction = get_direction(a, b); + + return ( + + + + + ); +} + +const direction_to_axis_map = { + n: "v", + s: "v", + e: "h", + w: "h", +} as const; + +/** + * make a svg path data to connect point a to point b + * + * the output will contain 2 commands + * - M - starting point + * - C - curve + * + * e.g. for a a[0, 0], b[1000, 500], (1000x500 box) + * - `"M 0 0 C 500 0 500 500 1000 500"` + * - M a[0], a[1] (start point) + * - C0 a[0] + w / 2, a[1] + * - C1 a[0] + w / 2, b[1] + * - C2 b[0], b[1] (end point) + * + * @param a - starting point + * @param b - ending point + */ +function make_bazier_curved_svg_path_data(a: XY, b: XY, axis: "h" | "v" = "h") { + const [x0, y0] = a; + const [x1, y1] = b; + const w = axis === "h" ? x1 - x0 : y1 - y0; + + if (axis === "h") { + return `M ${x0},${y0} C ${x0 + w / 2},${y0} ${ + x0 + w / 2 + },${y1} ${x1},${y1}`; + } else if (axis === "v") { + return `M ${x0},${y0} C ${x0},${y0 + w / 2} ${x1},${ + y0 + w / 2 + } ${x1},${y1}`; + } +} diff --git a/editor-packages/editor-canvas/flow-connections/connection-line-edge-curved.tsx b/editor-packages/editor-canvas/flow-connections/connection-line-edge-curved.tsx new file mode 100644 index 00000000..85cab50a --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/connection-line-edge-curved.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import type { XY } from "../types"; +import { Arrow } from "./arrow"; +import { get_direction } from "./math"; + +/** + * @deprecated - not implemented + * @param param0 + * @returns + */ +export function EdgeCurvedConnectionLine({ + a, + b, + width = 2, + color = "blue", +}: { a: XY; b: XY } & { + width?: number; + color?: React.CSSProperties["color"]; +}) { + const direction = get_direction(a, b); + return ( + + + + + ); +} + +function Line({ a, b }: { a: XY; b: XY }) { + return ; +} + +/** + * + * makes the svg path data that connects point a to point b, with extra parameters, curve delta and edge inset + * + * the shape looks line + * ``` + * (a) --- + * | + * | + * | + * | + * | + * --- (b) + * ``` + * + * the line components are.. + * 0. M | starting point + * 1. L L | the line from `a - edge` to `a` - [a - edge, a] + * 2. C | the curve to before 3 + * 3. L | the line from `a` to `b` - [a, b] + * 4. C | the curve to after 3 + * 5. L L | the line from `b` to `b + edge` - [b, b + edge] + * + * the output command is: + * - M - the start point (a) + * - L - line start + * - L - draw line to the curving point + * - C - curve + * - L - line between two curves + * - C - curve + * - L - line start + * - L - line end point + * + * e.g. the output of this function is: + * - `"M 0 0 L 0 0 L 8 0 L 8 0 C 17.1638 0 25.4139 5.5525 28.8641 14.042 L 165.907 351.249 C 169.358 359.739 177.608 365.291 186.772 365.291 L 186.772 365.291 L 194.772 365.291"` + * + * @param a the starting point a + * @param b the ending point b + * @param curve the curve delta + * @param edge the edge (margin) value + */ +function make_svg_path_data(a: XY, b: XY, edge) {} diff --git a/editor-packages/editor-canvas/flow-connections/index.ts b/editor-packages/editor-canvas/flow-connections/index.ts new file mode 100644 index 00000000..6ea59047 --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/index.ts @@ -0,0 +1,2 @@ +export { EdgeCurvedConnectionLine } from "./connection-line-edge-curved"; +export { BezierCurvedLine } from "./connection-line-bezier-curved"; diff --git a/editor-packages/editor-canvas/flow-connections/knob.tsx b/editor-packages/editor-canvas/flow-connections/knob.tsx new file mode 100644 index 00000000..5cdf97d4 --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/knob.tsx @@ -0,0 +1,27 @@ +import { + color_connection_knob_fill, + color_connection_knob_stroke, +} from "../theme"; +import type { XY } from "../types"; + +export function Knob({ point, size = 6 }: { point: XY; size: number }) { + return ( + + + + ); +} diff --git a/editor-packages/editor-canvas/flow-connections/line.tsx b/editor-packages/editor-canvas/flow-connections/line.tsx new file mode 100644 index 00000000..e69de29b diff --git a/editor-packages/editor-canvas/flow-connections/math.ts b/editor-packages/editor-canvas/flow-connections/math.ts new file mode 100644 index 00000000..1f540f2a --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/math.ts @@ -0,0 +1,43 @@ +import type { XY } from "../types"; + +/** + * get the direction based on two points (vector), a and b. + * returns the most powerful direction, e.g. [0, 0], [10, 50] -> "s" + * if the power is the same, return "e" | "w" first, then "s" | "n". + * + * examples + * - [0, 0], [10, 50] -> "s" + * - [0, 0], [10, 0] -> "e" + * - [0, 0], [0, 50] -> "s" + * - [0, 0], [0, 0] -> "e" + * - [0, 0], [-10, 50] -> "n" + * - [0, 0], [-10, 0] -> "w" + * - [0, 0], [-10, -50] -> "n" + * - [0, 0], [-10, 0] -> "w" + * - [0, 0], [-100, -100] -> "w" + * + * @param a + * @param b + * @returns + */ +export function get_direction(a: XY, b: XY): "n" | "s" | "e" | "w" { + const [x, y] = a; + const [x2, y2] = b; + + const x_diff = x2 - x; + const y_diff = y2 - y; + + if (Math.abs(x_diff) >= Math.abs(y_diff)) { + if (x_diff > 0) { + return "e"; + } else { + return "w"; + } + } + + if (y_diff > 0) { + return "s"; + } + + return "n"; +} diff --git a/editor-packages/editor-canvas/flow-connections/props.ts b/editor-packages/editor-canvas/flow-connections/props.ts new file mode 100644 index 00000000..ef0b21f2 --- /dev/null +++ b/editor-packages/editor-canvas/flow-connections/props.ts @@ -0,0 +1,6 @@ +import React from "react"; + +export interface ConnectionLineStyleProps { + width?: number; + color?: React.CSSProperties["color"]; +} diff --git a/editor-packages/editor-canvas/canvas/hud-surface.tsx b/editor-packages/editor-canvas/hud/hud-surface.tsx similarity index 100% rename from editor-packages/editor-canvas/canvas/hud-surface.tsx rename to editor-packages/editor-canvas/hud/hud-surface.tsx diff --git a/editor-packages/editor-canvas/hud/index.ts b/editor-packages/editor-canvas/hud/index.ts new file mode 100644 index 00000000..a69ded74 --- /dev/null +++ b/editor-packages/editor-canvas/hud/index.ts @@ -0,0 +1,2 @@ +export { HudSurface } from "./hud-surface"; +export type { HudCustomRenderers, DisplayNodeMeta } from "./hud-surface"; diff --git a/editor-packages/editor-canvas/lazy-frame/lazy-frame.tsx b/editor-packages/editor-canvas/lazy-frame/lazy-frame.tsx index 0ee65869..1a8a946c 100644 --- a/editor-packages/editor-canvas/lazy-frame/lazy-frame.tsx +++ b/editor-packages/editor-canvas/lazy-frame/lazy-frame.tsx @@ -24,7 +24,7 @@ export function LazyFrame({ pointerEvents: "none", transition: "opacity 50ms ease-out 0s", transformOrigin: "left top", - transform: `translateX(${x}px) translateY(${y}px)`, + transform: `translate3d(${x}px, ${y}px, 0)`, willChange: "transform", display: "block", position: "fixed", @@ -40,7 +40,7 @@ export function LazyFrame({ style={{ width: "100%", height: "100%", - display: inViewport ? "block" : "none", + contentVisibility: inViewport ? "visible" : "hidden", }} > {children} diff --git a/editor-packages/editor-canvas/marquee/marquee.tsx b/editor-packages/editor-canvas/marquee/marquee.tsx index a80d5637..9b407315 100644 --- a/editor-packages/editor-canvas/marquee/marquee.tsx +++ b/editor-packages/editor-canvas/marquee/marquee.tsx @@ -31,7 +31,7 @@ export function Marquee({ rect }: { rect: XYWH }) { height: Math.abs(h), willChange: "transform, opacity", transformOrigin: "0px 0px", - transform: `translateX(${x}px) translateY(${y}px) rotate(${r}deg)`, + transform: `translate3d(${x}px, ${y}px, 0) rotate(${r}deg)`, border: `${canvasSelectionRectBorderColor} 1px solid`, }} > diff --git a/editor-packages/editor-canvas/math/hovering-target.ts b/editor-packages/editor-canvas/math/hovering-target.ts deleted file mode 100644 index 301192a2..00000000 --- a/editor-packages/editor-canvas/math/hovering-target.ts +++ /dev/null @@ -1,87 +0,0 @@ -// TODO: -/** - * if the array's order represents the reversed index (depth) set this to true. - */ - -/** - * get the hovering target node from nested children tree. - * - ignore invisible nodes. - * - target is layer in higher level. (e.g. child of a parent is hovering target if both matches the point) - */ -export function get_hovering_target({ - point, - zoom, - tree, - offset = [0, 0], - ignore, - margin = 0, -}: { - /** - * relative mouse point from canvas 0, 0 - */ - point: [number, number]; - zoom: number; - tree: T[]; - /** - * offset of the canvas (canvas xy transform) - */ - offset: [number, number]; - ignore?: (item: T) => boolean; - margin?: number; -}): T | undefined { - const [ox, oy] = offset; - for (const item of tree) { - if ( - is_point_in_xywh(point, [ - (item.absoluteX + ox) * zoom, - (item.absoluteY + oy) * zoom, - item.width * zoom + margin, - item.height * zoom + margin, - ]) - ) { - if (ignore && ignore(item)) { - // TODO: invalid logic gate - continue; - } - if (item.children) { - const hovering_child = get_hovering_target({ - point, - zoom, - tree: item.children as T[], - ignore, - margin, - offset, - }); - if (hovering_child) { - return hovering_child; - } - } - return item; - } - } -} - -function is_point_in_xywh( - point: [number, number], - xywh: [number, number, number, number] -): boolean { - const [x, y] = point; - const [x0, y0, w, h] = xywh; - const inbound = x >= x0 && x <= x0 + w && y >= y0 && y <= y0 + h; - return inbound; -} - -interface Tree { - id: string; - /** - * absolute x point. - */ - absoluteX: number; - /** - * absolute y point. - */ - absoluteY: number; - width: number; - height: number; - children?: Tree[] | undefined; -} diff --git a/editor-packages/editor-canvas/math/index.ts b/editor-packages/editor-canvas/math/index.ts index f640ed54..70d1cd75 100644 --- a/editor-packages/editor-canvas/math/index.ts +++ b/editor-packages/editor-canvas/math/index.ts @@ -1,2 +1,4 @@ -export * from "./hovering-target"; +export * from "./target-of-area"; +export * from "./target-of-point"; export * from "./center-of"; +export * from "./viewbound-edge-scrolling"; diff --git a/editor-packages/editor-canvas/math/target-of-area.ts b/editor-packages/editor-canvas/math/target-of-area.ts new file mode 100644 index 00000000..e1b6a6b2 --- /dev/null +++ b/editor-packages/editor-canvas/math/target-of-area.ts @@ -0,0 +1,152 @@ +import type { Tree, XY, XYWH } from "../types"; + +/** + * target of area + * calculates the target node from nested children tree recursively. + * + * if the current looping node matches the condition, don't go any deeper. + * if the current looping node doesn't match the condition, go deeper, loop trough its children if present. + * + * for example: + * - if the area is (0, 0, 100, 100), and the children are [ [0, 0, 50, 50], [50, 50, 50, 50] ], the target is both two items. + * - when contain is true: if the area is (0, 0, 50, 50), and the children are [ [0, 0, 50, 50], [50, 50, 50, 50] ], the target is both two items. + * - when contain is false: if the area is (0, 0, 50, 50), and the children are [ [0, 0, 50, 50], [50, 50, 50, 50] ], the target is only first item. + * + * + * @param area - [x, y, w, h] the marquee data + * @param tree - the tree to search + * @param ignore - provide a function that returns boolean. if true, the current item will be ignored. this is usefull when you want to ignore grouping nodes, e.g. when command key is pressed while marquee, it should only select the deep down nodes. + * @param margin - the margin of raycasting + * @param contain - if true, the target node should be contained in the area. if false, the target node should be intersected with the area. + * @param contain - reverse the order of the tree (after copy with `Array.from()`). + * + */ +export function target_of_area( + { + area, + tree, + ignore, + margin = 0, + contain, + reverse = true, + }: { + area: XYWH; + tree: T[]; + margin?: number; + contain: boolean; + ignore?: (item: T) => boolean; + reverse?: boolean; + }, + depth: number = 0 +): T[] { + const items = reverse ? Array.from(tree).reverse() : tree; + + const result: T[] = []; + + for (const item of items) { + if (ignore?.(item)) { + continue; + } else { + if ( + is_rect_in_rect_raycast( + [item.absoluteX, item.absoluteY, item.width, item.height], + area, + contain + ) + ) { + // console.log(item, ) + result.push(item); + } else { + if (item.children) { + const targets = target_of_area( + { + area, + tree: item.children as T[], + ignore, + margin, + contain, + reverse, + }, + depth + 1 + ); + if (targets?.length) { + result.push(...targets); + } + } + } + } + } + return result; +} + +function is_rect_in_rect_raycast(a: XYWH, b: XYWH, contain: boolean): boolean { + if (contain) { + return is_rect_in_rect(a, b); + } else { + return is_rect_intersect_rect(a, b); + } +} + +/** + * check if the rect a is contained in the rect b + * @param a - [x, y, w, h] the first rect, where "rect a" in "rect b" + * @param b - [x, y, w, h] the second rect, where "rect a" in "rect b" + * @returns + */ +function is_rect_in_rect(a: XYWH, b: XYWH): boolean { + const [x1, y1, w1, h1] = a; + const [x2, y2, w2, h2] = b; + + throw new Error("not implemented"); + // return x1 >= x2 && y1 >= y2 && x1 + w1 <= x2 + w2 && y1 + h1 <= y2 + h2; +} + +/** + * check if two area has an intersection + * + * for example: + * - a: [0, 0, 100, 100], b: [0, 0, 50, 50] => true + * - a: [0, 0, 100, 100], b: [100, 100, 50, 50] => false + * - a: [0, 0, 100, 100], b: [50, 50, 50, 50] => true + * - a: [0, 0, 100, 100], b: [0, 0, 100, 100] => true + * - a: [0, 0, -100, -100], b: [0, 0, 100, 100] => false + * - a: [0, 0, -100, -100], b: [-10, -10, 100, 100] => true + * - a: [0, 0, 100, 100], b: [0, 0, -100, -100] => false + * - a: [-10, 0, 11, 1], b: [0, 0, 20, 20] => true + * + * @param a + * @param b + * @returns + */ +function is_rect_intersect_rect(a: XYWH, b: XYWH): boolean { + const [ax, ay, aw, ah] = a; + const [bx, by, bw, bh] = b; + + const [ax1, ay1, ax2, ay2] = [ax, ay, ax + aw, ay + ah]; + const [bx1, by1, bx2, by2] = [bx, by, bx + bw, by + bh]; + + return !( + Math.max(ax1, ax2) < Math.min(bx1, bx2) || + Math.min(ax1, ax2) > Math.max(bx1, bx2) || + Math.min(ay1, ay2) > Math.max(by1, by2) || + Math.max(ay1, ay2) < Math.min(by1, by2) + ); + + // preserve below for readability (above is optimized) + + // const max_ax = Math.max(ax1, ax2); + // const min_ax = Math.min(ax1, ax2); + // const max_ay = Math.max(ay1, ay2); + // const min_ay = Math.min(ay1, ay2); + // const max_bx = Math.max(bx1, bx2); + // const min_bx = Math.min(bx1, bx2); + // const max_by = Math.max(by1, by2); + // const min_by = Math.min(by1, by2); + + // return !( + // max_ax < min_bx || + // min_ax > max_bx || + // min_ay > max_by || + // max_ay < min_by + // ); +} diff --git a/editor-packages/editor-canvas/math/target-of-point.ts b/editor-packages/editor-canvas/math/target-of-point.ts new file mode 100644 index 00000000..e26a814c --- /dev/null +++ b/editor-packages/editor-canvas/math/target-of-point.ts @@ -0,0 +1,84 @@ +// TODO: +/** + * if the array's order represents the reversed index (depth) set this to true. + */ + +import type { Tree, XY, XYWH } from "../types"; + +/** + * get the hovering target node from nested children tree. + * - ignore invisible nodes. + * - target is layer in higher level. (e.g. child of a parent is hovering target if both matches the point) + */ +export function target_of_point( + { + point, + zoom, + tree, + offset = [0, 0], + ignore, + margin = 0, + reverse = true, + }: { + /** + * relative mouse point from canvas 0, 0 + */ + point: XY; + zoom: number; + tree: T[]; + /** + * offset of the canvas (canvas xy transform) + */ + offset: XY; + ignore?: (item: T) => boolean; + margin?: number; + reverse?: boolean; + }, + depth = 0 +): T | undefined { + const [ox, oy] = offset; + + const items = reverse ? Array.from(tree).reverse() : tree; + + for (const item of items) { + if ( + is_point_in_xywh(point, [ + (item.absoluteX + ox) * zoom, + (item.absoluteY + oy) * zoom, + item.width * zoom + margin, + item.height * zoom + margin, + ]) + ) { + if (ignore?.(item)) { + // TODO: invalid logic gate + continue; + } + if (item.children) { + const hovering_child = target_of_point( + { + point, + zoom, + tree: item.children as T[], + ignore, + margin, + offset, + reverse, + }, + depth + 1 + ); + if (hovering_child) { + return hovering_child; + } + } + + return item; + } + } +} + +function is_point_in_xywh(point: XY, xywh: XYWH): boolean { + const [x, y] = point; + const [x0, y0, w, h] = xywh; + const inbound = x >= x0 && x <= x0 + w && y >= y0 && y <= y0 + h; + return inbound; +} diff --git a/editor-packages/editor-canvas/math/viewbound-edge-scrolling.ts b/editor-packages/editor-canvas/math/viewbound-edge-scrolling.ts new file mode 100644 index 00000000..4146b59c --- /dev/null +++ b/editor-packages/editor-canvas/math/viewbound-edge-scrolling.ts @@ -0,0 +1,48 @@ +/** + * edge scrolling + * scroll (translate) the canvas if the cursor is near the edge (viewbound) with marginal value. + * get the distance from each edge. + * + * for example, + * - if the cursor is at [19, 19], and the margin is 20, the canvas will be translated to [-1, -1] + * - if the cursor is at [19, 19], and the margin is 10, the canvas will be translated to [0, 0] + * - if the cursor is at [0, 0], and the margin is 10, the canvas will be translated to [-10, -10] + * - if the cursor is at [0, 0], and the margin is 20, the canvas will be translated to [-20, -20] + * - if the cursor is at [1920, 1080] on a [0, 0, 1920, 1090] viewbound, and the margin is 20, the canvas will be translated to [20, 20] + * - if the cursor is at [1920, 0] on a [0, 0, 1920, 1090] viewbound, and the margin is 20, the canvas will be translated to [20, -20] + * - if the cursor is at [1920, 500] on a [0, 0, 1920, 1090] viewbound, and the margin is 20, the canvas will be translated to [20, 0] + * + * + * + * @param cx x coordinate of the cursor + * @param cy y coordinate of the cursor + * @param viewbound the viewbound of the canvas (l, t, b, r) + * @param margin the margin value (default 40px) + * @param factor the returned value will be multiplied by this factor (default 1/4) + * + * @returns [number, number] the translation of the canvas + */ +export function edge_scrolling( + cx: number, + cy: number, + viewbound: [number, number, number, number], + margin = 40, + factor = 1 / 4 +): [number, number] { + const [l, t, b, r] = viewbound; + let [dx, dy] = [0, 0]; + + if (cx < l + margin) { + dx = l - cx + margin; + } else if (cx > r - margin) { + dx = r - cx - margin; + } + + if (cy < t + margin) { + dy = t - cy + margin; + } else if (cy > b - margin) { + dy = b - cy - margin; + } + + return [dx * factor, dy * factor]; +} diff --git a/editor-packages/editor-canvas/nodes/README.md b/editor-packages/editor-canvas/nodes/README.md new file mode 100644 index 00000000..20d5e19e --- /dev/null +++ b/editor-packages/editor-canvas/nodes/README.md @@ -0,0 +1,8 @@ +# Nodes + +- TextNode +- FrameNode +- RectangleNode +- EllipseNode +- VectorNode +- PolygonNode diff --git a/editor-packages/editor-canvas/nodes/index.ts b/editor-packages/editor-canvas/nodes/index.ts new file mode 100644 index 00000000..e20cd3f8 --- /dev/null +++ b/editor-packages/editor-canvas/nodes/index.ts @@ -0,0 +1 @@ +export * from "./text"; diff --git a/editor-packages/editor-canvas/theme/default-theme.ts b/editor-packages/editor-canvas/theme/default-theme.ts index de843bed..a63d4be5 100644 --- a/editor-packages/editor-canvas/theme/default-theme.ts +++ b/editor-packages/editor-canvas/theme/default-theme.ts @@ -1,5 +1,8 @@ export const color_layer_highlight = "#0099ff"; export const color_layer_readonly_highlight = "#0099ff"; +export const color_connection_line = "#34AEFF"; +export const color_connection_knob_stroke = "#34AEFF"; +export const color_connection_knob_fill = "white"; export const color_frame_title = { default: "grey", highlight: color_layer_highlight, diff --git a/editor-packages/editor-canvas/types/index.ts b/editor-packages/editor-canvas/types/index.ts index a7c26966..907c5e37 100644 --- a/editor-packages/editor-canvas/types/index.ts +++ b/editor-packages/editor-canvas/types/index.ts @@ -5,3 +5,17 @@ export type CanvasTransform = { xy: XY; }; export type Box = [number, number, number, number]; +export interface Tree { + id: string; + /** + * absolute x point. + */ + absoluteX: number; + /** + * absolute y point. + */ + absoluteY: number; + width: number; + height: number; + children?: Tree[] | undefined; +} diff --git a/editor-packages/editor-devtools/components/console-feed/index.ts b/editor-packages/editor-devtools/components/console-feed/index.ts new file mode 100644 index 00000000..9977fad1 --- /dev/null +++ b/editor-packages/editor-devtools/components/console-feed/index.ts @@ -0,0 +1 @@ +export * from "./window-console-feed"; diff --git a/editor-packages/editor-devtools/components/console-feed/window-console-feed.tsx b/editor-packages/editor-devtools/components/console-feed/window-console-feed.tsx new file mode 100644 index 00000000..4d01f97f --- /dev/null +++ b/editor-packages/editor-devtools/components/console-feed/window-console-feed.tsx @@ -0,0 +1,24 @@ +import React, { useEffect, useState } from "react"; +import { Console, Hook, Unhook } from "@code-editor/console-feed"; + +export function WindowConsoleFeed({ style }: { style?: React.CSSProperties }) { + const [logs, setLogs] = useState([]); + + useEffect(() => { + // run once + Hook( + window.console, + (log) => setLogs((currLogs) => [...currLogs, log]), + false + ); + return () => { + Unhook(window.console as any); + }; + }, []); + + return ( +
+ +
+ ); +} diff --git a/editor-packages/editor-devtools/components/index.ts b/editor-packages/editor-devtools/components/index.ts new file mode 100644 index 00000000..72091986 --- /dev/null +++ b/editor-packages/editor-devtools/components/index.ts @@ -0,0 +1,4 @@ +export * from "./tab"; +export * from "./tab-badge"; +export * from "./console-feed"; +export * from "./visualization"; diff --git a/editor-packages/editor-devtools/components/tab-badge/index.tsx b/editor-packages/editor-devtools/components/tab-badge/index.tsx new file mode 100644 index 00000000..e1293e22 --- /dev/null +++ b/editor-packages/editor-devtools/components/tab-badge/index.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import styled from "@emotion/styled"; + +const bgcolortypemap = { + default: "rgba(255, 255, 255, 0.1)", + warning: "rgba(255, 230, 0, 0.1)", + error: "rgba(255, 0, 0, 0.1)", +}; + +export function TabBadge({ + type = "default", + value, +}: { + type?: "default" | "warning" | "error"; + value: string | number; +}) { + const background = bgcolortypemap[type]; + + if (value === undefined || value === null) { + return <>; + } + + return ( + + {value} + + ); +} + +const Value = styled.span` + color: rgb(151, 151, 151); + text-overflow: ellipsis; + font-size: 10px; + font-family: Inter, sans-serif; + font-weight: 400; + text-align: center; +`; + +const BaseDevtoolsTabBadge = styled.div<{ background: string }>` + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + flex: none; + gap: 10px; + border-radius: 50%; + width: 18px; + height: 18px; + background-color: ${(p) => p.background}; + box-sizing: border-box; + padding: 10px; +`; diff --git a/editor-packages/editor-devtools/components/tab/index.tsx b/editor-packages/editor-devtools/components/tab/index.tsx new file mode 100644 index 00000000..e3238b62 --- /dev/null +++ b/editor-packages/editor-devtools/components/tab/index.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { TabBadge } from "../tab-badge"; + +export function DevtoolsTab({ + label, + badge, + selected, + onTap, +}: { + selected?: boolean; + label: string; + badge?: string | number; + onTap?: () => void; +}) { + return ( + + + + + ); +} + +const TabBase = styled.div` + cursor: pointer; + user-select: none; + display: flex; + justify-content: flex-start; + flex-direction: row; + align-items: center; + gap: 8px; + box-sizing: border-box; +`; + +const Label = styled.span` + color: rgb(151, 151, 151); + text-overflow: ellipsis; + font-size: 12px; + font-family: Inter, sans-serif; + font-weight: 400; + text-align: left; + + &:hover { + color: white; + } + + &[data-selected="true"] { + color: white; + } +`; diff --git a/editor-packages/editor-debugger/components/visualization/README.md b/editor-packages/editor-devtools/components/visualization/README.md similarity index 100% rename from editor-packages/editor-debugger/components/visualization/README.md rename to editor-packages/editor-devtools/components/visualization/README.md diff --git a/editor-packages/editor-debugger/components/visualization/index.ts b/editor-packages/editor-devtools/components/visualization/index.ts similarity index 100% rename from editor-packages/editor-debugger/components/visualization/index.ts rename to editor-packages/editor-devtools/components/visualization/index.ts diff --git a/editor-packages/editor-debugger/components/visualization/json-visualization/json-tree.tsx b/editor-packages/editor-devtools/components/visualization/json-visualization/json-tree.tsx similarity index 100% rename from editor-packages/editor-debugger/components/visualization/json-visualization/json-tree.tsx rename to editor-packages/editor-devtools/components/visualization/json-visualization/json-tree.tsx diff --git a/editor-packages/editor-debugger/components/visualization/node-visualization/index.ts b/editor-packages/editor-devtools/components/visualization/node-visualization/index.ts similarity index 100% rename from editor-packages/editor-debugger/components/visualization/node-visualization/index.ts rename to editor-packages/editor-devtools/components/visualization/node-visualization/index.ts diff --git a/editor-packages/editor-debugger/components/visualization/node-visualization/tree-view.tsx b/editor-packages/editor-devtools/components/visualization/node-visualization/tree-view.tsx similarity index 100% rename from editor-packages/editor-debugger/components/visualization/node-visualization/tree-view.tsx rename to editor-packages/editor-devtools/components/visualization/node-visualization/tree-view.tsx diff --git a/editor-packages/editor-debugger/debugger-panel.tsx b/editor-packages/editor-devtools/debugger-panel.tsx similarity index 95% rename from editor-packages/editor-debugger/debugger-panel.tsx rename to editor-packages/editor-devtools/debugger-panel.tsx index f3a851ad..74660f5e 100644 --- a/editor-packages/editor-debugger/debugger-panel.tsx +++ b/editor-packages/editor-devtools/debugger-panel.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import styled from "@emotion/styled"; import { useRouter } from "next/router"; import { ClearRemoteDesignSessionCache } from "components/clear-remote-design-session-cache"; -import { WidgetTree } from "@code-editor/debugger/components/visualization/json-visualization/json-tree"; +import { WidgetTree } from "@code-editor/devtools/components/visualization/json-visualization/json-tree"; import Link from "next/link"; export const Debugger = ({ diff --git a/editor-packages/editor-debugger/index.ts b/editor-packages/editor-devtools/index.ts similarity index 53% rename from editor-packages/editor-debugger/index.ts rename to editor-packages/editor-devtools/index.ts index 86b95544..3c4e44c4 100644 --- a/editor-packages/editor-debugger/index.ts +++ b/editor-packages/editor-devtools/index.ts @@ -1 +1,2 @@ export * from "./debugger-panel"; +export * from "./components"; diff --git a/editor-packages/editor-devtools/package.json b/editor-packages/editor-devtools/package.json new file mode 100644 index 00000000..a3451301 --- /dev/null +++ b/editor-packages/editor-devtools/package.json @@ -0,0 +1,8 @@ +{ + "name": "@code-editor/devtools", + "version": "0.0.0", + "private": false, + "dependencies": { + "@code-editor/console-feed": "^3.3.1" + } +} \ No newline at end of file diff --git a/editor-packages/editor-preview-pip/lib/resizable-pip.tsx b/editor-packages/editor-preview-pip/lib/resizable-pip.tsx index ed71e430..66054b0f 100644 --- a/editor-packages/editor-preview-pip/lib/resizable-pip.tsx +++ b/editor-packages/editor-preview-pip/lib/resizable-pip.tsx @@ -2,7 +2,10 @@ import React from "react"; import PIP from "./pip"; import { ResizableBox } from "react-resizable"; import "react-resizable/css/styles.css"; -import type { ResizableBoxProps as RawResizableBoxProps } from "react-resizable"; +import type { + ResizableBoxProps as RawResizableBoxProps, + ResizableProps, +} from "react-resizable"; import styled from "@emotion/styled"; interface ResizableBoxProps @@ -17,7 +20,7 @@ interface ResizableBoxProps * resize handle to display - a react component * @default none */ - resizeHandle?: React.ReactNode; + resizeHandle?: ResizableProps["handle"]; /** * @default 500 */ diff --git a/editor-packages/editor-services-esbuild/index.ts b/editor-packages/editor-services-esbuild/index.ts index 724ac3e3..7d768c25 100644 --- a/editor-packages/editor-services-esbuild/index.ts +++ b/editor-packages/editor-services-esbuild/index.ts @@ -1,12 +1,8 @@ -import { Monaco } from "@monaco-editor/react"; import { nanoid } from "nanoid"; import { build, initialize, Loader } from "esbuild-wasm"; import { fetchPlugin } from "./fetch.plugin"; import { unpkgPathPlugin } from "./unpkg-path.plugin"; - -declare const window: { - monaco: Monaco; -}; +import { loadTypes } from "@code-editor/estypes-resolver"; let serviceLoaded: boolean | null = null; @@ -44,7 +40,6 @@ const bundler = async (rawCode: string, lang: Loader) => { return { code: result.outputFiles[0].text, err: null }; } catch (error: any) { - console.error("esbuild error: ", error); return { code: null, err: { method: "error", data: [error.message], id: nanoid() }, @@ -66,55 +61,3 @@ export const normalizeCss = (data: string) => { }; export default bundler; - -let typesWorker; - -const loadTypes = (types) => { - const disposables: any = []; - const monaco = window && window.monaco; - - const dependencies = types.map((e) => ({ name: e, version: "latest" })) || []; - - if (!typesWorker) { - typesWorker = new Worker( - new URL("./workers/fetch-types.worker.js", import.meta.url) - ); - } - - dependencies.forEach((dep) => { - typesWorker.postMessage({ - name: dep.name, - version: dep.version, - }); - }); - - typesWorker.addEventListener("message", (event) => { - // name, - // version, - // typings: result, - const key = `node_modules/${event.data.name}/index.d.ts`; - const source = event.data.typings[key]; - - // const path = `${MONACO_LIB_PREFIX}${event.data.name}`; - const libUri = `file:///node_modules/@types/${event.data.name}/index.d.ts`; - - disposables.push( - monaco.languages.typescript.javascriptDefaults.addExtraLib(source, libUri) - ); - disposables.push( - monaco.languages.typescript.typescriptDefaults.addExtraLib(source, libUri) - ); - - // When resolving definitions and references, the editor will try to use created models. - // Creating a model for the library allows "peek definition/references" commands to work with the library. - }); - - return { - dispose() { - disposables.forEach((d) => d.dispose()); - if (typesWorker) { - typesWorker.terminate(); - } - }, - }; -}; diff --git a/editor-packages/editor-services-esbuild/unpkg-path.plugin.ts b/editor-packages/editor-services-esbuild/unpkg-path.plugin.ts index dbfbcbe7..bec8de4c 100644 --- a/editor-packages/editor-services-esbuild/unpkg-path.plugin.ts +++ b/editor-packages/editor-services-esbuild/unpkg-path.plugin.ts @@ -27,10 +27,21 @@ export const unpkgPathPlugin = () => ({ * Resolve main module files */ build.onResolve({ filter: /.*/ }, async (args: any) => { + const name = get_package_name(args.path); return { namespace: "a", - path: new URL(args.path, unpkg_path + "/").href, + path: new URL(name, unpkg_path + "/").href, }; }); }, }); + +const get_package_name = (path: string) => { + // get the name of the package + // @org/package-name -> "@org/package-name" + // package/module -> "package" + if (path.startsWith("@")) { + return path; + } + return path.split("/")[0]; +}; diff --git a/editor-packages/editor-services-estypes-resolver/index.ts b/editor-packages/editor-services-estypes-resolver/index.ts new file mode 100644 index 00000000..85e62cbe --- /dev/null +++ b/editor-packages/editor-services-estypes-resolver/index.ts @@ -0,0 +1,57 @@ +import { Monaco } from "@monaco-editor/react"; + +declare const window: { + monaco: Monaco; +}; + +let typesWorker; + +export function loadTypes(types: string[]) { + const disposables: any = []; + const monaco = window && window.monaco; + + const dependencies = types.map((e) => ({ name: e, version: "latest" })) || []; + + if (!typesWorker) { + typesWorker = new Worker( + new URL("./workers/fetch-types.worker.js", import.meta.url) + ); + } + + dependencies.forEach((dep) => { + typesWorker.postMessage({ + name: dep.name, + version: dep.version, + }); + }); + + typesWorker.addEventListener("message", (event) => { + // name, + // version, + // typings: result, + const key = `node_modules/${event.data.name}/index.d.ts`; + const source = event.data.typings[key]; + + // const path = `${MONACO_LIB_PREFIX}${event.data.name}`; + const libUri = `file:///node_modules/@types/${event.data.name}/index.d.ts`; + + disposables.push( + monaco.languages.typescript.javascriptDefaults.addExtraLib(source, libUri) + ); + disposables.push( + monaco.languages.typescript.typescriptDefaults.addExtraLib(source, libUri) + ); + + // When resolving definitions and references, the editor will try to use created models. + // Creating a model for the library allows "peek definition/references" commands to work with the library. + }); + + return { + dispose() { + disposables.forEach((d) => d.dispose()); + if (typesWorker) { + typesWorker.terminate(); + } + }, + }; +} diff --git a/editor-packages/editor-debugger/package.json b/editor-packages/editor-services-estypes-resolver/package.json similarity index 50% rename from editor-packages/editor-debugger/package.json rename to editor-packages/editor-services-estypes-resolver/package.json index 7569c855..e7b2a4eb 100644 --- a/editor-packages/editor-debugger/package.json +++ b/editor-packages/editor-services-estypes-resolver/package.json @@ -1,5 +1,5 @@ { - "name": "@code-editor/debugger", + "name": "@code-editor/estypes-resolver", "version": "0.0.0", "private": false } \ No newline at end of file diff --git a/editor-packages/editor-services-esbuild/workers/fetch-types.worker.js b/editor-packages/editor-services-estypes-resolver/workers/fetch-types.worker.js similarity index 100% rename from editor-packages/editor-services-esbuild/workers/fetch-types.worker.js rename to editor-packages/editor-services-estypes-resolver/workers/fetch-types.worker.js diff --git a/editor/components/app-runner/vanilla-dedicated-preview-renderer.tsx b/editor/components/app-runner/vanilla-dedicated-preview-renderer.tsx index c51b1567..2a7e9b24 100644 --- a/editor/components/app-runner/vanilla-dedicated-preview-renderer.tsx +++ b/editor/components/app-runner/vanilla-dedicated-preview-renderer.tsx @@ -4,6 +4,7 @@ import { ScenePreviewData } from "core/states"; import { VanillaESBuildAppRunner } from "components/app-runner"; export function VanillaDedicatedPreviewRenderer({ + widgetKey, loader, componentName, source, @@ -15,6 +16,7 @@ export function VanillaDedicatedPreviewRenderer({ <> {loader === "vanilla-esbuild-template" ? ( ) : ( (); + const dispatch = useDispatch(); + + const consoleLog = useCallback( + (p: { method; data }) => { + dispatch({ + type: "devtools-console", + log: p, + }); + }, + [dispatch] + ); const loadCode = useCallback( (e: HTMLIFrameElement) => { @@ -29,6 +41,22 @@ export function VanillaESBuildAppRunner({ } }, [doc?.html, doc?.css, doc?.javascript]); + useEffect(() => { + const handler = (event: any) => { + if (event.data.type === "console") { + console[event.data.method](JSON.parse(event.data.data).join(" ")); + consoleLog({ + method: event.data.method, + data: JSON.parse(event.data.data), + }); + } + }; + + window.addEventListener("message", handler); + + return () => window.removeEventListener("message", handler); + }, []); + return ( {} @@ -35,8 +35,10 @@ export function CodeEditor({ @@ -51,8 +53,8 @@ export function CodeEditor({ onChange={(v: string, e) => { onChange?.(filekey, v, e); }} - defaultLanguage={file.language} - defaultValue={file.raw} + language={file.language} + value={file.raw} /> ); diff --git a/editor/components/code-editor/monaco-utils/register-preset-types.ts b/editor/components/code-editor/monaco-utils/register-preset-types.ts new file mode 100644 index 00000000..e9c61041 --- /dev/null +++ b/editor/components/code-editor/monaco-utils/register-preset-types.ts @@ -0,0 +1,21 @@ +import { loadTypes } from "@code-editor/estypes-resolver"; + +const react_preset_dependencies = [ + "react", + "react-dom", + "prop-types", + "react-router", + "react-router-dom", + "styled-components", + "@emotion/styled", + "@emotion/react", + "axios", +]; + +/** + * load the preset dependencies on initial boot (e.g. react) + */ +export function registerPresetTypes() { + // load the react presets + loadTypes(react_preset_dependencies); +} diff --git a/editor/components/code-editor/monaco-utils/register.ts b/editor/components/code-editor/monaco-utils/register.ts index 354982d0..866eb037 100644 --- a/editor/components/code-editor/monaco-utils/register.ts +++ b/editor/components/code-editor/monaco-utils/register.ts @@ -2,12 +2,14 @@ import * as monaco from "monaco-editor"; import { Monaco, OnMount } from "@monaco-editor/react"; import { registerDocumentPrettier } from "@code-editor/prettier-services"; import { registerJsxHighlighter } from "@code-editor/jsx-syntax-highlight-services"; +import { registerPresetTypes } from "./register-preset-types"; type CompilerOptions = monaco.languages.typescript.CompilerOptions; export const initEditor: OnMount = (editor, monaco) => { registerJsxHighlighter(editor, monaco); registerDocumentPrettier(editor, monaco); + registerPresetTypes(); }; export const initMonaco = (monaco: Monaco) => { diff --git a/editor/components/code-editor/monaco.tsx b/editor/components/code-editor/monaco.tsx index b949eb17..919011a0 100644 --- a/editor/components/code-editor/monaco.tsx +++ b/editor/components/code-editor/monaco.tsx @@ -1,20 +1,17 @@ -import React, { useRef, useEffect } from "react"; -import Editor, { - useMonaco, - Monaco, - OnMount, - OnChange, -} from "@monaco-editor/react"; +import React, { useRef } from "react"; +import Editor, { OnMount, OnChange } from "@monaco-editor/react"; import * as monaco from "monaco-editor/esm/vs/editor/editor.api"; import { MonacoEmptyMock } from "./monaco-mock-empty"; import { register } from "./monaco-utils"; import { __dangerous__lastFormattedValue__global } from "@code-editor/prettier-services"; +import { debounce } from "utils/debounce"; +import { downloadFile } from "utils/download"; type ICodeEditor = monaco.editor.IStandaloneCodeEditor; export interface MonacoEditorProps { - defaultValue?: string; - defaultLanguage?: string; + value?: string; + language?: string; onChange?: OnChange; width?: number | string; height?: number | string; @@ -23,7 +20,8 @@ export interface MonacoEditorProps { export function MonacoEditor(props: MonacoEditorProps) { const instance = useRef<{ editor: ICodeEditor; format: any } | null>(null); - const activeModel = useRef(); + + const path = "app." + lang2ext(props.language); const onMount: OnMount = (editor, monaco) => { const format = editor.getAction("editor.action.formatDocument"); @@ -31,8 +29,6 @@ export function MonacoEditor(props: MonacoEditorProps) { instance.current = { editor, format }; - activeModel.current = editor.getModel(); - register.initEditor(editor, monaco); editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () { @@ -41,6 +37,7 @@ export function MonacoEditor(props: MonacoEditorProps) { // disabled. todo: find a way to format on new line, but also with adding new line. // editor.addCommand(monaco.KeyCode.Enter, function () { + // // add new line via script, then run format // format.run(); // }); @@ -50,9 +47,24 @@ export function MonacoEditor(props: MonacoEditorProps) { rename.run(); }); - editor.onDidChangeModelContent((e) => { - /* add here */ + editor.addAction({ + // An unique identifier of the contributed action. + id: "export-module-as-file", + + // A label of the action that will be presented to the user. + label: "Export as file", + precondition: null, + keybindingContext: null, + contextMenuGroupId: "navigation", + contextMenuOrder: 1.5, + run: function (ed) { + downloadFile({ data: ed.getModel().getValue(), filename: path }); + }, }); + + editor.onDidChangeModelContent(() => + debounce(() => editor.saveViewState(), 200) + ); }; return ( @@ -61,11 +73,10 @@ export function MonacoEditor(props: MonacoEditorProps) { onMount={onMount} width={props.width} height={props.height} - defaultLanguage={ - pollyfill_language(props.defaultLanguage) ?? "typescript" - } + language={pollyfill_language(props.language) ?? "typescript"} + path={path} loading={} - defaultValue={props.defaultValue ?? "// no content"} + value={props.value ?? "// no content"} theme="vs-dark" onChange={(...v) => { if (v[0] === __dangerous__lastFormattedValue__global) { @@ -84,6 +95,23 @@ export function MonacoEditor(props: MonacoEditorProps) { ); } +const lang2ext = (lang: string) => { + switch (lang) { + case "typescript": + return "ts"; + case "javascript": + return "js"; + case "tsx": + return "tsx"; + case "jsx": + return "jsx"; + case "dart": + return "dart"; + default: + return lang; + } +}; + const pollyfill_language = (lang: string) => { switch (lang) { case "tsx": diff --git a/editor/components/editor-hierarchy/editor-layer-hierarchy/index.tsx b/editor/components/editor-hierarchy/editor-layer-hierarchy/index.tsx index 2da02d95..0cdf2a58 100644 --- a/editor/components/editor-hierarchy/editor-layer-hierarchy/index.tsx +++ b/editor/components/editor-hierarchy/editor-layer-hierarchy/index.tsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; import styled from "@emotion/styled"; -import TreeView from "@material-ui/lab/TreeView"; -import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; -import ChevronRightIcon from "@material-ui/icons/ChevronRight"; -import TreeItem from "@material-ui/lab/TreeItem"; +import TreeView from "@mui/lab/TreeView"; +import TreeItem from "@mui/lab/TreeItem"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import { SideNavigation } from "components/side-navigation"; interface LayerTree { diff --git a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx index e0cc5d59..93819795 100644 --- a/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx +++ b/editor/components/editor/editor-appbar/editor-appbar-fragment-for-sidebar.tsx @@ -1,6 +1,6 @@ import React from "react"; import styled from "@emotion/styled"; -import { ArrowBack } from "@material-ui/icons"; +import { ArrowBack } from "@mui/icons-material"; import { useRouter } from "next/router"; import { colors } from "theme"; import ClientOnly from "components/client-only"; diff --git a/editor/components/index.ts b/editor/components/index.ts index 301a4145..d6613956 100644 --- a/editor/components/index.ts +++ b/editor/components/index.ts @@ -2,5 +2,5 @@ export * as figmacomp from "./figma"; export * as canvas from "./design-preview-as-is"; export * as code from "./code-editor"; export * as runner from "./app-runner"; -export * as visualization from "../../editor-packages/editor-debugger/components/visualization"; +export * as visualization from "../../editor-packages/editor-devtools/components/visualization"; export * from "./client-only"; diff --git a/editor/config/index.ts b/editor/config/index.ts new file mode 100644 index 00000000..d8d2e8ad --- /dev/null +++ b/editor/config/index.ts @@ -0,0 +1 @@ +export * from "./scripting-enabled-frameworks"; diff --git a/editor/config/scripting-enabled-frameworks.ts b/editor/config/scripting-enabled-frameworks.ts new file mode 100644 index 00000000..7de61fd8 --- /dev/null +++ b/editor/config/scripting-enabled-frameworks.ts @@ -0,0 +1,46 @@ +import type { FrameworkConfig } from "@designto/config"; + +type Framework = FrameworkConfig["framework"]; + +interface FrameworkEditorSciprtingPreviewConfig { + nativePreview: boolean; + nativeScripting: boolean; + enabled: boolean; +} + +/** + * a config map by frameworks containing supported scripting and preview features. + */ +export const scripting_and_preview_framework_config: { + [key in Framework]: FrameworkEditorSciprtingPreviewConfig; +} = { + vanilla: { + nativePreview: true, + nativeScripting: true, + enabled: true, + }, + react: { + nativePreview: true, + nativeScripting: true, + enabled: true, + }, + "react-native": { + nativePreview: false, + nativeScripting: false, + enabled: false, + }, + flutter: { + nativePreview: false, + nativeScripting: false, + enabled: false, + }, + preview: null, +} as const; + +export function supportsScripting(framework: Framework) { + return scripting_and_preview_framework_config[framework].nativeScripting; +} + +export function supportsPreview(framework: Framework) { + return scripting_and_preview_framework_config[framework].nativePreview; +} diff --git a/editor/core/actions/index.ts b/editor/core/actions/index.ts index 784ee1a1..4f64e953 100644 --- a/editor/core/actions/index.ts +++ b/editor/core/actions/index.ts @@ -1,5 +1,5 @@ import type { FrameworkConfig } from "@designto/config"; -import type { EditorState, ScenePreviewData } from "core/states"; +import type { ConsoleLog, EditorState, ScenePreviewData } from "core/states"; export type WorkspaceAction = // @@ -20,14 +20,15 @@ export type Action = | HighlightLayerAction | CanvasModeAction | PreviewAction - | CodeEditorAction; + | CodeEditorAction + | DevtoolsAction; export type ActionType = Action["type"]; export type HierarchyAction = SelectNodeAction; export interface SelectNodeAction { type: "select-node"; - node: string; + node: string | string[]; } export type PageAction = SelectPageAction; @@ -74,3 +75,13 @@ export interface CodeEditorEditComponentCodeAction { componentName: string; raw: string; } + +export type DevtoolsAction = DevtoolsConsoleAction | DevtoolsConsoleClearAction; +export interface DevtoolsConsoleAction { + type: "devtools-console"; + log: ConsoleLog; +} + +export interface DevtoolsConsoleClearAction { + type: "devtools-console-clear"; +} diff --git a/editor/core/reducers/editor-reducer.ts b/editor/core/reducers/editor-reducer.ts index c8e97dca..8c1401b1 100644 --- a/editor/core/reducers/editor-reducer.ts +++ b/editor/core/reducers/editor-reducer.ts @@ -8,6 +8,8 @@ import type { CanvasModeGobackAction, PreviewBuildingStateUpdateAction, PreviewSetAction, + DevtoolsConsoleAction, + DevtoolsConsoleClearAction, } from "core/actions"; import { EditorState } from "core/states"; import { useRouter } from "next/router"; @@ -26,11 +28,14 @@ export function editorReducer(state: EditorState, action: Action): EditorState { console.clear(); console.info("cleard console by editorReducer#select-node"); + const ids = Array.isArray(node) ? node : [node]; + const primary = ids?.[0]; + // update router router.push( { pathname: _editor_path_name, - query: { ...router.query, node: node ?? state.selectedPage }, + query: { ...router.query, node: primary ?? state.selectedPage }, }, undefined, { shallow: true } @@ -42,7 +47,7 @@ export function editorReducer(state: EditorState, action: Action): EditorState { state.selectedPage ); - const new_selections = [node].filter(Boolean); + const new_selections = ids.filter(Boolean); _canvas_state_store.saveLastSelection(...new_selections); // assign new nodes set to the state. @@ -153,6 +158,35 @@ export function editorReducer(state: EditorState, action: Action): EditorState { draft.currentPreview = data; // set }); } + case "devtools-console": { + const { log } = action; + return produce(state, (draft) => { + if (!draft.devtoolsConsole?.logs?.length) { + draft.devtoolsConsole = { logs: [] }; + } + + const logs = Array.from(state.devtoolsConsole?.logs ?? []); + logs.push(log); + + draft.devtoolsConsole.logs = logs; + }); + break; + } + case "devtools-console-clear": { + const {} = action; + return produce(state, (draft) => { + if (draft.devtoolsConsole?.logs?.length) { + draft.devtoolsConsole.logs = [ + { + id: "clear", + method: "info", + data: ["Console was cleared"], + }, + ]; + } + }); + break; + } default: throw new Error(`Unhandled action type: ${action["type"]}`); } diff --git a/editor/core/states/editor-state.ts b/editor/core/states/editor-state.ts index 8bbe99a8..855ec2f2 100644 --- a/editor/core/states/editor-state.ts +++ b/editor/core/states/editor-state.ts @@ -27,6 +27,7 @@ export interface EditorState { currentPreview?: ScenePreviewData; code?: CodeRepository; editingModule?: EditingModule; + devtoolsConsole?: DevtoolsConsole; } export interface EditorSnapshot { @@ -109,3 +110,24 @@ export interface EditingModule { lang: string; raw: string; } + +interface DevtoolsConsole { + logs: ConsoleLog[]; +} + +export interface ConsoleLog { + id?: string; + data: any[]; + method: + | "log" + | "debug" + | "info" + | "warn" + | "error" + | "table" + | "clear" + | "time" + | "timeEnd" + | "count" + | "assert"; +} diff --git a/editor/hooks/use-design.ts b/editor/hooks/use-design.ts index 03735811..3e4a7f0f 100644 --- a/editor/hooks/use-design.ts +++ b/editor/hooks/use-design.ts @@ -14,7 +14,6 @@ import { RemoteDesignSessionCacheStore } from "../store"; import { convert } from "@design-sdk/figma-node-conversion"; import { mapFigmaRemoteToFigma } from "@design-sdk/figma-remote/lib/mapper"; import { useFigmaAccessToken } from "."; -import { FileResponse } from "@design-sdk/figma-remote-types"; import { FigmaDesignRepository, TFetchFileForApp, @@ -115,7 +114,9 @@ export function useDesign({ last_response.nodes[targetnodeconfig.node] ); const _2_converted_to_reflect = convert.intoReflectNode( - _1_converted_to_figma + _1_converted_to_figma, + null, + "rest" ); const res = { diff --git a/editor/icons/icon-angle-down.tsx b/editor/icons/icon-angle-down.tsx new file mode 100644 index 00000000..2ebd19ef --- /dev/null +++ b/editor/icons/icon-angle-down.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +export const AngleDownIcon = (props) => { + return ( + + + + ); +}; diff --git a/editor/icons/icon-angle-up.tsx b/editor/icons/icon-angle-up.tsx new file mode 100644 index 00000000..9d8e9041 --- /dev/null +++ b/editor/icons/icon-angle-up.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +export const AngleUpIcon = (props) => { + return ( + + + + ); +}; diff --git a/editor/icons/icon-trash.tsx b/editor/icons/icon-trash.tsx new file mode 100644 index 00000000..753f2593 --- /dev/null +++ b/editor/icons/icon-trash.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +export const TrashIcon = (props) => { + return ( + + + + + ); +}; diff --git a/editor/layouts/app-menu/app-menu.tsx b/editor/layouts/app-menu/app-menu.tsx deleted file mode 100644 index 450d8931..00000000 --- a/editor/layouts/app-menu/app-menu.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import Button from "@material-ui/core/Button"; -import Menu from "@material-ui/core/Menu"; -import MenuItem from "@material-ui/core/MenuItem"; -import styled from "@emotion/styled"; - -export function AppMenu() { - const [anchorEl, setAnchorEl] = React.useState(null); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleLoadDesignClick = () => { - console.log("load design click. open design loader prompt"); - handleClose(); - }; - - return ( - - - - Load design - - - ); -} - -const AppMenuRoot = styled.div` - flex-grow: 0; -`; diff --git a/editor/layouts/app-menu/index.ts b/editor/layouts/app-menu/index.ts deleted file mode 100644 index 2d3bd3ec..00000000 --- a/editor/layouts/app-menu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./app-menu"; diff --git a/editor/layouts/default-editor-workspace-layout.tsx b/editor/layouts/default-editor-workspace-layout.tsx index 470bc6de..1c8a1bdb 100644 --- a/editor/layouts/default-editor-workspace-layout.tsx +++ b/editor/layouts/default-editor-workspace-layout.tsx @@ -1,6 +1,5 @@ import styled from "@emotion/styled"; import React from "react"; -import { AppMenu } from "./app-menu"; import { Resizable } from "re-resizable"; type SidebarElementSignature = diff --git a/editor/next.config.js b/editor/next.config.js index 29a4f9a2..68120387 100644 --- a/editor/next.config.js +++ b/editor/next.config.js @@ -3,7 +3,7 @@ const withTM = require("next-transpile-modules")([ // region @editor-app "@editor-app/live-session", "@code-editor/preview-pip", // TODO: remove me. this is for development. for production, use npm ver instead. - "@code-editor/debugger", + "@code-editor/devtools", "@code-editor/canvas", // region editor-submodule deps diff --git a/editor/package.json b/editor/package.json index 32c94541..23792f53 100644 --- a/editor/package.json +++ b/editor/package.json @@ -19,11 +19,11 @@ "@emotion/css": "^11.5.0", "@emotion/react": "^11.1.5", "@emotion/styled": "^11.1.5", - "@material-ui/core": "^4.12.3", - "@material-ui/icons": "^4.11.2", - "@material-ui/lab": "^4.0.0-alpha.60", "@modulz/design-system": "^0.6.1", "@monaco-editor/react": "^4.4.1", + "@mui/icons-material": "^5.6.1", + "@mui/lab": "^5.0.0-alpha.77", + "@mui/material": "^5.6.1", "@reflect-blocks/figma-embed": "^0.0.5", "@use-gesture/react": "^10.2.11", "@visx/gradient": "^1.7.0", diff --git a/editor/pages/_app.tsx b/editor/pages/_app.tsx index 1aa02f62..0caa7db1 100644 --- a/editor/pages/_app.tsx +++ b/editor/pages/_app.tsx @@ -3,6 +3,7 @@ import { Global, css } from "@emotion/react"; import Head from "next/head"; import Script from "next/script"; import { EditorThemeProvider } from "@editor-ui/theme"; +import { MuiThemeProvider } from "theme/mui"; import { colors } from "theme"; import { useRouter } from "next/router"; @@ -146,9 +147,11 @@ function EditorApp({ Component, pageProps }) { return ( - - - + + + + + ); } diff --git a/editor/pages/_development/code-editor/index.tsx b/editor/pages/_development/code-editor/index.tsx index 61ea4395..c4392672 100644 --- a/editor/pages/_development/code-editor/index.tsx +++ b/editor/pages/_development/code-editor/index.tsx @@ -8,11 +8,11 @@ export default function CodeEditorDevPage() { language: "typescript", raw: `export * from "./components"`, }, - "components/index.ts": { - name: "index.ts", - language: "typescript", - raw: `export * from "./app-bar"`, - }, + // "components/index.ts": { + // name: "index.ts", + // language: "typescript", + // raw: `export * from "./app-bar"`, + // }, }; return ; diff --git a/editor/pages/canvas-server/index.tsx b/editor/pages/canvas-server/index.tsx index 1e25babe..a7bd1f8b 100644 --- a/editor/pages/canvas-server/index.tsx +++ b/editor/pages/canvas-server/index.tsx @@ -1,5 +1,87 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; +import { + D2CVanillaPreview, + WebWorkerD2CVanillaPreview, +} from "scaffolds/preview"; +import { Canvas } from "@code-editor/canvas"; +import useMeasure from "react-use-measure"; +import { FrameTitleRenderer } from "scaffolds/canvas/render/frame-title"; +import { useRouter } from "next/router"; -export default function CanvasServer() { - return <>; +export default function CanvasServerPage() { + const router = useRouter(); + + const { key: __fk } = router.query; + const filkey = __fk as string; + + const [canvasSizingRef, canvasBounds] = useMeasure(); + const [selectedPage, setSelectedPage] = useState(null); + + // useEffect(() => { + // const handler = (e) => { + // // + // }; + + // window.addEventListener("message", handler); + // return () => { + // window.removeEventListener("message", handler); + // }; + // }, []); + + // const thisPageNodes = selectedPage + // ? design.pages.find((p) => p.id == selectedPage).children.filter(Boolean) + // : []; + + const thisPageNodes = []; + + return ( +
+ { + // dispatch({ type: "select-node", node: node?.id }); + }} + onClearSelection={() => { + // dispatch({ type: "select-node", node: null }); + }} + nodes={thisPageNodes} + // initialTransform={ } // TODO: if the initial selection is provided from first load, from the query param, we have to focus to fit that node. + renderItem={(p) => { + return ( + // + + ); + }} + config={{ + can_highlight_selected_layer: true, + marquee: { + disabled: true, + }, + }} + renderFrameTitle={(p) => ( + { + // startIsolatedViewMode(); + }} + /> + )} + /> +
+ ); } diff --git a/editor/pages/figma/inspect-component.tsx b/editor/pages/figma/inspect-component.tsx index f3cdac37..18cb5e25 100644 --- a/editor/pages/figma/inspect-component.tsx +++ b/editor/pages/figma/inspect-component.tsx @@ -4,12 +4,12 @@ import { tokenize } from "@designto/token"; import React from "react"; import { canvas } from "components"; import { LayerHierarchy } from "components/editor-hierarchy"; -import { visualize_node } from "@code-editor/debugger/components/visualization"; +import { visualize_node } from "@code-editor/devtools/components/visualization"; import { JsonTree, WidgetTree, WidgetTreeLegend, -} from "@code-editor/debugger/components/visualization/json-visualization/json-tree"; +} from "@code-editor/devtools/components/visualization/json-visualization/json-tree"; import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; import LoadingLayout from "layouts/loading-overlay"; import { diff --git a/editor/pages/figma/inspect-frame.tsx b/editor/pages/figma/inspect-frame.tsx index e1a2f037..7222de65 100644 --- a/editor/pages/figma/inspect-frame.tsx +++ b/editor/pages/figma/inspect-frame.tsx @@ -24,8 +24,8 @@ export default function InspectAutolayout() { ); diff --git a/editor/pages/figma/inspect-raw.tsx b/editor/pages/figma/inspect-raw.tsx index 7acd05a1..ece227dc 100644 --- a/editor/pages/figma/inspect-raw.tsx +++ b/editor/pages/figma/inspect-raw.tsx @@ -20,8 +20,8 @@ export default function InspectRaw() { ); diff --git a/editor/pages/figma/to-reflect.tsx b/editor/pages/figma/to-reflect.tsx index 7f5145b3..b4e7217d 100644 --- a/editor/pages/figma/to-reflect.tsx +++ b/editor/pages/figma/to-reflect.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; import { canvas } from "components"; import { ReflectSceneNode } from "@design-sdk/core"; -import { visualize_node } from "@code-editor/debugger/components/visualization"; -import { JsonTree } from "@code-editor/debugger/components/visualization/json-visualization/json-tree"; +import { visualize_node } from "@code-editor/devtools/components/visualization"; +import { JsonTree } from "@code-editor/devtools/components/visualization/json-visualization/json-tree"; import { useReflectTargetNode } from "../../query/from-figma"; export default function FigmaToReflectNodePage() { diff --git a/editor/pages/figma/to-token.tsx b/editor/pages/figma/to-token.tsx index ae27ff47..7b0f04f5 100644 --- a/editor/pages/figma/to-token.tsx +++ b/editor/pages/figma/to-token.tsx @@ -4,7 +4,7 @@ import { tokenize } from "@designto/token"; import { JsonTree, WidgetTree, -} from "@code-editor/debugger/components/visualization/json-visualization/json-tree"; +} from "@code-editor/devtools/components/visualization/json-visualization/json-tree"; import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; import { LayerHierarchy } from "components/editor-hierarchy"; import { WorkspaceContentPanelGridLayout } from "layouts/panel/workspace-content-panel-grid-layout"; diff --git a/editor/pages/integrations/index.tsx b/editor/pages/integrations/index.tsx index 1e5f812d..a5675359 100644 --- a/editor/pages/integrations/index.tsx +++ b/editor/pages/integrations/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from "react"; -import styled from "@emotion/styled"; +import React from "react"; import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; import { HomeHeading, HomeSidebar } from "components/home"; import Link from "next/link"; diff --git a/editor/pages/preferences/access-tokens.tsx b/editor/pages/preferences/access-tokens.tsx index 3d0b960e..6e0e7da1 100644 --- a/editor/pages/preferences/access-tokens.tsx +++ b/editor/pages/preferences/access-tokens.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import styled from "@emotion/styled"; -import { TextField } from "@material-ui/core"; +import { TextField } from "@mui/material"; import { personal } from "@design-sdk/figma-auth-store"; export default function AccessTokenConfigurationPage_Dev() { diff --git a/editor/pages/preferences/index.tsx b/editor/pages/preferences/index.tsx index 286d32a2..4efaff0a 100644 --- a/editor/pages/preferences/index.tsx +++ b/editor/pages/preferences/index.tsx @@ -2,9 +2,9 @@ import React from "react"; import Link from "next/link"; import styled from "@emotion/styled"; -import FormGroup from "@material-ui/core/FormGroup"; -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import Checkbox from "@material-ui/core/Checkbox"; +import FormGroup from "@mui/material/FormGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; import { WorkspacePreferenceStore } from "store/workspace-preference-store"; export default function PreferencesHomePage() { diff --git a/editor/scaffolds/canvas/canvas.tsx b/editor/scaffolds/canvas/canvas.tsx index e79d81f1..dc67d8be 100644 --- a/editor/scaffolds/canvas/canvas.tsx +++ b/editor/scaffolds/canvas/canvas.tsx @@ -2,12 +2,15 @@ import React, { useCallback } from "react"; import styled from "@emotion/styled"; import { Canvas } from "@code-editor/canvas"; import { useEditorState, useWorkspace } from "core/states"; -import { Preview } from "scaffolds/preview"; +import { + D2CVanillaPreview, + WebWorkerD2CVanillaPreview, +} from "scaffolds/preview"; import useMeasure from "react-use-measure"; import { useDispatch } from "core/dispatch"; import { FrameTitleRenderer } from "./render/frame-title"; import { IsolateModeCanvas } from "./isolate-mode"; -import { Dialog } from "@material-ui/core"; +import { Dialog } from "@mui/material"; import { FullScreenPreview } from "scaffolds/preview-full-screen"; /** @@ -110,10 +113,10 @@ export function VisualContentArea() { ]} filekey={state.design.key} pageid={selectedPage} - selectedNodes={selectedNodes.filter(Boolean)} + selectedNodes={selectedNodes} highlightedLayer={highlightedLayer} - onSelectNode={(node) => { - dispatch({ type: "select-node", node: node?.id }); + onSelectNode={(...nodes) => { + dispatch({ type: "select-node", node: nodes.map((n) => n.id) }); }} onClearSelection={() => { dispatch({ type: "select-node", node: null }); @@ -121,18 +124,26 @@ export function VisualContentArea() { nodes={thisPageNodes} // initialTransform={ } // TODO: if the initial selection is provided from first load, from the query param, we have to focus to fit that node. renderItem={(p) => { - return ; + return ( + // + + ); }} config={{ can_highlight_selected_layer: true, marquee: { - disabled: true, + disabled: false, }, }} renderFrameTitle={(p) => ( )} diff --git a/editor/scaffolds/canvas/isolate-mode.tsx b/editor/scaffolds/canvas/isolate-mode.tsx index 7e4c28ae..3924e6b7 100644 --- a/editor/scaffolds/canvas/isolate-mode.tsx +++ b/editor/scaffolds/canvas/isolate-mode.tsx @@ -3,6 +3,7 @@ import { IsolatedCanvas } from "components/canvas"; import { PreviewAndRunPanel } from "components/preview-and-run"; import { useEditorState } from "core/states"; import { VanillaDedicatedPreviewRenderer } from "components/app-runner"; +import { Devtools } from "scaffolds/devtools"; export function IsolateModeCanvas({ hidden = false, @@ -50,6 +51,7 @@ export function IsolateModeCanvas({ )} + ); } diff --git a/editor/scaffolds/canvas/render/frame-title.tsx b/editor/scaffolds/canvas/render/frame-title.tsx index 61434cef..8681cd2d 100644 --- a/editor/scaffolds/canvas/render/frame-title.tsx +++ b/editor/scaffolds/canvas/render/frame-title.tsx @@ -17,7 +17,9 @@ export function FrameTitleRenderer({ onHoverChange, onSelect, onRunClick, + runnable = false, }: FrameTitleProps & { + runnable?: boolean; onRunClick: () => void; }) { const [x, y] = xy; @@ -47,7 +49,9 @@ export function FrameTitleRenderer({ xy={[x, height_considered_y_transform]} {...hoverProps} > - {selected && } + {selected && runnable && ( + + )} { + // + }, []); + + return ( +