* refactor(i18n): migrate app copy to domain namespaces - Replace the monolithic dialogs catalog with 16 flat product-domain catalogs - Preserve every translated locale value while moving all 356 messages - Expose narrow domain composables and retain useI18n as a compatibility aggregate - Document namespace ownership and admission rules * fix(i18n): complete migrated locale coverage - Translate exposed MCP automation, code editor, credential, and media placeholders - Restore missing German, Spanish, French, Italian, Polish, and Russian diacritics - Preserve technical product terms and interpolation placeholders * fix(i18n): polish remaining locale wording - Correct the mixed-language German code source label - Preserve stock_photo and Pexels semantics in German and Polish - Improve Italian MCP and code-editor grammar - Use the standard Russian term for MCP endpoint * fix(i18n): migrate diagnostics copy after rebase - Add a diagnostics domain for newly merged usage and telemetry settings - Keep settings navigation labels in the settings domain - Preserve translated diagnostics catalogs from current master * style(app): format rebased settings components * refactor(i18n): simplify domain message keys - Remove redundant domain prefixes from settings, diagnostics, automation, and integration catalogs - Move feature-specific actions out of the common namespace - Preserve current-master language picker and diagnostics copy across every locale - Update consumers to concise semantic keys * fix(app): preserve font and updater contracts - Restore browser and abort-aware font loading from current master - Remove the unused parameter helper from common messages - Update updater tests for semantic domain keys * fix(i18n): finish semantic diagnostics access * refactor(i18n): derive active MCP status message * fix(i18n): preserve rendering settings after rebase - Add a rendering domain for tiled canvas preferences - Preserve the current-master language and rendering settings UI - Move every translated renderer preference out of the retired dialogs catalog * fix(i18n): polish reviewed locale semantics * test(i18n): enforce translation completeness baseline - Fail on interpolation placeholder drift and suspicious mixed-script values - Reject new source-identical translations while tracking existing debt explicitly - Report translated-message coverage for every supported locale - Require baseline cleanup when existing placeholders are translated * refactor(i18n): baseline reviewed mixed-script messages - Replace brittle product-vocabulary exceptions with Unicode script detection - Track reviewed mixed-script messages by stable locale and message identity - Reject new entries and stale baseline debt without hardcoded terminology * fix(fig): resolve relocated stroke test helper
126 lines
4.2 KiB
Vue
126 lines
4.2 KiB
Vue
<script setup lang="ts">
|
|
import { tv } from 'tailwind-variants'
|
|
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from 'reka-ui'
|
|
|
|
import { formatShortcut, useI18n, useViewportKind } from '@open-pencil/vue'
|
|
|
|
import { useEditorStore } from '@/app/editor/active-store'
|
|
import { appRuntimeConfig } from '@/app/runtime/config'
|
|
import { appMenuShortcut } from '@/app/shell/menu/shortcut'
|
|
import { activeTab } from '@/app/tabs'
|
|
import CanvasSplitRoot from '@/components/canvas/CanvasSplitRoot.vue'
|
|
import CollabPanel from '@/components/CollabPanel/CollabPanel.vue'
|
|
import EditorCanvas from '@/components/EditorCanvas.vue'
|
|
import LayersPanel from '@/components/LayersPanel.vue'
|
|
import MobileDrawer from '@/components/MobileDrawer.vue'
|
|
import MobileHud from '@/components/MobileHud/MobileHud.vue'
|
|
import PropertiesPanel from '@/components/PropertiesPanel.vue'
|
|
import Tip from '@/components/ui/Tip.vue'
|
|
import Toolbar from '@/components/Toolbar/Toolbar.vue'
|
|
import { loadEditorLayout, saveEditorLayout } from '@/app/shell/layout-storage'
|
|
import splitterTheme from '@/theme/splitter'
|
|
|
|
const showChrome = appRuntimeConfig.showChrome
|
|
const store = useEditorStore()
|
|
const { editor } = useI18n()
|
|
const { isMobile } = useViewportKind()
|
|
const initialEditorLayout = loadEditorLayout()
|
|
const horizontalSplitterStyles = tv(splitterTheme)({ direction: 'horizontal' })
|
|
</script>
|
|
|
|
<template>
|
|
<SplitterGroup
|
|
v-if="!isMobile && showChrome && store.state.showUI"
|
|
:key="activeTab?.id"
|
|
direction="horizontal"
|
|
class="flex-1 overflow-hidden"
|
|
@layout="saveEditorLayout"
|
|
>
|
|
<SplitterPanel
|
|
id="layers"
|
|
:default-size="initialEditorLayout[0]"
|
|
:min-size="10"
|
|
:max-size="30"
|
|
class="flex"
|
|
>
|
|
<LayersPanel />
|
|
</SplitterPanel>
|
|
<SplitterResizeHandle
|
|
data-test-id="left-splitter-handle"
|
|
:class="horizontalSplitterStyles.handle()"
|
|
>
|
|
<div :class="horizontalSplitterStyles.divider()" />
|
|
</SplitterResizeHandle>
|
|
<SplitterPanel id="canvas" :default-size="initialEditorLayout[1]" :min-size="30" class="flex">
|
|
<div class="relative flex min-w-0 flex-1">
|
|
<CanvasSplitRoot />
|
|
<Toolbar />
|
|
</div>
|
|
</SplitterPanel>
|
|
<SplitterResizeHandle :class="horizontalSplitterStyles.handle()">
|
|
<div :class="horizontalSplitterStyles.divider()" />
|
|
</SplitterResizeHandle>
|
|
<SplitterPanel
|
|
id="properties"
|
|
:default-size="initialEditorLayout[2]"
|
|
:min-size="10"
|
|
:max-size="30"
|
|
class="flex flex-col"
|
|
>
|
|
<div class="flex shrink-0 items-center justify-between border-b border-border px-1.5 py-1.5">
|
|
<CollabPanel />
|
|
</div>
|
|
<PropertiesPanel />
|
|
</SplitterPanel>
|
|
</SplitterGroup>
|
|
|
|
<div
|
|
v-else-if="isMobile && showChrome && store.state.showUI"
|
|
:key="'mobile-' + activeTab?.id"
|
|
class="flex flex-1 overflow-hidden"
|
|
>
|
|
<div class="relative flex min-w-0 flex-1">
|
|
<EditorCanvas />
|
|
<MobileHud />
|
|
<Toolbar />
|
|
</div>
|
|
<MobileDrawer />
|
|
</div>
|
|
|
|
<div
|
|
v-else-if="showChrome"
|
|
:key="'collapsed-' + activeTab?.id"
|
|
class="flex flex-1 overflow-hidden"
|
|
>
|
|
<div class="relative flex min-w-0 flex-1">
|
|
<EditorCanvas />
|
|
<div
|
|
v-if="!isMobile"
|
|
class="absolute top-7 left-7 z-10 flex items-center gap-2 rounded-lg border border-border bg-panel px-2 py-1 shadow-sm"
|
|
>
|
|
<img src="/favicon-32.png" class="size-4" alt="OpenPencil" />
|
|
<span data-test-id="editor-document-name" class="text-xs text-surface">{{
|
|
store.state.documentName
|
|
}}</span>
|
|
<Tip
|
|
:label="editor.showUI({ shortcut: formatShortcut(appMenuShortcut('toggle-ui')) ?? '' })"
|
|
>
|
|
<button
|
|
data-test-id="editor-show-ui"
|
|
class="ml-1 flex size-6 cursor-pointer items-center justify-center rounded text-muted transition-colors hover:bg-hover hover:text-surface"
|
|
@click="store.state.showUI = true"
|
|
>
|
|
<icon-lucide-sidebar class="size-3.5" />
|
|
</button>
|
|
</Tip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else :key="'bare-' + activeTab?.id" class="flex flex-1 overflow-hidden">
|
|
<div class="relative flex min-w-0 flex-1">
|
|
<EditorCanvas />
|
|
</div>
|
|
</div>
|
|
</template>
|