refactor(app): move storage workspace navigation into File menu
- Replace the full-width editor header action with a shared browser and native menu command - Localize the destination and cover menu schema and browser navigation
This commit is contained in:
parent
d85e2ec5e1
commit
79aa6ba5dd
|
|
@ -12,6 +12,10 @@
|
|||
"label": "Open…",
|
||||
"accelerator": "CmdOrCtrl+O"
|
||||
},
|
||||
{
|
||||
"id": "open-storage-workspace",
|
||||
"label": "Open Storage Workspace…"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "Text",
|
||||
"new": "Neu",
|
||||
"open": "Öffnen…",
|
||||
"openStorageWorkspace": "Speicher-Arbeitsbereich öffnen…",
|
||||
"save": "Speichern",
|
||||
"saveAs": "Speichern unter…",
|
||||
"exportSelection": "Auswahl exportieren…",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "Texto",
|
||||
"new": "Nuevo",
|
||||
"open": "Abrir…",
|
||||
"openStorageWorkspace": "Abrir espacio de almacenamiento…",
|
||||
"save": "Guardar",
|
||||
"saveAs": "Guardar como…",
|
||||
"exportSelection": "Exportar selección…",
|
||||
|
|
|
|||
|
|
@ -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…",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "Testo",
|
||||
"new": "Nuovo",
|
||||
"open": "Apri…",
|
||||
"openStorageWorkspace": "Apri area di archiviazione…",
|
||||
"save": "Salva",
|
||||
"saveAs": "Salva con nome…",
|
||||
"exportSelection": "Esporta selezione…",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "テキスト",
|
||||
"new": "新規作成",
|
||||
"open": "開く…",
|
||||
"openStorageWorkspace": "ストレージワークスペースを開く…",
|
||||
"save": "保存",
|
||||
"saveAs": "名前を付けて保存…",
|
||||
"exportSelection": "選択範囲をエクスポート…",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "Tekst",
|
||||
"new": "Nowy",
|
||||
"open": "Otwórz…",
|
||||
"openStorageWorkspace": "Otwórz obszar przechowywania…",
|
||||
"save": "Zapisz",
|
||||
"saveAs": "Zapisz jako…",
|
||||
"exportSelection": "Eksportuj zaznaczenie…",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "Текст",
|
||||
"new": "Создать",
|
||||
"open": "Открыть…",
|
||||
"openStorageWorkspace": "Открыть область хранилища…",
|
||||
"save": "Сохранить",
|
||||
"saveAs": "Сохранить как…",
|
||||
"exportSelection": "Экспорт выделения…",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"text": "文本",
|
||||
"new": "新建",
|
||||
"open": "打开…",
|
||||
"openStorageWorkspace": "打开存储工作区…",
|
||||
"save": "保存",
|
||||
"saveAs": "另存为…",
|
||||
"exportSelection": "导出所选内容…",
|
||||
|
|
|
|||
|
|
@ -165,7 +165,6 @@ export const dialogMessageDefaults = {
|
|||
settingsStorage: 'Cloud storage',
|
||||
storageWorkspace: 'Storage workspace',
|
||||
openStorageWorkspace: 'Open workspace',
|
||||
backToStorageWorkspace: 'Back to workspace',
|
||||
newStoredDocument: 'New document',
|
||||
emptyStorageWorkspace: 'No stored documents yet.',
|
||||
loadingDocuments: 'Loading documents…',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const menuMessageDefaults = {
|
|||
|
||||
new: 'New',
|
||||
open: 'Open…',
|
||||
openStorageWorkspace: 'Open storage workspace…',
|
||||
save: 'Save',
|
||||
saveAs: 'Save as…',
|
||||
exportSelection: 'Export selection…',
|
||||
|
|
|
|||
|
|
@ -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<AppMenuEntry, { type
|
|||
|
||||
export function useAppMenu() {
|
||||
const store = useEditorStore()
|
||||
const router = useRouter()
|
||||
const {
|
||||
commands,
|
||||
menuItem: commandMenuItem,
|
||||
|
|
@ -41,6 +44,7 @@ export function useAppMenu() {
|
|||
const translatedMenuItemLabels: Partial<Record<string, keyof typeof menu.value>> = {
|
||||
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'),
|
||||
|
|
|
|||
5
src/app/shell/menu/navigation.ts
Normal file
5
src/app/shell/menu/navigation.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import type { Router } from 'vue-router'
|
||||
|
||||
export function openStorageWorkspace(router: Router): void {
|
||||
void router.push('/storage')
|
||||
}
|
||||
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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<Record<string, () => 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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { templateRef } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
|
|
@ -18,7 +17,6 @@ import {
|
|||
MenubarTrigger
|
||||
} from 'reka-ui'
|
||||
|
||||
import IconArrowLeft from '~icons/lucide/arrow-left'
|
||||
import IconChevronRight from '~icons/lucide/chevron-right'
|
||||
|
||||
import { vTestId, useI18n } from '@open-pencil/vue'
|
||||
|
|
@ -44,15 +42,6 @@ import { useEditorStore } from '@/app/editor/active-store'
|
|||
import { openSettingsDialog } from '@/app/settings/dialog'
|
||||
|
||||
const store = useEditorStore()
|
||||
const router = useRouter()
|
||||
|
||||
// The workspace is the app's top level. Browser back is history-dependent (and
|
||||
// absent in the desktop shell), so the way back has to be an explicit
|
||||
// destination, present on both platforms. Shown unconditionally: StorageView
|
||||
// prompts for setup itself when storage isn't configured yet.
|
||||
async function backToWorkspace(): Promise<void> {
|
||||
await router.push('/storage')
|
||||
}
|
||||
|
||||
const { rename, editingName, startRename, commitRename } = useDocumentNameRename(store)
|
||||
const nameInput = templateRef<HTMLInputElement>('nameInput')
|
||||
|
|
@ -71,15 +60,6 @@ const subMenuCls = useMenuUI({ content: 'min-w-44' })
|
|||
|
||||
<template>
|
||||
<div class="shrink-0 border-b border-border">
|
||||
<button
|
||||
type="button"
|
||||
data-test-id="app-back-to-workspace"
|
||||
class="flex w-full cursor-pointer items-center gap-1.5 border-b border-border px-2 py-1.5 text-xs text-muted transition-colors hover:bg-hover hover:text-surface"
|
||||
@click="backToWorkspace"
|
||||
>
|
||||
<IconArrowLeft class="size-3.5 shrink-0" />
|
||||
<span class="truncate">{{ dialogs.backToStorageWorkspace }}</span>
|
||||
</button>
|
||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||
<img data-test-id="app-logo" src="/favicon-32.png" class="size-4" alt="OpenPencil" />
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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) : []
|
||||
|
|
|
|||
Loading…
Reference in a new issue