@canvas-commons/2d 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +22 -0
  2. package/editor/index.css +40 -0
  3. package/editor/index.js +525 -0
  4. package/editor/index.js.map +1 -0
  5. package/lib/index-OvpZ-GsA.d.ts +5673 -0
  6. package/lib/index-OvpZ-GsA.d.ts.map +1 -0
  7. package/lib/index.d.ts +2 -0
  8. package/lib/index.js +10326 -0
  9. package/lib/index.js.map +1 -0
  10. package/lib/jsx-dev-runtime.d.ts +2 -0
  11. package/lib/jsx-dev-runtime.js +2 -0
  12. package/lib/jsx-runtime.d.ts +2 -0
  13. package/lib/jsx-runtime.js +28 -0
  14. package/lib/jsx-runtime.js.map +1 -0
  15. package/package.json +79 -0
  16. package/src/editor/NodeInspectorConfig.tsx +76 -0
  17. package/src/editor/PreviewOverlayConfig.tsx +65 -0
  18. package/src/editor/Provider.tsx +109 -0
  19. package/src/editor/SceneGraphTabConfig.tsx +87 -0
  20. package/src/editor/icons/CircleIcon.tsx +7 -0
  21. package/src/editor/icons/CodeIcon.tsx +8 -0
  22. package/src/editor/icons/CurveIcon.tsx +7 -0
  23. package/src/editor/icons/GridIcon.tsx +7 -0
  24. package/src/editor/icons/IconMap.ts +35 -0
  25. package/src/editor/icons/ImgIcon.tsx +8 -0
  26. package/src/editor/icons/LayoutIcon.tsx +9 -0
  27. package/src/editor/icons/LineIcon.tsx +7 -0
  28. package/src/editor/icons/NodeIcon.tsx +7 -0
  29. package/src/editor/icons/RayIcon.tsx +7 -0
  30. package/src/editor/icons/RectIcon.tsx +7 -0
  31. package/src/editor/icons/ShapeIcon.tsx +7 -0
  32. package/src/editor/icons/TxtIcon.tsx +8 -0
  33. package/src/editor/icons/VideoIcon.tsx +7 -0
  34. package/src/editor/icons/View2DIcon.tsx +10 -0
  35. package/src/editor/index.css +0 -0
  36. package/src/editor/index.ts +19 -0
  37. package/src/editor/shortcuts.ts +27 -0
  38. package/src/editor/tree/DetachedRoot.tsx +27 -0
  39. package/src/editor/tree/NodeElement.tsx +72 -0
  40. package/src/editor/tree/TreeElement.tsx +70 -0
  41. package/src/editor/tree/TreeRoot.tsx +10 -0
  42. package/src/editor/tree/ViewRoot.tsx +20 -0
  43. package/src/editor/tree/index.module.scss +45 -0
  44. package/src/editor/tree/index.ts +4 -0
  45. package/src/editor/tree/navigation.ts +145 -0
  46. package/src/editor/tsconfig.build.json +5 -0
  47. package/src/editor/tsconfig.json +12 -0
  48. package/src/editor/tsdoc.json +4 -0
  49. package/src/editor/utils/SignalSet.ts +37 -0
  50. package/src/editor/utils/index.ts +1 -0
  51. package/src/editor/vite-env.d.ts +1 -0
  52. package/src/lib/code/CodeCursor.ts +468 -0
  53. package/src/lib/code/CodeDiffer.ts +77 -0
  54. package/src/lib/code/CodeFragment.ts +96 -0
  55. package/src/lib/code/CodeHighlighter.ts +73 -0
  56. package/src/lib/code/CodeMetrics.ts +47 -0
  57. package/src/lib/code/CodeRange.test.ts +113 -0
  58. package/src/lib/code/CodeRange.ts +222 -0
  59. package/src/lib/code/CodeScope.ts +100 -0
  60. package/src/lib/code/CodeSelection.ts +28 -0
  61. package/src/lib/code/CodeSignal.ts +348 -0
  62. package/src/lib/code/CodeTokenizer.ts +54 -0
  63. package/src/lib/code/DefaultHighlightStyle.ts +98 -0
  64. package/src/lib/code/LezerHighlighter.ts +113 -0
  65. package/src/lib/code/diff.test.ts +311 -0
  66. package/src/lib/code/diff.ts +319 -0
  67. package/src/lib/code/extractRange.ts +125 -0
  68. package/src/lib/code/index.ts +13 -0
  69. package/src/lib/components/Bezier.ts +103 -0
  70. package/src/lib/components/Camera.ts +401 -0
  71. package/src/lib/components/Circle.ts +310 -0
  72. package/src/lib/components/Code.ts +532 -0
  73. package/src/lib/components/CubicBezier.ts +115 -0
  74. package/src/lib/components/Curve.ts +460 -0
  75. package/src/lib/components/Grid.ts +134 -0
  76. package/src/lib/components/Icon.ts +153 -0
  77. package/src/lib/components/Img.ts +328 -0
  78. package/src/lib/components/Knot.ts +156 -0
  79. package/src/lib/components/Latex.ts +537 -0
  80. package/src/lib/components/Layout.ts +1101 -0
  81. package/src/lib/components/Line.ts +394 -0
  82. package/src/lib/components/Node.ts +1941 -0
  83. package/src/lib/components/Path.ts +132 -0
  84. package/src/lib/components/Polygon.ts +266 -0
  85. package/src/lib/components/QuadBezier.ts +103 -0
  86. package/src/lib/components/Ray.ts +126 -0
  87. package/src/lib/components/Rect.ts +219 -0
  88. package/src/lib/components/SVG.ts +853 -0
  89. package/src/lib/components/Shape.ts +322 -0
  90. package/src/lib/components/Spline.ts +318 -0
  91. package/src/lib/components/Txt.test.tsx +81 -0
  92. package/src/lib/components/Txt.ts +204 -0
  93. package/src/lib/components/TxtLeaf.ts +210 -0
  94. package/src/lib/components/Video.ts +368 -0
  95. package/src/lib/components/View2D.ts +85 -0
  96. package/src/lib/components/__logs__/image-without-source.ts +18 -0
  97. package/src/lib/components/__logs__/line-without-points.ts +31 -0
  98. package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
  99. package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
  100. package/src/lib/components/__tests__/Camera.test.tsx +73 -0
  101. package/src/lib/components/__tests__/children.test.tsx +142 -0
  102. package/src/lib/components/__tests__/clone.test.tsx +126 -0
  103. package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
  104. package/src/lib/components/__tests__/generatorTest.ts +27 -0
  105. package/src/lib/components/__tests__/mockScene2D.ts +50 -0
  106. package/src/lib/components/__tests__/query.test.tsx +122 -0
  107. package/src/lib/components/__tests__/state.test.tsx +60 -0
  108. package/src/lib/components/index.ts +26 -0
  109. package/src/lib/components/types.ts +35 -0
  110. package/src/lib/curves/ArcSegment.ts +169 -0
  111. package/src/lib/curves/CircleSegment.ts +99 -0
  112. package/src/lib/curves/CubicBezierSegment.ts +80 -0
  113. package/src/lib/curves/CurveDrawingInfo.ts +11 -0
  114. package/src/lib/curves/CurvePoint.ts +15 -0
  115. package/src/lib/curves/CurveProfile.ts +28 -0
  116. package/src/lib/curves/KnotInfo.ts +10 -0
  117. package/src/lib/curves/LineSegment.ts +75 -0
  118. package/src/lib/curves/Polynomial.ts +355 -0
  119. package/src/lib/curves/Polynomial2D.ts +62 -0
  120. package/src/lib/curves/PolynomialSegment.ts +151 -0
  121. package/src/lib/curves/QuadBezierSegment.ts +66 -0
  122. package/src/lib/curves/Segment.ts +31 -0
  123. package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
  124. package/src/lib/curves/createCurveProfileLerp.ts +471 -0
  125. package/src/lib/curves/getBezierSplineProfile.ts +227 -0
  126. package/src/lib/curves/getCircleProfile.ts +86 -0
  127. package/src/lib/curves/getPathProfile.ts +177 -0
  128. package/src/lib/curves/getPointAtDistance.ts +21 -0
  129. package/src/lib/curves/getPolylineProfile.test.ts +21 -0
  130. package/src/lib/curves/getPolylineProfile.ts +88 -0
  131. package/src/lib/curves/getRectProfile.ts +138 -0
  132. package/src/lib/curves/index.ts +16 -0
  133. package/src/lib/decorators/canvasStyleSignal.ts +15 -0
  134. package/src/lib/decorators/colorSignal.ts +9 -0
  135. package/src/lib/decorators/compound.ts +85 -0
  136. package/src/lib/decorators/computed.ts +18 -0
  137. package/src/lib/decorators/defaultStyle.ts +15 -0
  138. package/src/lib/decorators/filtersSignal.ts +133 -0
  139. package/src/lib/decorators/index.ts +10 -0
  140. package/src/lib/decorators/initializers.ts +32 -0
  141. package/src/lib/decorators/nodeName.ts +13 -0
  142. package/src/lib/decorators/signal.test.ts +89 -0
  143. package/src/lib/decorators/signal.ts +348 -0
  144. package/src/lib/decorators/spacingSignal.ts +15 -0
  145. package/src/lib/decorators/transformSignals.test.ts +858 -0
  146. package/src/lib/decorators/transformSignals.ts +1633 -0
  147. package/src/lib/decorators/vector2Signal.ts +35 -0
  148. package/src/lib/globals.d.ts +2 -0
  149. package/src/lib/index.ts +9 -0
  150. package/src/lib/jsx-dev-runtime.ts +2 -0
  151. package/src/lib/jsx-runtime.ts +45 -0
  152. package/src/lib/morphers/PathMorpher.ts +8 -0
  153. package/src/lib/morphers/defaultMorpher.ts +43 -0
  154. package/src/lib/morphers/index.ts +4 -0
  155. package/src/lib/morphers/manimMorpher.ts +658 -0
  156. package/src/lib/parse-svg-path.d.ts +14 -0
  157. package/src/lib/partials/Filter.ts +185 -0
  158. package/src/lib/partials/Gradient.ts +103 -0
  159. package/src/lib/partials/Pattern.ts +35 -0
  160. package/src/lib/partials/RoughConfig.ts +180 -0
  161. package/src/lib/partials/ShaderConfig.ts +122 -0
  162. package/src/lib/partials/index.ts +5 -0
  163. package/src/lib/partials/types.ts +58 -0
  164. package/src/lib/scenes/Scene2D.ts +167 -0
  165. package/src/lib/scenes/index.ts +3 -0
  166. package/src/lib/scenes/makeScene2D.ts +19 -0
  167. package/src/lib/scenes/useScene2D.ts +6 -0
  168. package/src/lib/tsconfig.build.json +5 -0
  169. package/src/lib/tsconfig.json +11 -0
  170. package/src/lib/tsdoc.json +4 -0
  171. package/src/lib/utils/CanvasUtils.ts +304 -0
  172. package/src/lib/utils/PathDataBuilder.ts +320 -0
  173. package/src/lib/utils/diff.test.ts +453 -0
  174. package/src/lib/utils/diff.ts +148 -0
  175. package/src/lib/utils/index.ts +5 -0
  176. package/src/lib/utils/is.ts +11 -0
  177. package/src/lib/utils/makeSignalExtensions.ts +29 -0
  178. package/src/lib/utils/rough.ts +513 -0
  179. package/src/lib/utils/withDefaults.tsx +26 -0
  180. package/src/tsconfig.base.json +18 -0
  181. package/src/tsconfig.build.json +8 -0
  182. package/src/tsconfig.json +5 -0
  183. package/tsconfig.project.json +7 -0
