feat(editor): add event bus for editor lifecycle events
Wire nanoevents-based event bus to the editor core: - EditorEvents type with render, graph, selection, tool, page, viewport events - All node mutations (create/update/delete/reparent/reorder) forwarded from SceneGraph - Selection changes routed through setSelectedIds() with diff detection - Tool changes routed through setActiveTool() with change detection - Page switches emit page:changed - Viewport pan/zoom/fit/zoomTo emit viewport:changed - Graph replacement emits graph:replaced - Typed onEditorEvent() subscription on the Editor return object - useEditorEvent() Vue composable with automatic scope cleanup - Replace all direct state.selectedIds/state.activeTool mutations in core, app, and SDK
This commit is contained in:
parent
dfa9505805
commit
1835903e56
|
|
@ -38,7 +38,7 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newRootIds.length > 0) {
|
if (newRootIds.length > 0) {
|
||||||
ctx.state.selectedIds = new Set(newRootIds)
|
ctx.setSelectedIds(new Set(newRootIds))
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
forward: () => {
|
forward: () => {
|
||||||
|
|
@ -48,11 +48,11 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
const parentId = snapshot.parentId ?? ctx.state.currentPageId
|
const parentId = snapshot.parentId ?? ctx.state.currentPageId
|
||||||
restoreSubtree(ctx.graph, snapshot, parentId, allSnapshots)
|
restoreSubtree(ctx.graph, snapshot, parentId, allSnapshots)
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = new Set(newRootIds)
|
ctx.setSelectedIds(new Set(newRootIds))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const id of newRootIds.slice().reverse()) ctx.graph.deleteNode(id)
|
for (const id of newRootIds.slice().reverse()) ctx.graph.deleteNode(id)
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +82,7 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
const cy = cursorPos?.y ?? (-ctx.state.panY + viewH / 2) / ctx.state.zoom
|
const cy = cursorPos?.y ?? (-ctx.state.panY + viewH / 2) / ctx.state.zoom
|
||||||
placementActions.centerNodesAt(created, cx, cy)
|
placementActions.centerNodesAt(created, cx, cy)
|
||||||
computeAllLayouts(ctx.graph, ctx.state.currentPageId)
|
computeAllLayouts(ctx.graph, ctx.state.currentPageId)
|
||||||
ctx.state.selectedIds = new Set(created)
|
ctx.setSelectedIds(new Set(created))
|
||||||
|
|
||||||
const allNodes = collectSubtrees(ctx.graph, created)
|
const allNodes = collectSubtrees(ctx.graph, created)
|
||||||
const pageId = ctx.state.currentPageId
|
const pageId = ctx.state.currentPageId
|
||||||
|
|
@ -96,12 +96,12 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
computeAllLayouts(ctx.graph, pageId)
|
computeAllLayouts(ctx.graph, pageId)
|
||||||
ctx.state.selectedIds = new Set(created)
|
ctx.setSelectedIds(new Set(created))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const id of [...created].reverse()) ctx.graph.deleteNode(id)
|
for (const id of [...created].reverse()) ctx.graph.deleteNode(id)
|
||||||
computeAllLayouts(ctx.graph, pageId)
|
computeAllLayouts(ctx.graph, pageId)
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
void fontActions.loadFontsForNodes(created)
|
void fontActions.loadFontsForNodes(created)
|
||||||
|
|
@ -137,7 +137,7 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
|
|
||||||
if (cursorPos) placementActions.centerNodesAt(created, cursorPos.x, cursorPos.y)
|
if (cursorPos) placementActions.centerNodesAt(created, cursorPos.x, cursorPos.y)
|
||||||
computeAllLayouts(ctx.graph, ctx.state.currentPageId)
|
computeAllLayouts(ctx.graph, ctx.state.currentPageId)
|
||||||
ctx.state.selectedIds = new Set(created)
|
ctx.setSelectedIds(new Set(created))
|
||||||
|
|
||||||
const allNodes = collectSubtrees(ctx.graph, created)
|
const allNodes = collectSubtrees(ctx.graph, created)
|
||||||
const pageId = ctx.state.currentPageId
|
const pageId = ctx.state.currentPageId
|
||||||
|
|
@ -151,12 +151,12 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
computeAllLayouts(ctx.graph, pageId)
|
computeAllLayouts(ctx.graph, pageId)
|
||||||
ctx.state.selectedIds = new Set(created)
|
ctx.setSelectedIds(new Set(created))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const id of [...created].reverse()) ctx.graph.deleteNode(id)
|
for (const id of [...created].reverse()) ctx.graph.deleteNode(id)
|
||||||
computeAllLayouts(ctx.graph, pageId)
|
computeAllLayouts(ctx.graph, pageId)
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +192,7 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
forward: () => {
|
forward: () => {
|
||||||
for (const { id } of entries) ctx.graph.deleteNode(id)
|
for (const { id } of entries) ctx.graph.deleteNode(id)
|
||||||
ctx.state.selectedIds = new Set()
|
ctx.setSelectedIds(new Set())
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const { id, parentId, index, subtree } of [...entries].reverse()) {
|
for (const { id, parentId, index, subtree } of [...entries].reverse()) {
|
||||||
|
|
@ -200,10 +200,10 @@ export function createClipboardActions(ctx: EditorContext) {
|
||||||
if (rootSnap) restoreSubtree(ctx.graph, rootSnap, parentId, subtree)
|
if (rootSnap) restoreSubtree(ctx.graph, rootSnap, parentId, subtree)
|
||||||
if (index >= 0) ctx.graph.reorderChild(id, parentId, index)
|
if (index >= 0) ctx.graph.reorderChild(id, parentId, index)
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ctx.state.selectedIds = new Set()
|
ctx.setSelectedIds(new Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyActions = createClipboardCopyActions(ctx)
|
const copyActions = createClipboardCopyActions(ctx)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ export function createClipboardImageActions(ctx: EditorContext) {
|
||||||
ctx.graph.images.delete(hash)
|
ctx.graph.images.delete(hash)
|
||||||
const next = new Set(ctx.state.selectedIds)
|
const next = new Set(ctx.state.selectedIds)
|
||||||
next.delete(id)
|
next.delete(id)
|
||||||
ctx.state.selectedIds = next
|
ctx.setSelectedIds(next)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
|
|
@ -97,7 +97,7 @@ export function createClipboardImageActions(ctx: EditorContext) {
|
||||||
curX += p.w + IMAGE_GAP
|
curX += p.w + IMAGE_GAP
|
||||||
}
|
}
|
||||||
if (ids.length) {
|
if (ids.length) {
|
||||||
ctx.state.selectedIds = new Set(ids)
|
ctx.setSelectedIds(new Set(ids))
|
||||||
ctx.requestRender()
|
ctx.requestRender()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,16 @@ export function createComponentActions(ctx: EditorContext) {
|
||||||
|
|
||||||
if (node.type === 'FRAME' || node.type === 'GROUP') {
|
if (node.type === 'FRAME' || node.type === 'GROUP') {
|
||||||
ctx.graph.updateNode(node.id, { type: 'COMPONENT' })
|
ctx.graph.updateNode(node.id, { type: 'COMPONENT' })
|
||||||
ctx.state.selectedIds = new Set([node.id])
|
ctx.setSelectedIds(new Set([node.id]))
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: 'Create component',
|
label: 'Create component',
|
||||||
forward: () => {
|
forward: () => {
|
||||||
ctx.graph.updateNode(node.id, { type: 'COMPONENT' })
|
ctx.graph.updateNode(node.id, { type: 'COMPONENT' })
|
||||||
ctx.state.selectedIds = new Set([node.id])
|
ctx.setSelectedIds(new Set([node.id]))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
ctx.graph.updateNode(node.id, { type: prevType })
|
ctx.graph.updateNode(node.id, { type: prevType })
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function createComponentFocusActions(ctx: EditorContext) {
|
||||||
void switchPage(current.id)
|
void switchPage(current.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.state.selectedIds = new Set([main.id])
|
ctx.setSelectedIds(new Set([main.id]))
|
||||||
|
|
||||||
const abs = ctx.graph.getAbsolutePosition(main.id)
|
const abs = ctx.graph.getAbsolutePosition(main.id)
|
||||||
const { width: viewW, height: viewH } = ctx.getViewportSize()
|
const { width: viewW, height: viewH } = ctx.getViewportSize()
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,17 @@ export function createComponentInstanceActions(ctx: EditorContext) {
|
||||||
if (!instance) return null
|
if (!instance) return null
|
||||||
|
|
||||||
const instanceId = instance.id
|
const instanceId = instance.id
|
||||||
ctx.state.selectedIds = new Set([instanceId])
|
ctx.setSelectedIds(new Set([instanceId]))
|
||||||
|
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: 'Create instance',
|
label: 'Create instance',
|
||||||
forward: () => {
|
forward: () => {
|
||||||
ctx.graph.createInstance(componentId, parentId, { ...instance })
|
ctx.graph.createInstance(componentId, parentId, { ...instance })
|
||||||
ctx.state.selectedIds = new Set([instanceId])
|
ctx.setSelectedIds(new Set([instanceId]))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
ctx.graph.deleteNode(instanceId)
|
ctx.graph.deleteNode(instanceId)
|
||||||
ctx.state.selectedIds = new Set([componentId])
|
ctx.setSelectedIds(new Set([componentId]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return instanceId
|
return instanceId
|
||||||
|
|
@ -36,7 +36,7 @@ export function createComponentInstanceActions(ctx: EditorContext) {
|
||||||
const prevComponentId = selectedNode.componentId
|
const prevComponentId = selectedNode.componentId
|
||||||
|
|
||||||
ctx.graph.detachInstance(selectedNode.id)
|
ctx.graph.detachInstance(selectedNode.id)
|
||||||
ctx.state.selectedIds = new Set([selectedNode.id])
|
ctx.setSelectedIds(new Set([selectedNode.id]))
|
||||||
|
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: 'Detach instance',
|
label: 'Detach instance',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import type { CanvasKit } from 'canvaskit-wasm'
|
import type { CanvasKit } from 'canvaskit-wasm'
|
||||||
|
import { createNanoEvents } from 'nanoevents'
|
||||||
|
import type { Emitter } from 'nanoevents'
|
||||||
|
|
||||||
import type { SkiaRenderer } from '#core/canvas/renderer'
|
import type { SkiaRenderer } from '#core/canvas/renderer'
|
||||||
import { prefetchFigmaSchema } from '#core/clipboard'
|
import { prefetchFigmaSchema } from '#core/clipboard'
|
||||||
|
|
@ -28,7 +30,13 @@ import { createShapeActions } from './shapes'
|
||||||
import { createDefaultEditorState } from './state'
|
import { createDefaultEditorState } from './state'
|
||||||
import { createStructureActions } from './structure'
|
import { createStructureActions } from './structure'
|
||||||
import { createTextActions } from './text'
|
import { createTextActions } from './text'
|
||||||
import type { EditorContext, EditorOptions, EditorState } from './types'
|
import type {
|
||||||
|
EditorContext,
|
||||||
|
EditorEventName,
|
||||||
|
EditorEvents,
|
||||||
|
EditorOptions,
|
||||||
|
EditorState
|
||||||
|
} from './types'
|
||||||
import { createUndoActions } from './undo'
|
import { createUndoActions } from './undo'
|
||||||
import { createVariableActions } from './variables'
|
import { createVariableActions } from './variables'
|
||||||
import { createViewportActions } from './viewport'
|
import { createViewportActions } from './viewport'
|
||||||
|
|
@ -50,18 +58,56 @@ export function createEditor(options?: EditorOptions) {
|
||||||
let _renderer: SkiaRenderer | null = null
|
let _renderer: SkiaRenderer | null = null
|
||||||
const _renderers = new Set<SkiaRenderer>()
|
const _renderers = new Set<SkiaRenderer>()
|
||||||
let _textEditor: TextEditor | null = null
|
let _textEditor: TextEditor | null = null
|
||||||
|
const events: Emitter<EditorEvents> = createNanoEvents()
|
||||||
|
|
||||||
void prefetchFigmaSchema()
|
void prefetchFigmaSchema()
|
||||||
|
|
||||||
const state: EditorState = options?.state ?? createDefaultEditorState(_graph.getPages()[0].id)
|
const state: EditorState = options?.state ?? createDefaultEditorState(_graph.getPages()[0].id)
|
||||||
|
|
||||||
|
function emitEditorEvent<K extends EditorEventName>(
|
||||||
|
event: K,
|
||||||
|
...args: Parameters<EditorEvents[K]>
|
||||||
|
) {
|
||||||
|
events.emit(event, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEditorEvent<K extends EditorEventName>(event: K, handler: EditorEvents[K]) {
|
||||||
|
return events.on(event, handler)
|
||||||
|
}
|
||||||
|
|
||||||
function requestRender() {
|
function requestRender() {
|
||||||
state.renderVersion++
|
state.renderVersion++
|
||||||
state.sceneVersion++
|
state.sceneVersion++
|
||||||
|
emitEditorEvent('render:requested', {
|
||||||
|
renderVersion: state.renderVersion,
|
||||||
|
sceneVersion: state.sceneVersion
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestRepaint() {
|
function requestRepaint() {
|
||||||
state.renderVersion++
|
state.renderVersion++
|
||||||
|
emitEditorEvent('repaint:requested', {
|
||||||
|
renderVersion: state.renderVersion,
|
||||||
|
sceneVersion: state.sceneVersion
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectedIds(ids: Set<string>) {
|
||||||
|
const previous = [...state.selectedIds]
|
||||||
|
state.selectedIds = ids
|
||||||
|
const selected = [...ids]
|
||||||
|
if (
|
||||||
|
previous.length !== selected.length ||
|
||||||
|
previous.some((id, index) => id !== selected[index])
|
||||||
|
) {
|
||||||
|
emitEditorEvent('selection:changed', selected, previous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveTool(tool: EditorState['activeTool']) {
|
||||||
|
const previous = state.activeTool
|
||||||
|
state.activeTool = tool
|
||||||
|
if (previous !== tool) emitEditorEvent('tool:changed', tool, previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
const graphReads = createGraphReadActions(() => _graph)
|
const graphReads = createGraphReadActions(() => _graph)
|
||||||
|
|
@ -72,7 +118,8 @@ export function createEditor(options?: EditorOptions) {
|
||||||
getGraph: () => _graph,
|
getGraph: () => _graph,
|
||||||
getRenderers: () => _renderers,
|
getRenderers: () => _renderers,
|
||||||
scheduleComponentSync,
|
scheduleComponentSync,
|
||||||
requestRender
|
requestRender,
|
||||||
|
emitEditorEvent
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!skipInitialGraphSetup) {
|
if (!skipInitialGraphSetup) {
|
||||||
|
|
@ -96,6 +143,9 @@ export function createEditor(options?: EditorOptions) {
|
||||||
getTextEditor: () => _textEditor,
|
getTextEditor: () => _textEditor,
|
||||||
requestRender,
|
requestRender,
|
||||||
requestRepaint,
|
requestRepaint,
|
||||||
|
emitEditorEvent,
|
||||||
|
setSelectedIds,
|
||||||
|
setActiveTool,
|
||||||
runLayoutForNode,
|
runLayoutForNode,
|
||||||
subscribeToGraph
|
subscribeToGraph
|
||||||
}
|
}
|
||||||
|
|
@ -137,10 +187,15 @@ export function createEditor(options?: EditorOptions) {
|
||||||
function replaceGraph(newGraph: SceneGraph) {
|
function replaceGraph(newGraph: SceneGraph) {
|
||||||
_graph = newGraph
|
_graph = newGraph
|
||||||
subscribeToGraph()
|
subscribeToGraph()
|
||||||
|
const previousPageId = state.currentPageId
|
||||||
state.currentPageId = _graph.getPages()[0]?.id ?? _graph.rootId
|
state.currentPageId = _graph.getPages()[0]?.id ?? _graph.rootId
|
||||||
state.selectedIds = new Set()
|
setSelectedIds(new Set())
|
||||||
state.hoveredNodeId = null
|
state.hoveredNodeId = null
|
||||||
pages.clearPageViewports()
|
pages.clearPageViewports()
|
||||||
|
emitEditorEvent('graph:replaced', _graph)
|
||||||
|
if (previousPageId !== state.currentPageId) {
|
||||||
|
emitEditorEvent('page:changed', state.currentPageId, previousPageId)
|
||||||
|
}
|
||||||
requestRender()
|
requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,6 +218,7 @@ export function createEditor(options?: EditorOptions) {
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
requestRender,
|
requestRender,
|
||||||
requestRepaint,
|
requestRepaint,
|
||||||
|
onEditorEvent,
|
||||||
setCanvasKit,
|
setCanvasKit,
|
||||||
removeCanvasRenderer,
|
removeCanvasRenderer,
|
||||||
replaceGraph,
|
replaceGraph,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import type { SkiaRenderer } from '#core/canvas/renderer'
|
import type { SkiaRenderer } from '#core/canvas/renderer'
|
||||||
import type { SceneGraph, SceneNode } from '#core/scene-graph'
|
import type { SceneGraph, SceneGraphEvents, SceneNode } from '#core/scene-graph'
|
||||||
|
|
||||||
type GraphEventOptions = {
|
type GraphEventOptions = {
|
||||||
getGraph: () => SceneGraph
|
getGraph: () => SceneGraph
|
||||||
getRenderers: () => Iterable<SkiaRenderer>
|
getRenderers: () => Iterable<SkiaRenderer>
|
||||||
scheduleComponentSync: (nodeId: string) => void
|
scheduleComponentSync: (nodeId: string) => void
|
||||||
requestRender: () => void
|
requestRender: () => void
|
||||||
|
emitEditorEvent: <K extends keyof SceneGraphEvents>(
|
||||||
|
event: K,
|
||||||
|
...args: Parameters<SceneGraphEvents[K]>
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGraphEventSubscription(options: GraphEventOptions) {
|
export function createGraphEventSubscription(options: GraphEventOptions) {
|
||||||
|
|
@ -16,6 +20,7 @@ export function createGraphEventSubscription(options: GraphEventOptions) {
|
||||||
if ('vectorNetwork' in changes) renderer.invalidateVectorPath(id)
|
if ('vectorNetwork' in changes) renderer.invalidateVectorPath(id)
|
||||||
renderer.invalidateNodePicture(id)
|
renderer.invalidateNodePicture(id)
|
||||||
}
|
}
|
||||||
|
options.emitEditorEvent('node:updated', id, changes)
|
||||||
options.scheduleComponentSync(id)
|
options.scheduleComponentSync(id)
|
||||||
options.requestRender()
|
options.requestRender()
|
||||||
}
|
}
|
||||||
|
|
@ -29,10 +34,22 @@ export function createGraphEventSubscription(options: GraphEventOptions) {
|
||||||
unbindGraphEvents?.()
|
unbindGraphEvents?.()
|
||||||
unbindGraphEvents = options.getGraph().onNodeEvents({
|
unbindGraphEvents = options.getGraph().onNodeEvents({
|
||||||
updated: onNodeUpdated,
|
updated: onNodeUpdated,
|
||||||
created: (node) => onNodeStructureChanged(node.id),
|
created: (node) => {
|
||||||
deleted: onNodeStructureChanged,
|
options.emitEditorEvent('node:created', node)
|
||||||
reparented: onNodeStructureChanged,
|
onNodeStructureChanged(node.id)
|
||||||
reordered: onNodeStructureChanged
|
},
|
||||||
|
deleted: (id) => {
|
||||||
|
options.emitEditorEvent('node:deleted', id)
|
||||||
|
onNodeStructureChanged(id)
|
||||||
|
},
|
||||||
|
reparented: (nodeId, oldParentId, newParentId) => {
|
||||||
|
options.emitEditorEvent('node:reparented', nodeId, oldParentId, newParentId)
|
||||||
|
onNodeStructureChanged(nodeId)
|
||||||
|
},
|
||||||
|
reordered: (nodeId, parentId, index) => {
|
||||||
|
options.emitEditorEvent('node:reordered', nodeId, parentId, index)
|
||||||
|
onNodeStructureChanged(nodeId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function restorePageFromSnapshot(ctx: EditorContext, snapshot: PageSnapsh
|
||||||
|
|
||||||
ctx.graph.clearAbsPosCache()
|
ctx.graph.clearAbsPosCache()
|
||||||
computeAllLayouts(ctx.graph, pageId)
|
computeAllLayouts(ctx.graph, pageId)
|
||||||
ctx.state.selectedIds = new Set()
|
ctx.setSelectedIds(new Set())
|
||||||
ctx.state.hoveredNodeId = null
|
ctx.state.hoveredNodeId = null
|
||||||
ctx.requestRender()
|
ctx.requestRender()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,11 @@ export type { Editor } from './create'
|
||||||
export { createTextActions } from './text'
|
export { createTextActions } from './text'
|
||||||
export { EDITOR_TOOLS, TOOL_SHORTCUTS } from './tool-registry'
|
export { EDITOR_TOOLS, TOOL_SHORTCUTS } from './tool-registry'
|
||||||
export type { EditorToolDef } from './tool-registry'
|
export type { EditorToolDef } from './tool-registry'
|
||||||
export type { EditorContext, EditorOptions, EditorState, Tool } from './types'
|
export type {
|
||||||
|
EditorContext,
|
||||||
|
EditorEventName,
|
||||||
|
EditorEvents,
|
||||||
|
EditorOptions,
|
||||||
|
EditorState,
|
||||||
|
Tool
|
||||||
|
} from './types'
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@ export function createPageActions(ctx: EditorContext) {
|
||||||
|
|
||||||
pageViewportStore.saveCurrentPageViewport()
|
pageViewportStore.saveCurrentPageViewport()
|
||||||
|
|
||||||
|
const previousPageId = ctx.state.currentPageId
|
||||||
ctx.state.currentPageId = pageId
|
ctx.state.currentPageId = pageId
|
||||||
ctx.state.enteredContainerId = null
|
ctx.state.enteredContainerId = null
|
||||||
ctx.state.selectedIds = new Set()
|
ctx.setSelectedIds(new Set())
|
||||||
|
if (previousPageId !== pageId) ctx.emitEditorEvent('page:changed', pageId, previousPageId)
|
||||||
|
|
||||||
pageViewportStore.restorePageViewport(pageId)
|
pageViewportStore.restorePageViewport(pageId)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,19 @@ export function createSelectionActions(ctx: EditorContext) {
|
||||||
if (next.has(id)) next.delete(id)
|
if (next.has(id)) next.delete(id)
|
||||||
else next.add(id)
|
else next.add(id)
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = next
|
ctx.setSelectedIds(next)
|
||||||
} else {
|
} else {
|
||||||
ctx.state.selectedIds = new Set(ids)
|
ctx.setSelectedIds(new Set(ids))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelection() {
|
function clearSelection() {
|
||||||
ctx.state.selectedIds = new Set()
|
ctx.setSelectedIds(new Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAll() {
|
function selectAll() {
|
||||||
const children = ctx.graph.getChildren(ctx.state.currentPageId)
|
const children = ctx.graph.getChildren(ctx.state.currentPageId)
|
||||||
ctx.state.selectedIds = new Set(children.map((n) => n.id))
|
ctx.setSelectedIds(new Set(children.map((n) => n.id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerActions = createSelectionContainerActions(ctx)
|
const containerActions = createSelectionContainerActions(ctx)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export function createSelectionContainerActions(ctx: EditorContext) {
|
||||||
} else {
|
} else {
|
||||||
ctx.state.enteredContainerId = null
|
ctx.state.enteredContainerId = null
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = new Set(entered ? [entered] : [])
|
ctx.setSelectedIds(new Set(entered ? [entered] : []))
|
||||||
}
|
}
|
||||||
|
|
||||||
return { validateEnteredContainer, enterContainer, exitContainer }
|
return { validateEnteredContainer, enterContainer, exitContainer }
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export function createShapeActions(ctx: EditorContext) {
|
||||||
ctx.graph.deleteNode(id)
|
ctx.graph.deleteNode(id)
|
||||||
const next = new Set(ctx.state.selectedIds)
|
const next = new Set(ctx.state.selectedIds)
|
||||||
next.delete(id)
|
next.delete(id)
|
||||||
ctx.state.selectedIds = next
|
ctx.setSelectedIds(next)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
|
|
@ -79,7 +79,7 @@ export function createShapeActions(ctx: EditorContext) {
|
||||||
const penActions = createPenActions(ctx, createShape)
|
const penActions = createPenActions(ctx, createShape)
|
||||||
|
|
||||||
function setTool(tool: typeof ctx.state.activeTool) {
|
function setTool(tool: typeof ctx.state.activeTool) {
|
||||||
ctx.state.activeTool = tool
|
ctx.setActiveTool(tool)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -205,12 +205,12 @@ export function createPenActions(ctx: EditorContext, createShape: CreateShape) {
|
||||||
fills,
|
fills,
|
||||||
strokes
|
strokes
|
||||||
})
|
})
|
||||||
ctx.state.selectedIds = new Set([nodeId])
|
ctx.setSelectedIds(new Set([nodeId]))
|
||||||
|
|
||||||
ctx.state.penState = null
|
ctx.state.penState = null
|
||||||
ctx.state.penCursorX = null
|
ctx.state.penCursorX = null
|
||||||
ctx.state.penCursorY = null
|
ctx.state.penCursorY = null
|
||||||
ctx.state.activeTool = 'SELECT'
|
ctx.setActiveTool('SELECT')
|
||||||
ctx.requestRender()
|
ctx.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ export function createPenActions(ctx: EditorContext, createShape: CreateShape) {
|
||||||
ctx.state.penState = null
|
ctx.state.penState = null
|
||||||
ctx.state.penCursorX = null
|
ctx.state.penCursorX = null
|
||||||
ctx.state.penCursorY = null
|
ctx.state.penCursorY = null
|
||||||
ctx.state.activeTool = 'SELECT'
|
ctx.setActiveTool('SELECT')
|
||||||
ctx.requestRender()
|
ctx.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export function createStructureActions(ctx: EditorContext) {
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
ctx.graph.reparentNode(id, pageId)
|
ctx.graph.reparentNode(id, pageId)
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = new Set()
|
ctx.setSelectedIds(new Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
function renameNode(id: string, name: string) {
|
function renameNode(id: string, name: string) {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export function wrapInAutoLayout(
|
||||||
|
|
||||||
computeLayout(ctx.graph, frameId)
|
computeLayout(ctx.graph, frameId)
|
||||||
ctx.runLayoutForNode(frameId)
|
ctx.runLayoutForNode(frameId)
|
||||||
ctx.state.selectedIds = new Set([frameId])
|
ctx.setSelectedIds(new Set([frameId]))
|
||||||
|
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: 'Wrap in auto layout',
|
label: 'Wrap in auto layout',
|
||||||
|
|
@ -61,7 +61,7 @@ export function wrapInAutoLayout(
|
||||||
for (const n of origPositions) ctx.graph.reparentNode(n.id, f.id)
|
for (const n of origPositions) ctx.graph.reparentNode(n.id, f.id)
|
||||||
computeLayout(ctx.graph, f.id)
|
computeLayout(ctx.graph, f.id)
|
||||||
ctx.runLayoutForNode(f.id)
|
ctx.runLayoutForNode(f.id)
|
||||||
ctx.state.selectedIds = new Set([f.id])
|
ctx.setSelectedIds(new Set([f.id]))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const orig of origPositions) {
|
for (const orig of origPositions) {
|
||||||
|
|
@ -69,7 +69,7 @@ export function wrapInAutoLayout(
|
||||||
ctx.graph.updateNode(orig.id, { x: orig.x, y: orig.y })
|
ctx.graph.updateNode(orig.id, { x: orig.x, y: orig.y })
|
||||||
}
|
}
|
||||||
ctx.graph.deleteNode(frameId)
|
ctx.graph.deleteNode(frameId)
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export function wrapSelectionInContainer(
|
||||||
ctx.graph.reparentNode(n.id, containerId)
|
ctx.graph.reparentNode(n.id, containerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.state.selectedIds = new Set([containerId])
|
ctx.setSelectedIds(new Set([containerId]))
|
||||||
|
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: `Create ${containerType.toLowerCase().replace('_', ' ')}`,
|
label: `Create ${containerType.toLowerCase().replace('_', ' ')}`,
|
||||||
|
|
@ -82,7 +82,7 @@ export function wrapSelectionInContainer(
|
||||||
})
|
})
|
||||||
ctx.graph.insertChildAt(c.id, parentId, firstIndex)
|
ctx.graph.insertChildAt(c.id, parentId, firstIndex)
|
||||||
for (const n of origPositions) ctx.graph.reparentNode(n.id, c.id)
|
for (const n of origPositions) ctx.graph.reparentNode(n.id, c.id)
|
||||||
ctx.state.selectedIds = new Set([c.id])
|
ctx.setSelectedIds(new Set([c.id]))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const orig of origPositions) {
|
for (const orig of origPositions) {
|
||||||
|
|
@ -90,7 +90,7 @@ export function wrapSelectionInContainer(
|
||||||
ctx.graph.updateNode(orig.id, { x: orig.x, y: orig.y })
|
ctx.graph.updateNode(orig.id, { x: orig.x, y: orig.y })
|
||||||
}
|
}
|
||||||
ctx.graph.deleteNode(containerId)
|
ctx.graph.deleteNode(containerId)
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export function ungroupSelected(ctx: EditorContext, selectedNode: SceneNode | un
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.graph.deleteNode(node.id)
|
ctx.graph.deleteNode(node.id)
|
||||||
ctx.state.selectedIds = new Set(childIds)
|
ctx.setSelectedIds(new Set(childIds))
|
||||||
|
|
||||||
ctx.undo.push({
|
ctx.undo.push({
|
||||||
label: 'Ungroup',
|
label: 'Ungroup',
|
||||||
|
|
@ -36,7 +36,7 @@ export function ungroupSelected(ctx: EditorContext, selectedNode: SceneNode | un
|
||||||
ctx.graph.insertChildAt(childIds[i], parentId, groupIndex + i)
|
ctx.graph.insertChildAt(childIds[i], parentId, groupIndex + i)
|
||||||
}
|
}
|
||||||
ctx.graph.deleteNode(groupId)
|
ctx.graph.deleteNode(groupId)
|
||||||
ctx.state.selectedIds = new Set(childIds)
|
ctx.setSelectedIds(new Set(childIds))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
const g = ctx.graph.createNode('GROUP', parentId, {
|
const g = ctx.graph.createNode('GROUP', parentId, {
|
||||||
|
|
@ -49,7 +49,7 @@ export function ungroupSelected(ctx: EditorContext, selectedNode: SceneNode | un
|
||||||
ctx.graph.reparentNode(orig.id, g.id)
|
ctx.graph.reparentNode(orig.id, g.id)
|
||||||
ctx.graph.updateNode(orig.id, { x: orig.x, y: orig.y })
|
ctx.graph.updateNode(orig.id, { x: orig.x, y: orig.y })
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = prevSelection
|
ctx.setSelectedIds(prevSelection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { CanvasKit } from 'canvaskit-wasm'
|
import type { CanvasKit } from 'canvaskit-wasm'
|
||||||
|
|
||||||
import type { RulerTheme, SkiaRenderer } from '#core/canvas/renderer'
|
import type { RulerTheme, SkiaRenderer } from '#core/canvas/renderer'
|
||||||
import type { SceneGraph, VectorSegment, VectorVertex } from '#core/scene-graph'
|
import type { SceneGraph, SceneNode, VectorSegment, VectorVertex } from '#core/scene-graph'
|
||||||
import type { SnapGuide } from '#core/scene-graph/snap'
|
import type { SnapGuide } from '#core/scene-graph/snap'
|
||||||
import type { UndoManager } from '#core/scene-graph/undo'
|
import type { UndoManager } from '#core/scene-graph/undo'
|
||||||
import type { TextEditor } from '#core/text/editor'
|
import type { TextEditor } from '#core/text/editor'
|
||||||
|
|
@ -67,6 +67,26 @@ export interface EditorState {
|
||||||
enteredContainerId: string | null
|
enteredContainerId: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EditorEvents {
|
||||||
|
'render:requested': (versions: { renderVersion: number; sceneVersion: number }) => void
|
||||||
|
'repaint:requested': (versions: { renderVersion: number; sceneVersion: number }) => void
|
||||||
|
'graph:replaced': (graph: SceneGraph) => void
|
||||||
|
'node:created': (node: SceneNode) => void
|
||||||
|
'node:updated': (id: string, changes: Partial<SceneNode>) => void
|
||||||
|
'node:deleted': (id: string) => void
|
||||||
|
'node:reparented': (nodeId: string, oldParentId: string | null, newParentId: string) => void
|
||||||
|
'node:reordered': (nodeId: string, parentId: string, index: number) => void
|
||||||
|
'selection:changed': (selectedIds: string[], previousIds: string[]) => void
|
||||||
|
'tool:changed': (tool: Tool, previousTool: Tool) => void
|
||||||
|
'page:changed': (pageId: string, previousPageId: string) => void
|
||||||
|
'viewport:changed': (
|
||||||
|
viewport: { panX: number; panY: number; zoom: number },
|
||||||
|
previous: { panX: number; panY: number; zoom: number }
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditorEventName = keyof EditorEvents
|
||||||
|
|
||||||
export interface EditorOptions {
|
export interface EditorOptions {
|
||||||
graph?: SceneGraph
|
graph?: SceneGraph
|
||||||
state?: EditorState
|
state?: EditorState
|
||||||
|
|
@ -87,6 +107,12 @@ export interface EditorContext {
|
||||||
getTextEditor: () => TextEditor | null
|
getTextEditor: () => TextEditor | null
|
||||||
requestRender: () => void
|
requestRender: () => void
|
||||||
requestRepaint: () => void
|
requestRepaint: () => void
|
||||||
|
emitEditorEvent: <K extends EditorEventName>(
|
||||||
|
event: K,
|
||||||
|
...args: Parameters<EditorEvents[K]>
|
||||||
|
) => void
|
||||||
|
setSelectedIds: (ids: Set<string>) => void
|
||||||
|
setActiveTool: (tool: Tool) => void
|
||||||
runLayoutForNode: (id: string) => void
|
runLayoutForNode: (id: string) => void
|
||||||
subscribeToGraph: () => void
|
subscribeToGraph: () => void
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,11 @@ export function createUndoActions(ctx: EditorContext) {
|
||||||
)
|
)
|
||||||
ctx.runLayoutForNode(id)
|
ctx.runLayoutForNode(id)
|
||||||
}
|
}
|
||||||
ctx.state.selectedIds = new Set(nextSelection)
|
ctx.setSelectedIds(new Set(nextSelection))
|
||||||
},
|
},
|
||||||
inverse: () => {
|
inverse: () => {
|
||||||
for (const id of rootIds.toReversed()) ctx.graph.deleteNode(id)
|
for (const id of rootIds.toReversed()) ctx.graph.deleteNode(id)
|
||||||
ctx.state.selectedIds = new Set(previousSelection)
|
ctx.setSelectedIds(new Set(previousSelection))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,17 @@ import { computeBounds, computeAbsoluteBounds } from '#core/geometry'
|
||||||
import type { EditorContext } from './types'
|
import type { EditorContext } from './types'
|
||||||
|
|
||||||
export function createViewportActions(ctx: EditorContext) {
|
export function createViewportActions(ctx: EditorContext) {
|
||||||
|
function currentViewport() {
|
||||||
|
return { panX: ctx.state.panX, panY: ctx.state.panY, zoom: ctx.state.zoom }
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitViewportChanged(previous: ReturnType<typeof currentViewport>) {
|
||||||
|
const next = currentViewport()
|
||||||
|
if (next.panX !== previous.panX || next.panY !== previous.panY || next.zoom !== previous.zoom) {
|
||||||
|
ctx.emitEditorEvent('viewport:changed', next, previous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function screenToCanvas(sx: number, sy: number) {
|
function screenToCanvas(sx: number, sy: number) {
|
||||||
return {
|
return {
|
||||||
x: (sx - ctx.state.panX) / ctx.state.zoom,
|
x: (sx - ctx.state.panX) / ctx.state.zoom,
|
||||||
|
|
@ -12,11 +23,13 @@ export function createViewportActions(ctx: EditorContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setZoomAroundPoint(level: number, centerX: number, centerY: number) {
|
function setZoomAroundPoint(level: number, centerX: number, centerY: number) {
|
||||||
|
const previous = currentViewport()
|
||||||
const newZoom = Math.max(0.02, Math.min(256, level))
|
const newZoom = Math.max(0.02, Math.min(256, level))
|
||||||
ctx.state.panX = centerX - (centerX - ctx.state.panX) * (newZoom / ctx.state.zoom)
|
ctx.state.panX = centerX - (centerX - ctx.state.panX) * (newZoom / ctx.state.zoom)
|
||||||
ctx.state.panY = centerY - (centerY - ctx.state.panY) * (newZoom / ctx.state.zoom)
|
ctx.state.panY = centerY - (centerY - ctx.state.panY) * (newZoom / ctx.state.zoom)
|
||||||
ctx.state.zoom = newZoom
|
ctx.state.zoom = newZoom
|
||||||
ctx.requestRepaint()
|
ctx.requestRepaint()
|
||||||
|
emitViewportChanged(previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyZoom(delta: number, centerX: number, centerY: number) {
|
function applyZoom(delta: number, centerX: number, centerY: number) {
|
||||||
|
|
@ -28,12 +41,15 @@ export function createViewportActions(ctx: EditorContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function pan(dx: number, dy: number) {
|
function pan(dx: number, dy: number) {
|
||||||
|
const previous = currentViewport()
|
||||||
ctx.state.panX += dx
|
ctx.state.panX += dx
|
||||||
ctx.state.panY += dy
|
ctx.state.panY += dy
|
||||||
ctx.requestRepaint()
|
ctx.requestRepaint()
|
||||||
|
emitViewportChanged(previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomToBounds(minX: number, minY: number, maxX: number, maxY: number) {
|
function zoomToBounds(minX: number, minY: number, maxX: number, maxY: number) {
|
||||||
|
const previous = currentViewport()
|
||||||
const padding = 80
|
const padding = 80
|
||||||
const w = maxX - minX + padding * 2
|
const w = maxX - minX + padding * 2
|
||||||
const h = maxY - minY + padding * 2
|
const h = maxY - minY + padding * 2
|
||||||
|
|
@ -45,6 +61,7 @@ export function createViewportActions(ctx: EditorContext) {
|
||||||
ctx.state.panX = (viewW - w * zoom) / 2 - minX * zoom + padding * zoom
|
ctx.state.panX = (viewW - w * zoom) / 2 - minX * zoom + padding * zoom
|
||||||
ctx.state.panY = (viewH - h * zoom) / 2 - minY * zoom + padding * zoom
|
ctx.state.panY = (viewH - h * zoom) / 2 - minY * zoom + padding * zoom
|
||||||
ctx.requestRepaint()
|
ctx.requestRepaint()
|
||||||
|
emitViewportChanged(previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomToFit() {
|
function zoomToFit() {
|
||||||
|
|
@ -60,10 +77,12 @@ export function createViewportActions(ctx: EditorContext) {
|
||||||
const centerX = (-ctx.state.panX + viewW / 2) / ctx.state.zoom
|
const centerX = (-ctx.state.panX + viewW / 2) / ctx.state.zoom
|
||||||
const centerY = (-ctx.state.panY + viewH / 2) / ctx.state.zoom
|
const centerY = (-ctx.state.panY + viewH / 2) / ctx.state.zoom
|
||||||
|
|
||||||
|
const previous = currentViewport()
|
||||||
ctx.state.zoom = Math.max(0.02, Math.min(256, level))
|
ctx.state.zoom = Math.max(0.02, Math.min(256, level))
|
||||||
ctx.state.panX = viewW / 2 - centerX
|
ctx.state.panX = viewW / 2 - centerX
|
||||||
ctx.state.panY = viewH / 2 - centerY
|
ctx.state.panY = viewH / 2 - centerY
|
||||||
ctx.requestRepaint()
|
ctx.requestRepaint()
|
||||||
|
emitViewportChanged(previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomTo100() {
|
function zoomTo100() {
|
||||||
|
|
|
||||||
12
packages/vue/src/editor/events/use.ts
Normal file
12
packages/vue/src/editor/events/use.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { onScopeDispose } from 'vue'
|
||||||
|
|
||||||
|
import type { EditorEventName, EditorEvents } from '@open-pencil/core/editor'
|
||||||
|
|
||||||
|
import { useEditor } from '#vue/editor/context'
|
||||||
|
|
||||||
|
export function useEditorEvent<K extends EditorEventName>(event: K, handler: EditorEvents[K]) {
|
||||||
|
const editor = useEditor()
|
||||||
|
const stop = editor.onEditorEvent(event, handler)
|
||||||
|
onScopeDispose(stop)
|
||||||
|
return stop
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ export type {
|
||||||
Editor,
|
Editor,
|
||||||
EditorState,
|
EditorState,
|
||||||
EditorOptions,
|
EditorOptions,
|
||||||
|
EditorEvents,
|
||||||
|
EditorEventName,
|
||||||
Tool,
|
Tool,
|
||||||
EditorToolDef
|
EditorToolDef
|
||||||
} from '@open-pencil/core/editor'
|
} from '@open-pencil/core/editor'
|
||||||
|
|
@ -27,6 +29,7 @@ export { useNodeProps, MIXED } from '#vue/controls/node-props/use'
|
||||||
export type { MixedValue } from '#vue/controls/node-props/use'
|
export type { MixedValue } from '#vue/controls/node-props/use'
|
||||||
export { useSceneComputed } from '#vue/internal/scene-computed/use'
|
export { useSceneComputed } from '#vue/internal/scene-computed/use'
|
||||||
export { useSelectionState } from '#vue/editor/selection-state/use'
|
export { useSelectionState } from '#vue/editor/selection-state/use'
|
||||||
|
export { useEditorEvent } from '#vue/editor/events/use'
|
||||||
export { useSelectionCapabilities } from '#vue/editor/selection-capabilities/use'
|
export { useSelectionCapabilities } from '#vue/editor/selection-capabilities/use'
|
||||||
|
|
||||||
/** Command and menu composition helpers. */
|
/** Command and menu composition helpers. */
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import type { SceneGraph, SceneNode } from '@open-pencil/core/scene-graph'
|
||||||
export async function applyImportedDocument(editor: Editor, imported: SceneGraph) {
|
export async function applyImportedDocument(editor: Editor, imported: SceneGraph) {
|
||||||
editor.replaceGraph(imported)
|
editor.replaceGraph(imported)
|
||||||
editor.undo.clear()
|
editor.undo.clear()
|
||||||
editor.state.selectedIds = new Set()
|
editor.clearSelection()
|
||||||
const firstPage = editor.graph.getPages()[0] as SceneNode | undefined
|
const firstPage = editor.graph.getPages()[0] as SceneNode | undefined
|
||||||
const pageId = firstPage?.id ?? editor.graph.rootId
|
const pageId = firstPage?.id ?? editor.graph.rootId
|
||||||
await editor.switchPage(pageId)
|
await editor.switchPage(pageId)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export function restoreReloadState(
|
||||||
state: EditorState,
|
state: EditorState,
|
||||||
snapshot: ReloadStateSnapshot
|
snapshot: ReloadStateSnapshot
|
||||||
) {
|
) {
|
||||||
state.selectedIds = new Set()
|
editor.clearSelection()
|
||||||
if (editor.graph.getNode(snapshot.pageId)) {
|
if (editor.graph.getNode(snapshot.pageId)) {
|
||||||
state.currentPageId = snapshot.pageId
|
state.currentPageId = snapshot.pageId
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export function createPenActions(editor: Editor, graph: SceneGraph, state: PenSt
|
||||||
if (state.penState && tool !== 'PEN' && tool !== 'HAND') {
|
if (state.penState && tool !== 'PEN' && tool !== 'HAND') {
|
||||||
editor.penCommit(false)
|
editor.penCommit(false)
|
||||||
}
|
}
|
||||||
state.activeTool = tool
|
editor.setTool(tool)
|
||||||
}
|
}
|
||||||
|
|
||||||
function penResumeOnPath(nodeId: string) {
|
function penResumeOnPath(nodeId: string) {
|
||||||
|
|
@ -29,8 +29,8 @@ export function createPenActions(editor: Editor, graph: SceneGraph, state: PenSt
|
||||||
)
|
)
|
||||||
|
|
||||||
graph.deleteNode(nodeId)
|
graph.deleteNode(nodeId)
|
||||||
state.selectedIds = new Set()
|
editor.clearSelection()
|
||||||
state.activeTool = 'PEN'
|
editor.setTool('PEN')
|
||||||
editor.requestRender()
|
editor.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,8 +49,8 @@ export function createPenActions(editor: Editor, graph: SceneGraph, state: PenSt
|
||||||
|
|
||||||
state.penState = createResumedPenState(node, orderedVertices, orderedSegments)
|
state.penState = createResumedPenState(node, orderedVertices, orderedSegments)
|
||||||
graph.deleteNode(nodeId)
|
graph.deleteNode(nodeId)
|
||||||
state.selectedIds = new Set()
|
editor.clearSelection()
|
||||||
state.activeTool = 'PEN'
|
editor.setTool('PEN')
|
||||||
editor.requestRender()
|
editor.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export function createVectorEditLifecycle(
|
||||||
hoveredHandleInfo: null
|
hoveredHandleInfo: null
|
||||||
}
|
}
|
||||||
|
|
||||||
state.selectedIds = new Set([nodeId])
|
editor.select([nodeId])
|
||||||
editor.requestRender()
|
editor.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export async function openFileInNewTab(
|
||||||
store.replaceGraph(imported)
|
store.replaceGraph(imported)
|
||||||
store.undo.clear()
|
store.undo.clear()
|
||||||
store.setDocumentSource(file.name, sourceFormat, handle, path)
|
store.setDocumentSource(file.name, sourceFormat, handle, path)
|
||||||
store.state.selectedIds = new Set()
|
store.clearSelection()
|
||||||
const pageId = store.graph.getPages()[0]?.id ?? store.graph.rootId
|
const pageId = store.graph.getPages()[0]?.id ?? store.graph.rootId
|
||||||
await store.switchPage(pageId)
|
await store.switchPage(pageId)
|
||||||
await store.fitCurrentPageToViewport()
|
await store.fitCurrentPageToViewport()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue