Add ⌘\ to toggle side panels, header with logo and editable filename
Header row with app icon, double-click-to-rename document name, and sidebar toggle button. Shows in both Tauri and browser. When panels are hidden, a floating bar on the canvas provides the same controls to restore the UI.
This commit is contained in:
parent
575914b194
commit
ab658ac33f
2
components.d.ts
vendored
2
components.d.ts
vendored
|
|
@ -63,6 +63,7 @@ declare module 'vue' {
|
|||
IconLucideLink: typeof import('~icons/lucide/link')['default']
|
||||
IconLucideLoaderCircle: typeof import('~icons/lucide/loader-circle')['default']
|
||||
IconLucideMessageCircle: typeof import('~icons/lucide/message-circle')['default']
|
||||
IconLucidePanelLeft: typeof import('~icons/lucide/panel-left')['default']
|
||||
IconLucidePlus: typeof import('~icons/lucide/plus')['default']
|
||||
IconLucideRadius: typeof import('~icons/lucide/radius')['default']
|
||||
IconLucideRotateCcw: typeof import('~icons/lucide/rotate-ccw')['default']
|
||||
|
|
@ -71,6 +72,7 @@ declare module 'vue' {
|
|||
IconLucideSend: typeof import('~icons/lucide/send')['default']
|
||||
IconLucideSettings2: typeof import('~icons/lucide/settings2')['default']
|
||||
IconLucideShare2: typeof import('~icons/lucide/share2')['default']
|
||||
IconLucideSidebar: typeof import('~icons/lucide/sidebar')['default']
|
||||
IconLucideSparkles: typeof import('~icons/lucide/sparkles')['default']
|
||||
IconLucideSquare: typeof import('~icons/lucide/square')['default']
|
||||
IconLucideStrikethrough: typeof import('~icons/lucide/strikethrough')['default']
|
||||
|
|
|
|||
|
|
@ -14,12 +14,33 @@ import {
|
|||
|
||||
import IconChevronRight from '~icons/lucide/chevron-right'
|
||||
|
||||
import { ref, nextTick } from 'vue'
|
||||
|
||||
import { IS_TAURI } from '@/constants'
|
||||
import { openFileDialog } from '@/composables/use-menu'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
|
||||
const store = useEditorStore()
|
||||
|
||||
const editingName = ref(false)
|
||||
|
||||
function startRename() {
|
||||
editingName.value = true
|
||||
nextTick(() => {
|
||||
const input = document.querySelector<HTMLInputElement>('[data-doc-name-edit]')
|
||||
input?.focus()
|
||||
input?.select()
|
||||
})
|
||||
}
|
||||
|
||||
function commitRename(input: HTMLInputElement) {
|
||||
const value = input.value.trim()
|
||||
if (value) {
|
||||
store.state.documentName = value
|
||||
}
|
||||
editingName.value = false
|
||||
}
|
||||
|
||||
const isMac = navigator.platform.includes('Mac')
|
||||
const mod = isMac ? '⌘' : 'Ctrl+'
|
||||
|
||||
|
|
@ -122,17 +143,39 @@ const topMenus = [
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!IS_TAURI"
|
||||
class="shrink-0 overflow-x-auto border-b border-border px-1 py-1 scrollbar-none"
|
||||
>
|
||||
<MenubarRoot class="flex w-max items-center gap-0.5">
|
||||
<MenubarMenu v-for="menu in topMenus" :key="menu.label">
|
||||
<MenubarTrigger
|
||||
class="flex cursor-pointer items-center rounded px-2 py-1 text-xs text-muted transition-colors select-none hover:bg-hover hover:text-surface data-[state=open]:bg-hover data-[state=open]:text-surface"
|
||||
>
|
||||
{{ menu.label }}
|
||||
</MenubarTrigger>
|
||||
<div class="shrink-0 border-b border-border">
|
||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||
<img src="/favicon-32.png" class="size-4" alt="OpenPencil" />
|
||||
<input
|
||||
v-if="editingName"
|
||||
data-doc-name-edit
|
||||
class="min-w-0 flex-1 rounded border border-accent bg-input px-1 py-0.5 text-xs text-surface outline-none"
|
||||
:value="store.state.documentName"
|
||||
@blur="commitRename($event.target as HTMLInputElement)"
|
||||
@keydown.enter="($event.target as HTMLInputElement).blur()"
|
||||
@keydown.escape="editingName = false"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="min-w-0 flex-1 cursor-default truncate rounded px-1 py-0.5 text-xs text-surface hover:bg-hover"
|
||||
@dblclick="startRename"
|
||||
>{{ store.state.documentName }}</span>
|
||||
<button
|
||||
class="flex size-6 shrink-0 cursor-pointer items-center justify-center rounded text-muted transition-colors hover:bg-hover hover:text-surface"
|
||||
title="Toggle UI (⌘\)"
|
||||
@click="store.state.showUI = !store.state.showUI"
|
||||
>
|
||||
<icon-lucide-sidebar class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!IS_TAURI" class="flex items-center px-1 pb-1">
|
||||
<MenubarRoot class="flex items-center gap-0.5 overflow-x-auto scrollbar-none">
|
||||
<MenubarMenu v-for="menu in topMenus" :key="menu.label">
|
||||
<MenubarTrigger
|
||||
class="flex cursor-pointer items-center rounded px-2 py-1 text-xs text-muted transition-colors select-none hover:bg-hover hover:text-surface data-[state=open]:bg-hover data-[state=open]:text-surface"
|
||||
>
|
||||
{{ menu.label }}
|
||||
</MenubarTrigger>
|
||||
|
||||
<MenubarPortal>
|
||||
<MenubarContent
|
||||
|
|
@ -186,6 +229,7 @@ const topMenus = [
|
|||
</MenubarContent>
|
||||
</MenubarPortal>
|
||||
</MenubarMenu>
|
||||
</MenubarRoot>
|
||||
</MenubarRoot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ export function useKeyboard(store: EditorStore) {
|
|||
}
|
||||
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
if (e.code === 'Backslash') {
|
||||
e.preventDefault()
|
||||
store.state.showUI = !store.state.showUI
|
||||
return
|
||||
}
|
||||
if (e.code === 'KeyJ') {
|
||||
e.preventDefault()
|
||||
activeTab.value = activeTab.value === 'ai' ? 'design' : 'ai'
|
||||
|
|
|
|||
|
|
@ -174,6 +174,8 @@ export function createEditorStore() {
|
|||
y: number
|
||||
selection?: string[]
|
||||
}>,
|
||||
showUI: true,
|
||||
documentName: 'Untitled' as string,
|
||||
panX: 0,
|
||||
pageColor: { ...CANVAS_BG_COLOR } as Color,
|
||||
panY: 0,
|
||||
|
|
@ -565,6 +567,8 @@ export function createEditorStore() {
|
|||
pageViewports.clear()
|
||||
fileHandle = handle ?? null
|
||||
filePath = path ?? null
|
||||
state.documentName = file.name.replace(/\.fig$/i, '')
|
||||
downloadName = file.name
|
||||
state.selectedIds = new Set()
|
||||
const firstPage = graph.getPages()[0]
|
||||
state.currentPageId = firstPage?.id ?? graph.rootId
|
||||
|
|
@ -613,6 +617,7 @@ export function createEditorStore() {
|
|||
if (!path) return
|
||||
filePath = path
|
||||
fileHandle = null
|
||||
state.documentName = path.split('/').pop()?.replace(/\.fig$/i, '') ?? 'Untitled'
|
||||
await writeFile(data)
|
||||
return
|
||||
}
|
||||
|
|
@ -630,6 +635,7 @@ export function createEditorStore() {
|
|||
})
|
||||
fileHandle = handle
|
||||
filePath = null
|
||||
state.documentName = handle.name.replace(/\.fig$/i, '')
|
||||
await writeFile(data)
|
||||
return
|
||||
} catch (e) {
|
||||
|
|
@ -640,6 +646,7 @@ export function createEditorStore() {
|
|||
const filename = prompt('Save as:', downloadName ?? 'Untitled.fig')
|
||||
if (!filename) return
|
||||
downloadName = filename
|
||||
state.documentName = filename.replace(/\.fig$/i, '')
|
||||
downloadBlob(new Uint8Array(data), filename, 'application/octet-stream')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ function onDisconnect() {
|
|||
<div class="flex h-screen w-screen flex-col">
|
||||
<SafariBanner />
|
||||
<SplitterGroup
|
||||
v-if="showChrome"
|
||||
v-if="showChrome && store.state.showUI"
|
||||
direction="horizontal"
|
||||
class="flex-1 overflow-hidden"
|
||||
auto-save-id="editor-layout"
|
||||
|
|
@ -111,6 +111,25 @@ function onDisconnect() {
|
|||
<PropertiesPanel />
|
||||
</SplitterPanel>
|
||||
</SplitterGroup>
|
||||
<div v-else-if="showChrome" class="flex flex-1 overflow-hidden">
|
||||
<div class="relative flex min-w-0 flex-1">
|
||||
<EditorCanvas />
|
||||
<Toolbar />
|
||||
<div
|
||||
class="absolute left-7 top-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 class="text-xs text-surface">{{ store.state.documentName }}</span>
|
||||
<button
|
||||
class="ml-1 flex size-6 cursor-pointer items-center justify-center rounded text-muted transition-colors hover:bg-hover hover:text-surface"
|
||||
title="Show UI (⌘\)"
|
||||
@click="store.state.showUI = true"
|
||||
>
|
||||
<icon-lucide-sidebar class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-1 overflow-hidden">
|
||||
<div class="relative flex min-w-0 flex-1">
|
||||
<EditorCanvas />
|
||||
|
|
|
|||
Loading…
Reference in a new issue