Fix duplicate :class, rename race, useFileDialog leak, restore clip/wrap
This commit is contained in:
parent
3ed9ccbe3f
commit
3684caea87
|
|
@ -27,15 +27,17 @@ import { useEditorStore } from '@/stores/editor'
|
|||
const store = useEditorStore()
|
||||
|
||||
const DOCUMENT_NAME_ID = 'document-name'
|
||||
const nameInputRef = ref<HTMLInputElement | null>(null)
|
||||
const rename = useInlineRename<'document-name'>((_id, name) => {
|
||||
store.state.documentName = name
|
||||
})
|
||||
const editingName = computed(() => rename.editingId.value === DOCUMENT_NAME_ID)
|
||||
|
||||
function setNameInputRef(el: HTMLInputElement | null) {
|
||||
if (el) void rename.focusInput(el)
|
||||
}
|
||||
|
||||
function startRename() {
|
||||
rename.start(DOCUMENT_NAME_ID, store.state.documentName)
|
||||
void rename.focusInput(nameInputRef.value)
|
||||
}
|
||||
|
||||
function commitRename(input: HTMLInputElement) {
|
||||
|
|
@ -176,7 +178,7 @@ const topMenus = [
|
|||
<img data-test-id="app-logo" src="/favicon-32.png" class="size-4" alt="OpenPencil" />
|
||||
<input
|
||||
v-if="editingName"
|
||||
ref="nameInputRef"
|
||||
:ref="(el) => setNameInputRef(el as HTMLInputElement | null)"
|
||||
data-test-id="app-document-name-input"
|
||||
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"
|
||||
|
|
@ -219,17 +221,12 @@ const topMenus = [
|
|||
<template v-for="(item, i) in menu.items" :key="i">
|
||||
<MenubarSeparator v-if="item.separator" :class="menuSeparator()" />
|
||||
<MenubarSub v-else-if="item.sub">
|
||||
<MenubarSubTrigger
|
||||
:class="menuItem()"
|
||||
>
|
||||
<MenubarSubTrigger :class="menuItem()">
|
||||
<span class="flex-1">{{ item.label }}</span>
|
||||
<IconChevronRight class="size-3 text-muted" />
|
||||
</MenubarSubTrigger>
|
||||
<MenubarPortal>
|
||||
<MenubarSubContent
|
||||
:side-offset="4"
|
||||
:class="menuContent({ class: 'min-w-44' })"
|
||||
>
|
||||
<MenubarSubContent :side-offset="4" :class="menuContent({ class: 'min-w-44' })">
|
||||
<template v-for="(sub, j) in item.sub" :key="j">
|
||||
<MenubarSeparator v-if="sub.separator" :class="menuSeparator()" />
|
||||
<MenubarItem
|
||||
|
|
|
|||
|
|
@ -63,7 +63,13 @@ function onSelect(val: string) {
|
|||
<PopoverContent
|
||||
:side-offset="2"
|
||||
align="start"
|
||||
:class="panelSurface({ radius: 'md', padding: 'none', class: 'z-50 flex min-w-56 w-[var(--reka-popper-anchor-width)] flex-col overflow-hidden' })"
|
||||
:class="
|
||||
panelSurface({
|
||||
radius: 'md',
|
||||
padding: 'none',
|
||||
class: 'z-50 flex min-w-56 w-[var(--reka-popper-anchor-width)] flex-col overflow-hidden'
|
||||
})
|
||||
"
|
||||
@open-auto-focus.prevent
|
||||
>
|
||||
<ListboxRoot :model-value="modelValue" @update:model-value="onSelect">
|
||||
|
|
|
|||
|
|
@ -222,7 +222,13 @@ const onlineCount = computed(() => props.collabPeers.length + 1)
|
|||
<DropdownMenuItem
|
||||
v-for="item in menuItems"
|
||||
:key="item.label"
|
||||
:class="menuItem({ justify: 'start', class: 'w-full gap-2.5 rounded-lg border-none bg-transparent px-2.5 py-2 active:bg-hover' })"
|
||||
:class="
|
||||
menuItem({
|
||||
justify: 'start',
|
||||
class:
|
||||
'w-full gap-2.5 rounded-lg border-none bg-transparent px-2.5 py-2 active:bg-hover'
|
||||
})
|
||||
"
|
||||
@click="item.action()"
|
||||
>
|
||||
<component :is="item.icon" class="size-4 text-muted" />
|
||||
|
|
|
|||
|
|
@ -102,7 +102,9 @@ function copyAsJSX() {
|
|||
|
||||
const itemClass = menuItem()
|
||||
const componentItemClass = menuItem({ tone: 'component' })
|
||||
const menuClass = menuContent({ class: 'min-w-56 shadow-[0_8px_30px_rgb(0_0_0/0.4)] animate-in fade-in zoom-in-95' })
|
||||
const menuClass = menuContent({
|
||||
class: 'min-w-56 shadow-[0_8px_30px_rgb(0_0_0/0.4)] animate-in fade-in zoom-in-95'
|
||||
})
|
||||
const separatorClass = menuSeparator({ class: 'my-1' })
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -181,8 +181,11 @@ function goNext() {
|
|||
v-for="sub in tool.flyout"
|
||||
:key="sub"
|
||||
:data-test-id="`toolbar-flyout-item-${sub.toLowerCase()}`"
|
||||
:class="menuItem()"
|
||||
:class="store.state.activeTool === sub ? 'bg-accent text-white' : undefined"
|
||||
:class="
|
||||
menuItem({
|
||||
class: store.state.activeTool === sub ? 'bg-accent text-white' : undefined
|
||||
})
|
||||
"
|
||||
@select="store.setTool(sub)"
|
||||
>
|
||||
<component :is="toolIcons[sub]" class="size-3.5" />
|
||||
|
|
@ -294,11 +297,10 @@ function goNext() {
|
|||
v-for="sub in tool.flyout"
|
||||
:key="sub"
|
||||
:data-test-id="`mobile-toolbar-flyout-item-${sub.toLowerCase()}`"
|
||||
:class="menuItem()"
|
||||
:class="
|
||||
store.state.activeTool === sub
|
||||
? 'bg-accent text-white'
|
||||
: 'text-surface hover:bg-hover'
|
||||
menuItem({
|
||||
class: store.state.activeTool === sub ? 'bg-accent text-white' : undefined
|
||||
})
|
||||
"
|
||||
@select="store.setTool(sub)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,12 @@ function handleSubmit(e: Event) {
|
|||
<SelectRoot v-model="modelId">
|
||||
<SelectTrigger
|
||||
data-test-id="chat-model-selector"
|
||||
:class="selectTrigger({ class: 'gap-1 rounded border-none bg-transparent px-1.5 py-0.5 text-[10px] text-muted' })"
|
||||
:class="
|
||||
selectTrigger({
|
||||
class:
|
||||
'gap-1 rounded border-none bg-transparent px-1.5 py-0.5 text-[10px] text-muted'
|
||||
})
|
||||
"
|
||||
>
|
||||
<icon-lucide-bot class="size-3" />
|
||||
{{ selectedModelName }}
|
||||
|
|
@ -65,7 +70,9 @@ function handleSubmit(e: Event) {
|
|||
position="popper"
|
||||
side="top"
|
||||
:side-offset="4"
|
||||
:class="selectContent({ radius: 'lg', padding: 'md', class: 'max-h-60 overflow-y-auto' })"
|
||||
:class="
|
||||
selectContent({ radius: 'lg', padding: 'md', class: 'max-h-60 overflow-y-auto' })
|
||||
"
|
||||
>
|
||||
<SelectViewport>
|
||||
<SelectItem
|
||||
|
|
@ -103,7 +110,14 @@ function handleSubmit(e: Event) {
|
|||
<button
|
||||
type="button"
|
||||
data-test-id="chat-stop-button"
|
||||
:class="uiButton({ tone: 'ghost', shape: 'rounded', size: 'sm', class: 'shrink-0 border border-border px-2 py-1.5' })"
|
||||
:class="
|
||||
uiButton({
|
||||
tone: 'ghost',
|
||||
shape: 'rounded',
|
||||
size: 'sm',
|
||||
class: 'shrink-0 border border-border px-2 py-1.5'
|
||||
})
|
||||
"
|
||||
@click="emit('stop')"
|
||||
>
|
||||
<icon-lucide-square class="size-3" />
|
||||
|
|
@ -124,7 +138,14 @@ function handleSubmit(e: Event) {
|
|||
<button
|
||||
type="submit"
|
||||
data-test-id="chat-send-button"
|
||||
:class="uiButton({ tone: 'accent', shape: 'rounded', size: 'sm', class: 'shrink-0 px-2.5 py-1.5 font-medium' })"
|
||||
:class="
|
||||
uiButton({
|
||||
tone: 'accent',
|
||||
shape: 'rounded',
|
||||
size: 'sm',
|
||||
class: 'shrink-0 px-2.5 py-1.5 font-medium'
|
||||
})
|
||||
"
|
||||
:disabled="!input.trim()"
|
||||
>
|
||||
<icon-lucide-send class="size-3" />
|
||||
|
|
|
|||
|
|
@ -188,27 +188,44 @@ function setAlignment(primary: LayoutAlign, counter: LayoutCounterAlign) {
|
|||
<template v-if="node.layoutMode !== 'NONE'">
|
||||
<div class="mt-1.5 flex gap-0.5">
|
||||
<button
|
||||
data-test-id="layout-direction-horizontal"
|
||||
class="flex cursor-pointer items-center justify-center rounded border px-2 py-1"
|
||||
:class="
|
||||
node.layoutMode === 'HORIZONTAL'
|
||||
? 'border-accent bg-accent/10 text-accent'
|
||||
: 'border-border text-muted hover:bg-hover hover:text-surface'
|
||||
"
|
||||
title="Horizontal layout"
|
||||
@click="store.setLayoutMode(node.id, 'HORIZONTAL')"
|
||||
>
|
||||
<icon-lucide-arrow-right class="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
data-test-id="layout-direction-vertical"
|
||||
class="flex cursor-pointer items-center justify-center rounded border px-2 py-1"
|
||||
:class="
|
||||
node.layoutMode === 'VERTICAL'
|
||||
? 'border-accent bg-accent/10 text-accent'
|
||||
: 'border-border text-muted hover:bg-hover hover:text-surface'
|
||||
"
|
||||
title="Vertical layout"
|
||||
@click="store.setLayoutMode(node.id, 'VERTICAL')"
|
||||
>
|
||||
<icon-lucide-arrow-down class="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
data-test-id="layout-direction-wrap"
|
||||
class="flex cursor-pointer items-center justify-center rounded border px-2 py-1"
|
||||
:class="
|
||||
node.layoutWrap === 'WRAP'
|
||||
? 'border-accent bg-accent/10 text-accent'
|
||||
: 'border-border text-muted hover:bg-hover hover:text-surface'
|
||||
"
|
||||
title="Wrap"
|
||||
@click="updateProp('layoutWrap', node.layoutWrap === 'WRAP' ? 'NO_WRAP' : 'WRAP')"
|
||||
>
|
||||
<icon-lucide-wrap-text class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 grid grid-cols-2 gap-1.5">
|
||||
|
|
@ -237,7 +254,10 @@ function setAlignment(primary: LayoutAlign, counter: LayoutCounterAlign) {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="showIndividualPadding || !hasUniformPadding()" class="mt-1.5 grid grid-cols-2 gap-1.5">
|
||||
<div
|
||||
v-if="showIndividualPadding || !hasUniformPadding()"
|
||||
class="mt-1.5 grid grid-cols-2 gap-1.5"
|
||||
>
|
||||
<ScrubInput
|
||||
icon="Top"
|
||||
:model-value="Math.round(node.paddingTop)"
|
||||
|
|
@ -270,7 +290,7 @@ function setAlignment(primary: LayoutAlign, counter: LayoutCounterAlign) {
|
|||
|
||||
<div class="mt-2">
|
||||
<label class="mb-1 block text-[11px] text-muted">Alignment</label>
|
||||
<div class="grid grid-cols-3 gap-1">
|
||||
<div data-test-id="layout-alignment-grid" class="grid grid-cols-3 gap-1">
|
||||
<button
|
||||
v-for="cell in ALIGN_GRID"
|
||||
:key="`${cell.primary}-${cell.counter}`"
|
||||
|
|
@ -288,4 +308,22 @@ function setAlignment(primary: LayoutAlign, counter: LayoutCounterAlign) {
|
|||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="node.type === 'FRAME'" class="border-b border-border px-3 py-2">
|
||||
<label class="flex cursor-pointer items-center gap-2 text-xs text-surface">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="accent-accent"
|
||||
:checked="node.clipsContent"
|
||||
@change="
|
||||
store.updateNodeWithUndo(
|
||||
node.id,
|
||||
{ clipsContent: !node.clipsContent },
|
||||
'Toggle clip content'
|
||||
)
|
||||
"
|
||||
/>
|
||||
Clip content
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { tv } from 'tailwind-variants'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { tv } from 'tailwind-variants'
|
||||
|
||||
const button = tv({
|
||||
base: 'inline-flex items-center justify-center transition-colors select-none',
|
||||
|
|
@ -24,10 +24,6 @@ const button = tv({
|
|||
bordered: {
|
||||
true: 'border border-white/10',
|
||||
false: ''
|
||||
},
|
||||
active: {
|
||||
true: '',
|
||||
false: ''
|
||||
}
|
||||
},
|
||||
compoundVariants: [
|
||||
|
|
@ -43,8 +39,7 @@ const button = tv({
|
|||
tone: 'ghost',
|
||||
shape: 'rounded',
|
||||
size: 'sm',
|
||||
bordered: false,
|
||||
active: false
|
||||
bordered: false
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -53,7 +48,6 @@ export function uiButton(options?: {
|
|||
shape?: 'square' | 'rounded' | 'pill'
|
||||
size?: 'sm' | 'md' | 'icon' | 'iconSm'
|
||||
bordered?: boolean
|
||||
active?: boolean
|
||||
class?: string
|
||||
}) {
|
||||
return twMerge(button(options), options?.class)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { tv } from 'tailwind-variants'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { tv } from 'tailwind-variants'
|
||||
|
||||
const content = tv({
|
||||
base: 'z-50 rounded-lg border border-border bg-panel p-1 shadow-lg'
|
||||
|
|
@ -10,7 +10,8 @@ const item = tv({
|
|||
variants: {
|
||||
tone: {
|
||||
default: 'text-surface',
|
||||
component: 'text-[#9747ff] data-[highlighted]:bg-[#9747ff]/12 data-[disabled]:text-[#9747ff]/40'
|
||||
component:
|
||||
'text-[#9747ff] data-[highlighted]:bg-[#9747ff]/12 data-[disabled]:text-[#9747ff]/40'
|
||||
},
|
||||
justify: {
|
||||
between: 'justify-between gap-6',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { tv } from 'tailwind-variants'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { tv } from 'tailwind-variants'
|
||||
|
||||
const trigger = tv({
|
||||
base: 'flex items-center justify-between border border-border bg-input text-surface outline-none hover:bg-hover'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { tv } from 'tailwind-variants'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { tv } from 'tailwind-variants'
|
||||
|
||||
const surface = tv({
|
||||
base: 'border border-border bg-panel',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { onUnmounted } from 'vue'
|
||||
import { useFileDialog } from '@vueuse/core'
|
||||
|
||||
import { IS_TAURI } from '@/constants'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
|
|
@ -38,12 +37,14 @@ export async function openFileDialog() {
|
|||
}
|
||||
}
|
||||
|
||||
const { open, onChange } = useFileDialog({ accept: '.fig', multiple: false })
|
||||
onChange((files) => {
|
||||
const file = files?.[0]
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '.fig'
|
||||
input.addEventListener('change', () => {
|
||||
const file = input.files?.[0]
|
||||
if (file) void openFileInNewTab(file)
|
||||
})
|
||||
open()
|
||||
input.click()
|
||||
}
|
||||
|
||||
const store = useEditorStore()
|
||||
|
|
|
|||
Loading…
Reference in a new issue