refactor: added tooltips and i18n new keys
This commit is contained in:
parent
1e1939aea0
commit
33e89b30d1
|
|
@ -1,105 +1,122 @@
|
|||
import type { Ref } from 'vue'
|
||||
import type { Editor } from "@open-pencil/core/editor";
|
||||
import type { Effect, SceneNode } from "@open-pencil/core/scene-graph";
|
||||
import type { Color } from "@open-pencil/core/types";
|
||||
import type { Ref } from "vue";
|
||||
import { useI18n } from "#vue/i18n/useI18n.js";
|
||||
|
||||
import type { Editor } from '@open-pencil/core/editor'
|
||||
import type { Effect, SceneNode } from '@open-pencil/core/scene-graph'
|
||||
import type { Color } from '@open-pencil/core/types'
|
||||
type EffectType = Effect["type"];
|
||||
|
||||
type EffectType = Effect['type']
|
||||
const { panels } = useI18n();
|
||||
|
||||
const EFFECT_LABELS: Record<string, string> = {
|
||||
DROP_SHADOW: 'Drop shadow',
|
||||
INNER_SHADOW: 'Inner shadow',
|
||||
LAYER_BLUR: 'Layer blur',
|
||||
BACKGROUND_BLUR: 'Background blur',
|
||||
FOREGROUND_BLUR: 'Foreground blur'
|
||||
}
|
||||
DROP_SHADOW: panels.value.dropShadow || "Drop shadow",
|
||||
INNER_SHADOW: panels.value.innerShadow || "Inner shadow",
|
||||
LAYER_BLUR: panels.value.layerBlur || "Layer blur",
|
||||
BACKGROUND_BLUR: panels.value.backgroundBlur || "Background blur",
|
||||
FOREGROUND_BLUR: panels.value.foregroundBlur || "Foreground blur",
|
||||
};
|
||||
|
||||
export const EFFECT_TYPES = Object.keys(EFFECT_LABELS) as EffectType[]
|
||||
export const EFFECT_OPTIONS = EFFECT_TYPES.map((t) => ({ value: t, label: EFFECT_LABELS[t] }))
|
||||
export const EFFECT_TYPES = Object.keys(EFFECT_LABELS) as EffectType[];
|
||||
export const EFFECT_OPTIONS = EFFECT_TYPES.map((t) => ({
|
||||
value: t,
|
||||
label: EFFECT_LABELS[t],
|
||||
}));
|
||||
|
||||
export function isShadow(type: string) {
|
||||
return type === 'DROP_SHADOW' || type === 'INNER_SHADOW'
|
||||
return type === "DROP_SHADOW" || type === "INNER_SHADOW";
|
||||
}
|
||||
|
||||
export function createDefaultEffect(): Effect {
|
||||
return {
|
||||
type: 'DROP_SHADOW',
|
||||
color: { r: 0, g: 0, b: 0, a: 0.25 },
|
||||
offset: { x: 0, y: 4 },
|
||||
radius: 4,
|
||||
spread: 0,
|
||||
visible: true
|
||||
}
|
||||
return {
|
||||
type: "DROP_SHADOW",
|
||||
color: { r: 0, g: 0, b: 0, a: 0.25 },
|
||||
offset: { x: 0, y: 4 },
|
||||
radius: 4,
|
||||
spread: 0,
|
||||
visible: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function createEffectEditActions(editor: Editor, effectsBeforeScrub: Ref<Effect[] | null>) {
|
||||
function scrubEffect(node: SceneNode | null, index: number, changes: Partial<Effect>) {
|
||||
if (!node) return
|
||||
if (!effectsBeforeScrub.value) {
|
||||
effectsBeforeScrub.value = node.effects.map((e) => ({
|
||||
...e,
|
||||
color: { ...e.color },
|
||||
offset: { ...e.offset }
|
||||
}))
|
||||
}
|
||||
const effects = [...node.effects]
|
||||
effects[index] = { ...effects[index], ...changes }
|
||||
editor.updateNode(node.id, { effects })
|
||||
editor.requestRender()
|
||||
}
|
||||
export function createEffectEditActions(
|
||||
editor: Editor,
|
||||
effectsBeforeScrub: Ref<Effect[] | null>,
|
||||
) {
|
||||
function scrubEffect(
|
||||
node: SceneNode | null,
|
||||
index: number,
|
||||
changes: Partial<Effect>,
|
||||
) {
|
||||
if (!node) return;
|
||||
if (!effectsBeforeScrub.value) {
|
||||
effectsBeforeScrub.value = node.effects.map((e) => ({
|
||||
...e,
|
||||
color: { ...e.color },
|
||||
offset: { ...e.offset },
|
||||
}));
|
||||
}
|
||||
const effects = [...node.effects];
|
||||
effects[index] = { ...effects[index], ...changes };
|
||||
editor.updateNode(node.id, { effects });
|
||||
editor.requestRender();
|
||||
}
|
||||
|
||||
function commitEffect(node: SceneNode | null, index: number, changes: Partial<Effect>) {
|
||||
if (!node) return
|
||||
const previous = effectsBeforeScrub.value
|
||||
effectsBeforeScrub.value = null
|
||||
const effects = [...node.effects]
|
||||
effects[index] = { ...effects[index], ...changes }
|
||||
editor.updateNode(node.id, { effects })
|
||||
editor.requestRender()
|
||||
if (previous) {
|
||||
editor.commitNodeUpdate(node.id, { effects: previous }, 'Change effect')
|
||||
}
|
||||
}
|
||||
function commitEffect(
|
||||
node: SceneNode | null,
|
||||
index: number,
|
||||
changes: Partial<Effect>,
|
||||
) {
|
||||
if (!node) return;
|
||||
const previous = effectsBeforeScrub.value;
|
||||
effectsBeforeScrub.value = null;
|
||||
const effects = [...node.effects];
|
||||
effects[index] = { ...effects[index], ...changes };
|
||||
editor.updateNode(node.id, { effects });
|
||||
editor.requestRender();
|
||||
if (previous) {
|
||||
editor.commitNodeUpdate(node.id, { effects: previous }, "Change effect");
|
||||
}
|
||||
}
|
||||
|
||||
return { scrubEffect, commitEffect }
|
||||
return { scrubEffect, commitEffect };
|
||||
}
|
||||
|
||||
export function createEffectControlActions(expandedIndex: Ref<number | null>) {
|
||||
function updateType(
|
||||
patch: (index: number, changes: Partial<Effect>) => void,
|
||||
node: SceneNode | null,
|
||||
index: number,
|
||||
type: EffectType
|
||||
) {
|
||||
if (!node) return
|
||||
const changes: Partial<Effect> = { type }
|
||||
if (!isShadow(type)) {
|
||||
changes.offset = { x: 0, y: 0 }
|
||||
changes.spread = 0
|
||||
} else if (!isShadow(node.effects[index].type)) {
|
||||
changes.offset = { x: 0, y: 4 }
|
||||
changes.spread = 0
|
||||
}
|
||||
patch(index, changes)
|
||||
}
|
||||
function updateType(
|
||||
patch: (index: number, changes: Partial<Effect>) => void,
|
||||
node: SceneNode | null,
|
||||
index: number,
|
||||
type: EffectType,
|
||||
) {
|
||||
if (!node) return;
|
||||
const changes: Partial<Effect> = { type };
|
||||
if (!isShadow(type)) {
|
||||
changes.offset = { x: 0, y: 0 };
|
||||
changes.spread = 0;
|
||||
} else if (!isShadow(node.effects[index].type)) {
|
||||
changes.offset = { x: 0, y: 4 };
|
||||
changes.spread = 0;
|
||||
}
|
||||
patch(index, changes);
|
||||
}
|
||||
|
||||
function updateColor(
|
||||
patch: (index: number, changes: Partial<Effect>) => void,
|
||||
index: number,
|
||||
color: Color
|
||||
) {
|
||||
patch(index, { color })
|
||||
}
|
||||
function updateColor(
|
||||
patch: (index: number, changes: Partial<Effect>) => void,
|
||||
index: number,
|
||||
color: Color,
|
||||
) {
|
||||
patch(index, { color });
|
||||
}
|
||||
|
||||
function handleRemove(removeFn: (index: number) => void, index: number) {
|
||||
removeFn(index)
|
||||
if (expandedIndex.value === index) expandedIndex.value = null
|
||||
else if (expandedIndex.value !== null && expandedIndex.value > index) expandedIndex.value--
|
||||
}
|
||||
function handleRemove(removeFn: (index: number) => void, index: number) {
|
||||
removeFn(index);
|
||||
if (expandedIndex.value === index) expandedIndex.value = null;
|
||||
else if (expandedIndex.value !== null && expandedIndex.value > index)
|
||||
expandedIndex.value--;
|
||||
}
|
||||
|
||||
function toggleExpand(index: number) {
|
||||
expandedIndex.value = expandedIndex.value === index ? null : index
|
||||
}
|
||||
function toggleExpand(index: number) {
|
||||
expandedIndex.value = expandedIndex.value === index ? null : index;
|
||||
}
|
||||
|
||||
return { updateType, updateColor, handleRemove, toggleExpand }
|
||||
return { updateType, updateColor, handleRemove, toggleExpand };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,375 +1,395 @@
|
|||
import { createI18n, params } from '@nanostores/i18n'
|
||||
import type { ComponentsJSON } from '@nanostores/i18n'
|
||||
import { createI18n, params } from "@nanostores/i18n";
|
||||
import type { ComponentsJSON } from "@nanostores/i18n";
|
||||
|
||||
import { locale } from '#vue/i18n/locale'
|
||||
import type { Locale } from '#vue/i18n/locale'
|
||||
import { locale } from "#vue/i18n/locale";
|
||||
import type { Locale } from "#vue/i18n/locale";
|
||||
|
||||
const localeLoaders: Record<Exclude<Locale, 'en'>, () => Promise<{ default: ComponentsJSON }>> = {
|
||||
de: () => import('#vue/locales/de.json'),
|
||||
es: () => import('#vue/locales/es.json'),
|
||||
fr: () => import('#vue/locales/fr.json'),
|
||||
it: () => import('#vue/locales/it.json'),
|
||||
pl: () => import('#vue/locales/pl.json'),
|
||||
ru: () => import('#vue/locales/ru.json'),
|
||||
'zh-CN': () => import('#vue/locales/zh-CN.json')
|
||||
}
|
||||
const localeLoaders: Record<
|
||||
Exclude<Locale, "en">,
|
||||
() => Promise<{ default: ComponentsJSON }>
|
||||
> = {
|
||||
de: () => import("#vue/locales/de.json"),
|
||||
es: () => import("#vue/locales/es.json"),
|
||||
fr: () => import("#vue/locales/fr.json"),
|
||||
it: () => import("#vue/locales/it.json"),
|
||||
pl: () => import("#vue/locales/pl.json"),
|
||||
ru: () => import("#vue/locales/ru.json"),
|
||||
"zh-CN": () => import("#vue/locales/zh-CN.json"),
|
||||
};
|
||||
|
||||
const i18n = createI18n<Locale, 'en'>(locale, {
|
||||
baseLocale: 'en',
|
||||
async get(code) {
|
||||
if (code === 'en') return {}
|
||||
const mod = await localeLoaders[code]()
|
||||
return mod.default
|
||||
}
|
||||
})
|
||||
const i18n = createI18n<Locale, "en">(locale, {
|
||||
baseLocale: "en",
|
||||
async get(code) {
|
||||
if (code === "en") return {};
|
||||
const mod = await localeLoaders[code]();
|
||||
return mod.default;
|
||||
},
|
||||
});
|
||||
|
||||
export const menuMessages = i18n('menu', {
|
||||
file: 'File',
|
||||
edit: 'Edit',
|
||||
view: 'View',
|
||||
object: 'Object',
|
||||
arrange: 'Arrange',
|
||||
text: 'Text',
|
||||
export const menuMessages = i18n("menu", {
|
||||
file: "File",
|
||||
edit: "Edit",
|
||||
view: "View",
|
||||
object: "Object",
|
||||
arrange: "Arrange",
|
||||
text: "Text",
|
||||
|
||||
new: 'New',
|
||||
open: 'Open…',
|
||||
save: 'Save',
|
||||
saveAs: 'Save as…',
|
||||
exportSelection: 'Export selection…',
|
||||
autosave: 'Auto-save to local file',
|
||||
new: "New",
|
||||
open: "Open…",
|
||||
save: "Save",
|
||||
saveAs: "Save as…",
|
||||
exportSelection: "Export selection…",
|
||||
autosave: "Auto-save to local file",
|
||||
|
||||
copy: 'Copy',
|
||||
paste: 'Paste',
|
||||
copy: "Copy",
|
||||
paste: "Paste",
|
||||
|
||||
zoomIn: 'Zoom in',
|
||||
zoomOut: 'Zoom out',
|
||||
profiler: 'Performance profiler',
|
||||
language: 'Language',
|
||||
checkUpdates: 'Check for updates…',
|
||||
zoomIn: "Zoom in",
|
||||
zoomOut: "Zoom out",
|
||||
profiler: "Performance profiler",
|
||||
language: "Language",
|
||||
checkUpdates: "Check for updates…",
|
||||
|
||||
moveToPage: 'Move to page',
|
||||
createInstance: 'Create instance',
|
||||
hide: 'Hide',
|
||||
show: 'Show',
|
||||
lock: 'Lock',
|
||||
unlock: 'Unlock',
|
||||
cut: 'Cut',
|
||||
front: 'Front',
|
||||
back: 'Back',
|
||||
toggleUI: 'Toggle UI',
|
||||
moveToPage: "Move to page",
|
||||
createInstance: "Create instance",
|
||||
hide: "Hide",
|
||||
show: "Show",
|
||||
lock: "Lock",
|
||||
unlock: "Unlock",
|
||||
cut: "Cut",
|
||||
front: "Front",
|
||||
back: "Back",
|
||||
toggleUI: "Toggle UI",
|
||||
|
||||
bold: 'Bold',
|
||||
italic: 'Italic',
|
||||
underline: 'Underline',
|
||||
strikethrough: 'Strikethrough',
|
||||
bold: "Bold",
|
||||
italic: "Italic",
|
||||
underline: "Underline",
|
||||
strikethrough: "Strikethrough",
|
||||
|
||||
pasteHere: 'Paste here',
|
||||
pasteToReplace: 'Paste to replace',
|
||||
copyPasteAs: 'Copy/Paste as',
|
||||
copyAsText: 'Copy as text',
|
||||
copyAsSVG: 'Copy as SVG',
|
||||
copyAsPNG: 'Copy as PNG',
|
||||
copyAsJSX: 'Copy as JSX',
|
||||
copyNodeId: 'Copy node ID',
|
||||
copyXPath: 'Copy XPath',
|
||||
booleanOperations: 'Boolean operations'
|
||||
})
|
||||
pasteHere: "Paste here",
|
||||
pasteToReplace: "Paste to replace",
|
||||
copyPasteAs: "Copy/Paste as",
|
||||
copyAsText: "Copy as text",
|
||||
copyAsSVG: "Copy as SVG",
|
||||
copyAsPNG: "Copy as PNG",
|
||||
copyAsJSX: "Copy as JSX",
|
||||
copyNodeId: "Copy node ID",
|
||||
copyXPath: "Copy XPath",
|
||||
booleanOperations: "Boolean operations",
|
||||
});
|
||||
|
||||
export const commandMessages = i18n('commands', {
|
||||
undo: 'Undo',
|
||||
redo: 'Redo',
|
||||
selectAll: 'Select all',
|
||||
duplicate: 'Duplicate',
|
||||
delete: 'Delete',
|
||||
group: 'Group',
|
||||
groupSelection: 'Group selection',
|
||||
frameSelection: 'Frame selection',
|
||||
ungroup: 'Ungroup',
|
||||
createComponent: 'Create component',
|
||||
createComponentSet: 'Create component set',
|
||||
createInstance: 'Create instance',
|
||||
detachInstance: 'Detach instance',
|
||||
goToMainComponent: 'Go to main component',
|
||||
addAutoLayout: 'Add auto layout',
|
||||
bringToFront: 'Bring to front',
|
||||
sendToBack: 'Send to back',
|
||||
showHide: 'Show/Hide',
|
||||
lockUnlock: 'Lock/Unlock',
|
||||
unionSelection: 'Union selection',
|
||||
subtractSelection: 'Subtract selection',
|
||||
intersectSelection: 'Intersect selection',
|
||||
excludeSelection: 'Exclude selection',
|
||||
flattenSelection: 'Flatten',
|
||||
outlineText: 'Outline text',
|
||||
outlineStroke: 'Outline stroke',
|
||||
booleanOperations: 'Boolean operations',
|
||||
flipHorizontal: 'Flip horizontal',
|
||||
flipVertical: 'Flip vertical',
|
||||
moveToPage: 'Move to page',
|
||||
zoomTo100: 'Zoom to 100%',
|
||||
zoomToFit: 'Zoom to fit',
|
||||
zoomToSelection: 'Zoom to selection'
|
||||
})
|
||||
export const commandMessages = i18n("commands", {
|
||||
undo: "Undo",
|
||||
redo: "Redo",
|
||||
selectAll: "Select all",
|
||||
duplicate: "Duplicate",
|
||||
delete: "Delete",
|
||||
group: "Group",
|
||||
groupSelection: "Group selection",
|
||||
frameSelection: "Frame selection",
|
||||
ungroup: "Ungroup",
|
||||
createComponent: "Create component",
|
||||
createComponentSet: "Create component set",
|
||||
createInstance: "Create instance",
|
||||
detachInstance: "Detach instance",
|
||||
goToMainComponent: "Go to main component",
|
||||
addAutoLayout: "Add auto layout",
|
||||
bringToFront: "Bring to front",
|
||||
sendToBack: "Send to back",
|
||||
showHide: "Show/Hide",
|
||||
lockUnlock: "Lock/Unlock",
|
||||
unionSelection: "Union selection",
|
||||
subtractSelection: "Subtract selection",
|
||||
intersectSelection: "Intersect selection",
|
||||
excludeSelection: "Exclude selection",
|
||||
flattenSelection: "Flatten",
|
||||
outlineText: "Outline text",
|
||||
outlineStroke: "Outline stroke",
|
||||
booleanOperations: "Boolean operations",
|
||||
flipHorizontal: "Flip horizontal",
|
||||
flipVertical: "Flip vertical",
|
||||
moveToPage: "Move to page",
|
||||
zoomTo100: "Zoom to 100%",
|
||||
zoomToFit: "Zoom to fit",
|
||||
zoomToSelection: "Zoom to selection",
|
||||
});
|
||||
|
||||
export const toolMessages = i18n('tools', {
|
||||
move: 'Move',
|
||||
frame: 'Frame',
|
||||
section: 'Section',
|
||||
rectangle: 'Rectangle',
|
||||
ellipse: 'Ellipse',
|
||||
line: 'Line',
|
||||
polygon: 'Polygon',
|
||||
star: 'Star',
|
||||
pen: 'Pen',
|
||||
text: 'Text',
|
||||
hand: 'Hand'
|
||||
})
|
||||
export const toolMessages = i18n("tools", {
|
||||
move: "Move",
|
||||
frame: "Frame",
|
||||
section: "Section",
|
||||
rectangle: "Rectangle",
|
||||
ellipse: "Ellipse",
|
||||
line: "Line",
|
||||
polygon: "Polygon",
|
||||
star: "Star",
|
||||
pen: "Pen",
|
||||
text: "Text",
|
||||
hand: "Hand",
|
||||
});
|
||||
|
||||
export const panelMessages = i18n('panels', {
|
||||
layers: 'Layers',
|
||||
pages: 'Pages',
|
||||
design: 'Design',
|
||||
code: 'Code',
|
||||
ai: 'AI',
|
||||
assets: 'Assets',
|
||||
searchLocalComponents: 'Search local components',
|
||||
assetLibraryBadge: 'Library',
|
||||
assetVariantSummary: params('{count} variants · {names}'),
|
||||
duplicateVariantValues: 'Duplicate variant values',
|
||||
openDocumentation: 'Open documentation',
|
||||
noLocalComponents: 'No local components',
|
||||
componentSet: 'Component set',
|
||||
component: 'Component',
|
||||
insertInstance: 'Insert instance',
|
||||
description: 'Description',
|
||||
documentation: 'Documentation',
|
||||
openDocs: 'Open docs',
|
||||
properties: 'Properties',
|
||||
export const panelMessages = i18n("panels", {
|
||||
layers: "Layers",
|
||||
pages: "Pages",
|
||||
design: "Design",
|
||||
code: "Code",
|
||||
ai: "AI",
|
||||
assets: "Assets",
|
||||
searchLocalComponents: "Search local components",
|
||||
assetLibraryBadge: "Library",
|
||||
assetVariantSummary: params("{count} variants · {names}"),
|
||||
duplicateVariantValues: "Duplicate variant values",
|
||||
openDocumentation: "Open documentation",
|
||||
noLocalComponents: "No local components",
|
||||
componentSet: "Component set",
|
||||
component: "Component",
|
||||
insertInstance: "Insert instance",
|
||||
description: "Description",
|
||||
documentation: "Documentation",
|
||||
openDocs: "Open docs",
|
||||
properties: "Properties",
|
||||
|
||||
page: 'Page',
|
||||
position: 'Position',
|
||||
layout: 'Layout',
|
||||
autoLayout: 'Auto layout',
|
||||
alignment: 'Alignment',
|
||||
appearance: 'Appearance',
|
||||
fill: 'Fill',
|
||||
stroke: 'Stroke',
|
||||
effects: 'Effects',
|
||||
export: 'Export',
|
||||
typography: 'Typography',
|
||||
variables: 'Variables',
|
||||
variants: 'Variants',
|
||||
constraints: 'Constraints',
|
||||
xAxis: "X Axis",
|
||||
yAxis: "Y Axis",
|
||||
rotation: "Rotation",
|
||||
width: "Width",
|
||||
height: "Height",
|
||||
opacity: "Opacity",
|
||||
radius: "Radius",
|
||||
spread: "Spread",
|
||||
|
||||
addFill: 'Add fill',
|
||||
addStroke: 'Add stroke',
|
||||
addEffect: 'Add effect',
|
||||
addExport: 'Add export',
|
||||
removeFill: 'Remove fill',
|
||||
removeStroke: 'Remove stroke',
|
||||
removeEffect: 'Remove effect',
|
||||
removeExport: 'Remove export',
|
||||
effectSettings: 'Effect settings',
|
||||
expandEffectSettings: 'Expand effect settings',
|
||||
collapseEffectSettings: 'Collapse effect settings',
|
||||
toggleExportPreview: 'Toggle export preview',
|
||||
page: "Page",
|
||||
position: "Position",
|
||||
layout: "Layout",
|
||||
autoLayout: "Auto layout",
|
||||
alignment: "Alignment",
|
||||
appearance: "Appearance",
|
||||
fill: "Fill",
|
||||
stroke: "Stroke",
|
||||
effects: "Effects",
|
||||
export: "Export",
|
||||
typography: "Typography",
|
||||
variables: "Variables",
|
||||
variants: "Variants",
|
||||
constraints: "Constraints",
|
||||
|
||||
dropShadow: 'Drop shadow',
|
||||
innerShadow: 'Inner shadow',
|
||||
layerBlur: 'Layer blur',
|
||||
backgroundBlur: 'Background blur',
|
||||
addFill: "Add fill",
|
||||
addStroke: "Add stroke",
|
||||
addEffect: "Add effect",
|
||||
addExport: "Add export",
|
||||
removeFill: "Remove fill",
|
||||
removeStroke: "Remove stroke",
|
||||
removeEffect: "Remove effect",
|
||||
removeExport: "Remove export",
|
||||
effectSettings: "Effect settings",
|
||||
expandEffectSettings: "Expand effect settings",
|
||||
collapseEffectSettings: "Collapse effect settings",
|
||||
toggleExportPreview: "Toggle export preview",
|
||||
|
||||
noSelection: 'No selection',
|
||||
noLocalVariables: 'No local variables',
|
||||
openVariables: 'Open variables',
|
||||
addPage: 'Add page',
|
||||
toggleVisibility: 'Toggle visibility',
|
||||
independentCornerRadii: 'Independent corner radii',
|
||||
detachVariable: 'Detach variable',
|
||||
applyVariable: 'Apply variable',
|
||||
noVariablesFound: 'No variables found',
|
||||
addAutoLayout: 'Add auto layout',
|
||||
removeAutoLayout: 'Remove auto layout',
|
||||
alignLeft: 'Align left',
|
||||
alignCenterHorizontally: 'Align center horizontally',
|
||||
alignRight: 'Align right',
|
||||
alignTop: 'Align top',
|
||||
alignCenterVertically: 'Align center vertically',
|
||||
alignBottom: 'Align bottom',
|
||||
flipHorizontal: 'Flip horizontal',
|
||||
flipVertical: 'Flip vertical',
|
||||
rotate90: 'Rotate 90°',
|
||||
mixedFillsHelp: 'Click + to replace mixed fills',
|
||||
mixedStrokesHelp: 'Click + to replace mixed strokes',
|
||||
mixedEffectsHelp: 'Click + to replace mixed effects',
|
||||
strokeSides: 'Stroke sides',
|
||||
strokeDash: 'Dashed stroke',
|
||||
strokeAlignInside: 'Inside',
|
||||
strokeAlignCenter: 'Center',
|
||||
strokeAlignOutside: 'Outside',
|
||||
exportPreview: 'Preview',
|
||||
exportRenderingPreview: 'Rendering preview…',
|
||||
create: 'Create',
|
||||
add: 'Add',
|
||||
createVariable: 'Create variable',
|
||||
createColorVariable: params('Create color variable from {value}'),
|
||||
createNumberVariable: params('Create number variable from {value}'),
|
||||
variableName: 'Variable name',
|
||||
mixed: 'Mixed',
|
||||
layersCount: params('{count} layers'),
|
||||
goToMainComponent: 'Go to Main Component',
|
||||
detachInstance: 'Detach Instance',
|
||||
dropShadow: "Drop shadow",
|
||||
innerShadow: "Inner shadow",
|
||||
layerBlur: "Layer blur",
|
||||
backgroundBlur: "Background blur",
|
||||
foregroundBlur: "Foreground blur",
|
||||
|
||||
solid: 'Solid',
|
||||
linearGradient: 'Linear',
|
||||
radialGradient: 'Radial',
|
||||
image: 'Image',
|
||||
stops: 'Stops',
|
||||
addStop: 'Add stop',
|
||||
strokeType: "Stroke type",
|
||||
strokeWeight: "Stroke weight",
|
||||
|
||||
alignCenter: 'Align center',
|
||||
alignMiddle: 'Align middle',
|
||||
clipContent: 'Clip content',
|
||||
colorFormatRgb: 'RGB',
|
||||
colorFormatHsl: 'HSL',
|
||||
colorFormatHsb: 'HSB',
|
||||
colorFormatOkhcl: 'OkHCL',
|
||||
colorHintHsl: 'H hue · S saturation · L lightness',
|
||||
colorHintHsb: 'H hue · S saturation · B brightness',
|
||||
colorHintOkhcl: 'H hue · C chroma · L lightness · A alpha',
|
||||
colorPreviewClipped: params('Clipped to {space} preview gamut'),
|
||||
rulers: 'Rulers',
|
||||
multiplayerCursors: 'Multiplayer cursors',
|
||||
direction: 'Direction',
|
||||
flow: 'Flow',
|
||||
gapAuto: 'Auto gap',
|
||||
horizontalGap: 'Horizontal gap',
|
||||
verticalGap: 'Vertical gap',
|
||||
auto: 'Auto',
|
||||
columns: 'Columns',
|
||||
rows: 'Rows',
|
||||
sizingFixed: 'Fixed',
|
||||
sizingHug: 'Hug',
|
||||
sizingFill: 'Fill',
|
||||
sizingHugShort: 'Hug',
|
||||
sizingFillShort: 'Fill',
|
||||
addMinWidth: 'Add min width',
|
||||
removeMinWidth: 'Remove min width',
|
||||
addMaxWidth: 'Add max width',
|
||||
removeMaxWidth: 'Remove max width',
|
||||
addMinHeight: 'Add min height',
|
||||
removeMinHeight: 'Remove min height',
|
||||
addMaxHeight: 'Add max height',
|
||||
removeMaxHeight: 'Remove max height',
|
||||
minWidthShort: 'Min W',
|
||||
maxWidthShort: 'Max W',
|
||||
minHeightShort: 'Min H',
|
||||
maxHeightShort: 'Max H',
|
||||
setToCurrentWidth: 'Set to current width',
|
||||
setToCurrentHeight: 'Set to current height',
|
||||
sizingFillFr: 'Fill (fr)',
|
||||
sizingFixedPx: 'Fixed (px)'
|
||||
})
|
||||
noSelection: "No selection",
|
||||
noLocalVariables: "No local variables",
|
||||
openVariables: "Open variables",
|
||||
addPage: "Add page",
|
||||
toggleVisibility: "Toggle visibility",
|
||||
independentCornerRadii: "Independent corner radii",
|
||||
detachVariable: "Detach variable",
|
||||
applyVariable: "Apply variable",
|
||||
noVariablesFound: "No variables found",
|
||||
addAutoLayout: "Add auto layout",
|
||||
removeAutoLayout: "Remove auto layout",
|
||||
alignLeft: "Align left",
|
||||
alignCenterHorizontally: "Align center horizontally",
|
||||
alignRight: "Align right",
|
||||
alignTop: "Align top",
|
||||
alignCenterVertically: "Align center vertically",
|
||||
alignBottom: "Align bottom",
|
||||
flipHorizontal: "Flip horizontal",
|
||||
flipVertical: "Flip vertical",
|
||||
rotate90: "Rotate 90°",
|
||||
mixedFillsHelp: "Click + to replace mixed fills",
|
||||
mixedStrokesHelp: "Click + to replace mixed strokes",
|
||||
mixedEffectsHelp: "Click + to replace mixed effects",
|
||||
strokeSides: "Stroke sides",
|
||||
strokeDash: "Dashed stroke",
|
||||
strokeAlignInside: "Inside",
|
||||
strokeAlignCenter: "Center",
|
||||
strokeAlignOutside: "Outside",
|
||||
exportScale: "Export scale",
|
||||
exportFormat: "Export format",
|
||||
exportPreview: "Preview",
|
||||
exportRenderingPreview: "Rendering preview…",
|
||||
create: "Create",
|
||||
add: "Add",
|
||||
createVariable: "Create variable",
|
||||
createColorVariable: params("Create color variable from {value}"),
|
||||
createNumberVariable: params("Create number variable from {value}"),
|
||||
variableName: "Variable name",
|
||||
mixed: "Mixed",
|
||||
layersCount: params("{count} layers"),
|
||||
goToMainComponent: "Go to Main Component",
|
||||
detachInstance: "Detach Instance",
|
||||
|
||||
export const variableTypeMessages = i18n('variableTypes', {
|
||||
color: 'Color',
|
||||
colorHint: 'Paint values',
|
||||
number: 'Number',
|
||||
numberHint: 'Sizes, spacing, opacity',
|
||||
text: 'Text',
|
||||
textHint: 'Copy and labels',
|
||||
boolean: 'Boolean',
|
||||
booleanHint: 'True or false'
|
||||
})
|
||||
solid: "Solid",
|
||||
linearGradient: "Linear",
|
||||
radialGradient: "Radial",
|
||||
image: "Image",
|
||||
stops: "Stops",
|
||||
addStop: "Add stop",
|
||||
|
||||
export const pageMessages = i18n('pages', {
|
||||
newPage: 'New page',
|
||||
rename: 'Rename',
|
||||
delete: 'Delete',
|
||||
pageName: params('Page {number}')
|
||||
})
|
||||
alignCenter: "Align center",
|
||||
alignMiddle: "Align middle",
|
||||
clipContent: "Clip content",
|
||||
colorFormatRgb: "RGB",
|
||||
colorFormatHsl: "HSL",
|
||||
colorFormatHsb: "HSB",
|
||||
colorFormatOkhcl: "OkHCL",
|
||||
colorHintHsl: "H hue · S saturation · L lightness",
|
||||
colorHintHsb: "H hue · S saturation · B brightness",
|
||||
colorHintOkhcl: "H hue · C chroma · L lightness · A alpha",
|
||||
colorPreviewClipped: params("Clipped to {space} preview gamut"),
|
||||
rulers: "Rulers",
|
||||
multiplayerCursors: "Multiplayer cursors",
|
||||
direction: "Direction",
|
||||
flow: "Flow",
|
||||
gapAuto: "Auto gap",
|
||||
horizontalGap: "Horizontal gap",
|
||||
verticalGap: "Vertical gap",
|
||||
auto: "Auto",
|
||||
columns: "Columns",
|
||||
rows: "Rows",
|
||||
sizingFixed: "Fixed",
|
||||
sizingHug: "Hug",
|
||||
sizingFill: "Fill",
|
||||
sizingHugShort: "Hug",
|
||||
sizingFillShort: "Fill",
|
||||
addMinWidth: "Add min width",
|
||||
removeMinWidth: "Remove min width",
|
||||
addMaxWidth: "Add max width",
|
||||
removeMaxWidth: "Remove max width",
|
||||
addMinHeight: "Add min height",
|
||||
removeMinHeight: "Remove min height",
|
||||
addMaxHeight: "Add max height",
|
||||
removeMaxHeight: "Remove max height",
|
||||
minWidthShort: "Min W",
|
||||
maxWidthShort: "Max W",
|
||||
minHeightShort: "Min H",
|
||||
maxHeightShort: "Max H",
|
||||
setToCurrentWidth: "Set to current width",
|
||||
setToCurrentHeight: "Set to current height",
|
||||
sizingFillFr: "Fill (fr)",
|
||||
sizingFixedPx: "Fixed (px)",
|
||||
});
|
||||
|
||||
export const dialogMessages = i18n('dialogs', {
|
||||
cancel: 'Cancel',
|
||||
apply: 'Apply',
|
||||
close: 'Close',
|
||||
ok: 'OK',
|
||||
copy: 'Copy',
|
||||
copied: 'Copied',
|
||||
copiedExclamation: 'Copied!',
|
||||
copyMessage: 'Copy message',
|
||||
createCollection: 'Create collection',
|
||||
renameCollection: 'Rename collection',
|
||||
deleteCollection: 'Delete collection',
|
||||
localVariables: 'Local variables',
|
||||
noVariableCollections: 'No variable collections',
|
||||
modes: 'Modes',
|
||||
addMode: 'Add mode',
|
||||
renameMode: 'Rename mode',
|
||||
duplicateMode: 'Duplicate mode',
|
||||
deleteMode: 'Delete mode',
|
||||
setDefaultMode: 'Set as default',
|
||||
selectLayerForJSX: 'Select a layer to see its JSX code',
|
||||
copyJSXReference: 'Copy JSX prop reference to clipboard',
|
||||
newTab: 'New tab',
|
||||
closeTab: params('Close {name}'),
|
||||
showUI: params('Show UI ({shortcut})'),
|
||||
fontSettings: 'Font settings',
|
||||
you: 'You',
|
||||
youSuffix: 'you',
|
||||
followingPeerStop: params('Following {name} (click to stop)'),
|
||||
clickToFollowPeer: params('Click to follow {name}'),
|
||||
connectAIProvider: 'Connect an AI provider to start chatting.',
|
||||
connect: 'Connect',
|
||||
getAPIKey: params('Get an {provider} API key →'),
|
||||
oneKeyManyModels: 'One key for 100+ models from all providers.',
|
||||
describeChange: 'Describe a change…',
|
||||
describeCreateOrChange: 'Describe what you want to create or change.',
|
||||
stopGenerating: 'Stop generating',
|
||||
sendMessage: 'Send message',
|
||||
baseURLPlaceholder: 'Base URL (e.g. http://localhost:11434/v1)',
|
||||
modelIDPlaceholder: 'Model ID (e.g. llama-3.3-70b)',
|
||||
aiProvider: 'AI Provider',
|
||||
providerSettings: 'Provider settings',
|
||||
done: 'Done',
|
||||
apiKey: 'API Key',
|
||||
apiType: 'API Type',
|
||||
baseURL: 'Base URL',
|
||||
modelID: 'Model ID',
|
||||
maxOutputTokens: 'Max output tokens',
|
||||
clear: 'Clear',
|
||||
keySavedReplace: 'Key saved — enter new to replace',
|
||||
getAPIKeyGeneric: 'Get API key →',
|
||||
pexelsAPIKey: 'Pexels API Key (stock photos)',
|
||||
unsplashAccessKey: 'Unsplash Access Key',
|
||||
stockPhotoToolOptional: 'Optional — for stock_photo tool',
|
||||
pexelsAlternativeOptional: 'Optional — alternative to Pexels',
|
||||
getPexelsAPIKey: 'Get free Pexels API key →',
|
||||
getUnsplashAccessKey: 'Get free Unsplash access key →',
|
||||
completions: 'Completions',
|
||||
responses: 'Responses',
|
||||
yourName: 'Your name',
|
||||
enterYourName: 'Enter your name',
|
||||
shareThisFile: 'Share this file',
|
||||
joinRoom: 'Join room',
|
||||
join: 'Join',
|
||||
roomLink: 'Room link',
|
||||
joinCollaboration: 'Join collaboration',
|
||||
orJoinRoom: 'or join a room',
|
||||
pasteRoomLinkOrId: 'Paste room link or ID',
|
||||
connected: 'Connected',
|
||||
search: 'Search…',
|
||||
noResults: 'No results',
|
||||
share: 'Share',
|
||||
appUpToDate: 'OpenPencil is up to date',
|
||||
updateAvailableTitle: 'Update OpenPencil',
|
||||
updateAvailable: params('OpenPencil {version} is available.'),
|
||||
updateInstallPrompt:
|
||||
'Download and install it now? The app will restart after the update is installed.',
|
||||
downloadingUpdate: params('Downloading OpenPencil {version}'),
|
||||
updateInstalledTitle: 'Update installed',
|
||||
updateInstalled: params('OpenPencil {version} was installed{size}. Restarting now.'),
|
||||
updateUnavailable:
|
||||
'Updates are not available yet. Publish a signed release with latest.json first.',
|
||||
updateCheckFailed: params('Could not check for updates: {error}')
|
||||
})
|
||||
export const variableTypeMessages = i18n("variableTypes", {
|
||||
color: "Color",
|
||||
colorHint: "Paint values",
|
||||
number: "Number",
|
||||
numberHint: "Sizes, spacing, opacity",
|
||||
text: "Text",
|
||||
textHint: "Copy and labels",
|
||||
boolean: "Boolean",
|
||||
booleanHint: "True or false",
|
||||
});
|
||||
|
||||
export const pageMessages = i18n("pages", {
|
||||
newPage: "New page",
|
||||
rename: "Rename",
|
||||
delete: "Delete",
|
||||
pageName: params("Page {number}"),
|
||||
});
|
||||
|
||||
export const dialogMessages = i18n("dialogs", {
|
||||
cancel: "Cancel",
|
||||
apply: "Apply",
|
||||
close: "Close",
|
||||
ok: "OK",
|
||||
copy: "Copy",
|
||||
copied: "Copied",
|
||||
copiedExclamation: "Copied!",
|
||||
copyMessage: "Copy message",
|
||||
createCollection: "Create collection",
|
||||
renameCollection: "Rename collection",
|
||||
deleteCollection: "Delete collection",
|
||||
localVariables: "Local variables",
|
||||
noVariableCollections: "No variable collections",
|
||||
modes: "Modes",
|
||||
addMode: "Add mode",
|
||||
renameMode: "Rename mode",
|
||||
duplicateMode: "Duplicate mode",
|
||||
deleteMode: "Delete mode",
|
||||
setDefaultMode: "Set as default",
|
||||
selectLayerForJSX: "Select a layer to see its JSX code",
|
||||
copyJSXReference: "Copy JSX prop reference to clipboard",
|
||||
newTab: "New tab",
|
||||
closeTab: params("Close {name}"),
|
||||
showUI: params("Show UI ({shortcut})"),
|
||||
fontSettings: "Font settings",
|
||||
you: "You",
|
||||
youSuffix: "you",
|
||||
followingPeerStop: params("Following {name} (click to stop)"),
|
||||
clickToFollowPeer: params("Click to follow {name}"),
|
||||
connectAIProvider: "Connect an AI provider to start chatting.",
|
||||
connect: "Connect",
|
||||
getAPIKey: params("Get an {provider} API key →"),
|
||||
oneKeyManyModels: "One key for 100+ models from all providers.",
|
||||
describeChange: "Describe a change…",
|
||||
describeCreateOrChange: "Describe what you want to create or change.",
|
||||
stopGenerating: "Stop generating",
|
||||
sendMessage: "Send message",
|
||||
baseURLPlaceholder: "Base URL (e.g. http://localhost:11434/v1)",
|
||||
modelIDPlaceholder: "Model ID (e.g. llama-3.3-70b)",
|
||||
aiProvider: "AI Provider",
|
||||
providerSettings: "Provider settings",
|
||||
done: "Done",
|
||||
apiKey: "API Key",
|
||||
apiType: "API Type",
|
||||
baseURL: "Base URL",
|
||||
modelID: "Model ID",
|
||||
maxOutputTokens: "Max output tokens",
|
||||
clear: "Clear",
|
||||
keySavedReplace: "Key saved — enter new to replace",
|
||||
getAPIKeyGeneric: "Get API key →",
|
||||
pexelsAPIKey: "Pexels API Key (stock photos)",
|
||||
unsplashAccessKey: "Unsplash Access Key",
|
||||
stockPhotoToolOptional: "Optional — for stock_photo tool",
|
||||
pexelsAlternativeOptional: "Optional — alternative to Pexels",
|
||||
getPexelsAPIKey: "Get free Pexels API key →",
|
||||
getUnsplashAccessKey: "Get free Unsplash access key →",
|
||||
completions: "Completions",
|
||||
responses: "Responses",
|
||||
yourName: "Your name",
|
||||
enterYourName: "Enter your name",
|
||||
shareThisFile: "Share this file",
|
||||
joinRoom: "Join room",
|
||||
join: "Join",
|
||||
roomLink: "Room link",
|
||||
joinCollaboration: "Join collaboration",
|
||||
orJoinRoom: "or join a room",
|
||||
pasteRoomLinkOrId: "Paste room link or ID",
|
||||
connected: "Connected",
|
||||
search: "Search…",
|
||||
noResults: "No results",
|
||||
share: "Share",
|
||||
appUpToDate: "OpenPencil is up to date",
|
||||
updateAvailableTitle: "Update OpenPencil",
|
||||
updateAvailable: params("OpenPencil {version} is available."),
|
||||
updateInstallPrompt:
|
||||
"Download and install it now? The app will restart after the update is installed.",
|
||||
downloadingUpdate: params("Downloading OpenPencil {version}"),
|
||||
updateInstalledTitle: "Update installed",
|
||||
updateInstalled: params(
|
||||
"OpenPencil {version} was installed{size}. Restarting now.",
|
||||
),
|
||||
updateUnavailable:
|
||||
"Updates are not available yet. Publish a signed release with latest.json first.",
|
||||
updateCheckFailed: params("Could not check for updates: {error}"),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,332 +1,345 @@
|
|||
{
|
||||
"menu": {
|
||||
"file": "Archivo",
|
||||
"edit": "Editar",
|
||||
"view": "Ver",
|
||||
"object": "Objeto",
|
||||
"arrange": "Organizar",
|
||||
"text": "Texto",
|
||||
"new": "Nuevo",
|
||||
"open": "Abrir…",
|
||||
"save": "Guardar",
|
||||
"saveAs": "Guardar como…",
|
||||
"exportSelection": "Exportar selección…",
|
||||
"autosave": "Guardado automático en archivo",
|
||||
"copy": "Copiar",
|
||||
"paste": "Pegar",
|
||||
"zoomIn": "Ampliar",
|
||||
"zoomOut": "Reducir",
|
||||
"profiler": "Perfil de rendimiento",
|
||||
"language": "Idioma",
|
||||
"moveToPage": "Mover a página",
|
||||
"createInstance": "Crear instancia",
|
||||
"hide": "Ocultar",
|
||||
"show": "Mostrar",
|
||||
"lock": "Bloquear",
|
||||
"unlock": "Desbloquear",
|
||||
"cut": "Cortar",
|
||||
"front": "Al frente",
|
||||
"back": "Al fondo",
|
||||
"toggleUI": "Alternar interfaz",
|
||||
"bold": "Negrita",
|
||||
"italic": "Cursiva",
|
||||
"underline": "Subrayado",
|
||||
"pasteHere": "Pegar aquí",
|
||||
"pasteToReplace": "Paste to replace",
|
||||
"copyPasteAs": "Copiar/Pegar como",
|
||||
"copyAsText": "Copiar como texto",
|
||||
"copyAsSVG": "Copiar como SVG",
|
||||
"copyAsPNG": "Copiar como PNG",
|
||||
"copyAsJSX": "Copiar como JSX",
|
||||
"copyNodeId": "Copiar ID del nodo",
|
||||
"copyXPath": "Copiar XPath",
|
||||
"checkUpdates": "Buscar actualizaciones…",
|
||||
"booleanOperations": "Boolean operations",
|
||||
"strikethrough": "Tachado"
|
||||
},
|
||||
"commands": {
|
||||
"undo": "Deshacer",
|
||||
"redo": "Rehacer",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Eliminar",
|
||||
"group": "Agrupar",
|
||||
"ungroup": "Desagrupar",
|
||||
"createComponent": "Crear componente",
|
||||
"createComponentSet": "Crear conjunto de componentes",
|
||||
"createInstance": "Crear instancia",
|
||||
"detachInstance": "Separar instancia",
|
||||
"goToMainComponent": "Ir al componente principal",
|
||||
"addAutoLayout": "Añadir auto-layout",
|
||||
"bringToFront": "Traer al frente",
|
||||
"sendToBack": "Enviar al fondo",
|
||||
"moveToPage": "Mover a página",
|
||||
"zoomTo100": "Zoom 100 %",
|
||||
"zoomToFit": "Ajustar a pantalla",
|
||||
"zoomToSelection": "Zoom a selección",
|
||||
"groupSelection": "Agrupar",
|
||||
"frameSelection": "Frame selection",
|
||||
"showHide": "Visibilidad",
|
||||
"lockUnlock": "Bloqueo",
|
||||
"unionSelection": "Union selection",
|
||||
"subtractSelection": "Subtract selection",
|
||||
"intersectSelection": "Intersect selection",
|
||||
"excludeSelection": "Exclude selection",
|
||||
"booleanOperations": "Boolean operations",
|
||||
"flipHorizontal": "Voltear horizontalmente",
|
||||
"flipVertical": "Voltear verticalmente",
|
||||
"flattenSelection": "Flatten",
|
||||
"outlineText": "Outline text",
|
||||
"outlineStroke": "Outline stroke"
|
||||
},
|
||||
"tools": {
|
||||
"move": "Mover",
|
||||
"frame": "Marco",
|
||||
"section": "Sección",
|
||||
"rectangle": "Rectángulo",
|
||||
"ellipse": "Elipse",
|
||||
"line": "Línea",
|
||||
"polygon": "Polígono",
|
||||
"star": "Estrella",
|
||||
"pen": "Pluma",
|
||||
"text": "Texto",
|
||||
"hand": "Mano"
|
||||
},
|
||||
"panels": {
|
||||
"layers": "Capas",
|
||||
"pages": "Páginas",
|
||||
"design": "Diseño",
|
||||
"code": "Código",
|
||||
"ai": "IA",
|
||||
"assets": "Recursos",
|
||||
"page": "Página",
|
||||
"position": "Posición",
|
||||
"layout": "Diseño",
|
||||
"autoLayout": "Auto-layout",
|
||||
"alignment": "Alineación",
|
||||
"appearance": "Apariencia",
|
||||
"fill": "Relleno",
|
||||
"stroke": "Trazo",
|
||||
"effects": "Efectos",
|
||||
"export": "Exportar",
|
||||
"typography": "Tipografía",
|
||||
"variables": "Variables",
|
||||
"constraints": "Restricciones",
|
||||
"addFill": "Añadir relleno",
|
||||
"addStroke": "Añadir trazo",
|
||||
"addEffect": "Añadir efecto",
|
||||
"addExport": "Añadir exportación",
|
||||
"dropShadow": "Sombra",
|
||||
"innerShadow": "Sombra interior",
|
||||
"layerBlur": "Desenfoque de capa",
|
||||
"backgroundBlur": "Desenfoque de fondo",
|
||||
"noSelection": "Sin selección",
|
||||
"noLocalVariables": "Sin variables locales",
|
||||
"openVariables": "Abrir variables",
|
||||
"addPage": "Añadir página",
|
||||
"toggleVisibility": "Alternar visibilidad",
|
||||
"independentCornerRadii": "Radios de esquina independientes",
|
||||
"detachVariable": "Desvincular variable",
|
||||
"applyVariable": "Aplicar variable",
|
||||
"noVariablesFound": "No se encontraron variables",
|
||||
"addAutoLayout": "Añadir auto-layout",
|
||||
"removeAutoLayout": "Quitar auto-layout",
|
||||
"mixed": "Mixto",
|
||||
"layersCount": "{count} capas",
|
||||
"goToMainComponent": "Ir al componente principal",
|
||||
"detachInstance": "Separar instancia",
|
||||
"solid": "Sólido",
|
||||
"linearGradient": "Lineal",
|
||||
"radialGradient": "Radial",
|
||||
"image": "Imagen",
|
||||
"stops": "Paradas",
|
||||
"addStop": "Añadir parada",
|
||||
"alignLeft": "Alinear a la izquierda",
|
||||
"alignCenter": "Centrar",
|
||||
"alignRight": "Alinear a la derecha",
|
||||
"alignTop": "Alinear arriba",
|
||||
"alignMiddle": "Centrar verticalmente",
|
||||
"alignBottom": "Alinear abajo",
|
||||
"alignCenterHorizontally": "Centrar horizontalmente",
|
||||
"alignCenterVertically": "Centrar verticalmente",
|
||||
"flipHorizontal": "Voltear horizontalmente",
|
||||
"flipVertical": "Voltear verticalmente",
|
||||
"rotate90": "Girar 90°",
|
||||
"mixedFillsHelp": "Haz clic en + para reemplazar rellenos mixtos",
|
||||
"mixedStrokesHelp": "Haz clic en + para reemplazar trazos mixtos",
|
||||
"mixedEffectsHelp": "Haz clic en + para reemplazar efectos mixtos",
|
||||
"strokeSides": "Lados del trazo",
|
||||
"clipContent": "Recortar contenido",
|
||||
"colorFormatRgb": "RGB",
|
||||
"colorFormatHsl": "HSL",
|
||||
"colorFormatHsb": "HSB",
|
||||
"colorFormatOkhcl": "OkHCL",
|
||||
"colorHintHsl": "H tono · S saturación · L luminosidad",
|
||||
"colorHintHsb": "H tono · S saturación · B brillo",
|
||||
"colorHintOkhcl": "H tono · C croma · L luminosidad · A alfa",
|
||||
"colorPreviewClipped": "Recortado a gama de vista previa {space}",
|
||||
"rulers": "Reglas",
|
||||
"multiplayerCursors": "Cursores de otros participantes",
|
||||
"direction": "Dirección",
|
||||
"flow": "Flujo",
|
||||
"auto": "Auto",
|
||||
"columns": "Columnas",
|
||||
"rows": "Filas",
|
||||
"sizingFixed": "Fijo",
|
||||
"sizingHug": "Ajustar al contenido",
|
||||
"sizingFill": "Rellenar",
|
||||
"sizingHugShort": "Ajust.",
|
||||
"sizingFillShort": "Llenar",
|
||||
"sizingFillFr": "Rellenar (fr)",
|
||||
"sizingFixedPx": "Fijo (px)",
|
||||
"strokeAlignInside": "Interior",
|
||||
"strokeAlignCenter": "Centro",
|
||||
"strokeAlignOutside": "Exterior",
|
||||
"exportPreview": "Vista previa",
|
||||
"exportRenderingPreview": "Renderizando vista previa…",
|
||||
"createVariable": "Crear variable",
|
||||
"createColorVariable": "Crear variable de color desde {value}",
|
||||
"variableName": "Nombre de variable",
|
||||
"create": "Crear",
|
||||
"createNumberVariable": "Crear variable numérica desde {value}",
|
||||
"strokeDash": "Trazo discontinuo",
|
||||
"add": "Añadir",
|
||||
"variants": "Variantes",
|
||||
"gapAuto": "Espaciado auto",
|
||||
"horizontalGap": "Espaciado horizontal",
|
||||
"verticalGap": "Espaciado vertical",
|
||||
"addMinWidth": "Agregar ancho min",
|
||||
"removeMinWidth": "Eliminar ancho min",
|
||||
"addMaxWidth": "Agregar ancho max",
|
||||
"removeMaxWidth": "Eliminar ancho max",
|
||||
"addMinHeight": "Agregar alto min",
|
||||
"removeMinHeight": "Eliminar alto min",
|
||||
"addMaxHeight": "Agregar alto max",
|
||||
"removeMaxHeight": "Eliminar alto max",
|
||||
"minWidthShort": "Min A",
|
||||
"maxWidthShort": "Max A",
|
||||
"minHeightShort": "Min Al",
|
||||
"maxHeightShort": "Max Al",
|
||||
"setToCurrentWidth": "Establecer ancho actual",
|
||||
"setToCurrentHeight": "Establecer alto actual",
|
||||
"searchLocalComponents": "Buscar componentes locales",
|
||||
"assetLibraryBadge": "Biblioteca",
|
||||
"assetVariantSummary": "{count} variantes · {names}",
|
||||
"duplicateVariantValues": "Valores de variante duplicados",
|
||||
"openDocumentation": "Abrir documentación",
|
||||
"noLocalComponents": "No hay componentes locales",
|
||||
"componentSet": "Conjunto de componentes",
|
||||
"component": "Componente",
|
||||
"insertInstance": "Insertar instancia",
|
||||
"description": "Descripción",
|
||||
"documentation": "Documentación",
|
||||
"openDocs": "Abrir docs",
|
||||
"properties": "Propiedades",
|
||||
"removeFill": "Eliminar relleno",
|
||||
"removeStroke": "Eliminar trazo",
|
||||
"removeEffect": "Eliminar efecto",
|
||||
"removeExport": "Eliminar exportación",
|
||||
"effectSettings": "Ajustes del efecto",
|
||||
"expandEffectSettings": "Expandir ajustes del efecto",
|
||||
"collapseEffectSettings": "Contraer ajustes del efecto",
|
||||
"toggleExportPreview": "Alternar vista previa de exportación"
|
||||
},
|
||||
"pages": {
|
||||
"newPage": "Nueva página",
|
||||
"rename": "Renombrar",
|
||||
"delete": "Eliminar",
|
||||
"pageName": "Página {number}"
|
||||
},
|
||||
"dialogs": {
|
||||
"cancel": "Cancelar",
|
||||
"apply": "Aplicar",
|
||||
"close": "Cerrar",
|
||||
"ok": "Aceptar",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado",
|
||||
"createCollection": "Crear colección",
|
||||
"localVariables": "Variables locales",
|
||||
"noVariableCollections": "No hay colecciones de variables",
|
||||
"selectLayerForJSX": "Selecciona una capa para ver su código JSX",
|
||||
"connectAIProvider": "Conecta un proveedor de IA para empezar a chatear.",
|
||||
"connect": "Conectar",
|
||||
"getAPIKey": "Obtener una clave API de {provider} →",
|
||||
"oneKeyManyModels": "Una clave para más de 100 modelos de todos los proveedores.",
|
||||
"describeChange": "Describe un cambio…",
|
||||
"describeCreateOrChange": "Describe lo que quieres crear o cambiar.",
|
||||
"stopGenerating": "Detener generación",
|
||||
"sendMessage": "Enviar mensaje",
|
||||
"baseURLPlaceholder": "Base URL (p. ej. http://localhost:11434/v1)",
|
||||
"modelIDPlaceholder": "Model ID (p. ej. llama-3.3-70b)",
|
||||
"yourName": "Tu nombre",
|
||||
"enterYourName": "Introduce tu nombre",
|
||||
"shareThisFile": "Compartir este archivo",
|
||||
"joinRoom": "Unirse a la sala",
|
||||
"join": "Unirse",
|
||||
"roomLink": "Enlace de la sala",
|
||||
"joinCollaboration": "Unirse a la colaboración",
|
||||
"orJoinRoom": "o unirse a una sala",
|
||||
"pasteRoomLinkOrId": "Pega el enlace o ID de la sala",
|
||||
"connected": "Conectado",
|
||||
"search": "Buscar…",
|
||||
"noResults": "Sin resultados",
|
||||
"share": "Compartir",
|
||||
"appUpToDate": "OpenPencil está actualizado",
|
||||
"updateAvailableTitle": "Actualizar OpenPencil",
|
||||
"updateAvailable": "OpenPencil {version} está disponible.",
|
||||
"updateInstallPrompt": "¿Descargar e instalar ahora? La app se reiniciará después de instalar la actualización.",
|
||||
"downloadingUpdate": "Descargando OpenPencil {version}",
|
||||
"updateInstalledTitle": "Actualización instalada",
|
||||
"updateInstalled": "OpenPencil {version} se instaló{size}. Reiniciando ahora.",
|
||||
"updateCheckFailed": "No se pudo buscar actualizaciones: {error}",
|
||||
"updateUnavailable": "Las actualizaciones aún no están disponibles. Publica primero una versión firmada con latest.json.",
|
||||
"renameCollection": "Renombrar coleccion",
|
||||
"deleteCollection": "Eliminar coleccion",
|
||||
"modes": "Modos",
|
||||
"addMode": "Agregar modo",
|
||||
"renameMode": "Renombrar modo",
|
||||
"duplicateMode": "Duplicar modo",
|
||||
"deleteMode": "Eliminar modo",
|
||||
"setDefaultMode": "Establecer como predeterminado",
|
||||
"aiProvider": "AI Provider",
|
||||
"providerSettings": "Provider settings",
|
||||
"done": "Done",
|
||||
"apiKey": "API Key",
|
||||
"apiType": "API Type",
|
||||
"baseURL": "Base URL",
|
||||
"modelID": "Model ID",
|
||||
"maxOutputTokens": "Max output tokens",
|
||||
"clear": "Clear",
|
||||
"keySavedReplace": "Key saved — enter new to replace",
|
||||
"getAPIKeyGeneric": "Get API key →",
|
||||
"pexelsAPIKey": "Pexels API Key (stock photos)",
|
||||
"unsplashAccessKey": "Unsplash Access Key",
|
||||
"stockPhotoToolOptional": "Optional — for stock_photo tool",
|
||||
"pexelsAlternativeOptional": "Optional — alternative to Pexels",
|
||||
"getPexelsAPIKey": "Get free Pexels API key →",
|
||||
"getUnsplashAccessKey": "Get free Unsplash access key →",
|
||||
"completions": "Completions",
|
||||
"responses": "Responses",
|
||||
"copiedExclamation": "¡Copiado!",
|
||||
"copyMessage": "Copiar mensaje",
|
||||
"copyJSXReference": "Copiar referencia de props JSX al portapapeles",
|
||||
"newTab": "Nueva pestaña",
|
||||
"closeTab": "Cerrar {name}",
|
||||
"showUI": "Mostrar interfaz ({shortcut})",
|
||||
"fontSettings": "Ajustes de fuentes",
|
||||
"you": "Tú",
|
||||
"youSuffix": "tú",
|
||||
"followingPeerStop": "Siguiendo a {name} (haz clic para detener)",
|
||||
"clickToFollowPeer": "Haz clic para seguir a {name}"
|
||||
},
|
||||
"variableTypes": {
|
||||
"color": "Color",
|
||||
"colorHint": "Valores de pintura",
|
||||
"number": "Número",
|
||||
"numberHint": "Tamaños, espaciado, opacidad",
|
||||
"text": "Texto",
|
||||
"textHint": "Texto y etiquetas",
|
||||
"boolean": "Booleano",
|
||||
"booleanHint": "Verdadero o falso"
|
||||
}
|
||||
"menu": {
|
||||
"file": "Archivo",
|
||||
"edit": "Editar",
|
||||
"view": "Ver",
|
||||
"object": "Objeto",
|
||||
"arrange": "Organizar",
|
||||
"text": "Texto",
|
||||
"new": "Nuevo",
|
||||
"open": "Abrir…",
|
||||
"save": "Guardar",
|
||||
"saveAs": "Guardar como…",
|
||||
"exportSelection": "Exportar selección…",
|
||||
"autosave": "Guardado automático en archivo",
|
||||
"copy": "Copiar",
|
||||
"paste": "Pegar",
|
||||
"zoomIn": "Ampliar",
|
||||
"zoomOut": "Reducir",
|
||||
"profiler": "Perfil de rendimiento",
|
||||
"language": "Idioma",
|
||||
"moveToPage": "Mover a página",
|
||||
"createInstance": "Crear instancia",
|
||||
"hide": "Ocultar",
|
||||
"show": "Mostrar",
|
||||
"lock": "Bloquear",
|
||||
"unlock": "Desbloquear",
|
||||
"cut": "Cortar",
|
||||
"front": "Al frente",
|
||||
"back": "Al fondo",
|
||||
"toggleUI": "Alternar interfaz",
|
||||
"bold": "Negrita",
|
||||
"italic": "Cursiva",
|
||||
"underline": "Subrayado",
|
||||
"pasteHere": "Pegar aquí",
|
||||
"pasteToReplace": "Pegar y reemplazar",
|
||||
"copyPasteAs": "Copiar/Pegar como",
|
||||
"copyAsText": "Copiar como texto",
|
||||
"copyAsSVG": "Copiar como SVG",
|
||||
"copyAsPNG": "Copiar como PNG",
|
||||
"copyAsJSX": "Copiar como JSX",
|
||||
"copyNodeId": "Copiar ID del nodo",
|
||||
"copyXPath": "Copiar XPath",
|
||||
"checkUpdates": "Buscar actualizaciones…",
|
||||
"booleanOperations": "Operaciones booleanas",
|
||||
"strikethrough": "Tachado"
|
||||
},
|
||||
"commands": {
|
||||
"undo": "Deshacer",
|
||||
"redo": "Rehacer",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Eliminar",
|
||||
"group": "Agrupar",
|
||||
"ungroup": "Desagrupar",
|
||||
"createComponent": "Crear componente",
|
||||
"createComponentSet": "Crear conjunto de componentes",
|
||||
"createInstance": "Crear instancia",
|
||||
"detachInstance": "Separar instancia",
|
||||
"goToMainComponent": "Ir al componente principal",
|
||||
"addAutoLayout": "Añadir auto-layout",
|
||||
"bringToFront": "Traer al frente",
|
||||
"sendToBack": "Enviar al fondo",
|
||||
"moveToPage": "Mover a página",
|
||||
"zoomTo100": "Zoom 100 %",
|
||||
"zoomToFit": "Ajustar a pantalla",
|
||||
"zoomToSelection": "Zoom a selección",
|
||||
"groupSelection": "Agrupar",
|
||||
"frameSelection": "Frame selection",
|
||||
"showHide": "Visibilidad",
|
||||
"lockUnlock": "Bloqueo",
|
||||
"unionSelection": "Selección de unión",
|
||||
"subtractSelection": "Selección de sustración",
|
||||
"intersectSelection": "Selección de intersección",
|
||||
"excludeSelection": "Selección de exclusión",
|
||||
"booleanOperations": "Operaciones boolean",
|
||||
"flipHorizontal": "Voltear horizontalmente",
|
||||
"flipVertical": "Voltear verticalmente",
|
||||
"flattenSelection": "Aplanar",
|
||||
"outlineText": "Contornear texto",
|
||||
"outlineStroke": "Cortornear trazo"
|
||||
},
|
||||
"tools": {
|
||||
"move": "Mover",
|
||||
"frame": "Marco",
|
||||
"section": "Sección",
|
||||
"rectangle": "Rectángulo",
|
||||
"ellipse": "Elipse",
|
||||
"line": "Línea",
|
||||
"polygon": "Polígono",
|
||||
"star": "Estrella",
|
||||
"pen": "Pluma",
|
||||
"text": "Texto",
|
||||
"hand": "Mano"
|
||||
},
|
||||
"panels": {
|
||||
"layers": "Capas",
|
||||
"pages": "Páginas",
|
||||
"design": "Diseño",
|
||||
"code": "Código",
|
||||
"ai": "IA",
|
||||
"assets": "Recursos",
|
||||
"page": "Página",
|
||||
"position": "Posición",
|
||||
"layout": "Diseño",
|
||||
"autoLayout": "Auto-layout",
|
||||
"alignment": "Alineación",
|
||||
"appearance": "Apariencia",
|
||||
"xAxis": "Eje X",
|
||||
"yAxis": "Eje Y",
|
||||
"rotation": "Rotación",
|
||||
"width": "Anchura",
|
||||
"height": "Altura",
|
||||
"opacity": "Opacidad",
|
||||
"radius": "Redondeado",
|
||||
"spread": "Esparcimiento",
|
||||
"fill": "Relleno",
|
||||
"stroke": "Trazo",
|
||||
"effects": "Efectos",
|
||||
"export": "Exportar",
|
||||
"typography": "Tipografía",
|
||||
"variables": "Variables",
|
||||
"constraints": "Restricciones",
|
||||
"addFill": "Añadir relleno",
|
||||
"addStroke": "Añadir trazo",
|
||||
"addEffect": "Añadir efecto",
|
||||
"addExport": "Añadir exportación",
|
||||
"dropShadow": "Sombra exterior",
|
||||
"innerShadow": "Sombra interior",
|
||||
"layerBlur": "Desenfoque de capa",
|
||||
"backgroundBlur": "Desenfoque de fondo",
|
||||
"foregroundBlur": "Desenfoque",
|
||||
"strokeType": "Tipo de trazado",
|
||||
"strokeWeight": "Grosor del trazado",
|
||||
"noSelection": "Sin selección",
|
||||
"noLocalVariables": "Sin variables locales",
|
||||
"openVariables": "Abrir variables",
|
||||
"addPage": "Añadir página",
|
||||
"toggleVisibility": "Alternar visibilidad",
|
||||
"independentCornerRadii": "Redondeo de esquina independientes",
|
||||
"detachVariable": "Desvincular variable",
|
||||
"applyVariable": "Aplicar variable",
|
||||
"noVariablesFound": "No se encontraron variables",
|
||||
"addAutoLayout": "Añadir auto-layout",
|
||||
"removeAutoLayout": "Quitar auto-layout",
|
||||
"mixed": "Mixto",
|
||||
"layersCount": "{count} capas",
|
||||
"goToMainComponent": "Ir al componente principal",
|
||||
"detachInstance": "Separar instancia",
|
||||
"solid": "Sólido",
|
||||
"linearGradient": "Lineal",
|
||||
"radialGradient": "Radial",
|
||||
"image": "Imagen",
|
||||
"stops": "Paradas",
|
||||
"addStop": "Añadir parada",
|
||||
"alignLeft": "Alinear a la izquierda",
|
||||
"alignCenter": "Centrar",
|
||||
"alignRight": "Alinear a la derecha",
|
||||
"alignTop": "Alinear arriba",
|
||||
"alignMiddle": "Centrar verticalmente",
|
||||
"alignBottom": "Alinear abajo",
|
||||
"alignCenterHorizontally": "Centrar horizontalmente",
|
||||
"alignCenterVertically": "Centrar verticalmente",
|
||||
"flipHorizontal": "Voltear horizontalmente",
|
||||
"flipVertical": "Voltear verticalmente",
|
||||
"rotate90": "Girar 90°",
|
||||
"mixedFillsHelp": "Haz clic en + para reemplazar rellenos mixtos",
|
||||
"mixedStrokesHelp": "Haz clic en + para reemplazar trazos mixtos",
|
||||
"mixedEffectsHelp": "Haz clic en + para reemplazar efectos mixtos",
|
||||
"strokeSides": "Lados del trazo",
|
||||
"clipContent": "Recortar contenido",
|
||||
"colorFormatRgb": "RGB",
|
||||
"colorFormatHsl": "HSL",
|
||||
"colorFormatHsb": "HSB",
|
||||
"colorFormatOkhcl": "OkHCL",
|
||||
"colorHintHsl": "H tono · S saturación · L luminosidad",
|
||||
"colorHintHsb": "H tono · S saturación · B brillo",
|
||||
"colorHintOkhcl": "H tono · C croma · L luminosidad · A alfa",
|
||||
"colorPreviewClipped": "Recortado a gama de vista previa {space}",
|
||||
"rulers": "Reglas",
|
||||
"multiplayerCursors": "Cursores de otros participantes",
|
||||
"direction": "Dirección",
|
||||
"flow": "Flujo",
|
||||
"auto": "Auto",
|
||||
"columns": "Columnas",
|
||||
"rows": "Filas",
|
||||
"sizingFixed": "Fijo",
|
||||
"sizingHug": "Ajustar al contenido",
|
||||
"sizingFill": "Rellenar",
|
||||
"sizingHugShort": "Ajust.",
|
||||
"sizingFillShort": "Llenar",
|
||||
"sizingFillFr": "Rellenar (fr)",
|
||||
"sizingFixedPx": "Fijo (px)",
|
||||
"strokeAlignInside": "Interior",
|
||||
"strokeAlignCenter": "Centro",
|
||||
"strokeAlignOutside": "Exterior",
|
||||
"exportScale": "Escala de exportación",
|
||||
"exportFormat": "Formato de exportación",
|
||||
"exportPreview": "Vista previa",
|
||||
"exportRenderingPreview": "Renderizando vista previa…",
|
||||
"createVariable": "Crear variable",
|
||||
"createColorVariable": "Crear variable de color desde {value}",
|
||||
"variableName": "Nombre de variable",
|
||||
"create": "Crear",
|
||||
"createNumberVariable": "Crear variable numérica desde {value}",
|
||||
"strokeDash": "Trazo discontinuo",
|
||||
"add": "Añadir",
|
||||
"variants": "Variantes",
|
||||
"gapAuto": "Espaciado auto",
|
||||
"horizontalGap": "Espaciado horizontal",
|
||||
"verticalGap": "Espaciado vertical",
|
||||
"addMinWidth": "Agregar ancho min",
|
||||
"removeMinWidth": "Eliminar ancho min",
|
||||
"addMaxWidth": "Agregar ancho max",
|
||||
"removeMaxWidth": "Eliminar ancho max",
|
||||
"addMinHeight": "Agregar alto min",
|
||||
"removeMinHeight": "Eliminar alto min",
|
||||
"addMaxHeight": "Agregar alto max",
|
||||
"removeMaxHeight": "Eliminar alto max",
|
||||
"minWidthShort": "Min A",
|
||||
"maxWidthShort": "Max A",
|
||||
"minHeightShort": "Min Al",
|
||||
"maxHeightShort": "Max Al",
|
||||
"setToCurrentWidth": "Establecer ancho actual",
|
||||
"setToCurrentHeight": "Establecer alto actual",
|
||||
"searchLocalComponents": "Buscar componentes locales",
|
||||
"assetLibraryBadge": "Biblioteca",
|
||||
"assetVariantSummary": "{count} variantes · {names}",
|
||||
"duplicateVariantValues": "Valores de variante duplicados",
|
||||
"openDocumentation": "Abrir documentación",
|
||||
"noLocalComponents": "No hay componentes locales",
|
||||
"componentSet": "Conjunto de componentes",
|
||||
"component": "Componente",
|
||||
"insertInstance": "Insertar instancia",
|
||||
"description": "Descripción",
|
||||
"documentation": "Documentación",
|
||||
"openDocs": "Abrir docs",
|
||||
"properties": "Propiedades",
|
||||
"removeFill": "Eliminar relleno",
|
||||
"removeStroke": "Eliminar trazo",
|
||||
"removeEffect": "Eliminar efecto",
|
||||
"removeExport": "Eliminar exportación",
|
||||
"effectSettings": "Ajustes del efecto",
|
||||
"expandEffectSettings": "Expandir ajustes del efecto",
|
||||
"collapseEffectSettings": "Contraer ajustes del efecto",
|
||||
"toggleExportPreview": "Alternar vista previa de exportación"
|
||||
},
|
||||
"pages": {
|
||||
"newPage": "Nueva página",
|
||||
"rename": "Renombrar",
|
||||
"delete": "Eliminar",
|
||||
"pageName": "Página {number}"
|
||||
},
|
||||
"dialogs": {
|
||||
"cancel": "Cancelar",
|
||||
"apply": "Aplicar",
|
||||
"close": "Cerrar",
|
||||
"ok": "Aceptar",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado",
|
||||
"createCollection": "Crear colección",
|
||||
"localVariables": "Variables locales",
|
||||
"noVariableCollections": "No hay colecciones de variables",
|
||||
"selectLayerForJSX": "Selecciona una capa para ver su código JSX",
|
||||
"connectAIProvider": "Conecta un proveedor de IA para empezar a chatear.",
|
||||
"connect": "Conectar",
|
||||
"getAPIKey": "Obtener una clave API de {provider} →",
|
||||
"oneKeyManyModels": "Una clave para más de 100 modelos de todos los proveedores.",
|
||||
"describeChange": "Describe un cambio…",
|
||||
"describeCreateOrChange": "Describe lo que quieres crear o cambiar.",
|
||||
"stopGenerating": "Detener generación",
|
||||
"sendMessage": "Enviar mensaje",
|
||||
"baseURLPlaceholder": "Base URL (p. ej. http://localhost:11434/v1)",
|
||||
"modelIDPlaceholder": "Model ID (p. ej. llama-3.3-70b)",
|
||||
"yourName": "Tu nombre",
|
||||
"enterYourName": "Introduce tu nombre",
|
||||
"shareThisFile": "Compartir este archivo",
|
||||
"joinRoom": "Unirse a la sala",
|
||||
"join": "Unirse",
|
||||
"roomLink": "Enlace de la sala",
|
||||
"joinCollaboration": "Unirse a la colaboración",
|
||||
"orJoinRoom": "o unirse a una sala",
|
||||
"pasteRoomLinkOrId": "Pega el enlace o ID de la sala",
|
||||
"connected": "Conectado",
|
||||
"search": "Buscar…",
|
||||
"noResults": "Sin resultados",
|
||||
"share": "Compartir",
|
||||
"appUpToDate": "OpenPencil está actualizado",
|
||||
"updateAvailableTitle": "Actualizar OpenPencil",
|
||||
"updateAvailable": "Está disponible la versión {version} de OpenPencil.",
|
||||
"updateInstallPrompt": "¿Descargar e instalar ahora? La app se reiniciará después de instalar la actualización.",
|
||||
"downloadingUpdate": "Descargando OpenPencil {version}",
|
||||
"updateInstalledTitle": "Actualización instalada",
|
||||
"updateInstalled": "Se ha instalado la versión {version} de OpenPencil. Reiniciando ahora...",
|
||||
"updateCheckFailed": "Error al encontrar actualizaciones: {error}",
|
||||
"updateUnavailable": "Las actualizaciones aún no están disponibles. Publica primero una versión firmada con latest.json.",
|
||||
"renameCollection": "Renombrar coleccion",
|
||||
"deleteCollection": "Eliminar coleccion",
|
||||
"modes": "Modos",
|
||||
"addMode": "Agregar modo",
|
||||
"renameMode": "Renombrar modo",
|
||||
"duplicateMode": "Duplicar modo",
|
||||
"deleteMode": "Eliminar modo",
|
||||
"setDefaultMode": "Establecer como predeterminado",
|
||||
"aiProvider": "Proveedor IA",
|
||||
"providerSettings": "Opciones del proveedor",
|
||||
"done": "Hecho",
|
||||
"apiKey": "Clave API",
|
||||
"apiType": "Tipo API",
|
||||
"baseURL": "URL base",
|
||||
"modelID": "ID Modelo",
|
||||
"maxOutputTokens": "Tokens de salida máx.",
|
||||
"clear": "Limpiar",
|
||||
"keySavedReplace": "Clave guardada - introduce una nueva para reemplazarla",
|
||||
"getAPIKeyGeneric": "Obtener clave API →",
|
||||
"pexelsAPIKey": "Clave API de Pexels (fotos stock)",
|
||||
"unsplashAccessKey": "Clave de acceso de Unsplash",
|
||||
"stockPhotoToolOptional": "Opcional — para herramienta stock_photo",
|
||||
"pexelsAlternativeOptional": "Opcional — alternativa a Pexels",
|
||||
"getPexelsAPIKey": "Obtener clave a la API gratis de Pexels →",
|
||||
"getUnsplashAccessKey": "Obtener clave a la API gratis de Unsplash →",
|
||||
"completions": "Completados",
|
||||
"responses": "Respuestas",
|
||||
"copiedExclamation": "¡Copiado!",
|
||||
"copyMessage": "Copiar mensaje",
|
||||
"copyJSXReference": "Copiar referencia de props JSX al portapapeles",
|
||||
"newTab": "Nueva pestaña",
|
||||
"closeTab": "Cerrar {name}",
|
||||
"showUI": "Mostrar interfaz ({shortcut})",
|
||||
"fontSettings": "Ajustes de fuentes",
|
||||
"you": "Tú",
|
||||
"youSuffix": "tú",
|
||||
"followingPeerStop": "Siguiendo a {name} (haz clic para detener)",
|
||||
"clickToFollowPeer": "Haz clic para seguir a {name}"
|
||||
},
|
||||
"variableTypes": {
|
||||
"color": "Color",
|
||||
"colorHint": "Valores de pintura",
|
||||
"number": "Número",
|
||||
"numberHint": "Tamaños, espaciado, opacidad",
|
||||
"text": "Texto",
|
||||
"textHint": "Texto y etiquetas",
|
||||
"boolean": "Booleano",
|
||||
"booleanHint": "Verdadero o falso"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,62 +67,66 @@ function onToggleCorners() {
|
|||
</div>
|
||||
|
||||
<div class="flex gap-1.5">
|
||||
<VariableScrubInput
|
||||
v-if="node"
|
||||
suffix="%"
|
||||
:model-value="opacityPercent"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:node-id="node.id"
|
||||
binding-path="opacity"
|
||||
@update:model-value="updateProp('opacity', $event / 100)"
|
||||
@commit="(v: number, p: number) => commitProp('opacity', v / 100, p / 100)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-blend class="size-3" />
|
||||
</template>
|
||||
</VariableScrubInput>
|
||||
<ScrubInput
|
||||
v-else
|
||||
suffix="%"
|
||||
:model-value="opacityPercent"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@update:model-value="updateProp('opacity', $event / 100)"
|
||||
@commit="(v: number, p: number) => commitProp('opacity', v / 100, p / 100)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-blend class="size-3" />
|
||||
</template>
|
||||
</ScrubInput>
|
||||
<Tip :label="panels.opacity">
|
||||
<VariableScrubInput
|
||||
v-if="node"
|
||||
suffix="%"
|
||||
:model-value="opacityPercent"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:node-id="node.id"
|
||||
binding-path="opacity"
|
||||
@update:model-value="updateProp('opacity', $event / 100)"
|
||||
@commit="(v: number, p: number) => commitProp('opacity', v / 100, p / 100)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-blend class="size-3" />
|
||||
</template>
|
||||
</VariableScrubInput>
|
||||
<ScrubInput
|
||||
v-else
|
||||
suffix="%"
|
||||
:model-value="opacityPercent"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@update:model-value="updateProp('opacity', $event / 100)"
|
||||
@commit="(v: number, p: number) => commitProp('opacity', v / 100, p / 100)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-blend class="size-3" />
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</Tip>
|
||||
|
||||
<template v-if="hasCornerRadius">
|
||||
<VariableScrubInput
|
||||
v-if="!showIndependentCorners && node"
|
||||
data-test-id="corner-radius-input"
|
||||
:model-value="cornerRadiusValue"
|
||||
:min="0"
|
||||
:node-id="node.id"
|
||||
binding-path="cornerRadius"
|
||||
@update:model-value="updateProp('cornerRadius', $event)"
|
||||
@commit="(v: number, p: number) => commitProp('cornerRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-square-round-corner class="size-3" />
|
||||
</template>
|
||||
</VariableScrubInput>
|
||||
<ScrubInput
|
||||
v-else-if="!showIndependentCorners"
|
||||
data-test-id="corner-radius-input"
|
||||
:model-value="cornerRadiusValue"
|
||||
:min="0"
|
||||
@update:model-value="updateProp('cornerRadius', $event)"
|
||||
@commit="(v: number, p: number) => commitProp('cornerRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-square-round-corner class="size-3" />
|
||||
</template>
|
||||
</ScrubInput>
|
||||
<Tip :label="panels.radius">
|
||||
<VariableScrubInput
|
||||
v-if="!showIndependentCorners && node"
|
||||
data-test-id="corner-radius-input"
|
||||
:model-value="cornerRadiusValue"
|
||||
:min="0"
|
||||
:node-id="node.id"
|
||||
binding-path="cornerRadius"
|
||||
@update:model-value="updateProp('cornerRadius', $event)"
|
||||
@commit="(v: number, p: number) => commitProp('cornerRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-square-round-corner class="size-3" />
|
||||
</template>
|
||||
</VariableScrubInput>
|
||||
<ScrubInput
|
||||
v-else-if="!showIndependentCorners"
|
||||
data-test-id="corner-radius-input"
|
||||
:model-value="cornerRadiusValue"
|
||||
:min="0"
|
||||
@update:model-value="updateProp('cornerRadius', $event)"
|
||||
@commit="(v: number, p: number) => commitProp('cornerRadius', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-square-round-corner class="size-3" />
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</Tip>
|
||||
|
||||
<Tip :label="panels.independentCornerRadii">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -54,14 +54,16 @@ const { panels, dialogs } = useI18n()
|
|||
<slot />
|
||||
</div>
|
||||
|
||||
<ScrubInput
|
||||
class="w-12 shrink-0"
|
||||
suffix="%"
|
||||
:model-value="opacityPercent(item.opacity)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@update:model-value="emit('patch', { opacity: opacityFromPercent($event) })"
|
||||
/>
|
||||
<Tip :label="panels.opacity">
|
||||
<ScrubInput
|
||||
class="w-12 shrink-0"
|
||||
suffix="%"
|
||||
:model-value="opacityPercent(item.opacity)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@update:model-value="emit('patch', { opacity: opacityFromPercent($event) })"
|
||||
/>
|
||||
</Tip>
|
||||
|
||||
<VariablePickerPopover
|
||||
v-if="
|
||||
|
|
|
|||
|
|
@ -103,46 +103,54 @@ const sectionCls = useSectionUI()
|
|||
<div class="flex flex-col gap-1.5 py-1.5">
|
||||
<template v-if="effectsCtx.isShadow(effect.type)">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<ScrubInput
|
||||
icon="X"
|
||||
:model-value="effect.offset.x"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, i, { offset: { ...effect.offset, x: $event } })
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, i, {
|
||||
offset: { ...effect.offset, x: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
<ScrubInput
|
||||
icon="Y"
|
||||
:model-value="effect.offset.y"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, i, { offset: { ...effect.offset, y: $event } })
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, i, {
|
||||
offset: { ...effect.offset, y: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
<Tip :label="panels.xAxis">
|
||||
<ScrubInput
|
||||
icon="X"
|
||||
:model-value="effect.offset.x"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, i, { offset: { ...effect.offset, x: $event } })
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, i, {
|
||||
offset: { ...effect.offset, x: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.yAxis">
|
||||
<ScrubInput
|
||||
icon="Y"
|
||||
:model-value="effect.offset.y"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, i, { offset: { ...effect.offset, y: $event } })
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, i, {
|
||||
offset: { ...effect.offset, y: $event }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<ScrubInput
|
||||
icon="B"
|
||||
:model-value="effect.radius"
|
||||
:min="0"
|
||||
@update:model-value="effectsCtx.scrubEffect(activeNode, i, { radius: $event })"
|
||||
@commit="effectsCtx.commitEffect(activeNode, i, { radius: $event })"
|
||||
/>
|
||||
<ScrubInput
|
||||
icon="S"
|
||||
:model-value="effect.spread"
|
||||
@update:model-value="effectsCtx.scrubEffect(activeNode, i, { spread: $event })"
|
||||
@commit="effectsCtx.commitEffect(activeNode, i, { spread: $event })"
|
||||
/>
|
||||
<Tip :label="panels.radius">
|
||||
<ScrubInput
|
||||
icon="B"
|
||||
:model-value="effect.radius"
|
||||
:min="0"
|
||||
@update:model-value="effectsCtx.scrubEffect(activeNode, i, { radius: $event })"
|
||||
@commit="effectsCtx.commitEffect(activeNode, i, { radius: $event })"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.spread">
|
||||
<ScrubInput
|
||||
icon="S"
|
||||
:model-value="effect.spread"
|
||||
@update:model-value="effectsCtx.scrubEffect(activeNode, i, { spread: $event })"
|
||||
@commit="effectsCtx.commitEffect(activeNode, i, { spread: $event })"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
|
|
@ -151,23 +159,25 @@ const sectionCls = useSectionUI()
|
|||
editable
|
||||
@update="effectsCtx.updateColor(actions.patch, i, $event)"
|
||||
/>
|
||||
<ScrubInput
|
||||
class="w-14"
|
||||
suffix="%"
|
||||
:model-value="Math.round(effect.color.a * 100)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, i, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, i, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
/>
|
||||
<Tip :label="panels.opacity">
|
||||
<ScrubInput
|
||||
class="w-14"
|
||||
suffix="%"
|
||||
:model-value="Math.round(effect.color.a * 100)"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@update:model-value="
|
||||
effectsCtx.scrubEffect(activeNode, i, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
@commit="
|
||||
effectsCtx.commitEffect(activeNode, i, {
|
||||
color: { ...effect.color, a: Math.max(0, Math.min(1, $event / 100)) }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -150,17 +150,19 @@ onScopeDispose(() => {
|
|||
:data-test-index="i"
|
||||
class="flex items-center gap-1.5 py-0.5"
|
||||
>
|
||||
<AppSelect
|
||||
v-if="formatSupportsScale(setting.format)"
|
||||
:model-value="setting.scale"
|
||||
:options="SCALE_OPTIONS"
|
||||
@update:model-value="updateScale(i, Number($event))"
|
||||
/>
|
||||
<AppSelect
|
||||
:model-value="setting.format"
|
||||
:options="FORMAT_OPTIONS"
|
||||
@update:model-value="updateFormat(i, $event as ExportFormatId)"
|
||||
/>
|
||||
<AppSelect
|
||||
v-if="formatSupportsScale(setting.format)"
|
||||
:model-value="setting.scale"
|
||||
:options="SCALE_OPTIONS"
|
||||
:label="panels.exportScale"
|
||||
@update:model-value="updateScale(i, Number($event))"
|
||||
/>
|
||||
<AppSelect
|
||||
:model-value="setting.format"
|
||||
:options="FORMAT_OPTIONS"
|
||||
:label="panels.exportFormat"
|
||||
@update:model-value="updateFormat(i, $event as ExportFormatId)"
|
||||
/>
|
||||
<Tip :label="panels.removeExport">
|
||||
<button
|
||||
:class="useIconButtonUI({ ui: { base: 'shrink-0' } }).base"
|
||||
|
|
|
|||
|
|
@ -170,167 +170,171 @@ function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) {
|
|||
<template>
|
||||
<div class="flex gap-1.5">
|
||||
<div ref="widthFieldRef" class="min-w-0 flex-1">
|
||||
<ScrubInput
|
||||
data-test-id="layout-width-input"
|
||||
icon="W"
|
||||
:model-value="Math.round(resolvedBoundNumber('width') ?? ctx.node.width)"
|
||||
:min="0"
|
||||
@update:model-value="updateSizeProp('width', $event)"
|
||||
@commit="(v: number, p: number) => commitSizeProp('width', v, p)"
|
||||
>
|
||||
<template #suffix>
|
||||
<BoundVariableButton
|
||||
v-if="widthVariableBinding.getBoundVariable(ctx.node.id)"
|
||||
test-id="layout-width-unbind-variable"
|
||||
:label="panels.detachVariable"
|
||||
@detach="widthVariableBinding.unbindVariable(ctx.node.id)"
|
||||
/>
|
||||
<VariablePickerPopover
|
||||
v-else
|
||||
v-model:search-term="widthVariableBinding.searchTerm.value"
|
||||
:variables="widthVariableBinding.filteredVariables.value"
|
||||
:trigger-label="panels.applyVariable"
|
||||
:search-placeholder="dialogs.search"
|
||||
:empty-label="panels.noVariablesFound"
|
||||
:trigger-test-id="'layout-width-apply-variable'"
|
||||
:create-label="panels.createNumberVariable({ value: Math.round(ctx.node.width) })"
|
||||
:create-name-placeholder="panels.variableName"
|
||||
:create-submit-label="panels.create"
|
||||
:create-test-id="'layout-width-apply-variable-create'"
|
||||
@select="bindSizeVariable('width', $event.id)"
|
||||
@create="createAndBindSizeVariable('width', $event)"
|
||||
/>
|
||||
<SelectRoot
|
||||
:model-value="ctx.widthSizing"
|
||||
@update:model-value="handleSizeSelect('width', $event as SizeSelectValue)"
|
||||
>
|
||||
<SelectTrigger
|
||||
data-test-id="layout-width-sizing-menu"
|
||||
:reference="anchorRef(widthFieldRef)"
|
||||
class="flex shrink-0 cursor-pointer items-center justify-center self-stretch border-none bg-transparent px-1.5 text-[11px] text-muted outline-none"
|
||||
@pointerdown.stop
|
||||
>
|
||||
<icon-lucide-chevron-down class="size-3" />
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
align="start"
|
||||
:side-offset="4"
|
||||
:class="sizingSelect.content"
|
||||
>
|
||||
<SelectViewport class="p-0.5">
|
||||
<SelectItem
|
||||
v-for="opt in ctx.widthSizingOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemIndicator
|
||||
class="absolute left-1.5 inline-flex items-center justify-center"
|
||||
>
|
||||
<icon-lucide-check class="size-3 text-accent" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText>{{ opt.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
v-for="item in widthLimitItems"
|
||||
:key="item.prop"
|
||||
:value="`${ctx.node[item.prop] == null ? 'add' : 'remove'}-${item.prop}`"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemText>
|
||||
{{ ctx.node[item.prop] == null ? item.addLabel() : item.removeLabel() }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
</ScrubInput>
|
||||
<Tip :label="panels.width">
|
||||
<ScrubInput
|
||||
data-test-id="layout-width-input"
|
||||
icon="W"
|
||||
:model-value="Math.round(resolvedBoundNumber('width') ?? ctx.node.width)"
|
||||
:min="0"
|
||||
@update:model-value="updateSizeProp('width', $event)"
|
||||
@commit="(v: number, p: number) => commitSizeProp('width', v, p)"
|
||||
>
|
||||
<template #suffix>
|
||||
<BoundVariableButton
|
||||
v-if="widthVariableBinding.getBoundVariable(ctx.node.id)"
|
||||
test-id="layout-width-unbind-variable"
|
||||
:label="panels.detachVariable"
|
||||
@detach="widthVariableBinding.unbindVariable(ctx.node.id)"
|
||||
/>
|
||||
<VariablePickerPopover
|
||||
v-else
|
||||
v-model:search-term="widthVariableBinding.searchTerm.value"
|
||||
:variables="widthVariableBinding.filteredVariables.value"
|
||||
:trigger-label="panels.applyVariable"
|
||||
:search-placeholder="dialogs.search"
|
||||
:empty-label="panels.noVariablesFound"
|
||||
:trigger-test-id="'layout-width-apply-variable'"
|
||||
:create-label="panels.createNumberVariable({ value: Math.round(ctx.node.width) })"
|
||||
:create-name-placeholder="panels.variableName"
|
||||
:create-submit-label="panels.create"
|
||||
:create-test-id="'layout-width-apply-variable-create'"
|
||||
@select="bindSizeVariable('width', $event.id)"
|
||||
@create="createAndBindSizeVariable('width', $event)"
|
||||
/>
|
||||
<SelectRoot
|
||||
:model-value="ctx.widthSizing"
|
||||
@update:model-value="handleSizeSelect('width', $event as SizeSelectValue)"
|
||||
>
|
||||
<SelectTrigger
|
||||
data-test-id="layout-width-sizing-menu"
|
||||
:reference="anchorRef(widthFieldRef)"
|
||||
class="flex shrink-0 cursor-pointer items-center justify-center self-stretch border-none bg-transparent px-1.5 text-[11px] text-muted outline-none"
|
||||
@pointerdown.stop
|
||||
>
|
||||
<icon-lucide-chevron-down class="size-3" />
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
align="start"
|
||||
:side-offset="4"
|
||||
:class="sizingSelect.content"
|
||||
>
|
||||
<SelectViewport class="p-0.5">
|
||||
<SelectItem
|
||||
v-for="opt in ctx.widthSizingOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemIndicator
|
||||
class="absolute left-1.5 inline-flex items-center justify-center"
|
||||
>
|
||||
<icon-lucide-check class="size-3 text-accent" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText>{{ opt.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
v-for="item in widthLimitItems"
|
||||
:key="item.prop"
|
||||
:value="`${ctx.node[item.prop] == null ? 'add' : 'remove'}-${item.prop}`"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemText>
|
||||
{{ ctx.node[item.prop] == null ? item.addLabel() : item.removeLabel() }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div ref="heightFieldRef" class="min-w-0 flex-1">
|
||||
<ScrubInput
|
||||
data-test-id="layout-height-input"
|
||||
icon="H"
|
||||
:model-value="Math.round(resolvedBoundNumber('height') ?? ctx.node.height)"
|
||||
:min="0"
|
||||
@update:model-value="updateSizeProp('height', $event)"
|
||||
@commit="(v: number, p: number) => commitSizeProp('height', v, p)"
|
||||
>
|
||||
<template #suffix>
|
||||
<BoundVariableButton
|
||||
v-if="heightVariableBinding.getBoundVariable(ctx.node.id)"
|
||||
test-id="layout-height-unbind-variable"
|
||||
:label="panels.detachVariable"
|
||||
@detach="heightVariableBinding.unbindVariable(ctx.node.id)"
|
||||
/>
|
||||
<VariablePickerPopover
|
||||
v-else
|
||||
v-model:search-term="heightVariableBinding.searchTerm.value"
|
||||
:variables="heightVariableBinding.filteredVariables.value"
|
||||
:trigger-label="panels.applyVariable"
|
||||
:search-placeholder="dialogs.search"
|
||||
:empty-label="panels.noVariablesFound"
|
||||
:trigger-test-id="'layout-height-apply-variable'"
|
||||
:create-label="panels.createNumberVariable({ value: Math.round(ctx.node.height) })"
|
||||
:create-name-placeholder="panels.variableName"
|
||||
:create-submit-label="panels.create"
|
||||
:create-test-id="'layout-height-apply-variable-create'"
|
||||
@select="bindSizeVariable('height', $event.id)"
|
||||
@create="createAndBindSizeVariable('height', $event)"
|
||||
/>
|
||||
<SelectRoot
|
||||
:model-value="ctx.heightSizing"
|
||||
@update:model-value="handleSizeSelect('height', $event as SizeSelectValue)"
|
||||
>
|
||||
<SelectTrigger
|
||||
data-test-id="layout-height-sizing-menu"
|
||||
:reference="anchorRef(heightFieldRef)"
|
||||
class="flex shrink-0 cursor-pointer items-center justify-center self-stretch border-none bg-transparent px-1.5 text-[11px] text-muted outline-none"
|
||||
@pointerdown.stop
|
||||
>
|
||||
<icon-lucide-chevron-down class="size-3" />
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
align="start"
|
||||
:side-offset="4"
|
||||
:class="sizingSelect.content"
|
||||
>
|
||||
<SelectViewport class="p-0.5">
|
||||
<SelectItem
|
||||
v-for="opt in ctx.heightSizingOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemIndicator
|
||||
class="absolute left-1.5 inline-flex items-center justify-center"
|
||||
>
|
||||
<icon-lucide-check class="size-3 text-accent" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText>{{ opt.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
v-for="item in heightLimitItems"
|
||||
:key="item.prop"
|
||||
:value="`${ctx.node[item.prop] == null ? 'add' : 'remove'}-${item.prop}`"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemText>
|
||||
{{ ctx.node[item.prop] == null ? item.addLabel() : item.removeLabel() }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
</ScrubInput>
|
||||
<Tip :label="panels.height">
|
||||
<ScrubInput
|
||||
data-test-id="layout-height-input"
|
||||
icon="H"
|
||||
:model-value="Math.round(resolvedBoundNumber('height') ?? ctx.node.height)"
|
||||
:min="0"
|
||||
@update:model-value="updateSizeProp('height', $event)"
|
||||
@commit="(v: number, p: number) => commitSizeProp('height', v, p)"
|
||||
>
|
||||
<template #suffix>
|
||||
<BoundVariableButton
|
||||
v-if="heightVariableBinding.getBoundVariable(ctx.node.id)"
|
||||
test-id="layout-height-unbind-variable"
|
||||
:label="panels.detachVariable"
|
||||
@detach="heightVariableBinding.unbindVariable(ctx.node.id)"
|
||||
/>
|
||||
<VariablePickerPopover
|
||||
v-else
|
||||
v-model:search-term="heightVariableBinding.searchTerm.value"
|
||||
:variables="heightVariableBinding.filteredVariables.value"
|
||||
:trigger-label="panels.applyVariable"
|
||||
:search-placeholder="dialogs.search"
|
||||
:empty-label="panels.noVariablesFound"
|
||||
:trigger-test-id="'layout-height-apply-variable'"
|
||||
:create-label="panels.createNumberVariable({ value: Math.round(ctx.node.height) })"
|
||||
:create-name-placeholder="panels.variableName"
|
||||
:create-submit-label="panels.create"
|
||||
:create-test-id="'layout-height-apply-variable-create'"
|
||||
@select="bindSizeVariable('height', $event.id)"
|
||||
@create="createAndBindSizeVariable('height', $event)"
|
||||
/>
|
||||
<SelectRoot
|
||||
:model-value="ctx.heightSizing"
|
||||
@update:model-value="handleSizeSelect('height', $event as SizeSelectValue)"
|
||||
>
|
||||
<SelectTrigger
|
||||
data-test-id="layout-height-sizing-menu"
|
||||
:reference="anchorRef(heightFieldRef)"
|
||||
class="flex shrink-0 cursor-pointer items-center justify-center self-stretch border-none bg-transparent px-1.5 text-[11px] text-muted outline-none"
|
||||
@pointerdown.stop
|
||||
>
|
||||
<icon-lucide-chevron-down class="size-3" />
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
align="start"
|
||||
:side-offset="4"
|
||||
:class="sizingSelect.content"
|
||||
>
|
||||
<SelectViewport class="p-0.5">
|
||||
<SelectItem
|
||||
v-for="opt in ctx.heightSizingOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemIndicator
|
||||
class="absolute left-1.5 inline-flex items-center justify-center"
|
||||
>
|
||||
<icon-lucide-check class="size-3 text-accent" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText>{{ opt.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
v-for="item in heightLimitItems"
|
||||
:key="item.prop"
|
||||
:value="`${ctx.node[item.prop] == null ? 'add' : 'remove'}-${item.prop}`"
|
||||
:class="sizingSelect.item"
|
||||
>
|
||||
<SelectItemText>
|
||||
{{ ctx.node[item.prop] == null ? item.addLabel() : item.removeLabel() }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</Tip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -93,51 +93,61 @@ function handleAlign(
|
|||
</div>
|
||||
|
||||
<div class="flex gap-1.5">
|
||||
<ScrubInput
|
||||
icon="X"
|
||||
:model-value="xValue"
|
||||
@update:model-value="actions.updateProp('x', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('x', v, p)"
|
||||
/>
|
||||
<ScrubInput
|
||||
icon="Y"
|
||||
:model-value="yValue"
|
||||
@update:model-value="actions.updateProp('y', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('y', v, p)"
|
||||
/>
|
||||
<Tip :label="panels.xAxis">
|
||||
<ScrubInput
|
||||
icon="X"
|
||||
:model-value="xValue"
|
||||
@update:model-value="actions.updateProp('x', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('x', v, p)"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.yAxis">
|
||||
<ScrubInput
|
||||
icon="Y"
|
||||
:model-value="yValue"
|
||||
@update:model-value="actions.updateProp('y', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('y', v, p)"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div v-if="isMulti" class="mt-1.5 flex gap-1.5">
|
||||
<ScrubInput
|
||||
icon="W"
|
||||
:model-value="wValue"
|
||||
:min="1"
|
||||
@update:model-value="actions.updateProp('width', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('width', v, p)"
|
||||
/>
|
||||
<ScrubInput
|
||||
icon="H"
|
||||
:model-value="hValue"
|
||||
:min="1"
|
||||
@update:model-value="actions.updateProp('height', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('height', v, p)"
|
||||
/>
|
||||
<Tip :label="panels.width">
|
||||
<ScrubInput
|
||||
icon="W"
|
||||
:model-value="wValue"
|
||||
:min="1"
|
||||
@update:model-value="actions.updateProp('width', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('width', v, p)"
|
||||
/>
|
||||
</Tip>
|
||||
<Tip :label="panels.height">
|
||||
<ScrubInput
|
||||
icon="H"
|
||||
:model-value="hValue"
|
||||
:min="1"
|
||||
@update:model-value="actions.updateProp('height', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('height', v, p)"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
|
||||
<div class="mt-1.5 flex items-center gap-1.5">
|
||||
<ScrubInput
|
||||
class="flex-1"
|
||||
suffix="°"
|
||||
:model-value="rotationValue"
|
||||
:min="-360"
|
||||
:max="360"
|
||||
@update:model-value="actions.updateProp('rotation', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('rotation', v, p)"
|
||||
>
|
||||
<Tip :label="panels.rotation">
|
||||
<ScrubInput
|
||||
class="flex-1"
|
||||
suffix="°"
|
||||
:model-value="rotationValue"
|
||||
:min="-360"
|
||||
:max="360"
|
||||
@update:model-value="actions.updateProp('rotation', $event)"
|
||||
@commit="(v: number, p: number) => actions.commitProp('rotation', v, p)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-rotate-cw class="size-3" />
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</Tip>
|
||||
<Tip :label="panels.flipHorizontal">
|
||||
<button
|
||||
:class="useIconButtonUI({ size: 'md', ui: { base: 'shrink-0' } }).base"
|
||||
|
|
|
|||
|
|
@ -150,33 +150,36 @@ function setGap(stroke: Stroke | undefined, patch: StrokePatch, value: number) {
|
|||
v-if="!isMixed && (items as unknown[]).length > 0"
|
||||
class="mt-1 flex items-center gap-1.5"
|
||||
>
|
||||
<AppSelect
|
||||
class="w-[72px]"
|
||||
:model-value="strokeCtx.currentAlign(activeNode)"
|
||||
:options="strokeCtx.alignOptions"
|
||||
@update:model-value="strokeCtx.updateAlign($event as Stroke['align'], activeNode!)"
|
||||
/>
|
||||
<ScrubInput
|
||||
v-if="!expandedSides"
|
||||
class="flex-1"
|
||||
:model-value="activeNode!.strokes[0]?.weight ?? 1"
|
||||
:min="0"
|
||||
@update:model-value="actions.patch(0, { weight: $event })"
|
||||
>
|
||||
<template #icon>
|
||||
<svg
|
||||
class="size-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<line x1="1" y1="3" x2="11" y2="3" />
|
||||
<line x1="1" y1="6" x2="11" y2="6" />
|
||||
<line x1="1" y1="9" x2="11" y2="9" />
|
||||
</svg>
|
||||
</template>
|
||||
</ScrubInput>
|
||||
<AppSelect
|
||||
class="w-[72px]"
|
||||
:label="panels.strokeType"
|
||||
:model-value="strokeCtx.currentAlign(activeNode)"
|
||||
:options="strokeCtx.alignOptions"
|
||||
@update:model-value="strokeCtx.updateAlign($event as Stroke['align'], activeNode!)"
|
||||
/>
|
||||
<Tip :label="panels.strokeWeight">
|
||||
<ScrubInput
|
||||
v-if="!expandedSides"
|
||||
class="flex-1"
|
||||
:model-value="activeNode!.strokes[0]?.weight ?? 1"
|
||||
:min="0"
|
||||
@update:model-value="actions.patch(0, { weight: $event })"
|
||||
>
|
||||
<template #icon>
|
||||
<svg
|
||||
class="size-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<line x1="1" y1="3" x2="11" y2="3" />
|
||||
<line x1="1" y1="6" x2="11" y2="6" />
|
||||
<line x1="1" y1="9" x2="11" y2="9" />
|
||||
</svg>
|
||||
</template>
|
||||
</ScrubInput>
|
||||
</Tip>
|
||||
<Tip :label="panels.strokeSides">
|
||||
<button
|
||||
data-test-id="stroke-sides-toggle"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface AppSelectUi extends SelectUi {
|
|||
}
|
||||
|
||||
interface AppSelectProps<TValue extends string | number> extends TestIdProps {
|
||||
label?: string
|
||||
options: { value: TValue; label: string }[]
|
||||
placeholder?: string
|
||||
ui?: AppSelectUi
|
||||
|
|
@ -29,6 +30,7 @@ interface AppSelectProps<TValue extends string | number> extends TestIdProps {
|
|||
|
||||
const {
|
||||
options,
|
||||
label,
|
||||
placeholder,
|
||||
ui,
|
||||
testId = 'app-select-trigger'
|
||||
|
|
@ -47,24 +49,26 @@ const indicator = ui?.indicator ?? 'absolute left-1.5 inline-flex items-center j
|
|||
|
||||
<template>
|
||||
<SelectRoot v-model="modelValue">
|
||||
<SelectTrigger v-test-id="testId" :class="select.trigger">
|
||||
<SelectValue :placeholder="placeholder" />
|
||||
<icon-lucide-chevron-down class="ml-1 size-3 shrink-0 text-muted" />
|
||||
</SelectTrigger>
|
||||
<Tip :label="label">
|
||||
<SelectTrigger v-test-id="testId" :class="select.trigger">
|
||||
<SelectValue :placeholder="placeholder" />
|
||||
<icon-lucide-chevron-down class="ml-1 size-3 shrink-0 text-muted" />
|
||||
</SelectTrigger>
|
||||
</Tip>
|
||||
<SelectPortal>
|
||||
<SelectContent position="popper" :side-offset="2" :class="select.content">
|
||||
<SelectViewport :class="viewport">
|
||||
<SelectItem
|
||||
v-for="opt in options"
|
||||
:key="String(opt.value)"
|
||||
:value="opt.value"
|
||||
:class="select.item"
|
||||
>
|
||||
<SelectItemIndicator :class="indicator">
|
||||
<icon-lucide-check class="size-3 text-accent" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText>{{ opt.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
v-for="opt in options"
|
||||
:key="String(opt.value)"
|
||||
:value="opt.value"
|
||||
:class="select.item"
|
||||
>
|
||||
<SelectItemIndicator :class="indicator">
|
||||
<icon-lucide-check class="size-3 text-accent" />
|
||||
</SelectItemIndicator>
|
||||
<SelectItemText>{{ opt.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
|
|
|
|||
Loading…
Reference in a new issue