diff --git a/desktop/generated/menu.json b/desktop/generated/menu.json index 11ae1e163..db0f54726 100644 --- a/desktop/generated/menu.json +++ b/desktop/generated/menu.json @@ -12,6 +12,10 @@ "label": "Open…", "accelerator": "CmdOrCtrl+O" }, + { + "id": "open-storage-workspace", + "label": "Open Storage Workspace…" + }, { "type": "separator" }, diff --git a/packages/vue/src/i18n/locales/de/menu.json b/packages/vue/src/i18n/locales/de/menu.json index b134eaa5a..407588820 100644 --- a/packages/vue/src/i18n/locales/de/menu.json +++ b/packages/vue/src/i18n/locales/de/menu.json @@ -7,6 +7,7 @@ "text": "Text", "new": "Neu", "open": "Öffnen…", + "openStorageWorkspace": "Speicher-Arbeitsbereich öffnen…", "save": "Speichern", "saveAs": "Speichern unter…", "exportSelection": "Auswahl exportieren…", diff --git a/packages/vue/src/i18n/locales/es/menu.json b/packages/vue/src/i18n/locales/es/menu.json index 87b905704..65cf1e7f3 100644 --- a/packages/vue/src/i18n/locales/es/menu.json +++ b/packages/vue/src/i18n/locales/es/menu.json @@ -7,6 +7,7 @@ "text": "Texto", "new": "Nuevo", "open": "Abrir…", + "openStorageWorkspace": "Abrir espacio de almacenamiento…", "save": "Guardar", "saveAs": "Guardar como…", "exportSelection": "Exportar selección…", diff --git a/packages/vue/src/i18n/locales/fr/menu.json b/packages/vue/src/i18n/locales/fr/menu.json index 11b1b52ed..6f40df1df 100644 --- a/packages/vue/src/i18n/locales/fr/menu.json +++ b/packages/vue/src/i18n/locales/fr/menu.json @@ -7,6 +7,7 @@ "text": "Texte", "new": "Nouveau", "open": "Ouvrir…", + "openStorageWorkspace": "Ouvrir l’espace de stockage…", "save": "Enregistrer", "saveAs": "Enregistrer sous…", "exportSelection": "Exporter la sélection…", diff --git a/packages/vue/src/i18n/locales/it/menu.json b/packages/vue/src/i18n/locales/it/menu.json index 353bb59e6..38b0798d2 100644 --- a/packages/vue/src/i18n/locales/it/menu.json +++ b/packages/vue/src/i18n/locales/it/menu.json @@ -7,6 +7,7 @@ "text": "Testo", "new": "Nuovo", "open": "Apri…", + "openStorageWorkspace": "Apri area di archiviazione…", "save": "Salva", "saveAs": "Salva con nome…", "exportSelection": "Esporta selezione…", diff --git a/packages/vue/src/i18n/locales/ja/menu.json b/packages/vue/src/i18n/locales/ja/menu.json index fcc382d11..b61a9cee6 100644 --- a/packages/vue/src/i18n/locales/ja/menu.json +++ b/packages/vue/src/i18n/locales/ja/menu.json @@ -7,6 +7,7 @@ "text": "テキスト", "new": "新規作成", "open": "開く…", + "openStorageWorkspace": "ストレージワークスペースを開く…", "save": "保存", "saveAs": "名前を付けて保存…", "exportSelection": "選択範囲をエクスポート…", diff --git a/packages/vue/src/i18n/locales/pl/menu.json b/packages/vue/src/i18n/locales/pl/menu.json index a18548565..1661c6ce7 100644 --- a/packages/vue/src/i18n/locales/pl/menu.json +++ b/packages/vue/src/i18n/locales/pl/menu.json @@ -7,6 +7,7 @@ "text": "Tekst", "new": "Nowy", "open": "Otwórz…", + "openStorageWorkspace": "Otwórz obszar przechowywania…", "save": "Zapisz", "saveAs": "Zapisz jako…", "exportSelection": "Eksportuj zaznaczenie…", diff --git a/packages/vue/src/i18n/locales/ru/menu.json b/packages/vue/src/i18n/locales/ru/menu.json index 2cd15ea19..bf6091bcb 100644 --- a/packages/vue/src/i18n/locales/ru/menu.json +++ b/packages/vue/src/i18n/locales/ru/menu.json @@ -7,6 +7,7 @@ "text": "Текст", "new": "Создать", "open": "Открыть…", + "openStorageWorkspace": "Открыть рабочее пространство хранилища…", "save": "Сохранить", "saveAs": "Сохранить как…", "exportSelection": "Экспорт выделения…", diff --git a/packages/vue/src/i18n/locales/zh-cn/menu.json b/packages/vue/src/i18n/locales/zh-cn/menu.json index 2afa9985b..34dbf687e 100644 --- a/packages/vue/src/i18n/locales/zh-cn/menu.json +++ b/packages/vue/src/i18n/locales/zh-cn/menu.json @@ -7,6 +7,7 @@ "text": "文本", "new": "新建", "open": "打开…", + "openStorageWorkspace": "打开存储工作区…", "save": "保存", "saveAs": "另存为…", "exportSelection": "导出所选内容…", diff --git a/packages/vue/src/i18n/messages/menu.ts b/packages/vue/src/i18n/messages/menu.ts index a63a5edf2..7c7b731fd 100644 --- a/packages/vue/src/i18n/messages/menu.ts +++ b/packages/vue/src/i18n/messages/menu.ts @@ -10,6 +10,7 @@ export const menuMessageDefaults = { new: 'New', open: 'Open…', + openStorageWorkspace: 'Open storage workspace…', save: 'Save', saveAs: 'Save as…', exportSelection: 'Export selection…', diff --git a/src/app/shell/menu/app-menu.ts b/src/app/shell/menu/app-menu.ts index e3b40cbbe..916faec1c 100644 --- a/src/app/shell/menu/app-menu.ts +++ b/src/app/shell/menu/app-menu.ts @@ -1,4 +1,5 @@ import { computed } from 'vue' +import { useRouter } from 'vue-router' import type { MenuEntry } from '@open-pencil/vue' import { useEditorCommands, useI18n } from '@open-pencil/vue' @@ -6,6 +7,7 @@ import { useEditorCommands, useI18n } from '@open-pencil/vue' import { useEditorStore } from '@/app/editor/active-store' import { openSettingsDialog } from '@/app/settings/dialog' import { createSharedEditorMenuActions } from '@/app/shell/menu/editor-actions' +import { openStorageWorkspace } from '@/app/shell/menu/navigation' import type { AppMenuActionItem, AppMenuEntry, AppMenuGroupSchema } from '@/app/shell/menu/schema' import { APP_MENU_SCHEMA } from '@/app/shell/menu/schema' import { createSelectionMenuActions } from '@/app/shell/menu/selection-actions' @@ -29,6 +31,7 @@ function isSeparator(entry: AppMenuEntry): entry is Extract> = { new: 'new', open: 'open', + 'open-storage-workspace': 'openStorageWorkspace', save: 'save', 'save-as': 'saveAs', 'export-selection': 'exportSelection', @@ -94,6 +98,7 @@ export function useAppMenu() { void import('@/app/tabs').then((m) => m.createTab()) }, open: () => void openFileDialog(), + 'open-storage-workspace': () => openStorageWorkspace(router), save: () => void store.saveFigFile(), 'save-as': () => void store.saveFigFileAs(), 'export-selection': () => exportSelection('png'), diff --git a/src/app/shell/menu/navigation.ts b/src/app/shell/menu/navigation.ts new file mode 100644 index 000000000..945ead55a --- /dev/null +++ b/src/app/shell/menu/navigation.ts @@ -0,0 +1,5 @@ +import type { Router } from 'vue-router' + +export function openStorageWorkspace(router: Router): void { + void router.push('/storage') +} diff --git a/src/app/shell/menu/schema.ts b/src/app/shell/menu/schema.ts index a1a8603a4..cfb3c57ee 100644 --- a/src/app/shell/menu/schema.ts +++ b/src/app/shell/menu/schema.ts @@ -33,6 +33,7 @@ export const APP_MENU_SCHEMA = [ items: [ { id: 'new', label: 'New', shortcut: 'MOD+N' }, { id: 'open', label: 'Open…', shortcut: 'MOD+O' }, + { id: 'open-storage-workspace', label: 'Open Storage Workspace…' }, { type: 'separator' }, { id: 'save', label: 'Save', shortcut: 'MOD+S' }, { id: 'save-as', label: 'Save As…', shortcut: 'MOD+SHIFT+S' }, diff --git a/src/app/shell/menu/use.ts b/src/app/shell/menu/use.ts index a51ffae7b..303182945 100644 --- a/src/app/shell/menu/use.ts +++ b/src/app/shell/menu/use.ts @@ -7,6 +7,7 @@ import { useEditorStore } from '@/app/editor/active-store' import { openSettingsDialog } from '@/app/settings/dialog' import { createSharedEditorMenuActions } from '@/app/shell/menu/editor-actions' import { importFileDialog, openFileDialog } from '@/app/shell/menu/files' +import { openStorageWorkspace } from '@/app/shell/menu/navigation' import { APP_MENU_SCHEMA, type AppMenuEntry } from '@/app/shell/menu/schema' import { createSelectionMenuActions } from '@/app/shell/menu/selection-actions' import { useAppTheme } from '@/app/shell/theme' @@ -40,6 +41,9 @@ export function useMenu() { const actions: Partial void>> = { new: () => createTab(), open: () => void openFileDialog(), + 'open-storage-workspace': () => { + void import('@/router').then(({ default: router }) => openStorageWorkspace(router)) + }, close: () => { if (activeTab.value) closeTab(activeTab.value.id) }, diff --git a/tests/e2e/app/menu.spec.ts b/tests/e2e/app/menu.spec.ts index 3bd6703bd..97dca435f 100644 --- a/tests/e2e/app/menu.spec.ts +++ b/tests/e2e/app/menu.spec.ts @@ -20,6 +20,7 @@ test('File menu opens and shows items', async () => { const items = await menu.locator('[role="menuitem"]').allTextContents() expect(items.some((t) => t.includes('Open'))).toBe(true) + expect(items.some((t) => t.includes('Open storage workspace'))).toBe(true) expect(items.some((t) => t.includes('Save'))).toBe(true) expect(items.some((t) => t.includes('Save as'))).toBe(true) @@ -155,3 +156,11 @@ test('Zoom to fit via View menu works', async () => { const zoomAfter = await getStoreStateNumber('zoom') expect(zoomAfter).not.toBe(zoomBefore) }) + +test('Open storage workspace navigates from the File menu', async () => { + await editor.page.getByRole('menuitem', { name: 'File', exact: true }).click() + await editor.page.getByRole('menuitem', { name: 'Open storage workspace…' }).click() + + await expect(editor.page).toHaveURL(/\/storage$/) + await expect(editor.page.getByRole('heading', { name: 'Storage workspace' })).toBeVisible() +}) diff --git a/tests/engine/app/shell/menu/schema.test.ts b/tests/engine/app/shell/menu/schema.test.ts index 71505c033..68108fe35 100644 --- a/tests/engine/app/shell/menu/schema.test.ts +++ b/tests/engine/app/shell/menu/schema.test.ts @@ -43,6 +43,15 @@ describe('APP_MENU_SCHEMA', () => { expect(commandIds).toContain('selection.moveToPage') }) + test('includes storage workspace navigation in the shared File menu', () => { + const fileMenu = APP_MENU_SCHEMA.find((group) => group.label === 'File') + const entries = fileMenu ? actionItems(fileMenu.items) : [] + + expect(entries).toContainEqual( + expect.objectContaining({ id: 'open-storage-workspace', label: 'Open Storage Workspace…' }) + ) + }) + test('keeps move-to-page destination selection in the browser menu', () => { const objectMenu = APP_MENU_SCHEMA.find((group) => group.label === 'Object') const entries = objectMenu ? actionItems(objectMenu.items) : []