diff --git a/apps/web/src/pods/toolbar/components/new-button/new-button.tsx b/apps/web/src/pods/toolbar/components/new-button/new-button.tsx index 147843d3..24bc75ea 100644 --- a/apps/web/src/pods/toolbar/components/new-button/new-button.tsx +++ b/apps/web/src/pods/toolbar/components/new-button/new-button.tsx @@ -1,13 +1,20 @@ import { NewIcon } from '#common/components/icons/new-button.components'; -import classes from '#pods/toolbar/toolbar.pod.module.css'; +import { isVSCodeEnv } from '#common/utils/env.utils'; +import { sendToExtension } from '#common/utils/vscode-bridge.utils'; import { useCanvasContext } from '#core/providers'; -import { ToolbarButton } from '../toolbar-button'; +import classes from '#pods/toolbar/toolbar.pod.module.css'; +import { APP_MESSAGE_TYPE } from '@lemoncode/quickmock-bridge-protocol'; import { SHORTCUTS } from '../../shortcut/shortcut.const'; +import { ToolbarButton } from '../toolbar-button'; export const NewButton = () => { const { createNewFullDocument: clearCanvas } = useCanvasContext(); const handleClick = () => { + if (isVSCodeEnv()) { + sendToExtension({ type: APP_MESSAGE_TYPE.NEW_FILE }); + return; + } clearCanvas(); }; diff --git a/packages/bridge-protocol/src/constant.ts b/packages/bridge-protocol/src/constant.ts index 5caee2c2..b96aa128 100644 --- a/packages/bridge-protocol/src/constant.ts +++ b/packages/bridge-protocol/src/constant.ts @@ -10,4 +10,5 @@ export const APP_MESSAGE_TYPE = { SAVE: 'qm:save', RENDER_COMPLETE: 'qm:render-complete', WEBVIEW_READY: 'WEBVIEW_READY', + NEW_FILE: 'qm:new-file', } as const; diff --git a/packages/bridge-protocol/src/model.ts b/packages/bridge-protocol/src/model.ts index 513b2501..ccbdedb2 100644 --- a/packages/bridge-protocol/src/model.ts +++ b/packages/bridge-protocol/src/model.ts @@ -34,7 +34,8 @@ export type AppMessage = | { type: typeof APP_MESSAGE_TYPE.RENDER_COMPLETE; payload?: ContentBbox; - }; + } + | { type: typeof APP_MESSAGE_TYPE.NEW_FILE }; export type PayloadOf = Extract extends { payload: infer P } ? P : undefined; diff --git a/packages/vscode-extension/CHANGELOG.md b/packages/vscode-extension/CHANGELOG.md index 303f77de..87ff55cd 100644 --- a/packages/vscode-extension/CHANGELOG.md +++ b/packages/vscode-extension/CHANGELOG.md @@ -1,5 +1,15 @@ # @lemoncode/quickmock-vscode-extension +## 0.2.0 + +### Minor Changes + +- 3cd4892: Toolbar's **New** button now creates a real `.qm` file when running inside the VS Code extension instead of just clearing the canvas. + +### Patch Changes + +- b7f9cf1: Fix editor failing to load files opened from outside the workspace. The webview HTML was assigned before registering `onDidReceiveMessage`, causing a race where the initial `READY` / `WEBVIEW_READY` message from the app could be lost and the file content never delivered. The listener is now registered before the HTML assignment. + ## 0.1.0 ### Minor Changes diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index e17332ce..8929d7ee 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -1,6 +1,6 @@ { "name": "quickmock", - "version": "0.1.0", + "version": "0.2.0", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/vscode-extension/src/commands/new-wireframe.ts b/packages/vscode-extension/src/commands/new-wireframe.ts deleted file mode 100644 index 628f537c..00000000 --- a/packages/vscode-extension/src/commands/new-wireframe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as vscode from 'vscode'; - -export const registerNewWireframeCommand = ( - context: vscode.ExtensionContext -): void => { - context.subscriptions.push( - vscode.commands.registerCommand('quickmock.newWireframe', () => { - vscode.window.showInformationMessage('New wireframe coming soon'); // TODO: Implement the actual functionality for creating a new wireframe - }) - ); -}; diff --git a/packages/vscode-extension/src/commands/new-wireframe/index.ts b/packages/vscode-extension/src/commands/new-wireframe/index.ts new file mode 100644 index 00000000..9dc0e646 --- /dev/null +++ b/packages/vscode-extension/src/commands/new-wireframe/index.ts @@ -0,0 +1,2 @@ +export * from './new-wireframe.id'; +export * from './new-wireframe.command'; diff --git a/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.command.ts b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.command.ts new file mode 100644 index 00000000..5e839522 --- /dev/null +++ b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.command.ts @@ -0,0 +1,14 @@ +import * as vscode from 'vscode'; +import { QUICKMOCK_NEW_WIREFRAME_COMMAND_ID } from './new-wireframe.id'; +import { handleNewWireframe } from './new-wireframe.handler'; + +export const registerNewWireframeCommand = ( + context: vscode.ExtensionContext +): void => { + context.subscriptions.push( + vscode.commands.registerCommand( + QUICKMOCK_NEW_WIREFRAME_COMMAND_ID, + handleNewWireframe + ) + ); +}; diff --git a/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.handler.ts b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.handler.ts new file mode 100644 index 00000000..5aed18b0 --- /dev/null +++ b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.handler.ts @@ -0,0 +1,45 @@ +import { getPrimaryWorkspaceFolder } from '#core'; +import { writeFile } from '#editor/document'; +import * as vscode from 'vscode'; +import { + createEmptyQuickMockContent, + DEFAULT_QUICKMOCK_FILE_NAME, + QUICKMOCK_FILE_EXTENSION, +} from './new-wireframe.template'; + +const CUSTOM_EDITOR_VIEW_TYPE = 'quickmock.editor'; + +const pickSaveTarget = async (): Promise => { + const folder = getPrimaryWorkspaceFolder(); + const defaultUri = folder + ? vscode.Uri.joinPath(folder.uri, DEFAULT_QUICKMOCK_FILE_NAME) + : undefined; + + return vscode.window.showSaveDialog({ + defaultUri, + filters: { 'QuickMock Wireframe': [QUICKMOCK_FILE_EXTENSION] }, + saveLabel: 'Create', + title: 'Create QuickMock File', + }); +}; + +export const handleNewWireframe = async (): Promise => { + const target = await pickSaveTarget(); + if (!target) return; + + try { + await writeFile(target, createEmptyQuickMockContent()); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + vscode.window.showErrorMessage( + `Failed to create QuickMock file: ${message}` + ); + return; + } + + await vscode.commands.executeCommand( + 'vscode.openWith', + target, + CUSTOM_EDITOR_VIEW_TYPE + ); +}; diff --git a/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.id.ts b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.id.ts new file mode 100644 index 00000000..22b740b5 --- /dev/null +++ b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.id.ts @@ -0,0 +1 @@ +export const QUICKMOCK_NEW_WIREFRAME_COMMAND_ID = 'quickmock.newWireframe'; diff --git a/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.template.ts b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.template.ts new file mode 100644 index 00000000..5e53e87f --- /dev/null +++ b/packages/vscode-extension/src/commands/new-wireframe/new-wireframe.template.ts @@ -0,0 +1,17 @@ +const TEMPLATE_VERSION = '0.1'; +const DEFAULT_CANVAS_SIZE = { width: 1920, height: 1080 }; + +export const QUICKMOCK_FILE_EXTENSION = 'qm'; +export const DEFAULT_QUICKMOCK_FILE_NAME = `untitled.${QUICKMOCK_FILE_EXTENSION}`; + +export const createEmptyQuickMockContent = (): string => + JSON.stringify( + { + version: TEMPLATE_VERSION, + pages: [{ id: 'page-1', name: 'Page 1', shapes: [] }], + customColors: [], + size: DEFAULT_CANVAS_SIZE, + }, + null, + 2 + ); diff --git a/packages/vscode-extension/src/editor/handlers.ts b/packages/vscode-extension/src/editor/handlers.ts index e6795936..074d99ae 100644 --- a/packages/vscode-extension/src/editor/handlers.ts +++ b/packages/vscode-extension/src/editor/handlers.ts @@ -5,6 +5,8 @@ import { HOST_MESSAGE_TYPE, type HostMessage, } from '@lemoncode/quickmock-bridge-protocol'; +import * as vscode from 'vscode'; +import { QUICKMOCK_NEW_WIREFRAME_COMMAND_ID } from '#commands'; import { type QuickMockDocument, writeFile } from './document'; type PostMessageFn = (msg: HostMessage) => void; @@ -41,5 +43,9 @@ export const handleWebviewMessage = async ( await writeFile(doc.uri, doc.content); postMessage({ type: HOST_MESSAGE_TYPE.SAVED }); break; + + case APP_MESSAGE_TYPE.NEW_FILE: + await vscode.commands.executeCommand(QUICKMOCK_NEW_WIREFRAME_COMMAND_ID); + break; } }; diff --git a/packages/vscode-extension/src/editor/provider.ts b/packages/vscode-extension/src/editor/provider.ts index 64d018db..395936f7 100644 --- a/packages/vscode-extension/src/editor/provider.ts +++ b/packages/vscode-extension/src/editor/provider.ts @@ -109,11 +109,6 @@ export class QuickMockEditorProvider implements vscode.CustomEditorProvider { await handleWebviewMessage(msg, doc, reply => @@ -121,6 +116,12 @@ export class QuickMockEditorProvider implements vscode.CustomEditorProvider