refactor & improvements

This commit is contained in:
2026-03-29 22:35:57 +03:00
parent 5a51d3b4c3
commit 1675d2611c
68 changed files with 4803 additions and 7387 deletions

View File

@@ -0,0 +1,152 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import { useSchemaStore } from '../../stores/schema'
import type { BarcodeElement, BarcodeFormat, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: BarcodeElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
const schemaStore = useSchemaStore()
function update(updates: Partial<TemplateElement>) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, updates)
}
function updateStyle(key: string, value: unknown) {
update({ style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
const barcodeDefaults: Record<BarcodeFormat, string> = {
qr: 'https://example.com',
ean13: '5901234123457',
ean8: '96385074',
code128: 'DREPORT-001',
code39: 'DREPORT',
}
function eanCheckDigit(data: string): number {
let sum = 0
for (let i = 0; i < data.length; i++) {
const d = parseInt(data[i])
sum += d * (i % 2 === 0 ? 1 : 3)
}
return (10 - (sum % 10)) % 10
}
function validateBarcode(format: BarcodeFormat, value: string): boolean {
if (!value) return false
switch (format) {
case 'ean13':
if (!/^\d{13}$/.test(value)) return false
return eanCheckDigit(value.slice(0, 12)) === parseInt(value[12])
case 'ean8':
if (!/^\d{8}$/.test(value)) return false
return eanCheckDigit(value.slice(0, 7)) === parseInt(value[7])
case 'code39':
return /^[A-Z0-9\-. $/+%]+$/i.test(value)
case 'code128':
return value.length > 0 && [...value].every(c => c.charCodeAt(0) < 128)
case 'qr':
return value.length > 0
default:
return value.length > 0
}
}
const barcodeInputValue = ref('')
const barcodeInputInvalid = ref(false)
watch(() => props.element.value ?? '', (val) => {
barcodeInputValue.value = val
barcodeInputInvalid.value = false
}, { immediate: true })
function onBarcodeValueInput(e: Event) {
const val = (e.target as HTMLInputElement).value
barcodeInputValue.value = val
if (validateBarcode(props.element.format, val)) {
barcodeInputInvalid.value = false
update({ value: val } as any)
} else {
barcodeInputInvalid.value = true
}
}
function onBarcodeFormatChange(newFormat: BarcodeFormat) {
const currentValue = props.element.value ?? ''
if (validateBarcode(newFormat, currentValue)) {
update({ format: newFormat } as any)
} else {
const defaultVal = barcodeDefaults[newFormat]
barcodeInputValue.value = defaultVal
barcodeInputInvalid.value = false
update({ format: newFormat, value: defaultVal } as any)
}
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Barkod Ayarlari</div>
<div class="prop-row">
<label class="prop-label">Format</label>
<select class="prop-input prop-select"
:value="element.format"
@change="(e) => onBarcodeFormatChange((e.target as HTMLSelectElement).value as BarcodeFormat)">
<option value="qr">QR Kod</option>
<option value="ean13">EAN-13</option>
<option value="ean8">EAN-8</option>
<option value="code128">Code 128</option>
<option value="code39">Code 39</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Deger</label>
<input class="prop-input" type="text"
:class="{ 'prop-input--invalid': barcodeInputInvalid }"
:value="barcodeInputValue"
@input="onBarcodeValueInput" />
</div>
<div class="prop-row">
<label class="prop-label">Renk</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.color ?? '#000000'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.color" class="prop-clear" @click="updateStyle('color', undefined)">x</button>
</div>
</div>
<div v-if="element.format !== 'qr'" class="prop-row">
<label class="prop-label">Metin Goster</label>
<input type="checkbox"
:checked="element.style.includeText ?? (element.format === 'ean13' || element.format === 'ean8')"
@change="(e) => updateStyle('includeText', (e.target as HTMLInputElement).checked)" />
</div>
<div v-if="schemaStore.scalarFields.length > 0" class="prop-row">
<label class="prop-label">Veri Baglama</label>
<select class="prop-input prop-select"
:value="element.binding?.path ?? ''"
@change="(e) => {
const val = (e.target as HTMLSelectElement).value
if (val) {
update({ binding: { type: 'scalar', path: val } } as any)
} else {
update({ binding: undefined } as any)
}
}">
<option value="">Yok (statik deger)</option>
<option
v-for="field in schemaStore.scalarFields"
:key="field.path"
:value="field.path"
>{{ field.title }} ({{ field.path }})</option>
</select>
</div>
</div>
</template>

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import PaddingBox from './PaddingBox.vue'
import type { ContainerElement, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: ContainerElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
function update(updates: Partial<TemplateElement>) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, updates)
}
function updateStyle(key: string, value: unknown) {
update({ style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Container Ayarlari</div>
<div class="prop-row">
<label class="prop-label">Yon</label>
<select class="prop-input prop-select"
:value="element.direction"
@change="(e) => update({ direction: (e.target as HTMLSelectElement).value } as any)">
<option value="column">Dikey</option>
<option value="row">Yatay</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Bosluk (mm)</label>
<input class="prop-input" type="number" step="1" min="0"
:value="element.gap"
@input="(e) => update({ gap: parseFloat((e.target as HTMLInputElement).value) || 0 } as any)" />
</div>
<div class="prop-row">
<label class="prop-label">{{ element.direction === 'column' ? 'Yatay Hizalama' : 'Dikey Hizalama' }}</label>
<select class="prop-input prop-select"
:value="element.align"
@change="(e) => update({ align: (e.target as HTMLSelectElement).value } as any)">
<option value="start">{{ element.direction === 'column' ? 'Sol' : 'Ust' }}</option>
<option value="center">Orta</option>
<option value="end">{{ element.direction === 'column' ? 'Sag' : 'Alt' }}</option>
<option value="stretch">Esnet</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">{{ element.direction === 'column' ? 'Dikey Dagilim' : 'Yatay Dagilim' }}</label>
<select class="prop-input prop-select"
:value="element.justify"
@change="(e) => update({ justify: (e.target as HTMLSelectElement).value } as any)">
<option value="start">{{ element.direction === 'column' ? 'Ust' : 'Sol' }}</option>
<option value="center">Orta</option>
<option value="end">{{ element.direction === 'column' ? 'Alt' : 'Sag' }}</option>
<option value="space-between">Esit Aralik</option>
</select>
</div>
<div class="prop-section__subtitle">Padding (mm)</div>
<PaddingBox
:top="element.padding.top"
:right="element.padding.right"
:bottom="element.padding.bottom"
:left="element.padding.left"
@update="(side, value) => update({ padding: { ...element.padding, [side]: value } } as any)"
/>
<div class="prop-section__subtitle">Stil</div>
<div class="prop-row">
<label class="prop-label">Arka plan</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.backgroundColor ?? '#ffffff'"
@input="(e) => updateStyle('backgroundColor', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.backgroundColor" class="prop-clear" @click="updateStyle('backgroundColor', undefined)">x</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik (mm)</label>
<input class="prop-input" type="number" step="0.1" min="0"
:value="element.style.borderWidth ?? 0"
@input="(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik rengi</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.borderColor ?? '#000000'"
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.borderColor" class="prop-clear" @click="updateStyle('borderColor', undefined)">x</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik stili</label>
<select class="prop-input prop-select"
:value="element.style.borderStyle ?? 'solid'"
@change="(e) => updateStyle('borderStyle', (e.target as HTMLSelectElement).value)">
<option value="solid">Duz</option>
<option value="dashed">Kesikli</option>
<option value="dotted">Noktali</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Radius (mm)</label>
<input class="prop-input" type="number" step="0.5" min="0"
:value="element.style.borderRadius ?? 0"
@input="(e) => updateStyle('borderRadius', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
</div>
</template>

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import type { ImageElement, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: ImageElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
function update(updates: Partial<TemplateElement>) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, updates)
}
function updateStyle(key: string, value: unknown) {
update({ style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
function onImageFileSelect(e: Event) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = () => {
update({ src: reader.result as string } as Partial<TemplateElement>)
}
reader.readAsDataURL(file)
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Gorsel</div>
<div class="prop-row">
<label class="prop-label">Kaynak</label>
<label class="prop-file-btn">
Dosya Sec
<input type="file" accept="image/*" style="display: none" @change="onImageFileSelect" />
</label>
</div>
<div v-if="element.src" class="prop-row">
<label class="prop-label">Onizleme</label>
<img :src="element.src" class="prop-image-preview" />
</div>
<div v-if="element.src" class="prop-row">
<label class="prop-label"></label>
<button class="prop-clear" @click="update({ src: undefined } as any)">Gorseli kaldir</button>
</div>
<div class="prop-row">
<label class="prop-label">Sigdirma</label>
<select class="prop-input prop-select"
:value="element.style.objectFit ?? 'contain'"
@change="(e) => updateStyle('objectFit', (e.target as HTMLSelectElement).value)">
<option value="contain">Sigdir</option>
<option value="cover">Kap</option>
<option value="stretch">Esnet</option>
</select>
</div>
</div>
</template>

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import type { LineElement, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: LineElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
function updateStyle(key: string, value: unknown) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, { style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Cizgi Stili</div>
<div class="prop-row">
<label class="prop-label">Kalinlik (mm)</label>
<input class="prop-input" type="number" step="0.1" min="0.1"
:value="element.style.strokeWidth ?? 0.5"
@input="(e) => updateStyle('strokeWidth', parseFloat((e.target as HTMLInputElement).value) || 0.5)" />
</div>
<div class="prop-row">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="element.style.strokeColor ?? '#000000'"
@input="(e) => updateStyle('strokeColor', (e.target as HTMLInputElement).value)" />
</div>
</div>
</template>

View File

@@ -0,0 +1,59 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import type { PageNumberElement, TextStyle, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: PageNumberElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
function update(updates: Partial<TemplateElement>) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, updates)
}
function updateStyle(key: string, value: unknown) {
update({ style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Sayfa Numarasi</div>
<div class="prop-row">
<label class="prop-label">Format</label>
<select class="prop-input prop-select"
:value="element.format ?? '{current} / {total}'"
@change="(e) => update({ format: (e.target as HTMLSelectElement).value } as any)">
<option value="{current} / {total}">1 / 5</option>
<option value="{current}">1</option>
<option value="Sayfa {current}">Sayfa 1</option>
<option value="Sayfa {current} / {total}">Sayfa 1 / 5</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.style as TextStyle).fontSize ?? 10"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</div>
<div class="prop-row">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(element.style as TextStyle).color ?? '#666666'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).align ?? 'center'"
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
<option value="left">Sol</option>
<option value="center">Orta</option>
<option value="right">Sag</option>
</select>
</div>
</div>
</template>

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import type { TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: TemplateElement }>()
const templateStore = useTemplateStore()
function togglePositioning() {
if (props.element.position.type === 'flow') {
templateStore.updateElementPosition(props.element.id, { type: 'absolute', x: 0, y: 0 })
} else {
templateStore.updateElementPosition(props.element.id, { type: 'flow' })
}
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Pozisyon</div>
<div class="prop-row">
<label class="prop-label">Mod</label>
<select class="prop-input prop-select" :value="element.position.type" @change="togglePositioning">
<option value="flow">Flow</option>
<option value="absolute">Absolute</option>
</select>
</div>
<template v-if="element.position.type === 'absolute'">
<div class="prop-row">
<label class="prop-label">X (mm)</label>
<input class="prop-input" type="number" step="0.5"
:value="element.position.x"
@input="(e) => templateStore.updateElementPosition(element.id, { type: 'absolute', x: parseFloat((e.target as HTMLInputElement).value) || 0, y: (element.position as any).y ?? 0 })" />
</div>
<div class="prop-row">
<label class="prop-label">Y (mm)</label>
<input class="prop-input" type="number" step="0.5"
:value="element.position.y"
@input="(e) => templateStore.updateElementPosition(element.id, { type: 'absolute', x: (element.position as any).x ?? 0, y: parseFloat((e.target as HTMLInputElement).value) || 0 })" />
</div>
</template>
</div>
</template>

View File

@@ -0,0 +1,242 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import { useSchemaStore } from '../../stores/schema'
import { sz } from '../../core/types'
import { schemaFormatToFormatType, defaultAlignForSchema } from '../../core/schema-parser'
import type { RepeatingTableElement, TableColumn, FormatType, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: RepeatingTableElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
const schemaStore = useSchemaStore()
function update(updates: Partial<TemplateElement>) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, updates)
}
let colIdCounter = Date.now()
function nextColId() {
return `col_${(++colIdCounter).toString(36)}`
}
function updateTableDataSource(path: string) {
const itemFields = schemaStore.getArrayItemFields(path)
if (itemFields.length > 0) {
const columns: TableColumn[] = itemFields.map(field => ({
id: nextColId(),
field: field.key,
title: field.title,
width: sz.auto(),
align: defaultAlignForSchema(field),
format: schemaFormatToFormatType(field.format),
}))
update({
dataSource: { type: 'array', path },
columns,
} as Partial<TemplateElement>)
} else {
update({ dataSource: { type: 'array', path } } as Partial<TemplateElement>)
}
}
function updateTableStyle(key: string, value: unknown) {
const newStyle = { ...props.element.style, [key]: value }
if (value === undefined || value === '') delete (newStyle as Record<string, unknown>)[key]
update({ style: newStyle } as Partial<TemplateElement>)
}
function updateColumn(colId: string, updates: Partial<TableColumn>) {
const columns = props.element.columns.map(c => c.id === colId ? { ...c, ...updates } : c)
update({ columns } as Partial<TemplateElement>)
}
function addColumn() {
const newCol: TableColumn = {
id: nextColId(),
field: 'alan',
title: 'Yeni Sutun',
width: sz.auto(),
align: 'left',
}
update({ columns: [...props.element.columns, newCol] } as Partial<TemplateElement>)
}
function removeColumn(colId: string) {
update({ columns: props.element.columns.filter(c => c.id !== colId) } as Partial<TemplateElement>)
}
function moveColumn(colId: string, direction: -1 | 1) {
const cols = [...props.element.columns]
const idx = cols.findIndex(c => c.id === colId)
const newIdx = idx + direction
if (newIdx < 0 || newIdx >= cols.length) return
;[cols[idx], cols[newIdx]] = [cols[newIdx], cols[idx]]
update({ columns: cols } as Partial<TemplateElement>)
}
const tableItemFields = computed(() => {
return schemaStore.getArrayItemFields(props.element.dataSource.path)
})
</script>
<template>
<!-- Data source -->
<div class="prop-section">
<div class="prop-section__title">Veri Kaynagi</div>
<div class="prop-row">
<label class="prop-label">Kaynak</label>
<select class="prop-input prop-select"
:value="element.dataSource.path"
@change="(e) => updateTableDataSource((e.target as HTMLSelectElement).value)">
<option value="" disabled>Secin...</option>
<option
v-for="arr in schemaStore.arrayFields"
:key="arr.path"
:value="arr.path"
>{{ arr.title }} ({{ arr.path }})</option>
</select>
</div>
</div>
<!-- Columns -->
<div class="prop-section">
<div class="prop-section__title">
Sutunlar
<button class="prop-add-btn" @click="addColumn">+</button>
</div>
<div
v-for="col in element.columns"
:key="col.id"
class="prop-column-card"
>
<div class="prop-column-header">
<span class="prop-column-title">{{ col.title || col.field }}</span>
<div class="prop-column-actions">
<button class="prop-icon-btn" @click="moveColumn(col.id, -1)" title="Yukari">&#8593;</button>
<button class="prop-icon-btn" @click="moveColumn(col.id, 1)" title="Asagi">&#8595;</button>
<button class="prop-icon-btn prop-icon-btn--danger" @click="removeColumn(col.id)" title="Sil">x</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Baslik</label>
<input class="prop-input" type="text" :value="col.title"
@change="(e) => updateColumn(col.id, { title: (e.target as HTMLInputElement).value })" />
</div>
<div class="prop-row">
<label class="prop-label">Alan</label>
<select v-if="tableItemFields.length > 0" class="prop-input prop-select" :value="col.field"
@change="(e) => {
const field = (e.target as HTMLSelectElement).value
const node = tableItemFields.find(f => f.key === field)
if (node) {
updateColumn(col.id, {
field,
title: node.title,
align: defaultAlignForSchema(node),
format: schemaFormatToFormatType(node.format),
})
} else {
updateColumn(col.id, { field })
}
}">
<option v-for="f in tableItemFields" :key="f.key" :value="f.key">{{ f.title }} ({{ f.key }})</option>
</select>
<input v-else class="prop-input" type="text" :value="col.field"
@change="(e) => updateColumn(col.id, { field: (e.target as HTMLInputElement).value })" />
</div>
<div class="prop-row">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select" :value="col.align"
@change="(e) => updateColumn(col.id, { align: (e.target as HTMLSelectElement).value as 'left'|'center'|'right' })">
<option value="left">Sol</option>
<option value="center">Orta</option>
<option value="right">Sag</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Format</label>
<select class="prop-input prop-select" :value="col.format ?? ''"
@change="(e) => updateColumn(col.id, { format: ((e.target as HTMLSelectElement).value || undefined) as FormatType | undefined })">
<option value="">Yok</option>
<option value="currency">Para birimi</option>
<option value="number">Sayi</option>
<option value="date">Tarih</option>
<option value="percentage">Yuzde</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Genislik</label>
<select class="prop-input prop-select"
:value="col.width.type"
@change="(e) => {
const t = (e.target as HTMLSelectElement).value
if (t === 'auto') updateColumn(col.id, { width: { type: 'auto' } })
else if (t === 'fr') updateColumn(col.id, { width: { type: 'fr', value: 1 } })
else updateColumn(col.id, { width: { type: 'fixed', value: 30 } })
}">
<option value="auto">Otomatik</option>
<option value="fixed">Sabit (mm)</option>
<option value="fr">Oran (fr)</option>
</select>
</div>
<div v-if="col.width.type === 'fixed'" class="prop-row">
<label class="prop-label">mm</label>
<input class="prop-input" type="number" step="1" min="5"
:value="(col.width as any).value"
@change="(e) => updateColumn(col.id, { width: { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 30 } })" />
</div>
</div>
</div>
<!-- Table style -->
<div class="prop-section">
<div class="prop-section__title">Tablo Stili</div>
<div class="prop-row">
<label class="prop-label">Yazi boyutu</label>
<input class="prop-input" type="number" step="1" min="6"
:value="element.style.fontSize ?? 10"
@input="(e) => updateTableStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
</div>
<div class="prop-row">
<label class="prop-label">Header bg</label>
<input class="prop-input prop-color" type="color"
:value="element.style.headerBg ?? '#f0f0f0'"
@input="(e) => updateTableStyle('headerBg', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<label class="prop-label">Header renk</label>
<input class="prop-input prop-color" type="color"
:value="element.style.headerColor ?? '#000000'"
@input="(e) => updateTableStyle('headerColor', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<label class="prop-label">Zebra tek</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.zebraOdd ?? '#fafafa'"
@input="(e) => updateTableStyle('zebraOdd', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.zebraOdd" class="prop-clear" @click="updateTableStyle('zebraOdd', undefined)">x</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik rengi</label>
<div class="prop-row-inline">
<input class="prop-input prop-color" type="color"
:value="element.style.borderColor ?? '#cccccc'"
@input="(e) => updateTableStyle('borderColor', (e.target as HTMLInputElement).value)" />
<button v-if="element.style.borderColor" class="prop-clear" @click="updateTableStyle('borderColor', undefined)">x</button>
</div>
</div>
<div class="prop-row">
<label class="prop-label">Kenarlik (mm)</label>
<input class="prop-input" type="number" step="0.1" min="0"
:value="element.style.borderWidth ?? 0.5"
@input="(e) => updateTableStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
</div>
</div>
</template>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import type { TemplateElement, SizeValue } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: TemplateElement }>()
const templateStore = useTemplateStore()
function updateSize(axis: 'width' | 'height', sv: SizeValue) {
templateStore.updateElementSize(props.element.id, { [axis]: sv })
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Boyut</div>
<div class="prop-row">
<label class="prop-label">Genislik</label>
<select class="prop-input prop-select"
:value="element.size.width.type"
@change="(e) => {
const t = (e.target as HTMLSelectElement).value
if (t === 'auto') updateSize('width', { type: 'auto' })
else if (t === 'fr') updateSize('width', { type: 'fr', value: 1 })
else updateSize('width', { type: 'fixed', value: 50 })
}">
<option value="auto">Otomatik</option>
<option value="fixed">Sabit (mm)</option>
<option value="fr">Oran (fr)</option>
</select>
</div>
<div v-if="element.size.width.type === 'fixed'" class="prop-row">
<label class="prop-label">mm</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.size.width as any).value"
@input="(e) => updateSize('width', { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 10 })" />
</div>
<div v-if="element.size.width.type === 'fr'" class="prop-row">
<label class="prop-label">fr</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.size.width as any).value"
@input="(e) => updateSize('width', { type: 'fr', value: parseFloat((e.target as HTMLInputElement).value) || 1 })" />
</div>
<div class="prop-row">
<label class="prop-label">Yukseklik</label>
<select class="prop-input prop-select"
:value="element.size.height.type"
@change="(e) => {
const t = (e.target as HTMLSelectElement).value
if (t === 'auto') updateSize('height', { type: 'auto' })
else if (t === 'fr') updateSize('height', { type: 'fr', value: 1 })
else updateSize('height', { type: 'fixed', value: 20 })
}">
<option value="auto">Otomatik</option>
<option value="fixed">Sabit (mm)</option>
<option value="fr">Oran (fr)</option>
</select>
</div>
<div v-if="element.size.height.type === 'fixed'" class="prop-row">
<label class="prop-label">mm</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.size.height as any).value"
@input="(e) => updateSize('height', { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 10 })" />
</div>
</div>
</template>

View File

@@ -0,0 +1,65 @@
<script setup lang="ts">
import { useTemplateStore } from '../../stores/template'
import { useEditorStore } from '../../stores/editor'
import type { StaticTextElement, TextStyle, TemplateElement } from '../../core/types'
import '../../styles/properties.css'
const props = defineProps<{ element: TemplateElement }>()
const templateStore = useTemplateStore()
const editorStore = useEditorStore()
function update(updates: Partial<TemplateElement>) {
const id = editorStore.selectedElementId
if (!id) return
templateStore.updateElement(id, updates)
}
function updateStyle(key: string, value: unknown) {
update({ style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
}
</script>
<template>
<div class="prop-section">
<div class="prop-section__title">Metin Stili</div>
<div v-if="element.type === 'static_text'" class="prop-row">
<label class="prop-label">Metin</label>
<input class="prop-input" type="text"
:value="(element as StaticTextElement).content"
@input="(e) => update({ content: (e.target as HTMLInputElement).value } as any)" />
</div>
<div class="prop-row">
<label class="prop-label">Boyut (pt)</label>
<input class="prop-input" type="number" step="1" min="1"
:value="(element.style as TextStyle).fontSize ?? 11"
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
</div>
<div class="prop-row">
<label class="prop-label">Kalinlik</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
@change="(e) => updateStyle('fontWeight', (e.target as HTMLSelectElement).value)">
<option value="normal">Normal</option>
<option value="bold">Kalin</option>
</select>
</div>
<div class="prop-row">
<label class="prop-label">Renk</label>
<input class="prop-input prop-color" type="color"
:value="(element.style as TextStyle).color ?? '#000000'"
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
</div>
<div class="prop-row">
<label class="prop-label">Hizalama</label>
<select class="prop-input prop-select"
:value="(element.style as TextStyle).align ?? 'left'"
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
<option value="left">Sol</option>
<option value="center">Orta</option>
<option value="right">Sag</option>
</select>
</div>
</div>
</template>