Replace store thunks with a late-binding proxy
useEditorStore() now returns a Proxy that delegates to the current active store at access time. No more store() wrapper functions, try/catch guards, or passing stores through function parameters.
This commit is contained in:
parent
a6634b9543
commit
af9b9fd0fa
|
|
@ -9,7 +9,7 @@ import CodePanel from './CodePanel.vue'
|
|||
import DesignPanel from './DesignPanel.vue'
|
||||
|
||||
const store = useEditorStore()
|
||||
const { activeTab } = useAIChat(store)
|
||||
const { activeTab } = useAIChat()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { computed, ref, watch } from 'vue'
|
|||
import { createAITools } from '@/ai/tools'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
|
||||
import type { EditorStore } from '@/stores/editor'
|
||||
import type { UIMessage } from 'ai'
|
||||
|
||||
export { AI_MODELS as MODELS } from '@open-pencil/core'
|
||||
|
|
@ -34,8 +33,6 @@ const apiKey = ref(localStorage.getItem(API_KEY_STORAGE) ?? '')
|
|||
const modelId = ref(localStorage.getItem(MODEL_STORAGE) ?? DEFAULT_AI_MODEL)
|
||||
const activeTab = ref<'design' | 'ai'>('design')
|
||||
|
||||
let editorStore: EditorStore | null = null
|
||||
|
||||
watch(apiKey, (key) => {
|
||||
if (key) {
|
||||
localStorage.setItem(API_KEY_STORAGE, key)
|
||||
|
|
@ -66,13 +63,7 @@ function createTransport() {
|
|||
}
|
||||
})
|
||||
|
||||
let currentStore: EditorStore | null = editorStore
|
||||
try {
|
||||
currentStore = useEditorStore()
|
||||
} catch {
|
||||
// store not yet available
|
||||
}
|
||||
const tools = currentStore ? createAITools(currentStore) : {}
|
||||
const tools = createAITools(useEditorStore())
|
||||
|
||||
const agent = new ToolLoopAgent({
|
||||
model: openrouter(modelId.value),
|
||||
|
|
@ -103,10 +94,7 @@ if (typeof window !== 'undefined') {
|
|||
}
|
||||
}
|
||||
|
||||
export function useAIChat(store?: EditorStore) {
|
||||
if (store) {
|
||||
editorStore = store
|
||||
}
|
||||
export function useAIChat() {
|
||||
return {
|
||||
apiKey,
|
||||
modelId,
|
||||
|
|
|
|||
|
|
@ -12,50 +12,46 @@ function isEditing(e: Event) {
|
|||
|
||||
export function useKeyboard() {
|
||||
const { activeTab } = useAIChat()
|
||||
|
||||
function store() {
|
||||
return useEditorStore()
|
||||
}
|
||||
const store = useEditorStore()
|
||||
|
||||
useEventListener(window, 'copy', (e: ClipboardEvent) => {
|
||||
if (isEditing(e)) return
|
||||
e.preventDefault()
|
||||
if (e.clipboardData) store().writeCopyData(e.clipboardData)
|
||||
if (e.clipboardData) store.writeCopyData(e.clipboardData)
|
||||
})
|
||||
|
||||
useEventListener(window, 'cut', (e: ClipboardEvent) => {
|
||||
if (isEditing(e)) return
|
||||
e.preventDefault()
|
||||
if (e.clipboardData) store().writeCopyData(e.clipboardData)
|
||||
store().deleteSelected()
|
||||
if (e.clipboardData) store.writeCopyData(e.clipboardData)
|
||||
store.deleteSelected()
|
||||
})
|
||||
|
||||
useEventListener(window, 'paste', (e: ClipboardEvent) => {
|
||||
if (isEditing(e)) return
|
||||
e.preventDefault()
|
||||
const html = e.clipboardData?.getData('text/html') ?? ''
|
||||
if (html) store().pasteFromHTML(html)
|
||||
if (html) store.pasteFromHTML(html)
|
||||
})
|
||||
|
||||
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
||||
if (isEditing(e)) return
|
||||
const s = store()
|
||||
|
||||
const tool = TOOL_SHORTCUTS[e.key.toLowerCase()]
|
||||
if (tool) {
|
||||
s.setTool(tool)
|
||||
store.setTool(tool)
|
||||
return
|
||||
}
|
||||
|
||||
if ((e.metaKey || e.ctrlKey) && e.altKey) {
|
||||
if (e.code === 'KeyK') {
|
||||
e.preventDefault()
|
||||
s.createComponentFromSelection()
|
||||
store.createComponentFromSelection()
|
||||
return
|
||||
}
|
||||
if (e.code === 'KeyB') {
|
||||
e.preventDefault()
|
||||
s.detachInstance()
|
||||
store.detachInstance()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -63,23 +59,23 @@ export function useKeyboard() {
|
|||
if ((e.metaKey || e.ctrlKey) && e.shiftKey) {
|
||||
if (e.code === 'KeyK') {
|
||||
e.preventDefault()
|
||||
s.createComponentSetFromComponents()
|
||||
store.createComponentSetFromComponents()
|
||||
return
|
||||
}
|
||||
if (e.code === 'KeyH') {
|
||||
e.preventDefault()
|
||||
s.toggleVisibility()
|
||||
store.toggleVisibility()
|
||||
return
|
||||
}
|
||||
if (e.code === 'KeyL') {
|
||||
e.preventDefault()
|
||||
s.toggleLock()
|
||||
store.toggleLock()
|
||||
return
|
||||
}
|
||||
if (e.code === 'KeyE') {
|
||||
e.preventDefault()
|
||||
if (s.state.selectedIds.size > 0) {
|
||||
s.exportSelection(1, 'PNG')
|
||||
if (store.state.selectedIds.size > 0) {
|
||||
store.exportSelection(1, 'PNG')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -88,7 +84,7 @@ export function useKeyboard() {
|
|||
if (e.metaKey || e.ctrlKey) {
|
||||
if (e.code === 'Backslash') {
|
||||
e.preventDefault()
|
||||
s.state.showUI = !s.state.showUI
|
||||
store.state.showUI = !store.state.showUI
|
||||
return
|
||||
}
|
||||
if (e.code === 'KeyJ') {
|
||||
|
|
@ -108,76 +104,76 @@ export function useKeyboard() {
|
|||
}
|
||||
if (e.key === 'z' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
s.undoAction()
|
||||
store.undoAction()
|
||||
} else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') {
|
||||
e.preventDefault()
|
||||
s.redoAction()
|
||||
store.redoAction()
|
||||
} else if (e.key === '0') {
|
||||
e.preventDefault()
|
||||
s.zoomToFit()
|
||||
store.zoomToFit()
|
||||
} else if (e.key === 'd') {
|
||||
e.preventDefault()
|
||||
s.duplicateSelected()
|
||||
store.duplicateSelected()
|
||||
} else if (e.key === 'a') {
|
||||
e.preventDefault()
|
||||
s.selectAll()
|
||||
store.selectAll()
|
||||
} else if (e.key === 's' && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
s.saveFigFileAs()
|
||||
store.saveFigFileAs()
|
||||
} else if (e.key === 's') {
|
||||
e.preventDefault()
|
||||
s.saveFigFile()
|
||||
store.saveFigFile()
|
||||
} else if (e.key === 'o') {
|
||||
e.preventDefault()
|
||||
openFileDialog()
|
||||
} else if (e.key === 'g' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
s.groupSelected()
|
||||
store.groupSelected()
|
||||
} else if (e.key === 'g' && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
s.ungroupSelected()
|
||||
store.ungroupSelected()
|
||||
}
|
||||
}
|
||||
|
||||
if (e.shiftKey && e.key === 'A') {
|
||||
e.preventDefault()
|
||||
const node = s.selectedNode.value
|
||||
if (node && node.type === 'FRAME' && s.selectedNodes.value.length === 1) {
|
||||
s.setLayoutMode(node.id, node.layoutMode === 'NONE' ? 'VERTICAL' : 'NONE')
|
||||
} else if (s.selectedNodes.value.length > 0) {
|
||||
s.wrapInAutoLayout()
|
||||
const node = store.selectedNode.value
|
||||
if (node && node.type === 'FRAME' && store.selectedNodes.value.length === 1) {
|
||||
store.setLayoutMode(node.id, node.layoutMode === 'NONE' ? 'VERTICAL' : 'NONE')
|
||||
} else if (store.selectedNodes.value.length > 0) {
|
||||
store.wrapInAutoLayout()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === ']') {
|
||||
e.preventDefault()
|
||||
s.bringToFront()
|
||||
store.bringToFront()
|
||||
return
|
||||
}
|
||||
if (e.key === '[') {
|
||||
e.preventDefault()
|
||||
s.sendToBack()
|
||||
store.sendToBack()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Backspace' || e.key === 'Delete') {
|
||||
s.deleteSelected()
|
||||
store.deleteSelected()
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && s.state.penState) {
|
||||
if (e.key === 'Enter' && store.state.penState) {
|
||||
e.preventDefault()
|
||||
s.penCommit(false)
|
||||
store.penCommit(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (s.state.penState) {
|
||||
s.penCancel()
|
||||
if (store.state.penState) {
|
||||
store.penCancel()
|
||||
return
|
||||
}
|
||||
s.clearSelection()
|
||||
s.setTool('SELECT')
|
||||
store.clearSelection()
|
||||
store.setTool('SELECT')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ export async function openFileDialog() {
|
|||
input.click()
|
||||
}
|
||||
|
||||
function getStore() {
|
||||
return useEditorStore()
|
||||
}
|
||||
const store = useEditorStore()
|
||||
|
||||
const MENU_ACTIONS: Record<string, () => void> = {
|
||||
new: () => createTab(),
|
||||
|
|
@ -57,19 +55,18 @@ const MENU_ACTIONS: Record<string, () => void> = {
|
|||
close: () => {
|
||||
if (activeTab.value) closeTab(activeTab.value.id)
|
||||
},
|
||||
save: () => getStore().saveFigFile(),
|
||||
'save-as': () => getStore().saveFigFileAs(),
|
||||
duplicate: () => getStore().duplicateSelected(),
|
||||
delete: () => getStore().deleteSelected(),
|
||||
group: () => getStore().groupSelected(),
|
||||
ungroup: () => getStore().ungroupSelected(),
|
||||
'create-component': () => getStore().createComponentFromSelection(),
|
||||
'create-component-set': () => getStore().createComponentSetFromComponents(),
|
||||
'detach-instance': () => getStore().detachInstance(),
|
||||
'zoom-fit': () => getStore().zoomToFit(),
|
||||
save: () => store.saveFigFile(),
|
||||
'save-as': () => store.saveFigFileAs(),
|
||||
duplicate: () => store.duplicateSelected(),
|
||||
delete: () => store.deleteSelected(),
|
||||
group: () => store.groupSelected(),
|
||||
ungroup: () => store.ungroupSelected(),
|
||||
'create-component': () => store.createComponentFromSelection(),
|
||||
'create-component-set': () => store.createComponentSetFromComponents(),
|
||||
'detach-instance': () => store.detachInstance(),
|
||||
'zoom-fit': () => store.zoomToFit(),
|
||||
export: () => {
|
||||
const s = getStore()
|
||||
if (s.state.selectedIds.size > 0) s.exportSelection(1, 'PNG')
|
||||
if (store.state.selectedIds.size > 0) store.exportSelection(1, 'PNG')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1973,13 +1973,17 @@ export function setActiveEditorStore(store: EditorStore) {
|
|||
storeRef.value = store
|
||||
}
|
||||
|
||||
export function provideEditorStore(): EditorStore {
|
||||
const store = createEditorStore()
|
||||
storeRef.value = store
|
||||
return store
|
||||
}
|
||||
|
||||
export function useEditorStore(): EditorStore {
|
||||
export function getActiveEditorStore(): EditorStore {
|
||||
if (!storeRef.value) throw new Error('Editor store not provided')
|
||||
return storeRef.value
|
||||
}
|
||||
|
||||
const storeProxy = new Proxy({} as EditorStore, {
|
||||
get(_, prop) {
|
||||
return Reflect.get(getActiveEditorStore(), prop)
|
||||
}
|
||||
})
|
||||
|
||||
export function useEditorStore(): EditorStore {
|
||||
return storeProxy
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, provide } from 'vue'
|
||||
import { provide } from 'vue'
|
||||
import { useEventListener, useUrlSearchParams } from '@vueuse/core'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from 'reka-ui'
|
||||
|
|
@ -24,7 +24,7 @@ const route = useRoute()
|
|||
const router = useRouter()
|
||||
|
||||
const firstTab = createTab()
|
||||
const store = computed(() => useEditorStore())
|
||||
const store = useEditorStore()
|
||||
useKeyboard()
|
||||
useMenu()
|
||||
const collab = useCollab(firstTab.store)
|
||||
|
|
|
|||
Loading…
Reference in a new issue