From 48bfac5d840df07a4249b3216b6f2d9610c4803b Mon Sep 17 00:00:00 2001 From: dineug Date: Sun, 20 Oct 2024 00:38:09 +0900 Subject: [PATCH] feat: Refactor vscode bridge --- packages/erd-editor-schema/vite.config.ts | 5 +- packages/intellij-webview/src/main.ts | 86 +++++---- .../src/services/replicationStore.worker.ts | 26 +-- packages/r-html/vite.config.ts | 5 +- packages/schema-sql-parser/vite.config.ts | 5 +- packages/shared/vite.config.ts | 5 +- packages/vite-plugin-r-html/vite.config.ts | 5 +- packages/vscode-bridge/src/bridge.ts | 68 +++++++ packages/vscode-bridge/src/commands.ts | 42 +++++ packages/vscode-bridge/src/index.ts | 178 ++---------------- .../{themes/radix-ui-theme.ts => theme.ts} | 0 packages/vscode-bridge/vite.config.ts | 5 +- .../vscode-extension/src/configuration.ts | 12 -- packages/vscode-extension/src/editor.ts | 4 +- packages/vscode-extension/src/erd-editor.ts | 139 ++++++++------ .../src/services/replicationStore.worker.ts | 26 +-- .../vite.config.ts | 5 +- packages/vscode-webview/src/index.ts | 90 ++++----- 18 files changed, 356 insertions(+), 350 deletions(-) create mode 100644 packages/vscode-bridge/src/bridge.ts create mode 100644 packages/vscode-bridge/src/commands.ts rename packages/vscode-bridge/src/{themes/radix-ui-theme.ts => theme.ts} (100%) diff --git a/packages/erd-editor-schema/vite.config.ts b/packages/erd-editor-schema/vite.config.ts index eb61fcf1..f4c205b5 100644 --- a/packages/erd-editor-schema/vite.config.ts +++ b/packages/erd-editor-schema/vite.config.ts @@ -39,5 +39,8 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], }); diff --git a/packages/intellij-webview/src/main.ts b/packages/intellij-webview/src/main.ts index 918c9c5e..2c7f360d 100644 --- a/packages/intellij-webview/src/main.ts +++ b/packages/intellij-webview/src/main.ts @@ -6,18 +6,23 @@ import { } from '@dineug/erd-editor'; import { AnyAction, - Emitter, + Bridge, + hostExportFileCommand, + hostInitialCommand, + hostSaveReplicationCommand, + hostSaveThemeCommand, + hostSaveValueCommand, ThemeOptions, - vscodeExportFileAction, - vscodeInitialAction, - vscodeSaveReplicationAction, - vscodeSaveThemeAction, - webviewReplicationAction, + webviewImportFileCommand, + webviewInitialValueCommand, + webviewReplicationCommand, + webviewUpdateReadonlyCommand, + webviewUpdateThemeCommand, } from '@dineug/erd-editor-vscode-bridge'; import { encode } from 'base64-arraybuffer'; -const bridge = new Emitter(); -const workerBridge = new Emitter(); +const bridge = new Bridge(); +const workerBridge = new Bridge(); const editor = document.createElement('erd-editor'); const sharedStore = editor.getSharedStore({ mouseTracker: false }); const replicationStoreWorker = new Worker( @@ -47,7 +52,7 @@ import('@dineug/erd-editor-shiki-worker').then(({ getShikiService }) => { setExportFileCallback(async (blob, options) => { const arrayBuffer = await blob.arrayBuffer(); dispatch( - vscodeExportFileAction({ + Bridge.executeCommand(hostExportFileCommand, { value: encode(arrayBuffer), fileName: options.fileName, }) @@ -56,11 +61,11 @@ setExportFileCallback(async (blob, options) => { const handleChangePresetTheme = (event: Event) => { const e = event as CustomEvent; - dispatch(vscodeSaveThemeAction(e.detail)); + dispatch(Bridge.executeCommand(hostSaveThemeCommand, e.detail)); }; -bridge.on({ - webviewImportFile: ({ payload: { type, op, value } }) => { +Bridge.mergeRegister( + bridge.registerCommand(webviewImportFileCommand, ({ type, op, value }) => { switch (type) { case 'json': op === 'set' ? (editor.value = value) : editor.setDiffValue(value); @@ -69,48 +74,47 @@ bridge.on({ op === 'set' && editor.setSchemaSQL(value); break; } - }, - webviewInitialValue: action => { - const { - payload: { value }, - } = action; - dispatchWorker(action); + }), + bridge.registerCommand(webviewInitialValueCommand, ({ value }) => { + dispatchWorker( + Bridge.executeCommand(webviewInitialValueCommand, { value }) + ); editor.addEventListener('changePresetTheme', handleChangePresetTheme); editor.setInitialValue(value); editor.enableThemeBuilder = true; sharedStore.subscribe(actions => { - dispatchWorker(webviewReplicationAction({ actions })); - dispatch(vscodeSaveReplicationAction({ actions })); + dispatchWorker( + Bridge.executeCommand(webviewReplicationCommand, { actions }) + ); + dispatch(Bridge.executeCommand(hostSaveReplicationCommand, { actions })); }); document.body.appendChild(editor); - }, - webviewReplication: action => { - const { - payload: { actions }, - } = action; + }), + bridge.registerCommand(webviewReplicationCommand, ({ actions }) => { sharedStore.dispatch(actions); - dispatchWorker(action); - }, - webviewUpdateTheme: ({ payload }) => { + dispatchWorker( + Bridge.executeCommand(webviewReplicationCommand, { actions }) + ); + }), + bridge.registerCommand(webviewUpdateThemeCommand, payload => { editor.setPresetTheme({ ...payload, appearance: payload.appearance === 'auto' ? 'dark' : payload.appearance, }); - }, - webviewUpdateReadonly: ({ payload }) => { - editor.readonly = payload; - }, -}); + }), + bridge.registerCommand(webviewUpdateReadonlyCommand, readonly => { + editor.readonly = readonly; + }), + workerBridge.registerCommand(hostSaveValueCommand, ({ value }) => { + dispatch(Bridge.executeCommand(hostSaveValueCommand, { value })); + }) +); -workerBridge.on({ - vscodeSaveValue: action => { - dispatch(action); - }, +globalThis.addEventListener('message', event => { + bridge.executeAction(event.data); }); - -window.addEventListener('message', event => bridge.emit(event.data)); replicationStoreWorker.addEventListener('message', event => { - workerBridge.emit(event.data); + workerBridge.executeAction(event.data); }); -dispatch(vscodeInitialAction()); +dispatch(Bridge.executeCommand(hostInitialCommand, undefined)); diff --git a/packages/intellij-webview/src/services/replicationStore.worker.ts b/packages/intellij-webview/src/services/replicationStore.worker.ts index b97c935a..81148e85 100644 --- a/packages/intellij-webview/src/services/replicationStore.worker.ts +++ b/packages/intellij-webview/src/services/replicationStore.worker.ts @@ -1,14 +1,16 @@ import { createReplicationStore } from '@dineug/erd-editor/engine.js'; import { AnyAction, - Emitter, - vscodeSaveValueAction, + Bridge, + hostSaveValueCommand, + webviewInitialValueCommand, + webviewReplicationCommand, } from '@dineug/erd-editor-vscode-bridge'; import { toWidth } from '@/utils/text'; const store = createReplicationStore({ toWidth }); -const bridge = new Emitter(); +const bridge = new Bridge(); const dispatch = (action: AnyAction) => { globalThis.postMessage(action); @@ -17,20 +19,22 @@ const dispatch = (action: AnyAction) => { store.on({ change: () => { dispatch( - vscodeSaveValueAction({ + Bridge.executeCommand(hostSaveValueCommand, { value: store.value, }) ); }, }); -bridge.on({ - webviewInitialValue: ({ payload: { value } }) => { +Bridge.mergeRegister( + bridge.registerCommand(webviewInitialValueCommand, ({ value }) => { store.setInitialValue(value); - }, - webviewReplication: ({ payload: { actions } }) => { + }), + bridge.registerCommand(webviewReplicationCommand, ({ actions }) => { store.dispatch(actions); - }, -}); + }) +); -globalThis.addEventListener('message', event => bridge.emit(event.data)); +globalThis.addEventListener('message', event => { + bridge.executeAction(event.data); +}); diff --git a/packages/r-html/vite.config.ts b/packages/r-html/vite.config.ts index 29b22ff1..c89c7a90 100644 --- a/packages/r-html/vite.config.ts +++ b/packages/r-html/vite.config.ts @@ -31,7 +31,10 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], server: { open: true, }, diff --git a/packages/schema-sql-parser/vite.config.ts b/packages/schema-sql-parser/vite.config.ts index c7281423..b66408ff 100644 --- a/packages/schema-sql-parser/vite.config.ts +++ b/packages/schema-sql-parser/vite.config.ts @@ -32,5 +32,8 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], }); diff --git a/packages/shared/vite.config.ts b/packages/shared/vite.config.ts index b801e611..b4d210a7 100644 --- a/packages/shared/vite.config.ts +++ b/packages/shared/vite.config.ts @@ -16,5 +16,8 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], }); diff --git a/packages/vite-plugin-r-html/vite.config.ts b/packages/vite-plugin-r-html/vite.config.ts index fc9af8ca..c31bd7dd 100644 --- a/packages/vite-plugin-r-html/vite.config.ts +++ b/packages/vite-plugin-r-html/vite.config.ts @@ -30,5 +30,8 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], }); diff --git a/packages/vscode-bridge/src/bridge.ts b/packages/vscode-bridge/src/bridge.ts new file mode 100644 index 00000000..508914cc --- /dev/null +++ b/packages/vscode-bridge/src/bridge.ts @@ -0,0 +1,68 @@ +import { isObject, isString, safeCallback } from '@dineug/shared'; + +export type Command

= { + type: string; +}; + +export type CommandListener

= (payload: P) => void; + +export type CommandPayload> = + T extends Command ? P : never; + +export type Dispose = () => void; + +export type AnyAction

= { + type: string; + payload: P; +}; + +export function createCommand(type: string): Command { + return { type }; +} + +export class Bridge { + #commands = new Map>>(); + + static mergeRegister(...disposables: Dispose[]) { + return () => { + disposables.forEach(dispose => dispose()); + }; + } + + static executeCommand>( + command: T, + payload: CommandPayload + ): AnyAction { + return { + type: command.type, + payload, + }; + } + + registerCommand

( + command: Command

, + listener: CommandListener

+ ): Dispose { + const listeners = this.#commands.get(command.type) ?? new Set(); + listeners.add(listener); + + if (!this.#commands.has(command.type)) { + this.#commands.set(command.type, listeners); + } + + return () => { + listeners.delete(listener); + }; + } + + executeAction(action: AnyAction) { + if (!isAction(action)) return; + + const listeners = this.#commands.get(action.type); + listeners?.forEach(listener => safeCallback(listener, action.payload)); + } +} + +function isAction(action: AnyAction) { + return isObject(action) && isString(action.type); +} diff --git a/packages/vscode-bridge/src/commands.ts b/packages/vscode-bridge/src/commands.ts new file mode 100644 index 00000000..34bfb89d --- /dev/null +++ b/packages/vscode-bridge/src/commands.ts @@ -0,0 +1,42 @@ +import { createCommand } from './bridge'; +import { type ThemeOptions } from './theme'; + +type Base64 = string; + +export const hostExportFileCommand = createCommand<{ + value: Base64; + fileName: string; +}>('hostExportFileCommand'); +export const hostImportFileCommand = createCommand<{ + type: 'json' | 'sql'; + op: 'set' | 'diff'; + accept: string; +}>('hostImportFileCommand'); +export const hostInitialCommand = createCommand('hostInitialCommand'); +export const hostSaveValueCommand = createCommand<{ + value: string; +}>('hostSaveValueCommand'); +export const hostSaveReplicationCommand = createCommand<{ + actions: any; +}>('hostSaveReplicationCommand'); +export const hostSaveThemeCommand = createCommand( + 'hostSaveThemeCommand' +); + +export const webviewImportFileCommand = createCommand<{ + type: 'json' | 'sql'; + op: 'set' | 'diff'; + value: string; +}>('webviewImportFileCommand'); +export const webviewInitialValueCommand = createCommand<{ + value: string; +}>('webviewInitialValueCommand'); +export const webviewUpdateThemeCommand = createCommand>( + 'webviewUpdateThemeCommand' +); +export const webviewUpdateReadonlyCommand = createCommand( + 'webviewUpdateReadonlyCommand' +); +export const webviewReplicationCommand = createCommand<{ + actions: any; +}>('webviewReplicationCommand'); diff --git a/packages/vscode-bridge/src/index.ts b/packages/vscode-bridge/src/index.ts index 107e4580..0f85579b 100644 --- a/packages/vscode-bridge/src/index.ts +++ b/packages/vscode-bridge/src/index.ts @@ -1,167 +1,11 @@ -import { isObject, safeCallback } from '@dineug/shared'; - -import { AnyAction, ReducerRecord, ValuesType } from '@/internal-types'; -import { - AccentColor, - Appearance, - GrayColor, - ThemeOptions, -} from '@/themes/radix-ui-theme'; - -export type { AnyAction, ThemeOptions }; - -export { AccentColor, Appearance, GrayColor }; - -const BridgeActionType = { - vscodeExportFile: 'vscodeExportFile', - vscodeImportFile: 'vscodeImportFile', - vscodeInitial: 'vscodeInitial', - vscodeSaveValue: 'vscodeSaveValue', - vscodeSaveReplication: 'vscodeSaveReplication', - vscodeSaveTheme: 'vscodeSaveTheme', - webviewImportFile: 'webviewImportFile', - webviewInitialValue: 'webviewInitialValue', - webviewUpdateTheme: 'webviewUpdateTheme', - webviewUpdateThemeLegacy: 'webviewUpdateThemeLegacy', - webviewUpdateReadonly: 'webviewUpdateReadonly', - webviewReplication: 'webviewReplication', -} as const; -type BridgeActionType = ValuesType; - -type Base64 = string; - -type BridgeActionMap = { - [BridgeActionType.vscodeExportFile]: { - value: Base64; - fileName: string; - }; - [BridgeActionType.vscodeImportFile]: { - type: 'json' | 'sql'; - op: 'set' | 'diff'; - accept: string; - }; - [BridgeActionType.vscodeInitial]: void; - [BridgeActionType.vscodeSaveValue]: { - value: string; - }; - [BridgeActionType.vscodeSaveReplication]: { - actions: any; - }; - [BridgeActionType.vscodeSaveTheme]: ThemeOptions; - [BridgeActionType.webviewImportFile]: { - type: 'json' | 'sql'; - op: 'set' | 'diff'; - value: string; - }; - [BridgeActionType.webviewInitialValue]: { - value: string; - }; - [BridgeActionType.webviewUpdateTheme]: Partial; - [BridgeActionType.webviewUpdateThemeLegacy]: { - themeSync: boolean; - theme: Partial<{ - canvas: string; - table: string; - tableActive: string; - focus: string; - keyPK: string; - keyFK: string; - keyPFK: string; - font: string; - fontActive: string; - fontPlaceholder: string; - contextmenu: string; - contextmenuActive: string; - edit: string; - columnSelect: string; - columnActive: string; - minimapShadow: string; - scrollbarThumb: string; - scrollbarThumbActive: string; - menubar: string; - visualization: string; - }>; - }; - [BridgeActionType.webviewUpdateReadonly]: boolean; - [BridgeActionType.webviewReplication]: { - actions: any; - }; -}; - -function createAction

(type: string) { - function actionCreator(payload: P): AnyAction

{ - return { type, payload }; - } - - actionCreator.toString = () => `${type}`; - actionCreator.type = type; - return actionCreator; -} - -export class Emitter { - #observers = new Set>>(); - - on(reducers: Partial>) { - this.#observers.has(reducers) || this.#observers.add(reducers); - - return () => { - this.#observers.delete(reducers); - }; - } - - emit(action: AnyAction) { - if (!isObject(action)) return; - this.#observers.forEach(reducers => { - const reducer = Reflect.get(reducers, action.type); - safeCallback(reducer, action); - }); - } -} - -export const vscodeExportFileAction = createAction< - BridgeActionMap[typeof BridgeActionType.vscodeExportFile] ->(BridgeActionType.vscodeExportFile); - -export const vscodeImportFileAction = createAction< - BridgeActionMap[typeof BridgeActionType.vscodeImportFile] ->(BridgeActionType.vscodeImportFile); - -export const vscodeInitialAction = createAction< - BridgeActionMap[typeof BridgeActionType.vscodeInitial] ->(BridgeActionType.vscodeInitial); - -export const vscodeSaveValueAction = createAction< - BridgeActionMap[typeof BridgeActionType.vscodeSaveValue] ->(BridgeActionType.vscodeSaveValue); - -export const vscodeSaveReplicationAction = createAction< - BridgeActionMap[typeof BridgeActionType.vscodeSaveReplication] ->(BridgeActionType.vscodeSaveReplication); - -export const vscodeSaveThemeAction = createAction< - BridgeActionMap[typeof BridgeActionType.vscodeSaveTheme] ->(BridgeActionType.vscodeSaveTheme); - -export const webviewImportFileAction = createAction< - BridgeActionMap[typeof BridgeActionType.webviewImportFile] ->(BridgeActionType.webviewImportFile); - -export const webviewInitialValueAction = createAction< - BridgeActionMap[typeof BridgeActionType.webviewInitialValue] ->(BridgeActionType.webviewInitialValue); - -export const webviewUpdateThemeAction = createAction< - BridgeActionMap[typeof BridgeActionType.webviewUpdateTheme] ->(BridgeActionType.webviewUpdateTheme); - -export const webviewUpdateThemeLegacyAction = createAction< - BridgeActionMap[typeof BridgeActionType.webviewUpdateThemeLegacy] ->(BridgeActionType.webviewUpdateThemeLegacy); - -export const webviewUpdateReadonlyAction = createAction< - BridgeActionMap[typeof BridgeActionType.webviewUpdateReadonly] ->(BridgeActionType.webviewUpdateReadonly); - -export const webviewReplicationAction = createAction< - BridgeActionMap[typeof BridgeActionType.webviewReplication] ->(BridgeActionType.webviewReplication); +export { + type AnyAction, + Bridge, + type Command, + type CommandListener, + type CommandPayload, + createCommand, + type Dispose, +} from './bridge'; +export * from './commands'; +export { AccentColor, Appearance, GrayColor, type ThemeOptions } from './theme'; diff --git a/packages/vscode-bridge/src/themes/radix-ui-theme.ts b/packages/vscode-bridge/src/theme.ts similarity index 100% rename from packages/vscode-bridge/src/themes/radix-ui-theme.ts rename to packages/vscode-bridge/src/theme.ts diff --git a/packages/vscode-bridge/vite.config.ts b/packages/vscode-bridge/vite.config.ts index 4e7af80e..3a9a7608 100644 --- a/packages/vscode-bridge/vite.config.ts +++ b/packages/vscode-bridge/vite.config.ts @@ -29,5 +29,8 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], }); diff --git a/packages/vscode-extension/src/configuration.ts b/packages/vscode-extension/src/configuration.ts index 5b9b29db..361509cf 100644 --- a/packages/vscode-extension/src/configuration.ts +++ b/packages/vscode-extension/src/configuration.ts @@ -3,7 +3,6 @@ import { Appearance, GrayColor, ThemeOptions, - webviewUpdateThemeLegacyAction, } from '@dineug/erd-editor-vscode-bridge'; import * as vscode from 'vscode'; @@ -50,14 +49,3 @@ export function getTheme(): ThemeOptions { accentColor: config.get('accentColor', AccentColor.indigo), }; } - -export function getThemeLegacy(): ReturnType< - typeof webviewUpdateThemeLegacyAction ->['payload'] { - const config = vscode.workspace.getConfiguration('dineug.vuerd-vscode'); - - return { - themeSync: config.get('themeSync', false), - theme: config.get('theme', {}), - }; -} diff --git a/packages/vscode-extension/src/editor.ts b/packages/vscode-extension/src/editor.ts index c34ba98b..a91b8424 100644 --- a/packages/vscode-extension/src/editor.ts +++ b/packages/vscode-extension/src/editor.ts @@ -1,4 +1,4 @@ -import { Emitter } from '@dineug/erd-editor-vscode-bridge'; +import { Bridge } from '@dineug/erd-editor-vscode-bridge'; import * as vscode from 'vscode'; import { ErdDocument } from '@/erd-document'; @@ -9,7 +9,7 @@ export type CreateEditor = ( ) => Editor; export abstract class Editor { - protected bridge = new Emitter(); + protected bridge = new Bridge(); protected abstract assetsDir: string; constructor( diff --git a/packages/vscode-extension/src/erd-editor.ts b/packages/vscode-extension/src/erd-editor.ts index 5477630b..450a553c 100644 --- a/packages/vscode-extension/src/erd-editor.ts +++ b/packages/vscode-extension/src/erd-editor.ts @@ -1,10 +1,17 @@ import { AnyAction, - webviewImportFileAction, - webviewInitialValueAction, - webviewReplicationAction, - webviewUpdateReadonlyAction, - webviewUpdateThemeAction, + Bridge, + hostExportFileCommand, + hostImportFileCommand, + hostInitialCommand, + hostSaveReplicationCommand, + hostSaveThemeCommand, + hostSaveValueCommand, + webviewImportFileCommand, + webviewInitialValueCommand, + webviewReplicationCommand, + webviewUpdateReadonlyCommand, + webviewUpdateThemeCommand, } from '@dineug/erd-editor-vscode-bridge'; import { decode } from 'base64-arraybuffer'; import * as os from 'os'; @@ -44,73 +51,93 @@ export class ErdEditor extends Editor { .forEach(webview => webview.postMessage(action)); }; - const unsubscribe = this.bridge.on({ - vscodeInitial: () => { - dispatch(webviewUpdateThemeAction(getTheme())); - dispatch(webviewUpdateReadonlyAction(this.readonly)); + const dispose = Bridge.mergeRegister( + this.bridge.registerCommand(hostInitialCommand, () => { + dispatch(Bridge.executeCommand(webviewUpdateThemeCommand, getTheme())); dispatch( - webviewInitialValueAction({ + Bridge.executeCommand(webviewUpdateReadonlyCommand, this.readonly) + ); + dispatch( + Bridge.executeCommand(webviewInitialValueCommand, { value: textDecoder.decode(this.document.content), }) ); - }, - vscodeSaveValue: async ({ payload: { value } }) => { + }), + this.bridge.registerCommand(hostSaveValueCommand, async ({ value }) => { await this.document.update(textEncoder.encode(value)); - }, - vscodeSaveReplication: ({ payload: { actions } }) => { - dispatchBroadcast(webviewReplicationAction({ actions })); - }, - vscodeImportFile: async ({ payload: { type, op } }) => { - const uris = await vscode.window.showOpenDialog(); - if (!uris || !uris.length) return; - - const uri = uris[0]; - const regexp = new RegExp(`\.(${type}|erd|vuerd)$`, 'i'); - - if (!regexp.test(uri.path)) { - vscode.window.showInformationMessage(`Just import the ${type} file`); - return; - } - - const value = await vscode.workspace.fs.readFile(uris[0]); - dispatch( - webviewImportFileAction({ - type, - op, - value: textDecoder.decode(value), - }) + }), + this.bridge.registerCommand(hostSaveReplicationCommand, ({ actions }) => { + dispatchBroadcast( + Bridge.executeCommand(webviewReplicationCommand, { actions }) ); - }, - vscodeExportFile: async ({ payload: { value, fileName } }) => { - let defaultPath = os.homedir(); - - if ( - Array.isArray(vscode.workspace.workspaceFolders) && - vscode.workspace.workspaceFolders.length - ) { - defaultPath = vscode.workspace.workspaceFolders[0].uri.fsPath; + }), + this.bridge.registerCommand( + hostImportFileCommand, + async ({ type, op }) => { + const uris = await vscode.window.showOpenDialog(); + if (!uris || !uris.length) return; + + const uri = uris[0]; + const regexp = new RegExp(`\.(${type}|erd|vuerd)$`, 'i'); + + if (!regexp.test(uri.path)) { + vscode.window.showInformationMessage( + `Just import the ${type} file` + ); + return; + } + + const value = await vscode.workspace.fs.readFile(uris[0]); + dispatch( + Bridge.executeCommand(webviewImportFileCommand, { + type, + op, + value: textDecoder.decode(value), + }) + ); } + ), + this.bridge.registerCommand( + hostExportFileCommand, + async ({ value, fileName }) => { + let defaultPath = os.homedir(); + + if ( + Array.isArray(vscode.workspace.workspaceFolders) && + vscode.workspace.workspaceFolders.length + ) { + defaultPath = vscode.workspace.workspaceFolders[0].uri.fsPath; + } - const uri = await vscode.window.showSaveDialog({ - defaultUri: vscode.Uri.file(path.join(defaultPath, fileName)), - }); - if (!uri) return; + const uri = await vscode.window.showSaveDialog({ + defaultUri: vscode.Uri.file(path.join(defaultPath, fileName)), + }); + if (!uri) return; - await vscode.workspace.fs.writeFile(uri, new Uint8Array(decode(value))); - }, - vscodeSaveTheme: ({ payload }) => { + await vscode.workspace.fs.writeFile( + uri, + new Uint8Array(decode(value)) + ); + } + ), + this.bridge.registerCommand(hostSaveThemeCommand, payload => { saveTheme(payload); - }, - }); + }) + ); const listeners: vscode.Disposable[] = [ - this.webview.onDidReceiveMessage(action => this.bridge.emit(action)), + this.webview.onDidReceiveMessage(action => { + this.bridge.executeAction(action); + }), ...THEME_KEYS.map(key => vscode.workspace.onDidChangeConfiguration(event => { if (!event.affectsConfiguration(key, this.document.uri)) { return; } - dispatch(webviewUpdateThemeAction(getTheme())); + + dispatch( + Bridge.executeCommand(webviewUpdateThemeCommand, getTheme()) + ); }) ), ]; @@ -118,8 +145,8 @@ export class ErdEditor extends Editor { this.webview.html = await this.buildHtmlForWebview(); return new vscode.Disposable(() => { - unsubscribe(); listeners.forEach(listener => listener.dispose()); + dispose(); }); } } diff --git a/packages/vscode-replication-store-worker/src/services/replicationStore.worker.ts b/packages/vscode-replication-store-worker/src/services/replicationStore.worker.ts index b97c935a..81148e85 100644 --- a/packages/vscode-replication-store-worker/src/services/replicationStore.worker.ts +++ b/packages/vscode-replication-store-worker/src/services/replicationStore.worker.ts @@ -1,14 +1,16 @@ import { createReplicationStore } from '@dineug/erd-editor/engine.js'; import { AnyAction, - Emitter, - vscodeSaveValueAction, + Bridge, + hostSaveValueCommand, + webviewInitialValueCommand, + webviewReplicationCommand, } from '@dineug/erd-editor-vscode-bridge'; import { toWidth } from '@/utils/text'; const store = createReplicationStore({ toWidth }); -const bridge = new Emitter(); +const bridge = new Bridge(); const dispatch = (action: AnyAction) => { globalThis.postMessage(action); @@ -17,20 +19,22 @@ const dispatch = (action: AnyAction) => { store.on({ change: () => { dispatch( - vscodeSaveValueAction({ + Bridge.executeCommand(hostSaveValueCommand, { value: store.value, }) ); }, }); -bridge.on({ - webviewInitialValue: ({ payload: { value } }) => { +Bridge.mergeRegister( + bridge.registerCommand(webviewInitialValueCommand, ({ value }) => { store.setInitialValue(value); - }, - webviewReplication: ({ payload: { actions } }) => { + }), + bridge.registerCommand(webviewReplicationCommand, ({ actions }) => { store.dispatch(actions); - }, -}); + }) +); -globalThis.addEventListener('message', event => bridge.emit(event.data)); +globalThis.addEventListener('message', event => { + bridge.executeAction(event.data); +}); diff --git a/packages/vscode-replication-store-worker/vite.config.ts b/packages/vscode-replication-store-worker/vite.config.ts index b801e611..b4d210a7 100644 --- a/packages/vscode-replication-store-worker/vite.config.ts +++ b/packages/vscode-replication-store-worker/vite.config.ts @@ -16,5 +16,8 @@ export default defineConfig({ '@': join(__dirname, 'src'), }, }, - plugins: [dts(), typescript({ noEmitOnError: true })], + plugins: [ + dts({ compilerOptions: { declarationMap: true } }), + typescript({ noEmitOnError: true }), + ], }); diff --git a/packages/vscode-webview/src/index.ts b/packages/vscode-webview/src/index.ts index d840e92b..59588198 100644 --- a/packages/vscode-webview/src/index.ts +++ b/packages/vscode-webview/src/index.ts @@ -8,20 +8,25 @@ import { import { AnyAction, Appearance, - Emitter, + Bridge, + hostExportFileCommand, + hostImportFileCommand, + hostInitialCommand, + hostSaveReplicationCommand, + hostSaveThemeCommand, + hostSaveValueCommand, ThemeOptions, - vscodeExportFileAction, - vscodeImportFileAction, - vscodeInitialAction, - vscodeSaveReplicationAction, - vscodeSaveThemeAction, - webviewReplicationAction, + webviewImportFileCommand, + webviewInitialValueCommand, + webviewReplicationCommand, + webviewUpdateReadonlyCommand, + webviewUpdateThemeCommand, } from '@dineug/erd-editor-vscode-bridge'; import { ReplicationStoreWorker } from '@dineug/erd-editor-vscode-replication-store-worker'; import { encode } from 'base64-arraybuffer'; -const bridge = new Emitter(); -const workerBridge = new Emitter(); +const bridge = new Bridge(); +const workerBridge = new Bridge(); const vscode = acquireVsCodeApi(); const editor = document.createElement('erd-editor'); const sharedStore = editor.getSharedStore({ mouseTracker: false }); @@ -44,12 +49,12 @@ import('@dineug/erd-editor-shiki-worker').then(({ getShikiService }) => { setGetShikiServiceCallback(getShikiService); }); setImportFileCallback(options => { - dispatch(vscodeImportFileAction(options)); + dispatch(Bridge.executeCommand(hostImportFileCommand, options)); }); setExportFileCallback(async (blob, options) => { const arrayBuffer = await blob.arrayBuffer(); dispatch( - vscodeExportFileAction({ + Bridge.executeCommand(hostExportFileCommand, { value: encode(arrayBuffer), fileName: options.fileName, }) @@ -70,12 +75,12 @@ function getSystemTheme(): Appearance { const handleChangePresetTheme = (event: Event) => { const e = event as CustomEvent; - dispatch(vscodeSaveThemeAction(e.detail)); + dispatch(Bridge.executeCommand(hostSaveThemeCommand, e.detail)); appearance = e.detail.appearance; }; -bridge.on({ - webviewImportFile: ({ payload: { type, op, value } }) => { +Bridge.mergeRegister( + bridge.registerCommand(webviewImportFileCommand, ({ type, op, value }) => { switch (type) { case 'json': op === 'set' ? (editor.value = value) : editor.setDiffValue(value); @@ -84,31 +89,31 @@ bridge.on({ op === 'set' && editor.setSchemaSQL(value); break; } - }, - webviewInitialValue: action => { - const { - payload: { value }, - } = action; - dispatchWorker(action); + }), + bridge.registerCommand(webviewInitialValueCommand, ({ value }) => { + dispatchWorker( + Bridge.executeCommand(webviewInitialValueCommand, { value }) + ); editor.addEventListener('changePresetTheme', handleChangePresetTheme); editor.setInitialValue(value); editor.enableThemeBuilder = true; sharedStore.subscribe(actions => { - dispatchWorker(webviewReplicationAction({ actions })); - dispatch(vscodeSaveReplicationAction({ actions })); + dispatchWorker( + Bridge.executeCommand(webviewReplicationCommand, { actions }) + ); + dispatch(Bridge.executeCommand(hostSaveReplicationCommand, { actions })); }); loading?.remove(); document.body.appendChild(editor); - }, - webviewReplication: action => { - const { - payload: { actions }, - } = action; + }), + bridge.registerCommand(webviewReplicationCommand, ({ actions }) => { sharedStore.dispatch(actions); - dispatchWorker(action); - }, - webviewUpdateTheme: ({ payload }) => { + dispatchWorker( + Bridge.executeCommand(webviewReplicationCommand, { actions }) + ); + }), + bridge.registerCommand(webviewUpdateThemeCommand, payload => { if (payload.appearance) { appearance = payload.appearance; } @@ -118,11 +123,14 @@ bridge.on({ appearance: payload.appearance === 'auto' ? getSystemTheme() : payload.appearance, }); - }, - webviewUpdateReadonly: ({ payload }) => { - editor.readonly = payload; - }, -}); + }), + bridge.registerCommand(webviewUpdateReadonlyCommand, readonly => { + editor.readonly = readonly; + }), + workerBridge.registerCommand(hostSaveValueCommand, ({ value }) => { + dispatch(Bridge.executeCommand(hostSaveValueCommand, { value })); + }) +); const observer = new MutationObserver(() => { if (appearance === 'auto') { @@ -136,14 +144,10 @@ observer.observe(document.body, { attributeFilter: ['class', 'data-vscode-theme-kind'], }); -workerBridge.on({ - vscodeSaveValue: action => { - dispatch(action); - }, +globalThis.addEventListener('message', event => { + bridge.executeAction(event.data); }); - -globalThis.addEventListener('message', event => bridge.emit(event.data)); replicationStoreWorker.addEventListener('message', event => { - workerBridge.emit(event.data); + workerBridge.executeAction(event.data); }); -dispatch(vscodeInitialAction()); +dispatch(Bridge.executeCommand(hostInitialCommand, undefined));