@@ -0,0 +1,2 @@
1
+ import { F as Fragment, L as jsx } from "./index-OvpZ-GsA.js";
2
+ export { Fragment, jsx as jsxDEV };
@@ -0,0 +1,2 @@
1
+ import { Fragment, jsx } from "./jsx-runtime.js";
2
+ export { Fragment, jsx as jsxDEV };
@@ -0,0 +1,2 @@
1
+ import { F as Fragment, I as JSX, L as jsx } from "./index-OvpZ-GsA.js";
2
+ export { Fragment, JSX, jsx, jsx as jsxs };
@@ -0,0 +1,28 @@
1
+ //#region src/lib/jsx-runtime.ts
2
+ function isClassComponent(fn) {
3
+ return !!fn.prototype?.isClass;
4
+ }
5
+ const Fragment = Symbol.for("@canvas-commons/2d/fragment");
6
+ function jsx(type, config, key) {
7
+ const { ref, children, ...rest } = config;
8
+ const flatChildren = Array.isArray(children) ? children.flat() : children;
9
+ if (type === Fragment) return flatChildren;
10
+ if (isClassComponent(type)) {
11
+ const node = new type({
12
+ ...rest,
13
+ children: flatChildren,
14
+ key
15
+ });
16
+ ref?.(node);
17
+ return node;
18
+ } else return type({
19
+ ...rest,
20
+ ref,
21
+ children: flatChildren,
22
+ key
23
+ });
24
+ }
25
+ //#endregion
26
+ export { Fragment, jsx, jsx as jsxs };
27
+
28
+ //# sourceMappingURL=jsx-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-runtime.js","names":[],"sources":["../src/lib/jsx-runtime.ts"],"sourcesContent":["import type {\n ComponentChildren,\n FunctionComponent,\n JSXProps,\n Node,\n NodeConstructor,\n} from './components';\n\nexport namespace JSX {\n export type Element = Node;\n export type ElementClass = Node;\n export interface ElementChildrenAttribute {\n children: any;\n }\n}\n\nfunction isClassComponent(\n // eslint-disable-next-line @typescript-eslint/ban-types\n fn: Function,\n): fn is new (...args: unknown[]) => unknown {\n return !!fn.prototype?.isClass;\n}\n\nexport const Fragment = Symbol.for('@canvas-commons/2d/fragment');\nexport function jsx(\n type: NodeConstructor | FunctionComponent | typeof Fragment,\n config: JSXProps,\n key?: any,\n): ComponentChildren {\n const {ref, children, ...rest} = config;\n const flatChildren = Array.isArray(children) ? children.flat() : children;\n\n if (type === Fragment) {\n return flatChildren;\n }\n\n if (isClassComponent(type)) {\n const node = new type({...rest, children: flatChildren, key});\n ref?.(node);\n return node;\n } else {\n return type({...rest, ref, children: flatChildren, key});\n }\n}\nexport {jsx as jsxs};\n"],"mappings":";AAgBA,SAAS,iBAEP,IAC2C;CAC3C,OAAO,CAAC,CAAC,GAAG,WAAW;AACzB;AAEA,MAAa,WAAW,OAAO,IAAI,6BAA6B;AAChE,SAAgB,IACd,MACA,QACA,KACmB;CACnB,MAAM,EAAC,KAAK,UAAU,GAAG,SAAQ;CACjC,MAAM,eAAe,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK,IAAI;CAEjE,IAAI,SAAS,UACX,OAAO;CAGT,IAAI,iBAAiB,IAAI,GAAG;EAC1B,MAAM,OAAO,IAAI,KAAK;GAAC,GAAG;GAAM,UAAU;GAAc;EAAG,CAAC;EAC5D,MAAM,IAAI;EACV,OAAO;CACT,OACE,OAAO,KAAK;EAAC,GAAG;EAAM;EAAK,UAAU;EAAc;CAAG,CAAC;AAE3D"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@canvas-commons/2d",
3
+ "version": "0.2.0",
4
+ "description": "A 2D renderer for Canvas Commons",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./jsx-runtime": {
14
+ "types": "./lib/jsx-runtime.d.ts",
15
+ "default": "./lib/jsx-runtime.js"
16
+ },
17
+ "./jsx-dev-runtime": {
18
+ "types": "./lib/jsx-dev-runtime.d.ts",
19
+ "default": "./lib/jsx-dev-runtime.js"
20
+ },
21
+ "./editor": {
22
+ "default": "./editor/index.js"
23
+ },
24
+ "./tsconfig.project.json": "./tsconfig.project.json",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "engines": {
28
+ "node": ">=20.19.0"
29
+ },
30
+ "author": "canvas-commons",
31
+ "homepage": "https://canvascommons.io/",
32
+ "bugs": "https://github.com/canvas-commons/canvas-commons/issues",
33
+ "license": "MIT",
34
+ "sideEffects": false,
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/canvas-commons/canvas-commons.git",
38
+ "directory": "packages/2d"
39
+ },
40
+ "files": [
41
+ "lib",
42
+ "editor",
43
+ "src",
44
+ "tsconfig.project.json"
45
+ ],
46
+ "devDependencies": {
47
+ "@preact/signals": "^1.2.1",
48
+ "@rollup/plugin-node-resolve": "^15.2.3",
49
+ "@rollup/plugin-typescript": "^11.1.5",
50
+ "@types/node": "^20.19.0",
51
+ "clsx": "^2.0.0",
52
+ "geometry-polyfill": "^1.0.7",
53
+ "jsdom": "^29.1.0",
54
+ "preact": "^10.19.2",
55
+ "rollup": "^4.9.1",
56
+ "rollup-plugin-postcss": "^4.0.2",
57
+ "vitest": "^4.1.0",
58
+ "@canvas-commons/editor": "^0.2.0",
59
+ "@canvas-commons/2d": "0.2.0"
60
+ },
61
+ "dependencies": {
62
+ "@codemirror/language": "^6.10.1",
63
+ "@lezer/common": "^1.2.1",
64
+ "@lezer/highlight": "^1.2.0",
65
+ "escape-string-regexp": "^5.0.0",
66
+ "mathjax-full": "^3.2.2",
67
+ "parse-svg-path": "^0.1.2",
68
+ "roughjs": "^4.6.6",
69
+ "@canvas-commons/core": "^0.2.0"
70
+ },
71
+ "scripts": {
72
+ "dev": "tsdown --watch",
73
+ "build": "pnpm run build-lib && pnpm run build-editor",
74
+ "build-lib": "tsdown",
75
+ "build-editor": "rollup -c rollup.editor.mjs",
76
+ "test": "vitest run",
77
+ "lint:pkg": "publint --strict && attw --pack . --profile esm-only --ignore-rules internal-resolution-error --exclude-entrypoints ./editor"
78
+ }
79
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ AutoField,
3
+ Button,
4
+ Group,
5
+ Label,
6
+ Pane,
7
+ PluginInspectorConfig,
8
+ Separator,
9
+ UnknownField,
10
+ findAndOpenFirstUserFile,
11
+ useApplication,
12
+ } from '@canvas-commons/editor';
13
+ import {useComputed} from '@preact/signals';
14
+ import {NodeInspectorKey, usePluginState} from './Provider';
15
+
16
+ function Component() {
17
+ const {inspection} = useApplication();
18
+ const {scene, afterRender} = usePluginState();
19
+ const node = useComputed(() => {
20
+ afterRender.value;
21
+ const {payload} = inspection.value;
22
+ return scene.value?.getNode(payload as string);
23
+ });
24
+
25
+ const attributes = useComputed(() => {
26
+ afterRender.value;
27
+ const currentNode = node.value;
28
+ const attributes: [string, unknown][] = [];
29
+
30
+ if (currentNode) {
31
+ for (const {key, meta, signal} of currentNode) {
32
+ if (!meta.inspectable) continue;
33
+ attributes.push([key, signal()]);
34
+ }
35
+ }
36
+
37
+ return attributes;
38
+ });
39
+
40
+ const stack = node.value?.creationStack;
41
+
42
+ return (
43
+ <Pane title="Node Inspector" id="node-inspector-pane">
44
+ <Separator size={1} />
45
+ {stack && (
46
+ <Group>
47
+ <Label />
48
+ <Button onClick={() => findAndOpenFirstUserFile(stack)} main>
49
+ GO TO SOURCE
50
+ </Button>
51
+ </Group>
52
+ )}
53
+ <Group>
54
+ <Label>key</Label>
55
+ <UnknownField value={inspection.value.payload} />
56
+ </Group>
57
+ {!node.value && (
58
+ <Group>
59
+ <Label />
60
+ Couldn't find the node. It may have been deleted or doesn't exist yet.
61
+ </Group>
62
+ )}
63
+ {attributes.value.map(([key, value]) => (
64
+ <Group key={key}>
65
+ <Label>{key}</Label>
66
+ <AutoField value={value} />
67
+ </Group>
68
+ ))}
69
+ </Pane>
70
+ );
71
+ }
72
+
73
+ export const NodeInspectorConfig: PluginInspectorConfig = {
74
+ key: NodeInspectorKey,
75
+ component: Component,
76
+ };
@@ -0,0 +1,65 @@
1
+ import {Vector2} from '@canvas-commons/core';
2
+ import {
3
+ MouseButton,
4
+ OverlayWrapper,
5
+ PluginOverlayConfig,
6
+ useViewportContext,
7
+ useViewportMatrix,
8
+ } from '@canvas-commons/editor';
9
+ import {ComponentChildren} from 'preact';
10
+ import {usePluginState} from './Provider';
11
+
12
+ function Component({children}: {children?: ComponentChildren}) {
13
+ const state = useViewportContext();
14
+ const {scene, selectNode} = usePluginState();
15
+ const matrix = useViewportMatrix();
16
+
17
+ return (
18
+ <OverlayWrapper
19
+ onPointerDown={event => {
20
+ if (event.button !== MouseButton.Left || event.shiftKey) return;
21
+ if (!scene.value) return;
22
+ event.stopPropagation();
23
+
24
+ const position = new Vector2(
25
+ event.x - state.rect.x,
26
+ event.y - state.rect.y,
27
+ ).transformAsPoint(matrix.inverse());
28
+
29
+ selectNode(
30
+ scene.value.inspectPosition(position.x, position.y) as string,
31
+ );
32
+ }}
33
+ >
34
+ {children}
35
+ </OverlayWrapper>
36
+ );
37
+ }
38
+
39
+ function drawHook() {
40
+ const {selectedNode, hoveredKey, afterRender, scene} = usePluginState();
41
+ selectedNode.value;
42
+ hoveredKey.value;
43
+ afterRender.value;
44
+
45
+ return (ctx: CanvasRenderingContext2D, matrix: DOMMatrix) => {
46
+ const currentScene = scene.peek();
47
+ if (!currentScene) return;
48
+
49
+ let node = selectedNode.value;
50
+ if (node) {
51
+ currentScene.drawOverlay(node.key, matrix, ctx);
52
+ }
53
+
54
+ node = currentScene.getNode(hoveredKey.value);
55
+ if (node && hoveredKey.value !== selectedNode.value?.key) {
56
+ ctx.globalAlpha = 0.5;
57
+ currentScene.drawOverlay(hoveredKey.value, matrix, ctx);
58
+ }
59
+ };
60
+ }
61
+
62
+ export const PreviewOverlayConfig: PluginOverlayConfig = {
63
+ drawHook,
64
+ component: Component,
65
+ };
@@ -0,0 +1,109 @@
1
+ import {Node, Scene2D} from '@canvas-commons/2d';
2
+ import {SceneRenderEvent} from '@canvas-commons/core';
3
+ import {useApplication, useCurrentScene} from '@canvas-commons/editor';
4
+ import {
5
+ ReadonlySignal,
6
+ Signal,
7
+ computed,
8
+ signal,
9
+ useSignalEffect,
10
+ } from '@preact/signals';
11
+ import {ComponentChildren, createContext} from 'preact';
12
+ import {useContext, useMemo} from 'preact/hooks';
13
+ import {SignalSet} from './utils';
14
+
15
+ export interface PluginState {
16
+ selectedNode: ReadonlySignal<Node | null>;
17
+ hoveredKey: Signal<string | null>;
18
+ selectNode: (nodeKey: string | null) => void;
19
+ openNodes: SignalSet<string>;
20
+ openDetached: Signal<boolean>;
21
+ visibleNodes: Set<string>;
22
+ scene: ReadonlySignal<Scene2D | null>;
23
+ afterRender: ReadonlySignal<number>;
24
+ }
25
+
26
+ const PluginContext = createContext<PluginState | null>(null);
27
+
28
+ export const NodeInspectorKey = '@canvas-commons/2d/node-inspector';
29
+
30
+ export function usePluginState() {
31
+ return useContext(PluginContext)!;
32
+ }
33
+
34
+ export function Provider({children}: {children?: ComponentChildren}) {
35
+ const {inspection} = useApplication();
36
+ const currentScene = useCurrentScene();
37
+
38
+ const state = useMemo(() => {
39
+ const afterRender = signal(0);
40
+ const openDetached = signal(false);
41
+ const visibleNodes = new Set<string>();
42
+ const scene = signal(currentScene as Scene2D);
43
+ const selectedNode = computed(() => {
44
+ afterRender.value;
45
+ const {key, payload} = inspection.value;
46
+ if (key === NodeInspectorKey) {
47
+ return scene.value?.getNode(payload as string) ?? null;
48
+ }
49
+ return null;
50
+ });
51
+ const hoveredKey = signal<string | null>(null);
52
+ const openNodes = new SignalSet<string>();
53
+ const selectNode = (nodeKey: string | null) => {
54
+ const {key, payload} = inspection.peek();
55
+
56
+ if (key === NodeInspectorKey && !nodeKey) {
57
+ inspection.value = {key: '', payload: null};
58
+ } else if (payload !== nodeKey) {
59
+ inspection.value = {key: NodeInspectorKey, payload: nodeKey};
60
+ }
61
+ };
62
+
63
+ return {
64
+ selectedNode,
65
+ selectNode,
66
+ hoveredKey,
67
+ afterRender,
68
+ openNodes,
69
+ openDetached,
70
+ visibleNodes,
71
+ scene,
72
+ } satisfies PluginState;
73
+ }, []);
74
+
75
+ state.scene.value = currentScene as Scene2D;
76
+
77
+ useSignalEffect(() =>
78
+ state.scene.value?.onRenderLifecycle.subscribe(([event]) => {
79
+ if (event === SceneRenderEvent.AfterRender) {
80
+ state.afterRender.value++;
81
+ }
82
+ }),
83
+ );
84
+
85
+ // Expand all nodes necessary to reveal the selected one:
86
+ useSignalEffect(() => {
87
+ const selectedNode = state.selectedNode.value;
88
+
89
+ if (selectedNode && !state.visibleNodes.has(selectedNode.key ?? '')) {
90
+ let node = selectedNode.parent() ?? null;
91
+ if (!node && selectedNode !== selectedNode.view()) {
92
+ state.openDetached.value = true;
93
+ }
94
+
95
+ while (node) {
96
+ state.openNodes.add(node.key);
97
+ const parent = node.parent();
98
+ if (!parent && node !== node.view()) {
99
+ state.openDetached.value = true;
100
+ }
101
+ node = parent;
102
+ }
103
+ }
104
+ });
105
+
106
+ return (
107
+ <PluginContext.Provider value={state}>{children}</PluginContext.Provider>
108
+ );
109
+ }
@@ -0,0 +1,87 @@
1
+ import {
2
+ AccountTree,
3
+ emphasize,
4
+ Pane,
5
+ PluginTabConfig,
6
+ PluginTabProps,
7
+ Tab,
8
+ useApplication,
9
+ usePanels,
10
+ useReducedMotion,
11
+ useSurfaceShortcuts,
12
+ } from '@canvas-commons/editor';
13
+ import {useSignalEffect} from '@preact/signals';
14
+ import {useEffect, useRef} from 'preact/hooks';
15
+ import {usePluginState} from './Provider';
16
+ import {SCENE_GRAPH_SHORTCUTS} from './shortcuts';
17
+ import {DetachedRoot, useKeyboardNavigation, ViewRoot} from './tree';
18
+
19
+ function TabComponent({tab}: PluginTabProps) {
20
+ const {sidebar} = usePanels();
21
+ const inspectorTab = useRef<HTMLButtonElement>(null);
22
+ const reducedMotion = useReducedMotion();
23
+ const {selectedNode, selectNode} = usePluginState();
24
+ const {logger} = useApplication();
25
+
26
+ useEffect(
27
+ () =>
28
+ logger.onInspected.subscribe(key => {
29
+ sidebar.set(tab);
30
+ selectNode(key);
31
+ }),
32
+ [tab],
33
+ );
34
+
35
+ useSignalEffect(() => {
36
+ if (
37
+ selectedNode.value &&
38
+ sidebar.current.peek() !== tab &&
39
+ !reducedMotion &&
40
+ inspectorTab.current &&
41
+ inspectorTab.current.getAnimations().length < 2
42
+ ) {
43
+ inspectorTab.current.animate(emphasize(), {duration: 400});
44
+ inspectorTab.current.animate([{color: 'white'}, {color: ''}], {
45
+ duration: 800,
46
+ });
47
+ }
48
+ });
49
+
50
+ return (
51
+ <Tab
52
+ forwardRef={inspectorTab}
53
+ title="Scene Graph"
54
+ id="scene-graph-tab"
55
+ tab={tab}
56
+ >
57
+ <AccountTree />
58
+ </Tab>
59
+ );
60
+ }
61
+
62
+ function PaneComponent() {
63
+ const ref = useSurfaceShortcuts<HTMLDivElement>(SCENE_GRAPH_SHORTCUTS);
64
+ const {selectNode} = usePluginState();
65
+
66
+ useKeyboardNavigation();
67
+
68
+ return (
69
+ <Pane
70
+ forwardRef={ref}
71
+ title="Scene Graph"
72
+ id="scene-graph-pane"
73
+ onClick={() => {
74
+ selectNode(null);
75
+ }}
76
+ >
77
+ <ViewRoot />
78
+ <DetachedRoot />
79
+ </Pane>
80
+ );
81
+ }
82
+
83
+ export const SceneGraphTabConfig: PluginTabConfig = {
84
+ name: 'scene-graph',
85
+ tabComponent: TabComponent,
86
+ paneComponent: PaneComponent,
87
+ };
@@ -0,0 +1,7 @@
1
+ export function CircleIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M10,5C12.76,5 15,7.24 15,10C15,12.76 12.76,15 10,15C7.24,15 5,12.76 5,10C5,7.24 7.24,5 10,5ZM10,7C8.344,7 7,8.344 7,10C7,11.656 8.344,13 10,13C11.656,13 13,11.656 13,10C13,8.344 11.656,7 10,7Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,8 @@
1
+ export function CodeIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M5,9L5,6.999C5,6.469 5.211,5.96 5.585,5.586C5.96,5.211 6.469,5 6.999,5L9,5L9,7L6.999,7L7,9L7,11L7,13L8.985,13L9,15L7,15C5.895,15 5,14.105 5,13L5,11L4,11L4,9L5,9Z" />
5
+ <path d="M15,11L15,13.001C15,13.531 14.789,14.04 14.415,14.414C14.04,14.789 13.531,15 13.001,15L11,15L11,13L13,13L13,11L13,9L13,7L11.015,7L11,5L13,5C14.105,5 15,5.895 15,7L15,9L16,9L16,11L15,11Z" />
6
+ </svg>
7
+ );
8
+ }
@@ -0,0 +1,7 @@
1
+ export function CurveIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M12.19,6.47L13.595,5.047C15.519,6.947 15.187,8.932 14.229,9.951C13.675,10.541 12.879,10.861 12.016,10.767C11.261,10.685 10.426,10.278 9.708,9.348C9.292,8.809 8.878,8.441 8.471,8.249C8.217,8.13 7.979,8.084 7.77,8.154C7.565,8.222 7.409,8.394 7.287,8.621C7.097,8.975 7.001,9.444 7,10.003C6.996,11.584 7.848,12.746 8.91,12.946C9.535,13.064 10.185,12.783 10.687,12.082L12.313,13.247C11,15.079 9.118,15.344 7.581,14.591C6.161,13.896 4.994,12.246 5,9.997C5.005,7.945 5.963,6.649 7.136,6.257C8.281,5.874 9.866,6.278 11.292,8.126C11.81,8.799 12.421,8.954 12.772,8.581C13.196,8.13 13.042,7.312 12.19,6.47Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export function GridIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M6,8L4,8L4,6L6,6L6,4L8,4L8,6L12,6L12,4L14,4L14,6L16,6L16,8L14,8L14,12L16,12L16,14L14,14L14,16L12,16L12,14L8,14L8,16L6,16L6,14L4,14L4,12L6,12L6,8ZM8,12L12,12L12,8L8,8L8,12Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,35 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+
3
+ import {FunctionComponent} from 'preact';
4
+ import {CircleIcon} from './CircleIcon';
5
+ import {CodeIcon} from './CodeIcon';
6
+ import {CurveIcon} from './CurveIcon';
7
+ import {GridIcon} from './GridIcon';
8
+ import {ImgIcon} from './ImgIcon';
9
+ import {LayoutIcon} from './LayoutIcon';
10
+ import {LineIcon} from './LineIcon';
11
+ import {NodeIcon} from './NodeIcon';
12
+ import {RayIcon} from './RayIcon';
13
+ import {RectIcon} from './RectIcon';
14
+ import {ShapeIcon} from './ShapeIcon';
15
+ import {TxtIcon} from './TxtIcon';
16
+ import {VideoIcon} from './VideoIcon';
17
+ import {View2DIcon} from './View2DIcon';
18
+
19
+ export const IconMap: Record<string, FunctionComponent> = {
20
+ Circle: CircleIcon,
21
+ Code: CodeIcon,
22
+ Curve: CurveIcon,
23
+ Grid: GridIcon,
24
+ Img: ImgIcon,
25
+ Layout: LayoutIcon,
26
+ Line: LineIcon,
27
+ Node: NodeIcon,
28
+ Ray: RayIcon,
29
+ Rect: RectIcon,
30
+ Shape: ShapeIcon,
31
+ Txt: TxtIcon,
32
+ TxtLeaf: TxtIcon,
33
+ Video: VideoIcon,
34
+ View2D: View2DIcon,
35
+ };
@@ -0,0 +1,8 @@
1
+ export function ImgIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M5,15L15,15L15,10L13,8L8,13L5,10L5,15Z" />
5
+ <circle cx="8" cy="7" r="2" />
6
+ </svg>
7
+ );
8
+ }
@@ -0,0 +1,9 @@
1
+ export function LayoutIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M14,5C14.552,5 15,5.448 15,6C15,7.916 15,12.084 15,14C15,14.552 14.552,15 14,15C12.815,15 11,15 11,15L11,5L14,5Z" />
5
+ <path d="M9,5L9,9L5,9L5,6C5,5.448 5.448,5 6,5L9,5Z" />
6
+ <path d="M9,11L9,15L6,15C5.448,15 5,14.552 5,14L5,11L9,11Z" />
7
+ </svg>
8
+ );
9
+ }
@@ -0,0 +1,7 @@
1
+ export function LineIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M9.906,4.589L11.411,5.906L8.529,9.2L13.859,8.439C14.273,8.379 14.68,8.584 14.879,8.952C15.078,9.319 15.028,9.772 14.753,10.087L10.094,15.411L8.589,14.094L11.471,10.8L6.141,11.561C5.727,11.621 5.32,11.416 5.121,11.048C4.922,10.681 4.972,10.228 5.247,9.913L9.906,4.589Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export function NodeIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M7,9L5,9L5,7L7,7L7,5L9,5L9,7L12,7L12,5L15,8L12,11L12,9L9,9L9,12L11,12L8,15L5,12L7,12L7,9Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export function RayIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M12,9.414L6.707,14.707L5.293,13.293L10.586,8L8,8L8,6L13,6C13.552,6 14,6.448 14,7L14,12L12,12L12,9.414Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export function RectIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M15,6L15,14C15,14.552 14.552,15 14,15L6,15C5.448,15 5,14.552 5,14L5,6C5,5.448 5.448,5 6,5L14,5C14.552,5 15,5.448 15,6ZM13,7L7,7L7,13L13,13L13,7Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export function ShapeIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M11.746,10.93C12.637,12.664 11.973,14.504 10.611,15.244C9.692,15.743 8.385,15.804 6.94,14.829C5.555,13.893 4.689,12.16 4.544,10.388C4.395,8.572 5,6.752 6.399,5.701C8.069,4.445 10.793,4.271 12.765,4.921C14.324,5.436 15.374,6.473 15.495,7.691C15.651,9.262 14.613,10.061 13.26,10.5C12.847,10.634 12.41,10.735 12.02,10.841C11.936,10.864 11.838,10.897 11.746,10.93ZM7.601,7.299C6.737,7.949 6.445,9.103 6.537,10.224C6.633,11.389 7.149,12.556 8.06,13.171C8.696,13.601 9.251,13.706 9.656,13.486C10.207,13.187 10.315,12.395 9.886,11.701C9.48,11.044 9.513,10.523 9.68,10.122C9.835,9.75 10.164,9.417 10.678,9.187C11.243,8.935 12.157,8.8 12.908,8.503C13.216,8.381 13.542,8.264 13.505,7.888C13.485,7.691 13.359,7.53 13.197,7.384C12.928,7.143 12.558,6.959 12.138,6.821C10.736,6.358 8.789,6.406 7.601,7.299Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,8 @@
1
+ export function TxtIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M9,13L9,6L11,6L11,13L12,13L12,15L8,15L8,13L9,13Z" />
5
+ <path d="M7,8L5,8L5,6C5,5.448 5.448,5 6,5L14,5C14.552,5 15,5.448 15,6L15,8L13,8L13,7L7,7L7,8Z" />
6
+ </svg>
7
+ );
8
+ }
@@ -0,0 +1,7 @@
1
+ export function VideoIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M14,10.866L7.25,14.763C6.941,14.942 6.559,14.942 6.25,14.763C5.941,14.585 5.75,14.254 5.75,13.897L5.75,6.103C5.75,5.746 5.941,5.415 6.25,5.237C6.559,5.058 6.941,5.058 7.25,5.237L14,9.134C14.309,9.313 14.5,9.643 14.5,10C14.5,10.357 14.309,10.687 14,10.866ZM11.5,10L7.75,7.835L7.75,12.165L11.5,10Z" />
5
+ </svg>
6
+ );
7
+ }
@@ -0,0 +1,10 @@
1
+ export function View2DIcon() {
2
+ return (
3
+ <svg viewBox="0 0 20 20" fill="currentColor">
4
+ <path d="M9,5L9,7L7,7L7,9L5,9L5,6C5,5.448 5.448,5 6,5L9,5Z" />
5
+ <path d="M5,11L7,11L7,13L9,13L9,15L6,15C5.448,15 5,14.552 5,14L5,11Z" />
6
+ <path d="M11,15L11,13L13,13L13,11L15,11L15,14C15,14.552 14.552,15 14,15L11,15Z" />
7
+ <path d="M15,9L13,9L13,7L11,7L11,5L14,5C14.552,5 15,5.448 15,6L15,9Z" />
8
+ </svg>
9
+ );
10
+ }
File without changes
@@ -0,0 +1,19 @@
1
+ import './index.css';
2
+
3
+ import {makeEditorPlugin} from '@canvas-commons/editor';
4
+ import {NodeInspectorConfig} from './NodeInspectorConfig';
5
+ import {PreviewOverlayConfig} from './PreviewOverlayConfig';
6
+ import {Provider} from './Provider';
7
+ import {SceneGraphTabConfig} from './SceneGraphTabConfig';
8
+ import {SCENE_GRAPH_SHORTCUTS} from './shortcuts';
9
+
10
+ export default makeEditorPlugin(() => {
11
+ return {
12
+ name: '@canvas-commons/2d',
13
+ provider: Provider,
14
+ previewOverlay: PreviewOverlayConfig,
15
+ tabs: [SceneGraphTabConfig],
16
+ inspectors: [NodeInspectorConfig],
17
+ shortcuts: [SCENE_GRAPH_SHORTCUTS],
18
+ };
19
+ });