create-substrate 0.1.4 → 0.1.6
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.
- package/dist/index.js +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +87 -7
- package/dist/scaffold.js.map +1 -1
- package/dist/surfaces/3d-scene.d.ts.map +1 -1
- package/dist/surfaces/3d-scene.js +414 -67
- package/dist/surfaces/3d-scene.js.map +1 -1
- package/dist/surfaces/animation.d.ts.map +1 -1
- package/dist/surfaces/animation.js +95 -103
- package/dist/surfaces/animation.js.map +1 -1
- package/dist/surfaces/blank.d.ts.map +1 -1
- package/dist/surfaces/blank.js +28 -16
- package/dist/surfaces/blank.js.map +1 -1
- package/dist/surfaces/canvas-2d.d.ts.map +1 -1
- package/dist/surfaces/canvas-2d.js +369 -92
- package/dist/surfaces/canvas-2d.js.map +1 -1
- package/dist/surfaces/data-vis.d.ts.map +1 -1
- package/dist/surfaces/data-vis.js +262 -98
- package/dist/surfaces/data-vis.js.map +1 -1
- package/dist/surfaces/image-gen.d.ts.map +1 -1
- package/dist/surfaces/image-gen.js +354 -92
- package/dist/surfaces/image-gen.js.map +1 -1
- package/dist/surfaces/node-editor.d.ts.map +1 -1
- package/dist/surfaces/node-editor.js +559 -114
- package/dist/surfaces/node-editor.js.map +1 -1
- package/package.json +3 -2
- package/skills/canvas-2d/SKILL.md +1 -1
- package/skills/composing-panels/SKILL.md +8 -8
- package/skills/scaffold-playground/SKILL.md +4 -4
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export const canvas2d = {
|
|
2
2
|
components: [
|
|
3
|
+
"types",
|
|
4
|
+
"document-store",
|
|
5
|
+
"tool-store",
|
|
6
|
+
"element-factory",
|
|
3
7
|
"design-canvas",
|
|
4
8
|
"toolbar",
|
|
5
9
|
"panel",
|
|
6
10
|
"pane",
|
|
7
|
-
"slider",
|
|
8
11
|
"layer-list",
|
|
9
12
|
"number-input",
|
|
10
13
|
"colour-picker",
|
|
@@ -12,7 +15,7 @@ export const canvas2d = {
|
|
|
12
15
|
"status-bar",
|
|
13
16
|
"zoom-controls",
|
|
14
17
|
],
|
|
15
|
-
app: () => `import {
|
|
18
|
+
app: () => `import type { ElementType } from "react"
|
|
16
19
|
import { DesignCanvas } from "@/components/substrate/design-canvas"
|
|
17
20
|
import {
|
|
18
21
|
Toolbar,
|
|
@@ -21,18 +24,30 @@ import {
|
|
|
21
24
|
ToolbarSeparator,
|
|
22
25
|
} from "@/components/substrate/toolbar"
|
|
23
26
|
import { Panel } from "@/components/substrate/panel"
|
|
27
|
+
import { Pane, PaneHeader, PaneLabel, PaneContent } from "@/components/substrate/pane"
|
|
24
28
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
LayerList,
|
|
30
|
+
LayerItem,
|
|
31
|
+
LayerIcon,
|
|
32
|
+
LayerName,
|
|
33
|
+
LayerActions,
|
|
34
|
+
LayerVisibilityToggle,
|
|
35
|
+
LayerLockToggle,
|
|
36
|
+
} from "@/components/substrate/layer-list"
|
|
31
37
|
import { NumberInput } from "@/components/substrate/number-input"
|
|
32
|
-
import { ColourPicker } from "@/components/substrate/colour-picker"
|
|
38
|
+
import { ColourPicker, ColourSwatch } from "@/components/substrate/colour-picker"
|
|
33
39
|
import { HistoryControls } from "@/components/substrate/history-controls"
|
|
34
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
StatusBar,
|
|
42
|
+
StatusBarSection,
|
|
43
|
+
StatusBarItem,
|
|
44
|
+
StatusBarSeparator,
|
|
45
|
+
} from "@/components/substrate/status-bar"
|
|
35
46
|
import { ZoomControls } from "@/components/substrate/zoom-controls"
|
|
47
|
+
import { useDocumentStore } from "@/stores/substrate/document-store"
|
|
48
|
+
import { useToolStore } from "@/stores/substrate/tool-store"
|
|
49
|
+
import { createRectangle, createEllipse } from "@/lib/substrate/element-factory"
|
|
50
|
+
import type { DesignElement, ToolType } from "@/lib/substrate/types"
|
|
36
51
|
import {
|
|
37
52
|
RiCursorLine,
|
|
38
53
|
RiHand,
|
|
@@ -40,102 +55,364 @@ import {
|
|
|
40
55
|
RiCircleLine,
|
|
41
56
|
RiSubtractLine,
|
|
42
57
|
RiText,
|
|
43
|
-
|
|
58
|
+
RiArtboardLine,
|
|
59
|
+
RiGridLine,
|
|
60
|
+
RiDeleteBinLine,
|
|
44
61
|
} from "@remixicon/react"
|
|
45
62
|
|
|
46
|
-
|
|
63
|
+
type ToolDefinition = {
|
|
64
|
+
value: ToolType
|
|
65
|
+
icon: ElementType
|
|
66
|
+
label: string
|
|
67
|
+
shortcut: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const POINTER_TOOLS: ToolDefinition[] = [
|
|
47
71
|
{ value: "select", icon: RiCursorLine, label: "Select", shortcut: "V" },
|
|
48
72
|
{ value: "hand", icon: RiHand, label: "Hand", shortcut: "H" },
|
|
49
|
-
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
const SHAPE_TOOLS: ToolDefinition[] = [
|
|
50
76
|
{ value: "rectangle", icon: RiSquareLine, label: "Rectangle", shortcut: "R" },
|
|
51
77
|
{ value: "ellipse", icon: RiCircleLine, label: "Ellipse", shortcut: "O" },
|
|
52
78
|
{ value: "line", icon: RiSubtractLine, label: "Line", shortcut: "L" },
|
|
53
79
|
{ value: "text", icon: RiText, label: "Text", shortcut: "T" },
|
|
54
|
-
{ value: "frame", icon:
|
|
55
|
-
]
|
|
80
|
+
{ value: "frame", icon: RiArtboardLine, label: "Frame", shortcut: "F" },
|
|
81
|
+
]
|
|
56
82
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
const TOOLS = [...POINTER_TOOLS, ...SHAPE_TOOLS]
|
|
84
|
+
|
|
85
|
+
// The colour picker edits hex values. Shapes drawn with the tools start with
|
|
86
|
+
// an oklch fill, so the picker opens on this grey until a colour is chosen.
|
|
87
|
+
const PICKER_FALLBACK = "#d4d4d4"
|
|
88
|
+
const ACTION_BUTTON =
|
|
89
|
+
"inline-flex h-7 min-w-7 items-center justify-center rounded-md px-2 outline-none transition-all hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-active:bg-accent data-active:text-accent-foreground"
|
|
90
|
+
|
|
91
|
+
// Seed the document with a couple of shapes so the canvas is not empty.
|
|
92
|
+
// Everything on the canvas lives in the document store; replace this with
|
|
93
|
+
// your own starting document (or load one from storage).
|
|
94
|
+
if (useDocumentStore.getState().elementOrder.length === 0) {
|
|
95
|
+
const seed: DesignElement[] = [
|
|
96
|
+
{
|
|
97
|
+
...createRectangle({ x: 120, y: 120, width: 220, height: 140 }),
|
|
98
|
+
fill: { type: "solid", color: "#6366f1", opacity: 1 },
|
|
99
|
+
cornerRadius: 12,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
...createEllipse({ x: 280, y: 200, width: 160, height: 160 }),
|
|
103
|
+
fill: { type: "solid", color: "#f59e0b", opacity: 1 },
|
|
104
|
+
},
|
|
105
|
+
]
|
|
106
|
+
for (const element of seed) useDocumentStore.getState().addElement(element)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function Tools() {
|
|
110
|
+
const activeTool = useToolStore((s) => s.activeTool)
|
|
111
|
+
const setTool = useToolStore((s) => s.setTool)
|
|
112
|
+
const showGrid = useDocumentStore((s) => s.showGrid)
|
|
113
|
+
const toggleGrid = useDocumentStore((s) => s.toggleGrid)
|
|
114
|
+
const selectedIds = useDocumentStore((s) => s.selectedIds)
|
|
115
|
+
const deleteElements = useDocumentStore((s) => s.deleteElements)
|
|
116
|
+
const deselectAll = useDocumentStore((s) => s.deselectAll)
|
|
117
|
+
const pushSnapshot = useDocumentStore((s) => s.pushSnapshot)
|
|
118
|
+
|
|
119
|
+
const deleteSelection = () => {
|
|
120
|
+
pushSnapshot()
|
|
121
|
+
deleteElements([...selectedIds])
|
|
122
|
+
deselectAll()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const renderTool = (tool: ToolDefinition) => (
|
|
126
|
+
<ToolbarToggle
|
|
127
|
+
key={tool.value}
|
|
128
|
+
value={tool.value}
|
|
129
|
+
tooltip={tool.label}
|
|
130
|
+
shortcut={tool.shortcut}
|
|
131
|
+
aria-label={tool.label}
|
|
132
|
+
>
|
|
133
|
+
<tool.icon className="size-4" />
|
|
134
|
+
</ToolbarToggle>
|
|
135
|
+
)
|
|
65
136
|
|
|
66
137
|
return (
|
|
67
|
-
<
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</Pane>
|
|
105
|
-
<Pane>
|
|
106
|
-
<PaneHeader>
|
|
107
|
-
<PaneLabel>Fill</PaneLabel>
|
|
108
|
-
</PaneHeader>
|
|
109
|
-
<PaneContent>
|
|
110
|
-
<ColourPicker value={fill} onChange={setFill} />
|
|
111
|
-
</PaneContent>
|
|
112
|
-
</Pane>
|
|
113
|
-
</Panel>
|
|
114
|
-
</div>
|
|
138
|
+
<Toolbar className="gap-1">
|
|
139
|
+
<ToolbarToggleGroup
|
|
140
|
+
value={activeTool}
|
|
141
|
+
onValueChange={(value) => {
|
|
142
|
+
// Radix sends "" when the active item is clicked again; keep the tool
|
|
143
|
+
const next = TOOLS.find((tool) => tool.value === value)
|
|
144
|
+
if (next) setTool(next.value)
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{POINTER_TOOLS.map(renderTool)}
|
|
148
|
+
<ToolbarSeparator />
|
|
149
|
+
{SHAPE_TOOLS.map(renderTool)}
|
|
150
|
+
</ToolbarToggleGroup>
|
|
151
|
+
<ToolbarSeparator />
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
aria-label="Toggle grid"
|
|
155
|
+
title="Toggle grid"
|
|
156
|
+
data-active={showGrid || undefined}
|
|
157
|
+
className={ACTION_BUTTON}
|
|
158
|
+
onClick={toggleGrid}
|
|
159
|
+
>
|
|
160
|
+
<RiGridLine className="size-4" />
|
|
161
|
+
</button>
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
aria-label="Delete selection"
|
|
165
|
+
title="Delete selection"
|
|
166
|
+
disabled={selectedIds.size === 0}
|
|
167
|
+
className={ACTION_BUTTON}
|
|
168
|
+
onClick={deleteSelection}
|
|
169
|
+
>
|
|
170
|
+
<RiDeleteBinLine className="size-4" />
|
|
171
|
+
</button>
|
|
172
|
+
</Toolbar>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
115
175
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
176
|
+
function LayersPanel() {
|
|
177
|
+
const elements = useDocumentStore((s) => s.elements)
|
|
178
|
+
const elementOrder = useDocumentStore((s) => s.elementOrder)
|
|
179
|
+
const selectedIds = useDocumentStore((s) => s.selectedIds)
|
|
180
|
+
const hoveredId = useDocumentStore((s) => s.hoveredId)
|
|
181
|
+
const select = useDocumentStore((s) => s.select)
|
|
182
|
+
const toggleSelection = useDocumentStore((s) => s.toggleSelection)
|
|
183
|
+
const setHovered = useDocumentStore((s) => s.setHovered)
|
|
184
|
+
const updateElement = useDocumentStore((s) => s.updateElement)
|
|
185
|
+
const pushSnapshot = useDocumentStore((s) => s.pushSnapshot)
|
|
186
|
+
|
|
187
|
+
// Topmost element first, like every other layer list
|
|
188
|
+
const layers = [...elementOrder].reverse()
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Panel position="left" className="shrink-0">
|
|
192
|
+
<Pane>
|
|
193
|
+
<PaneHeader>
|
|
194
|
+
<PaneLabel>Layers</PaneLabel>
|
|
195
|
+
</PaneHeader>
|
|
196
|
+
<PaneContent className="-mx-3">
|
|
197
|
+
{layers.length === 0 ? (
|
|
198
|
+
<p className="px-3 text-xs text-muted-foreground">
|
|
199
|
+
Pick a shape tool and drag on the canvas.
|
|
200
|
+
</p>
|
|
201
|
+
) : (
|
|
202
|
+
<LayerList>
|
|
203
|
+
{layers.map((id) => {
|
|
204
|
+
const element = elements[id]
|
|
205
|
+
if (!element) return null
|
|
206
|
+
return (
|
|
207
|
+
<LayerItem
|
|
208
|
+
key={id}
|
|
209
|
+
selected={selectedIds.has(id)}
|
|
210
|
+
hovered={hoveredId === id}
|
|
211
|
+
onClick={(e) => {
|
|
212
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) toggleSelection(id)
|
|
213
|
+
else select([id])
|
|
214
|
+
}}
|
|
215
|
+
onMouseEnter={() => setHovered(id)}
|
|
216
|
+
onMouseLeave={() => setHovered(null)}
|
|
217
|
+
>
|
|
218
|
+
<LayerIcon colour={element.fill?.color} />
|
|
219
|
+
<LayerName
|
|
220
|
+
onRename={(name) => {
|
|
221
|
+
pushSnapshot()
|
|
222
|
+
updateElement(id, { name })
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
{element.name}
|
|
226
|
+
</LayerName>
|
|
227
|
+
<LayerActions>
|
|
228
|
+
<LayerVisibilityToggle
|
|
229
|
+
visible={element.visible}
|
|
230
|
+
onVisibleChange={(visible) => {
|
|
231
|
+
pushSnapshot()
|
|
232
|
+
updateElement(id, { visible })
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
<LayerLockToggle
|
|
236
|
+
locked={element.locked}
|
|
237
|
+
onLockedChange={(locked) => {
|
|
238
|
+
pushSnapshot()
|
|
239
|
+
updateElement(id, { locked })
|
|
240
|
+
}}
|
|
241
|
+
/>
|
|
242
|
+
</LayerActions>
|
|
243
|
+
</LayerItem>
|
|
244
|
+
)
|
|
245
|
+
})}
|
|
246
|
+
</LayerList>
|
|
126
247
|
)}
|
|
127
|
-
</
|
|
128
|
-
</
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
248
|
+
</PaneContent>
|
|
249
|
+
</Pane>
|
|
250
|
+
</Panel>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function PropertiesPanel() {
|
|
255
|
+
const elements = useDocumentStore((s) => s.elements)
|
|
256
|
+
const selectedIds = useDocumentStore((s) => s.selectedIds)
|
|
257
|
+
const updateElement = useDocumentStore((s) => s.updateElement)
|
|
258
|
+
const pushSnapshot = useDocumentStore((s) => s.pushSnapshot)
|
|
259
|
+
|
|
260
|
+
const selected = [...selectedIds].map((id) => elements[id]).filter(Boolean)
|
|
261
|
+
const element = selected[0]
|
|
262
|
+
|
|
263
|
+
if (!element) {
|
|
264
|
+
return (
|
|
265
|
+
<Panel position="right" className="shrink-0">
|
|
266
|
+
<Pane>
|
|
267
|
+
<PaneHeader>
|
|
268
|
+
<PaneLabel>Properties</PaneLabel>
|
|
269
|
+
</PaneHeader>
|
|
270
|
+
<PaneContent>
|
|
271
|
+
<p className="text-xs text-muted-foreground">
|
|
272
|
+
Select an element to edit its properties.
|
|
273
|
+
</p>
|
|
274
|
+
</PaneContent>
|
|
275
|
+
</Pane>
|
|
276
|
+
</Panel>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const id = element.id
|
|
281
|
+
const fill = element.fill
|
|
282
|
+
const fillHex = fill && fill.color.startsWith("#") ? fill.color : PICKER_FALLBACK
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<Panel position="right" className="shrink-0">
|
|
286
|
+
<Pane>
|
|
287
|
+
<PaneHeader>
|
|
288
|
+
<PaneLabel>Transform</PaneLabel>
|
|
135
289
|
<HistoryControls size="sm" />
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
290
|
+
</PaneHeader>
|
|
291
|
+
<PaneContent>
|
|
292
|
+
<div className="grid grid-cols-2 gap-2">
|
|
293
|
+
<NumberInput
|
|
294
|
+
label="X"
|
|
295
|
+
value={element.x}
|
|
296
|
+
onStart={pushSnapshot}
|
|
297
|
+
onChange={(x) => updateElement(id, { x })}
|
|
298
|
+
/>
|
|
299
|
+
<NumberInput
|
|
300
|
+
label="Y"
|
|
301
|
+
value={element.y}
|
|
302
|
+
onStart={pushSnapshot}
|
|
303
|
+
onChange={(y) => updateElement(id, { y })}
|
|
304
|
+
/>
|
|
305
|
+
<NumberInput
|
|
306
|
+
label="W"
|
|
307
|
+
value={element.width}
|
|
308
|
+
min={1}
|
|
309
|
+
onStart={pushSnapshot}
|
|
310
|
+
onChange={(width) => updateElement(id, { width })}
|
|
311
|
+
/>
|
|
312
|
+
<NumberInput
|
|
313
|
+
label="H"
|
|
314
|
+
value={element.height}
|
|
315
|
+
min={1}
|
|
316
|
+
onStart={pushSnapshot}
|
|
317
|
+
onChange={(height) => updateElement(id, { height })}
|
|
318
|
+
/>
|
|
319
|
+
<NumberInput
|
|
320
|
+
label="R"
|
|
321
|
+
suffix="°"
|
|
322
|
+
value={element.rotation}
|
|
323
|
+
onStart={pushSnapshot}
|
|
324
|
+
onChange={(rotation) => updateElement(id, { rotation })}
|
|
325
|
+
/>
|
|
326
|
+
<NumberInput
|
|
327
|
+
label="O"
|
|
328
|
+
suffix="%"
|
|
329
|
+
min={0}
|
|
330
|
+
max={100}
|
|
331
|
+
value={element.opacity * 100}
|
|
332
|
+
onStart={pushSnapshot}
|
|
333
|
+
onChange={(opacity) => updateElement(id, { opacity: opacity / 100 })}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
</PaneContent>
|
|
337
|
+
</Pane>
|
|
338
|
+
<Pane>
|
|
339
|
+
<PaneHeader>
|
|
340
|
+
<PaneLabel>Fill</PaneLabel>
|
|
341
|
+
</PaneHeader>
|
|
342
|
+
<PaneContent className="flex items-center gap-2">
|
|
343
|
+
{/* One undo step per drag: snapshot when the picker is pressed */}
|
|
344
|
+
<ColourPicker
|
|
345
|
+
value={fillHex}
|
|
346
|
+
onPointerDown={pushSnapshot}
|
|
347
|
+
onChange={(color) =>
|
|
348
|
+
updateElement(id, {
|
|
349
|
+
fill: { type: "solid", color, opacity: fill?.opacity ?? 1 },
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
>
|
|
353
|
+
<ColourSwatch
|
|
354
|
+
colour={fill ? fill.color : "transparent"}
|
|
355
|
+
className="cursor-pointer"
|
|
356
|
+
/>
|
|
357
|
+
</ColourPicker>
|
|
358
|
+
<span className="truncate font-mono text-[11px] text-muted-foreground">
|
|
359
|
+
{fill ? fill.color : "No fill"}
|
|
360
|
+
</span>
|
|
361
|
+
</PaneContent>
|
|
362
|
+
</Pane>
|
|
363
|
+
</Panel>
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function EditorStatusBar() {
|
|
368
|
+
const activeTool = useToolStore((s) => s.activeTool)
|
|
369
|
+
const zoom = useDocumentStore((s) => s.camera.zoom)
|
|
370
|
+
const setCamera = useDocumentStore((s) => s.setCamera)
|
|
371
|
+
const selectedCount = useDocumentStore((s) => s.selectedIds.size)
|
|
372
|
+
const elementCount = useDocumentStore((s) => s.elementOrder.length)
|
|
373
|
+
|
|
374
|
+
const tool = TOOLS.find((t) => t.value === activeTool)
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<StatusBar>
|
|
378
|
+
<StatusBarSection align="left">
|
|
379
|
+
<StatusBarItem>{tool ? tool.label : activeTool}</StatusBarItem>
|
|
380
|
+
<StatusBarSeparator />
|
|
381
|
+
<StatusBarItem>
|
|
382
|
+
{selectedCount > 0
|
|
383
|
+
? \`\${selectedCount} selected\`
|
|
384
|
+
: \`\${elementCount} element\${elementCount === 1 ? "" : "s"}\`}
|
|
385
|
+
</StatusBarItem>
|
|
386
|
+
</StatusBarSection>
|
|
387
|
+
<StatusBarSection align="center">
|
|
388
|
+
<HistoryControls size="sm" />
|
|
389
|
+
</StatusBarSection>
|
|
390
|
+
<StatusBarSection align="right">
|
|
391
|
+
<ZoomControls
|
|
392
|
+
zoom={zoom}
|
|
393
|
+
onZoomChange={(next) => setCamera({ zoom: next })}
|
|
394
|
+
onZoomToFit={() => setCamera({ x: 0, y: 0, zoom: 1 })}
|
|
395
|
+
min={0.1}
|
|
396
|
+
max={10}
|
|
397
|
+
step={0.1}
|
|
398
|
+
/>
|
|
399
|
+
</StatusBarSection>
|
|
400
|
+
</StatusBar>
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export default function App() {
|
|
405
|
+
return (
|
|
406
|
+
<div className="flex h-screen w-screen flex-col overflow-hidden bg-background text-foreground">
|
|
407
|
+
<div className="flex flex-1 overflow-hidden">
|
|
408
|
+
<LayersPanel />
|
|
409
|
+
<div className="relative min-w-0 flex-1">
|
|
410
|
+
<DesignCanvas />
|
|
411
|
+
</div>
|
|
412
|
+
<PropertiesPanel />
|
|
413
|
+
</div>
|
|
414
|
+
<Tools />
|
|
415
|
+
<EditorStatusBar />
|
|
139
416
|
</div>
|
|
140
417
|
)
|
|
141
418
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-2d.js","sourceRoot":"","sources":["../../src/surfaces/canvas-2d.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,UAAU,EAAE;QACV,eAAe;QACf,SAAS;QACT,OAAO;QACP,MAAM;QACN,
|
|
1
|
+
{"version":3,"file":"canvas-2d.js","sourceRoot":"","sources":["../../src/surfaces/canvas-2d.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,UAAU,EAAE;QACV,OAAO;QACP,gBAAgB;QAChB,YAAY;QACZ,iBAAiB;QACjB,eAAe;QACf,SAAS;QACT,OAAO;QACP,MAAM;QACN,YAAY;QACZ,cAAc;QACd,eAAe;QACf,kBAAkB;QAClB,YAAY;QACZ,eAAe;KAChB;IACD,GAAG,EAAE,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiZZ;CACA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-vis.d.ts","sourceRoot":"","sources":["../../src/surfaces/data-vis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"data-vis.d.ts","sourceRoot":"","sources":["../../src/surfaces/data-vis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,OAAO,EAAE,aAiVrB,CAAA"}
|