diff --git a/frontend/src/components/editor/ElementToolbar.vue b/frontend/src/components/editor/ElementToolbar.vue index 135580c..58656ad 100644 --- a/frontend/src/components/editor/ElementToolbar.vue +++ b/frontend/src/components/editor/ElementToolbar.vue @@ -3,15 +3,18 @@ import { computed } from 'vue' import { useTemplateStore } from '../../stores/template' import { useEditorStore } from '../../stores/editor' import { isContainer } from '../../core/types' +import ContainerToolbar from './toolbars/ContainerToolbar.vue' +import TextToolbar from './toolbars/TextToolbar.vue' +import TableToolbar from './toolbars/TableToolbar.vue' +import ChartToolbar from './toolbars/ChartToolbar.vue' import type { ContainerElement, - TextStyle, RepeatingTableElement, TableStyle, ChartElement, - ChartType, } from '../../core/types' import type { LayoutMapEntry } from '../../core/layout-types' +import '../../styles/toolbar.css' const PAGE_GAP_PX = 24 @@ -30,7 +33,7 @@ const selected = computed(() => { return templateStore.getElementById(id) ?? null }) -const container = computed(() => { +const containerEl = computed(() => { const el = selected.value return el && isContainer(el) ? (el as ContainerElement) : null }) @@ -43,8 +46,9 @@ const isText = computed(() => { const isLine = computed(() => selected.value?.type === 'line') const isTable = computed(() => selected.value?.type === 'repeating_table') -const tableEl = computed(() => (isTable.value ? (selected.value as RepeatingTableElement) : null)) -const tableStyle = computed(() => tableEl.value?.style as TableStyle | undefined) +const tableStyle = computed(() => + isTable.value ? ((selected.value as RepeatingTableElement).style as TableStyle) : null, +) const isChart = computed(() => selected.value?.type === 'chart') const chartEl = computed(() => (isChart.value ? (selected.value as ChartElement) : null)) @@ -81,43 +85,6 @@ function updateStyle(key: string, value: unknown) { update({ style: { ...selected.value.style, [key]: value } }) } -// Container -function setDirection(dir: 'row' | 'column') { - update({ direction: dir }) -} -function setAlign(align: string) { - update({ align }) -} -function setJustify(justify: string) { - update({ justify }) -} -function setGap(e: Event) { - update({ gap: parseFloat((e.target as HTMLInputElement).value) || 0 }) -} - -// Text -function setFontWeight(w: string) { - updateStyle('fontWeight', w) -} -function setTextAlign(a: string) { - updateStyle('align', a) -} - -// Table -function updateTableStyle(key: string, value: unknown) { - if (!selected.value) return - update({ style: { ...selected.value.style, [key]: value } }) -} - -// Chart -function setChartType(t: ChartType) { - update({ chartType: t }) -} -function updateChartStyle(key: string, value: unknown) { - if (!selected.value) return - update({ style: { ...selected.value.style, [key]: value } }) -} - // Z-order function bringForward() { if (selected.value) templateStore.bringForward(selected.value.id) @@ -135,1286 +102,51 @@ function sendToBack() { - diff --git a/frontend/src/components/editor/toolbars/ChartToolbar.vue b/frontend/src/components/editor/toolbars/ChartToolbar.vue new file mode 100644 index 0000000..0a7e472 --- /dev/null +++ b/frontend/src/components/editor/toolbars/ChartToolbar.vue @@ -0,0 +1,53 @@ + + + diff --git a/frontend/src/components/editor/toolbars/ContainerToolbar.vue b/frontend/src/components/editor/toolbars/ContainerToolbar.vue new file mode 100644 index 0000000..f392d7d --- /dev/null +++ b/frontend/src/components/editor/toolbars/ContainerToolbar.vue @@ -0,0 +1,97 @@ + + + diff --git a/frontend/src/components/editor/toolbars/TableToolbar.vue b/frontend/src/components/editor/toolbars/TableToolbar.vue new file mode 100644 index 0000000..5daf7ab --- /dev/null +++ b/frontend/src/components/editor/toolbars/TableToolbar.vue @@ -0,0 +1,51 @@ + + + diff --git a/frontend/src/components/editor/toolbars/TextToolbar.vue b/frontend/src/components/editor/toolbars/TextToolbar.vue new file mode 100644 index 0000000..2bb8514 --- /dev/null +++ b/frontend/src/components/editor/toolbars/TextToolbar.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/components/properties/BarcodeProperties.vue b/frontend/src/components/properties/BarcodeProperties.vue index fc55429..58f731d 100644 --- a/frontend/src/components/properties/BarcodeProperties.vue +++ b/frontend/src/components/properties/BarcodeProperties.vue @@ -1,25 +1,26 @@ diff --git a/frontend/src/components/properties/CalculatedTextProperties.vue b/frontend/src/components/properties/CalculatedTextProperties.vue index 8b940d1..7f76924 100644 --- a/frontend/src/components/properties/CalculatedTextProperties.vue +++ b/frontend/src/components/properties/CalculatedTextProperties.vue @@ -1,32 +1,26 @@ diff --git a/frontend/src/components/properties/ChartProperties.vue b/frontend/src/components/properties/ChartProperties.vue index 2ba09f6..4dcde07 100644 --- a/frontend/src/components/properties/ChartProperties.vue +++ b/frontend/src/components/properties/ChartProperties.vue @@ -1,32 +1,43 @@ diff --git a/frontend/src/components/properties/ContainerProperties.vue b/frontend/src/components/properties/ContainerProperties.vue index 59b5e4b..e0208b2 100644 --- a/frontend/src/components/properties/ContainerProperties.vue +++ b/frontend/src/components/properties/ContainerProperties.vue @@ -1,52 +1,51 @@ diff --git a/frontend/src/components/properties/CurrentDateProperties.vue b/frontend/src/components/properties/CurrentDateProperties.vue index 8408b26..98455a9 100644 --- a/frontend/src/components/properties/CurrentDateProperties.vue +++ b/frontend/src/components/properties/CurrentDateProperties.vue @@ -1,73 +1,40 @@ diff --git a/frontend/src/components/properties/ImageProperties.vue b/frontend/src/components/properties/ImageProperties.vue index 0ee4eef..bd2f8ee 100644 --- a/frontend/src/components/properties/ImageProperties.vue +++ b/frontend/src/components/properties/ImageProperties.vue @@ -1,28 +1,28 @@ diff --git a/frontend/src/components/properties/RichTextProperties.vue b/frontend/src/components/properties/RichTextProperties.vue index 96d2e0d..29dbe39 100644 --- a/frontend/src/components/properties/RichTextProperties.vue +++ b/frontend/src/components/properties/RichTextProperties.vue @@ -1,27 +1,19 @@ diff --git a/frontend/src/components/properties/shared/PropSelect.vue b/frontend/src/components/properties/shared/PropSelect.vue new file mode 100644 index 0000000..99a82da --- /dev/null +++ b/frontend/src/components/properties/shared/PropSelect.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/components/properties/shared/PropTextStyleGroup.vue b/frontend/src/components/properties/shared/PropTextStyleGroup.vue new file mode 100644 index 0000000..3f94673 --- /dev/null +++ b/frontend/src/components/properties/shared/PropTextStyleGroup.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/src/components/properties/table/TableColumnEditor.vue b/frontend/src/components/properties/table/TableColumnEditor.vue new file mode 100644 index 0000000..356e481 --- /dev/null +++ b/frontend/src/components/properties/table/TableColumnEditor.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/frontend/src/components/properties/table/TableStyleEditor.vue b/frontend/src/components/properties/table/TableStyleEditor.vue new file mode 100644 index 0000000..1dd572b --- /dev/null +++ b/frontend/src/components/properties/table/TableStyleEditor.vue @@ -0,0 +1,366 @@ + + + + + diff --git a/frontend/src/composables/usePropertyUpdate.ts b/frontend/src/composables/usePropertyUpdate.ts new file mode 100644 index 0000000..df1df09 --- /dev/null +++ b/frontend/src/composables/usePropertyUpdate.ts @@ -0,0 +1,30 @@ +import { useTemplateStore } from '../stores/template' +import { useEditorStore } from '../stores/editor' +import type { TemplateElement } from '../core/types' + +export function usePropertyUpdate(elementRef: () => TemplateElement) { + const templateStore = useTemplateStore() + const editorStore = useEditorStore() + + function update(updates: Partial) { + const id = editorStore.selectedElementId + if (!id) return + templateStore.updateElement(id, updates) + } + + function updateStyle(key: string, value: unknown) { + update({ style: { ...elementRef().style, [key]: value } } as Partial) + } + + function updateNested( + field: string, + key: string, + value: unknown, + defaults: Record = {}, + ) { + const current = (elementRef() as any)[field] ?? defaults + update({ [field]: { ...current, [key]: value } } as any) + } + + return { update, updateStyle, updateNested } +} diff --git a/frontend/src/styles/toolbar.css b/frontend/src/styles/toolbar.css new file mode 100644 index 0000000..5a9f6c3 --- /dev/null +++ b/frontend/src/styles/toolbar.css @@ -0,0 +1,119 @@ +.et { + display: flex; + align-items: center; + gap: 2px; + background: #1e293b; + border-radius: 6px; + padding: 3px 4px; + box-shadow: + 0 2px 8px rgba(0, 0, 0, 0.25), + 0 0 0 1px rgba(255, 255, 255, 0.06); + pointer-events: auto; + white-space: nowrap; +} + +.et__group { + display: flex; + align-items: center; + gap: 1px; +} + +.et__sep { + width: 1px; + height: 16px; + background: #334155; + margin: 0 2px; + flex-shrink: 0; +} + +.et__btn { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + border-radius: 4px; + background: transparent; + color: #94a3b8; + cursor: pointer; + padding: 0; + transition: + background 0.1s, + color 0.1s; +} + +.et__btn:hover { + background: #334155; + color: #e2e8f0; +} + +.et__btn--active { + background: #3b82f6; + color: white; +} + +.et__btn--active:hover { + background: #2563eb; +} + +.et__group--gap { + gap: 3px; +} + +.et__gap-icon { + color: #64748b; + flex-shrink: 0; +} + +.et__num { + width: 32px; + height: 22px; + border: 1px solid #334155; + border-radius: 4px; + background: #0f172a; + color: #e2e8f0; + text-align: center; + font-size: 11px; + font-family: inherit; + padding: 0; + outline: none; + -moz-appearance: textfield; +} + +.et__num::-webkit-inner-spin-button, +.et__num::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.et__num:focus { + border-color: #3b82f6; +} + +.et__color-wrap { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 4px; + cursor: pointer; + position: relative; + color: #94a3b8; + transition: background 0.1s; +} + +.et__color-wrap:hover { + background: #334155; + color: #e2e8f0; +} + +.et__color { + position: absolute; + inset: 0; + opacity: 0; + cursor: pointer; + width: 100%; + height: 100%; +}