mirror of
https://github.com/duhanbalci/dreport.git
synced 2026-07-01 18:39:16 +00:00
format
This commit is contained in:
@@ -94,9 +94,13 @@ if (savedSchema) {
|
|||||||
currentSchema.value = savedSchema
|
currentSchema.value = savedSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(currentSchema, (val) => {
|
watch(
|
||||||
localStorage.setItem(SCHEMA_STORAGE_KEY, JSON.stringify(val))
|
currentSchema,
|
||||||
}, { deep: true })
|
(val) => {
|
||||||
|
localStorage.setItem(SCHEMA_STORAGE_KEY, JSON.stringify(val))
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
// --- Sample Invoice Data ---
|
// --- Sample Invoice Data ---
|
||||||
|
|
||||||
@@ -125,10 +129,38 @@ const sampleData: Record<string, unknown> = {
|
|||||||
telefon: '+90 216 444 0018',
|
telefon: '+90 216 444 0018',
|
||||||
},
|
},
|
||||||
kalemler: [
|
kalemler: [
|
||||||
{ siraNo: 1, adi: 'Web Uygulama Gelistirme', miktar: 1, birim: 'Adet', birimFiyat: 45000, tutar: 45000 },
|
{
|
||||||
{ siraNo: 2, adi: 'Mobil Uygulama Gelistirme', miktar: 1, birim: 'Adet', birimFiyat: 35000, tutar: 35000 },
|
siraNo: 1,
|
||||||
{ siraNo: 3, adi: 'UI/UX Tasarim Hizmeti', miktar: 40, birim: 'Saat', birimFiyat: 750, tutar: 30000 },
|
adi: 'Web Uygulama Gelistirme',
|
||||||
{ siraNo: 4, adi: 'Sunucu Bakim Sozlesmesi (Yillik)', miktar: 1, birim: 'Adet', birimFiyat: 12000, tutar: 12000 },
|
miktar: 1,
|
||||||
|
birim: 'Adet',
|
||||||
|
birimFiyat: 45000,
|
||||||
|
tutar: 45000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
siraNo: 2,
|
||||||
|
adi: 'Mobil Uygulama Gelistirme',
|
||||||
|
miktar: 1,
|
||||||
|
birim: 'Adet',
|
||||||
|
birimFiyat: 35000,
|
||||||
|
tutar: 35000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
siraNo: 3,
|
||||||
|
adi: 'UI/UX Tasarim Hizmeti',
|
||||||
|
miktar: 40,
|
||||||
|
birim: 'Saat',
|
||||||
|
birimFiyat: 750,
|
||||||
|
tutar: 30000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
siraNo: 4,
|
||||||
|
adi: 'Sunucu Bakim Sozlesmesi (Yillik)',
|
||||||
|
miktar: 1,
|
||||||
|
birim: 'Adet',
|
||||||
|
birimFiyat: 12000,
|
||||||
|
tutar: 12000,
|
||||||
|
},
|
||||||
{ siraNo: 5, adi: 'SSL Sertifikasi', miktar: 3, birim: 'Adet', birimFiyat: 500, tutar: 1500 },
|
{ siraNo: 5, adi: 'SSL Sertifikasi', miktar: 3, birim: 'Adet', birimFiyat: 500, tutar: 1500 },
|
||||||
],
|
],
|
||||||
toplamlar: {
|
toplamlar: {
|
||||||
@@ -370,10 +402,30 @@ const defaultInvoiceTemplate: Template = {
|
|||||||
columns: [
|
columns: [
|
||||||
{ id: 'col_sira', field: 'siraNo', title: '#', width: sz.fixed(10), align: 'center' },
|
{ id: 'col_sira', field: 'siraNo', title: '#', width: sz.fixed(10), align: 'center' },
|
||||||
{ id: 'col_adi', field: 'adi', title: 'Urun / Hizmet', width: sz.fr(), align: 'left' },
|
{ id: 'col_adi', field: 'adi', title: 'Urun / Hizmet', width: sz.fr(), align: 'left' },
|
||||||
{ id: 'col_miktar', field: 'miktar', title: 'Miktar', width: sz.fixed(18), align: 'right' },
|
{
|
||||||
|
id: 'col_miktar',
|
||||||
|
field: 'miktar',
|
||||||
|
title: 'Miktar',
|
||||||
|
width: sz.fixed(18),
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
{ id: 'col_birim', field: 'birim', title: 'Birim', width: sz.fixed(18), align: 'center' },
|
{ id: 'col_birim', field: 'birim', title: 'Birim', width: sz.fixed(18), align: 'center' },
|
||||||
{ id: 'col_fiyat', field: 'birimFiyat', title: 'Birim Fiyat', width: sz.fixed(28), align: 'right', format: 'currency' as const },
|
{
|
||||||
{ id: 'col_tutar', field: 'tutar', title: 'Tutar', width: sz.fixed(28), align: 'right', format: 'currency' as const },
|
id: 'col_fiyat',
|
||||||
|
field: 'birimFiyat',
|
||||||
|
title: 'Birim Fiyat',
|
||||||
|
width: sz.fixed(28),
|
||||||
|
align: 'right',
|
||||||
|
format: 'currency' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'col_tutar',
|
||||||
|
field: 'tutar',
|
||||||
|
title: 'Tutar',
|
||||||
|
width: sz.fixed(28),
|
||||||
|
align: 'right',
|
||||||
|
format: 'currency' as const,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
style: {
|
style: {
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
@@ -486,12 +538,16 @@ function loadFromLocalStorage(): Template | null {
|
|||||||
const template = ref<Template>(loadFromLocalStorage() ?? structuredClone(defaultInvoiceTemplate))
|
const template = ref<Template>(loadFromLocalStorage() ?? structuredClone(defaultInvoiceTemplate))
|
||||||
|
|
||||||
let saveTimeout: ReturnType<typeof setTimeout> | null = null
|
let saveTimeout: ReturnType<typeof setTimeout> | null = null
|
||||||
watch(template, (val) => {
|
watch(
|
||||||
if (saveTimeout) clearTimeout(saveTimeout)
|
template,
|
||||||
saveTimeout = setTimeout(() => {
|
(val) => {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(val))
|
if (saveTimeout) clearTimeout(saveTimeout)
|
||||||
}, 500)
|
saveTimeout = setTimeout(() => {
|
||||||
}, { deep: true })
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(val))
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
// --- Editor ref ---
|
// --- Editor ref ---
|
||||||
|
|
||||||
@@ -626,36 +682,120 @@ function resetTemplate() {
|
|||||||
<h1>dreport</h1>
|
<h1>dreport</h1>
|
||||||
<span class="app-header__subtitle">Belge Tasarim Araci</span>
|
<span class="app-header__subtitle">Belge Tasarim Araci</span>
|
||||||
<div style="flex: 1"></div>
|
<div style="flex: 1"></div>
|
||||||
<input ref="fileInputRef" type="file" accept=".json" style="display: none" @change="onImportFile" />
|
<input
|
||||||
<input ref="schemaFileInputRef" type="file" accept=".json" style="display: none" @change="onSchemaImportFile" />
|
ref="fileInputRef"
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
style="display: none"
|
||||||
|
@change="onImportFile"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
ref="schemaFileInputRef"
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
style="display: none"
|
||||||
|
@change="onSchemaImportFile"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Template operations -->
|
<!-- Template operations -->
|
||||||
<button class="header-btn header-btn--secondary" @click="resetTemplate" title="Sifirla">
|
<button class="header-btn header-btn--secondary" @click="resetTemplate" title="Sifirla">
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8a6 6 0 0 1 10.2-4.3L14 2v4h-4l1.7-1.7A4.5 4.5 0 1 0 12.5 8" /><path d="M12.5 8a4.5 4.5 0 0 1-8.2 2.5" /></svg>
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M2 8a6 6 0 0 1 10.2-4.3L14 2v4h-4l1.7-1.7A4.5 4.5 0 1 0 12.5 8" />
|
||||||
|
<path d="M12.5 8a4.5 4.5 0 0 1-8.2 2.5" />
|
||||||
|
</svg>
|
||||||
Sifirla
|
Sifirla
|
||||||
</button>
|
</button>
|
||||||
<button class="header-btn header-btn--secondary" @click="triggerImport" title="Sablon Yukle">
|
<button class="header-btn header-btn--secondary" @click="triggerImport" title="Sablon Yukle">
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 10V2m0 0L5 5m3-3 3 3" /><path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" /></svg>
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M8 10V2m0 0L5 5m3-3 3 3" />
|
||||||
|
<path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" />
|
||||||
|
</svg>
|
||||||
Yukle
|
Yukle
|
||||||
</button>
|
</button>
|
||||||
<button class="header-btn header-btn--secondary" @click="exportTemplate" title="Sablon Kaydet">
|
<button
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v8m0 0 3-3m-3 3L5 7" /><path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" /></svg>
|
class="header-btn header-btn--secondary"
|
||||||
|
@click="exportTemplate"
|
||||||
|
title="Sablon Kaydet"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M8 2v8m0 0 3-3m-3 3L5 7" />
|
||||||
|
<path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" />
|
||||||
|
</svg>
|
||||||
Kaydet
|
Kaydet
|
||||||
</button>
|
</button>
|
||||||
<button class="header-btn header-btn--secondary" @click="exportBundle" title="Sablon + Schema Birlikte Kaydet">
|
<button
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="1" width="12" height="14" rx="1.5" /><path d="M5 4h6M5 7h6M5 10h4" /></svg>
|
class="header-btn header-btn--secondary"
|
||||||
|
@click="exportBundle"
|
||||||
|
title="Sablon + Schema Birlikte Kaydet"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect x="2" y="1" width="12" height="14" rx="1.5" />
|
||||||
|
<path d="M5 4h6M5 7h6M5 10h4" />
|
||||||
|
</svg>
|
||||||
Paket
|
Paket
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="header-divider"></div>
|
<div class="header-divider"></div>
|
||||||
|
|
||||||
<!-- Schema operations -->
|
<!-- Schema operations -->
|
||||||
<button class="header-btn header-btn--secondary" @click="triggerSchemaImport" title="Schema Yukle">
|
<button
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 10V2m0 0L5 5m3-3 3 3" /><path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" /></svg>
|
class="header-btn header-btn--secondary"
|
||||||
|
@click="triggerSchemaImport"
|
||||||
|
title="Schema Yukle"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M8 10V2m0 0L5 5m3-3 3 3" />
|
||||||
|
<path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" />
|
||||||
|
</svg>
|
||||||
Schema
|
Schema
|
||||||
</button>
|
</button>
|
||||||
<button class="header-btn header-btn--secondary" @click="exportSchema" title="Schema Kaydet">
|
<button class="header-btn header-btn--secondary" @click="exportSchema" title="Schema Kaydet">
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v8m0 0 3-3m-3 3L5 7" /><path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" /></svg>
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M8 2v8m0 0 3-3m-3 3L5 7" />
|
||||||
|
<path d="M2 10v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2" />
|
||||||
|
</svg>
|
||||||
Schema
|
Schema
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -663,7 +803,17 @@ function resetTemplate() {
|
|||||||
|
|
||||||
<!-- Output -->
|
<!-- Output -->
|
||||||
<button class="header-btn" :disabled="pdfLoading" @click="downloadPdf">
|
<button class="header-btn" :disabled="pdfLoading" @click="downloadPdf">
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="1" width="10" height="14" rx="1.5" /><path d="M6 5h4M6 8h4M6 11h2" /></svg>
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect x="3" y="1" width="10" height="14" rx="1.5" />
|
||||||
|
<path d="M6 5h4M6 8h4M6 11h2" />
|
||||||
|
</svg>
|
||||||
{{ pdfLoading ? 'Hazirlaniyor...' : 'PDF Onizle' }}
|
{{ pdfLoading ? 'Hazirlaniyor...' : 'PDF Onizle' }}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function schemaToLanguageInfo(): DexprLanguageInfo {
|
|||||||
const tree = schemaStore.schemaTree
|
const tree = schemaStore.schemaTree
|
||||||
for (const child of tree.children) {
|
for (const child of tree.children) {
|
||||||
if (child.type === 'object') {
|
if (child.type === 'object') {
|
||||||
const fields = child.children.map(f => ({
|
const fields = child.children.map((f) => ({
|
||||||
name: f.key,
|
name: f.key,
|
||||||
type: schemaToDexprType(f),
|
type: schemaToDexprType(f),
|
||||||
}))
|
}))
|
||||||
@@ -112,7 +112,9 @@ function schemaToLanguageInfo(): DexprLanguageInfo {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
function schemaToDexprType(node: SchemaNode): 'String' | 'Number' | 'Boolean' | 'Object' | 'NumberList' | 'StringList' {
|
function schemaToDexprType(
|
||||||
|
node: SchemaNode,
|
||||||
|
): 'String' | 'Number' | 'Boolean' | 'Object' | 'NumberList' | 'StringList' {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'number':
|
case 'number':
|
||||||
case 'integer':
|
case 'integer':
|
||||||
@@ -134,7 +136,7 @@ function createState(doc: string): EditorState {
|
|||||||
return EditorState.create({
|
return EditorState.create({
|
||||||
doc,
|
doc,
|
||||||
extensions: [
|
extensions: [
|
||||||
EditorView.updateListener.of(update => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
const val = update.state.doc.toString()
|
const val = update.state.doc.toString()
|
||||||
if (val !== props.modelValue) {
|
if (val !== props.modelValue) {
|
||||||
@@ -207,22 +209,29 @@ onBeforeUnmount(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Disaridan gelen deger degisikligi (undo/redo vs.)
|
// Disaridan gelen deger degisikligi (undo/redo vs.)
|
||||||
watch(() => props.modelValue, (newVal) => {
|
watch(
|
||||||
if (!view) return
|
() => props.modelValue,
|
||||||
const current = view.state.doc.toString()
|
(newVal) => {
|
||||||
if (current !== newVal) {
|
if (!view) return
|
||||||
view.dispatch({
|
const current = view.state.doc.toString()
|
||||||
changes: { from: 0, to: current.length, insert: newVal ?? '' },
|
if (current !== newVal) {
|
||||||
})
|
view.dispatch({
|
||||||
}
|
changes: { from: 0, to: current.length, insert: newVal ?? '' },
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Schema degisince editor'u yeniden olustur (autocomplete guncellenmeli)
|
// Schema degisince editor'u yeniden olustur (autocomplete guncellenmeli)
|
||||||
watch(langInfo, () => {
|
watch(
|
||||||
if (!view) return
|
langInfo,
|
||||||
const doc = view.state.doc.toString()
|
() => {
|
||||||
view.setState(createState(doc))
|
if (!view) return
|
||||||
}, { deep: true })
|
const doc = view.state.doc.toString()
|
||||||
|
view.setState(createState(doc))
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ import LayoutRenderer from './LayoutRenderer.vue'
|
|||||||
import InteractionOverlay from './InteractionOverlay.vue'
|
import InteractionOverlay from './InteractionOverlay.vue'
|
||||||
import RulerBar from './RulerBar.vue'
|
import RulerBar from './RulerBar.vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(
|
||||||
handleErrors?: boolean
|
defineProps<{
|
||||||
}>(), {
|
handleErrors?: boolean
|
||||||
handleErrors: true,
|
}>(),
|
||||||
})
|
{
|
||||||
|
handleErrors: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const templateStore = useTemplateStore()
|
const templateStore = useTemplateStore()
|
||||||
const editorStore = useEditorStore()
|
const editorStore = useEditorStore()
|
||||||
@@ -26,7 +29,14 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
// Layout engine — template + data'yı worker'a gönderir, WASM ile layout hesaplar
|
// Layout engine — template + data'yı worker'a gönderir, WASM ile layout hesaplar
|
||||||
const { layout, layoutMap, error, computing: compiling, generateBarcode, dispose } = useLayoutEngine(template, mockData, layoutVersion)
|
const {
|
||||||
|
layout,
|
||||||
|
layoutMap,
|
||||||
|
error,
|
||||||
|
computing: compiling,
|
||||||
|
generateBarcode,
|
||||||
|
dispose,
|
||||||
|
} = useLayoutEngine(template, mockData, layoutVersion)
|
||||||
|
|
||||||
// LayoutRenderer'ın barcode üretmek için kullanabileceği fonksiyon
|
// LayoutRenderer'ın barcode üretmek için kullanabileceği fonksiyon
|
||||||
provide('generateBarcode', generateBarcode)
|
provide('generateBarcode', generateBarcode)
|
||||||
@@ -86,7 +96,7 @@ let resizeObserver: ResizeObserver | null = null
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (containerRef.value) {
|
if (containerRef.value) {
|
||||||
resizeObserver = new ResizeObserver(entries => {
|
resizeObserver = new ResizeObserver((entries) => {
|
||||||
const entry = entries[0]
|
const entry = entries[0]
|
||||||
if (entry) containerWidth.value = entry.contentRect.width
|
if (entry) containerWidth.value = entry.contentRect.width
|
||||||
})
|
})
|
||||||
@@ -132,10 +142,7 @@ function onWheel(e: WheelEvent) {
|
|||||||
} else {
|
} else {
|
||||||
// İki parmak pan (touchpad) veya normal scroll
|
// İki parmak pan (touchpad) veya normal scroll
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
editorStore.setPan(
|
editorStore.setPan(editorStore.panX - e.deltaX, editorStore.panY - e.deltaY)
|
||||||
editorStore.panX - e.deltaX,
|
|
||||||
editorStore.panY - e.deltaY,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +176,16 @@ function applyZoom(delta: number, clientX: number, clientY: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
if (e.code === 'Space' && !e.repeat && !(e.target instanceof HTMLInputElement || e.target instanceof HTMLSelectElement || e.target instanceof HTMLTextAreaElement || (e.target as HTMLElement)?.isContentEditable)) {
|
if (
|
||||||
|
e.code === 'Space' &&
|
||||||
|
!e.repeat &&
|
||||||
|
!(
|
||||||
|
e.target instanceof HTMLInputElement ||
|
||||||
|
e.target instanceof HTMLSelectElement ||
|
||||||
|
e.target instanceof HTMLTextAreaElement ||
|
||||||
|
(e.target as HTMLElement)?.isContentEditable
|
||||||
|
)
|
||||||
|
) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
spaceHeld.value = true
|
spaceHeld.value = true
|
||||||
}
|
}
|
||||||
@@ -225,9 +241,18 @@ function onPointerUp(e: PointerEvent) {
|
|||||||
@pointerup="onPointerUp"
|
@pointerup="onPointerUp"
|
||||||
>
|
>
|
||||||
<!-- Sayfalar -->
|
<!-- Sayfalar -->
|
||||||
<div ref="pageRef" class="editor-canvas__pages" :style="[pagesContainerStyle, panTransform ? { transform: panTransform } : {}]">
|
<div
|
||||||
|
ref="pageRef"
|
||||||
|
class="editor-canvas__pages"
|
||||||
|
:style="[pagesContainerStyle, panTransform ? { transform: panTransform } : {}]"
|
||||||
|
>
|
||||||
<LayoutRenderer :layout="layout" :scale="scale" />
|
<LayoutRenderer :layout="layout" :scale="scale" />
|
||||||
<InteractionOverlay :scale="scale" :layout-map="layoutMap" :page-count="layoutPages.length" :page-height-px="pageHeightPx" />
|
<InteractionOverlay
|
||||||
|
:scale="scale"
|
||||||
|
:layout-map="layoutMap"
|
||||||
|
:page-count="layoutPages.length"
|
||||||
|
:page-height-px="pageHeightPx"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -235,12 +260,8 @@ function onPointerUp(e: PointerEvent) {
|
|||||||
<div v-if="props.handleErrors && error" class="editor-canvas__error">
|
<div v-if="props.handleErrors && error" class="editor-canvas__error">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="compiling" class="editor-canvas__compiling">
|
<div v-if="compiling" class="editor-canvas__compiling">Derleniyor...</div>
|
||||||
Derleniyor...
|
<div class="editor-canvas__zoom">%{{ editorStore.zoomPercent }}</div>
|
||||||
</div>
|
|
||||||
<div class="editor-canvas__zoom">
|
|
||||||
%{{ editorStore.zoomPercent }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,12 @@ const layoutStyle = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// justify (main-axis)
|
// justify (main-axis)
|
||||||
const justifyMap = { start: 'flex-start', center: 'center', end: 'flex-end', 'space-between': 'space-between' }
|
const justifyMap = {
|
||||||
|
start: 'flex-start',
|
||||||
|
center: 'center',
|
||||||
|
end: 'flex-end',
|
||||||
|
'space-between': 'space-between',
|
||||||
|
}
|
||||||
style.justifyContent = justifyMap[c.justify] || 'flex-start'
|
style.justifyContent = justifyMap[c.justify] || 'flex-start'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const templateStore = useTemplateStore()
|
const templateStore = useTemplateStore()
|
||||||
const editorStore = useEditorStore()
|
const editorStore = useEditorStore()
|
||||||
const { activeGuides, collectEdges, calculateSnap, calculateResizeSnap, clearGuides } = useSnapGuides()
|
const { activeGuides, collectEdges, calculateSnap, calculateResizeSnap, clearGuides } =
|
||||||
|
useSnapGuides()
|
||||||
|
|
||||||
// Tüm elemanları flat olarak topla (root hariç)
|
// Tüm elemanları flat olarak topla (root hariç)
|
||||||
const flatElements = computed(() => {
|
const flatElements = computed(() => {
|
||||||
@@ -69,7 +70,7 @@ const allContainers = computed(() => {
|
|||||||
/** Sayfa index'ine göre y offset hesapla (sayfalar arası gap dahil) */
|
/** Sayfa index'ine göre y offset hesapla (sayfalar arası gap dahil) */
|
||||||
function pageYOffset(pageIndex: number): number {
|
function pageYOffset(pageIndex: number): number {
|
||||||
if (pageIndex <= 0) return 0
|
if (pageIndex <= 0) return 0
|
||||||
const pageH = props.pageHeightPx ?? (templateStore.template.page.height * props.scale)
|
const pageH = props.pageHeightPx ?? templateStore.template.page.height * props.scale
|
||||||
return pageIndex * (pageH + PAGE_GAP_PX)
|
return pageIndex * (pageH + PAGE_GAP_PX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +119,11 @@ const dropVisualIndex = ref<number | null>(null)
|
|||||||
const dropLogicalIndex = ref<number | null>(null)
|
const dropLogicalIndex = ref<number | null>(null)
|
||||||
|
|
||||||
/** Mouse pozisyonuna göre en derin container'ı bul */
|
/** Mouse pozisyonuna göre en derin container'ı bul */
|
||||||
function findDeepestContainer(mouseX: number, mouseY: number, excludeId?: string): ContainerElement {
|
function findDeepestContainer(
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
excludeId?: string,
|
||||||
|
): ContainerElement {
|
||||||
const s = props.scale
|
const s = props.scale
|
||||||
let best: ContainerElement = templateStore.template.root
|
let best: ContainerElement = templateStore.template.root
|
||||||
|
|
||||||
@@ -135,7 +140,7 @@ function findDeepestContainer(mouseX: number, mouseY: number, excludeId?: string
|
|||||||
if (mouseX >= cx && mouseX <= cx + cw && mouseY >= cy && mouseY <= cy + ch) {
|
if (mouseX >= cx && mouseX <= cx + cw && mouseY >= cy && mouseY <= cy + ch) {
|
||||||
// Daha küçük (daha derin) container'ı tercih et
|
// Daha küçük (daha derin) container'ı tercih et
|
||||||
const bestL = props.layoutMap[best.id]
|
const bestL = props.layoutMap[best.id]
|
||||||
if (!bestL || (cw * ch < bestL.width_mm * s * bestL.height_mm * s)) {
|
if (!bestL || cw * ch < bestL.width_mm * s * bestL.height_mm * s) {
|
||||||
best = c
|
best = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,9 +149,16 @@ function findDeepestContainer(mouseX: number, mouseY: number, excludeId?: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Container içinde drop index hesapla */
|
/** Container içinde drop index hesapla */
|
||||||
function computeDropIndex(container: ContainerElement, mouseX: number, mouseY: number, excludeId?: string) {
|
function computeDropIndex(
|
||||||
|
container: ContainerElement,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
excludeId?: string,
|
||||||
|
) {
|
||||||
const s = props.scale
|
const s = props.scale
|
||||||
const flowChildren = container.children.filter(c => c.type !== 'page_break' && c.position.type !== 'absolute' && c.id !== excludeId)
|
const flowChildren = container.children.filter(
|
||||||
|
(c) => c.type !== 'page_break' && c.position.type !== 'absolute' && c.id !== excludeId,
|
||||||
|
)
|
||||||
const isRow = container.direction === 'row'
|
const isRow = container.direction === 'row'
|
||||||
|
|
||||||
let visualIdx = flowChildren.length
|
let visualIdx = flowChildren.length
|
||||||
@@ -156,18 +168,26 @@ function computeDropIndex(container: ContainerElement, mouseX: number, mouseY: n
|
|||||||
if (!l) continue
|
if (!l) continue
|
||||||
if (isRow) {
|
if (isRow) {
|
||||||
const centerX = l.x_mm * s + (l.width_mm * s) / 2
|
const centerX = l.x_mm * s + (l.width_mm * s) / 2
|
||||||
if (mouseX < centerX) { visualIdx = i; break }
|
if (mouseX < centerX) {
|
||||||
|
visualIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const centerY = l.y_mm * s + pageYOffset(l.pageIndex) + (l.height_mm * s) / 2
|
const centerY = l.y_mm * s + pageYOffset(l.pageIndex) + (l.height_mm * s) / 2
|
||||||
if (mouseY < centerY) { visualIdx = i; break }
|
if (mouseY < centerY) {
|
||||||
|
visualIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mantıksal index: excludeId aynı container'daysa offset hesapla
|
// Mantıksal index: excludeId aynı container'daysa offset hesapla
|
||||||
let logicalIdx = visualIdx
|
let logicalIdx = visualIdx
|
||||||
if (excludeId) {
|
if (excludeId) {
|
||||||
const allFlow = container.children.filter(c => c.type !== 'page_break' && c.position.type !== 'absolute')
|
const allFlow = container.children.filter(
|
||||||
const currentIdx = allFlow.findIndex(c => c.id === excludeId)
|
(c) => c.type !== 'page_break' && c.position.type !== 'absolute',
|
||||||
|
)
|
||||||
|
const currentIdx = allFlow.findIndex((c) => c.id === excludeId)
|
||||||
if (currentIdx >= 0) {
|
if (currentIdx >= 0) {
|
||||||
// visualIdx, excludeId çıkarılmış listede. Gerçek listedeki pozisyona çevir.
|
// visualIdx, excludeId çıkarılmış listede. Gerçek listedeki pozisyona çevir.
|
||||||
// flowChildren zaten excludeId hariç, dolayısıyla visualIdx doğrudan gerçek insert indexi
|
// flowChildren zaten excludeId hariç, dolayısıyla visualIdx doğrudan gerçek insert indexi
|
||||||
@@ -177,7 +197,10 @@ function computeDropIndex(container: ContainerElement, mouseX: number, mouseY: n
|
|||||||
let count = 0
|
let count = 0
|
||||||
for (let i = 0; i < allFlow.length; i++) {
|
for (let i = 0; i < allFlow.length; i++) {
|
||||||
if (allFlow[i].id === excludeId) continue
|
if (allFlow[i].id === excludeId) continue
|
||||||
if (count === visualIdx) { realIdx = i; break }
|
if (count === visualIdx) {
|
||||||
|
realIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
count++
|
count++
|
||||||
realIdx = i + 1
|
realIdx = i + 1
|
||||||
}
|
}
|
||||||
@@ -219,7 +242,9 @@ const dropIndicatorStyle = computed(() => {
|
|||||||
|
|
||||||
// Sürüklenen elemanı çıkar
|
// Sürüklenen elemanı çıkar
|
||||||
const dragId = dragElementId.value
|
const dragId = dragElementId.value
|
||||||
const flowChildren = container.children.filter(c => c.type !== 'page_break' && c.position.type !== 'absolute' && c.id !== dragId)
|
const flowChildren = container.children.filter(
|
||||||
|
(c) => c.type !== 'page_break' && c.position.type !== 'absolute' && c.id !== dragId,
|
||||||
|
)
|
||||||
|
|
||||||
const cl = props.layoutMap[container.id]
|
const cl = props.layoutMap[container.id]
|
||||||
if (!cl) return { display: 'none' }
|
if (!cl) return { display: 'none' }
|
||||||
@@ -380,13 +405,18 @@ function onDragEnd() {
|
|||||||
window.removeEventListener('pointermove', onDragMove)
|
window.removeEventListener('pointermove', onDragMove)
|
||||||
window.removeEventListener('pointerup', onDragEnd)
|
window.removeEventListener('pointerup', onDragEnd)
|
||||||
|
|
||||||
if (isDragging.value && dragElementId.value && dropTargetContainerId.value !== null && dropLogicalIndex.value !== null) {
|
if (
|
||||||
|
isDragging.value &&
|
||||||
|
dragElementId.value &&
|
||||||
|
dropTargetContainerId.value !== null &&
|
||||||
|
dropLogicalIndex.value !== null
|
||||||
|
) {
|
||||||
const currentParent = templateStore.getParent(dragElementId.value)
|
const currentParent = templateStore.getParent(dragElementId.value)
|
||||||
const targetContainerId = dropTargetContainerId.value
|
const targetContainerId = dropTargetContainerId.value
|
||||||
|
|
||||||
if (currentParent && currentParent.id === targetContainerId) {
|
if (currentParent && currentParent.id === targetContainerId) {
|
||||||
// Aynı container içinde reorder
|
// Aynı container içinde reorder
|
||||||
const currentIdx = currentParent.children.findIndex(c => c.id === dragElementId.value)
|
const currentIdx = currentParent.children.findIndex((c) => c.id === dragElementId.value)
|
||||||
if (currentIdx !== -1 && currentIdx !== dropLogicalIndex.value) {
|
if (currentIdx !== -1 && currentIdx !== dropLogicalIndex.value) {
|
||||||
templateStore.reorderChild(currentParent.id, currentIdx, dropLogicalIndex.value)
|
templateStore.reorderChild(currentParent.id, currentIdx, dropLogicalIndex.value)
|
||||||
}
|
}
|
||||||
@@ -400,7 +430,9 @@ function onDragEnd() {
|
|||||||
dragElementId.value = null
|
dragElementId.value = null
|
||||||
editorStore.setDragging(false)
|
editorStore.setDragging(false)
|
||||||
clearDropTarget()
|
clearDropTarget()
|
||||||
setTimeout(() => { didDrag.value = false }, 50)
|
setTimeout(() => {
|
||||||
|
didDrag.value = false
|
||||||
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Absolute eleman drag ---
|
// --- Absolute eleman drag ---
|
||||||
@@ -420,7 +452,12 @@ function onAbsoluteDragStart(e: PointerEvent, el: TemplateElement) {
|
|||||||
elY: el.position.y,
|
elY: el.position.y,
|
||||||
}
|
}
|
||||||
|
|
||||||
collectEdges(props.layoutMap, el.id, templateStore.template.page.width, templateStore.template.page.height)
|
collectEdges(
|
||||||
|
props.layoutMap,
|
||||||
|
el.id,
|
||||||
|
templateStore.template.page.width,
|
||||||
|
templateStore.template.page.height,
|
||||||
|
)
|
||||||
|
|
||||||
window.addEventListener('pointermove', onAbsoluteDragMove)
|
window.addEventListener('pointermove', onAbsoluteDragMove)
|
||||||
window.addEventListener('pointerup', onAbsoluteDragEnd)
|
window.addEventListener('pointerup', onAbsoluteDragEnd)
|
||||||
@@ -466,7 +503,9 @@ function onAbsoluteDragEnd() {
|
|||||||
absoluteDragId.value = null
|
absoluteDragId.value = null
|
||||||
editorStore.setDragging(false)
|
editorStore.setDragging(false)
|
||||||
clearGuides()
|
clearGuides()
|
||||||
setTimeout(() => { didDrag.value = false }, 50)
|
setTimeout(() => {
|
||||||
|
didDrag.value = false
|
||||||
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Resize ---
|
// --- Resize ---
|
||||||
@@ -493,18 +532,34 @@ function onResizeStart(e: PointerEvent, elId: string, handle: string) {
|
|||||||
const s = props.scale
|
const s = props.scale
|
||||||
|
|
||||||
// Barkod ve görsel elemanları için aspect ratio'yu kaydet
|
// Barkod ve görsel elemanları için aspect ratio'yu kaydet
|
||||||
const el = flatElements.value.find(e => e.id === elId)
|
const el = flatElements.value.find((e) => e.id === elId)
|
||||||
resizeAspectRatio.value = ((el?.type === 'barcode' || el?.type === 'image') && l.height_mm > 0) ? l.width_mm / l.height_mm : 0
|
resizeAspectRatio.value =
|
||||||
|
(el?.type === 'barcode' || el?.type === 'image') && l.height_mm > 0
|
||||||
|
? l.width_mm / l.height_mm
|
||||||
|
: 0
|
||||||
|
|
||||||
resizeStart.value = {
|
resizeStart.value = {
|
||||||
mouseX: e.clientX, mouseY: e.clientY,
|
mouseX: e.clientX,
|
||||||
x: l.x_mm * s, y: l.y_mm * s,
|
mouseY: e.clientY,
|
||||||
width: l.width_mm * s, height: l.height_mm * s,
|
x: l.x_mm * s,
|
||||||
|
y: l.y_mm * s,
|
||||||
|
width: l.width_mm * s,
|
||||||
|
height: l.height_mm * s,
|
||||||
|
}
|
||||||
|
resizeGhost.value = {
|
||||||
|
x: l.x_mm * s,
|
||||||
|
y: l.y_mm * s,
|
||||||
|
width: l.width_mm * s,
|
||||||
|
height: l.height_mm * s,
|
||||||
}
|
}
|
||||||
resizeGhost.value = { x: l.x_mm * s, y: l.y_mm * s, width: l.width_mm * s, height: l.height_mm * s }
|
|
||||||
resizeFinalMm.value = { width: l.width_mm, height: l.height_mm }
|
resizeFinalMm.value = { width: l.width_mm, height: l.height_mm }
|
||||||
|
|
||||||
collectEdges(props.layoutMap, elId, templateStore.template.page.width, templateStore.template.page.height)
|
collectEdges(
|
||||||
|
props.layoutMap,
|
||||||
|
elId,
|
||||||
|
templateStore.template.page.width,
|
||||||
|
templateStore.template.page.height,
|
||||||
|
)
|
||||||
|
|
||||||
window.addEventListener('pointermove', onResizeMove)
|
window.addEventListener('pointermove', onResizeMove)
|
||||||
window.addEventListener('pointerup', onResizeEnd)
|
window.addEventListener('pointerup', onResizeEnd)
|
||||||
@@ -519,13 +574,21 @@ function onResizeMove(e: PointerEvent) {
|
|||||||
const pxToMm = 1 / props.scale
|
const pxToMm = 1 / props.scale
|
||||||
const ar = resizeAspectRatio.value
|
const ar = resizeAspectRatio.value
|
||||||
|
|
||||||
let gx = resizeStart.value.x, gy = resizeStart.value.y
|
let gx = resizeStart.value.x,
|
||||||
let gw = resizeStart.value.width, gh = resizeStart.value.height
|
gy = resizeStart.value.y
|
||||||
|
let gw = resizeStart.value.width,
|
||||||
|
gh = resizeStart.value.height
|
||||||
|
|
||||||
if (handle.includes('e')) gw = Math.max(20, resizeStart.value.width + dx)
|
if (handle.includes('e')) gw = Math.max(20, resizeStart.value.width + dx)
|
||||||
if (handle.includes('w')) { gw = Math.max(20, resizeStart.value.width - dx); gx = resizeStart.value.x + dx }
|
if (handle.includes('w')) {
|
||||||
|
gw = Math.max(20, resizeStart.value.width - dx)
|
||||||
|
gx = resizeStart.value.x + dx
|
||||||
|
}
|
||||||
if (handle.includes('s')) gh = Math.max(10, resizeStart.value.height + dy)
|
if (handle.includes('s')) gh = Math.max(10, resizeStart.value.height + dy)
|
||||||
if (handle.includes('n')) { gh = Math.max(10, resizeStart.value.height - dy); gy = resizeStart.value.y + dy }
|
if (handle.includes('n')) {
|
||||||
|
gh = Math.max(10, resizeStart.value.height - dy)
|
||||||
|
gy = resizeStart.value.y + dy
|
||||||
|
}
|
||||||
|
|
||||||
// Aspect ratio koruma (barkod)
|
// Aspect ratio koruma (barkod)
|
||||||
if (ar > 0) {
|
if (ar > 0) {
|
||||||
@@ -538,7 +601,8 @@ function onResizeMove(e: PointerEvent) {
|
|||||||
const startHMm = resizeStart.value.height * pxToMm
|
const startHMm = resizeStart.value.height * pxToMm
|
||||||
const startXMm = resizeStart.value.x * pxToMm
|
const startXMm = resizeStart.value.x * pxToMm
|
||||||
const startYMm = resizeStart.value.y * pxToMm
|
const startYMm = resizeStart.value.y * pxToMm
|
||||||
let wMm = startWMm, hMm = startHMm
|
let wMm = startWMm,
|
||||||
|
hMm = startHMm
|
||||||
if (handle.includes('e')) {
|
if (handle.includes('e')) {
|
||||||
const rightEdge = calculateResizeSnap('right', startXMm + startWMm + dx * pxToMm)
|
const rightEdge = calculateResizeSnap('right', startXMm + startWMm + dx * pxToMm)
|
||||||
wMm = Math.max(5, rightEdge - startXMm)
|
wMm = Math.max(5, rightEdge - startXMm)
|
||||||
@@ -571,8 +635,10 @@ function onResizeEnd() {
|
|||||||
const handle = resizeHandle.value
|
const handle = resizeHandle.value
|
||||||
const ar = resizeAspectRatio.value
|
const ar = resizeAspectRatio.value
|
||||||
const sizeUpdate: { width?: SizeValue; height?: SizeValue } = {}
|
const sizeUpdate: { width?: SizeValue; height?: SizeValue } = {}
|
||||||
if (handle.includes('e') || handle.includes('w')) sizeUpdate.width = sz.fixed(resizeFinalMm.value.width)
|
if (handle.includes('e') || handle.includes('w'))
|
||||||
if (handle.includes('s') || handle.includes('n')) sizeUpdate.height = sz.fixed(resizeFinalMm.value.height)
|
sizeUpdate.width = sz.fixed(resizeFinalMm.value.width)
|
||||||
|
if (handle.includes('s') || handle.includes('n'))
|
||||||
|
sizeUpdate.height = sz.fixed(resizeFinalMm.value.height)
|
||||||
// Aspect ratio aktifken her zaman hem width hem height güncelle
|
// Aspect ratio aktifken her zaman hem width hem height güncelle
|
||||||
if (ar > 0) {
|
if (ar > 0) {
|
||||||
sizeUpdate.width = sz.fixed(resizeFinalMm.value.width)
|
sizeUpdate.width = sz.fixed(resizeFinalMm.value.width)
|
||||||
@@ -621,8 +687,8 @@ function onToolboxDrop(_e: DragEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aktif sürükleme var mı (eleman veya toolbox)
|
// Aktif sürükleme var mı (eleman veya toolbox)
|
||||||
const isAnyDragActive = computed(() =>
|
const isAnyDragActive = computed(
|
||||||
(isDragging.value && dragElementId.value !== null) || !!editorStore.draggedNewElement
|
() => (isDragging.value && dragElementId.value !== null) || !!editorStore.draggedNewElement,
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -644,26 +710,57 @@ const isAnyDragActive = computed(() =>
|
|||||||
'element-handle--selected': editorStore.isSelected(el.id),
|
'element-handle--selected': editorStore.isSelected(el.id),
|
||||||
'element-handle--container': isContainer(el),
|
'element-handle--container': isContainer(el),
|
||||||
'element-handle--dragging': isDragging && dragElementId === el.id,
|
'element-handle--dragging': isDragging && dragElementId === el.id,
|
||||||
'element-handle--drop-target': isContainer(el) && dropTargetContainerId === el.id && isAnyDragActive,
|
'element-handle--drop-target':
|
||||||
|
isContainer(el) && dropTargetContainerId === el.id && isAnyDragActive,
|
||||||
}"
|
}"
|
||||||
:style="getElementStyle(el)"
|
:style="getElementStyle(el)"
|
||||||
@pointerdown="(e: PointerEvent) => { onElementClick(e, el.id); onDragStart(e, el) }"
|
@pointerdown="
|
||||||
|
(e: PointerEvent) => {
|
||||||
|
onElementClick(e, el.id)
|
||||||
|
onDragStart(e, el)
|
||||||
|
}
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<!-- Selection border -->
|
<!-- Selection border -->
|
||||||
<div v-if="editorStore.isSelected(el.id)" class="selection-border" />
|
<div v-if="editorStore.isSelected(el.id)" class="selection-border" />
|
||||||
|
|
||||||
<!-- Resize handles (sadece tek seçimde) -->
|
<!-- Resize handles (sadece tek seçimde) -->
|
||||||
<template v-if="editorStore.isSelected(el.id) && editorStore.selectedElementIds.size === 1 && !isResizing && el.type !== 'page_break'">
|
<template
|
||||||
|
v-if="
|
||||||
|
editorStore.isSelected(el.id) &&
|
||||||
|
editorStore.selectedElementIds.size === 1 &&
|
||||||
|
!isResizing &&
|
||||||
|
el.type !== 'page_break'
|
||||||
|
"
|
||||||
|
>
|
||||||
<template v-if="el.type === 'barcode' || el.type === 'image'">
|
<template v-if="el.type === 'barcode' || el.type === 'image'">
|
||||||
<!-- Barkod/Görsel: sadece yatay resize (aspect ratio korunur) -->
|
<!-- Barkod/Görsel: sadece yatay resize (aspect ratio korunur) -->
|
||||||
<div class="resize-handle resize-handle--e" @pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'e')" />
|
<div
|
||||||
<div class="resize-handle resize-handle--w" @pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'w')" />
|
class="resize-handle resize-handle--e"
|
||||||
|
@pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'e')"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="resize-handle resize-handle--w"
|
||||||
|
@pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'w')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="resize-handle resize-handle--se" @pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'se')" />
|
<div
|
||||||
<div class="resize-handle resize-handle--sw" @pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'sw')" />
|
class="resize-handle resize-handle--se"
|
||||||
<div class="resize-handle resize-handle--ne" @pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'ne')" />
|
@pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'se')"
|
||||||
<div class="resize-handle resize-handle--nw" @pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'nw')" />
|
/>
|
||||||
|
<div
|
||||||
|
class="resize-handle resize-handle--sw"
|
||||||
|
@pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'sw')"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="resize-handle resize-handle--ne"
|
||||||
|
@pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'ne')"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="resize-handle resize-handle--nw"
|
||||||
|
@pointerdown="(e: PointerEvent) => onResizeStart(e, el.id, 'nw')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -777,12 +874,36 @@ const isAnyDragActive = computed(() =>
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resize-handle--se { right: -3px; bottom: -3px; cursor: se-resize; }
|
.resize-handle--se {
|
||||||
.resize-handle--sw { left: -3px; bottom: -3px; cursor: sw-resize; }
|
right: -3px;
|
||||||
.resize-handle--ne { right: -3px; top: -3px; cursor: ne-resize; }
|
bottom: -3px;
|
||||||
.resize-handle--nw { left: -3px; top: -3px; cursor: nw-resize; }
|
cursor: se-resize;
|
||||||
.resize-handle--e { right: -3px; top: calc(50% - 3px); cursor: e-resize; }
|
}
|
||||||
.resize-handle--w { left: -3px; top: calc(50% - 3px); cursor: w-resize; }
|
.resize-handle--sw {
|
||||||
|
left: -3px;
|
||||||
|
bottom: -3px;
|
||||||
|
cursor: sw-resize;
|
||||||
|
}
|
||||||
|
.resize-handle--ne {
|
||||||
|
right: -3px;
|
||||||
|
top: -3px;
|
||||||
|
cursor: ne-resize;
|
||||||
|
}
|
||||||
|
.resize-handle--nw {
|
||||||
|
left: -3px;
|
||||||
|
top: -3px;
|
||||||
|
cursor: nw-resize;
|
||||||
|
}
|
||||||
|
.resize-handle--e {
|
||||||
|
right: -3px;
|
||||||
|
top: calc(50% - 3px);
|
||||||
|
cursor: e-resize;
|
||||||
|
}
|
||||||
|
.resize-handle--w {
|
||||||
|
left: -3px;
|
||||||
|
top: calc(50% - 3px);
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
|
||||||
/* Drag ghost */
|
/* Drag ghost */
|
||||||
.drag-ghost {
|
.drag-ghost {
|
||||||
|
|||||||
@@ -8,7 +8,16 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
// WASM barcode üretme fonksiyonu (EditorCanvas'tan provide edilir)
|
// WASM barcode üretme fonksiyonu (EditorCanvas'tan provide edilir)
|
||||||
const generateBarcode = inject<(format: string, value: string, width: number, height: number, includeText: boolean) => Promise<{ width: number; height: number; rgba: ArrayBuffer } | null>>('generateBarcode')
|
const generateBarcode =
|
||||||
|
inject<
|
||||||
|
(
|
||||||
|
format: string,
|
||||||
|
value: string,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
includeText: boolean,
|
||||||
|
) => Promise<{ width: number; height: number; rgba: ArrayBuffer } | null>
|
||||||
|
>('generateBarcode')
|
||||||
|
|
||||||
function pageContainerStyle(page: PageLayout): Record<string, string> {
|
function pageContainerStyle(page: PageLayout): Record<string, string> {
|
||||||
const s = props.scale
|
const s = props.scale
|
||||||
@@ -92,7 +101,12 @@ function lineStyle(el: ElementLayout): Record<string, string> {
|
|||||||
|
|
||||||
// --- Barcode rendering (WASM ile) ---
|
// --- Barcode rendering (WASM ile) ---
|
||||||
|
|
||||||
async function renderBarcodeToCanvas(canvas: HTMLCanvasElement, format: string, value: string, includeText: boolean = false) {
|
async function renderBarcodeToCanvas(
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
format: string,
|
||||||
|
value: string,
|
||||||
|
includeText: boolean = false,
|
||||||
|
) {
|
||||||
if (!value || !generateBarcode) return
|
if (!value || !generateBarcode) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -106,7 +120,13 @@ async function renderBarcodeToCanvas(canvas: HTMLCanvasElement, format: string,
|
|||||||
const hPt = elHmm * MM_TO_PT
|
const hPt = elHmm * MM_TO_PT
|
||||||
const size = Math.max(1, Math.round(wPt * 4))
|
const size = Math.max(1, Math.round(wPt * 4))
|
||||||
const barcodeHeight = Math.max(1, Math.round(hPt * 4))
|
const barcodeHeight = Math.max(1, Math.round(hPt * 4))
|
||||||
const result = await generateBarcode(format, value, size, barcodeHeight, isQr ? false : includeText)
|
const result = await generateBarcode(
|
||||||
|
format,
|
||||||
|
value,
|
||||||
|
size,
|
||||||
|
barcodeHeight,
|
||||||
|
isQr ? false : includeText,
|
||||||
|
)
|
||||||
if (!result) return
|
if (!result) return
|
||||||
|
|
||||||
// Canvas boyutlarını WASM çıktısına ayarla (crisp rendering)
|
// Canvas boyutlarını WASM çıktısına ayarla (crisp rendering)
|
||||||
@@ -116,11 +136,7 @@ async function renderBarcodeToCanvas(canvas: HTMLCanvasElement, format: string,
|
|||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
|
|
||||||
const imageData = new ImageData(
|
const imageData = new ImageData(new Uint8ClampedArray(result.rgba), result.width, result.height)
|
||||||
new Uint8ClampedArray(result.rgba),
|
|
||||||
result.width,
|
|
||||||
result.height,
|
|
||||||
)
|
|
||||||
ctx.putImageData(imageData, 0, 0)
|
ctx.putImageData(imageData, 0, 0)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(`[dreport] WASM barcode render hatası (${format}):`, e)
|
console.warn(`[dreport] WASM barcode render hatası (${format}):`, e)
|
||||||
@@ -159,7 +175,7 @@ watch(
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const canvases = document.querySelectorAll<HTMLCanvasElement>('canvas[data-barcode]')
|
const canvases = document.querySelectorAll<HTMLCanvasElement>('canvas[data-barcode]')
|
||||||
canvases.forEach(canvas => {
|
canvases.forEach((canvas) => {
|
||||||
const format = canvas.dataset.format
|
const format = canvas.dataset.format
|
||||||
const value = canvas.dataset.value
|
const value = canvas.dataset.value
|
||||||
const includeText = canvas.dataset.includeText === 'true'
|
const includeText = canvas.dataset.includeText === 'true'
|
||||||
@@ -168,7 +184,7 @@ watch(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true },
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -187,7 +203,7 @@ watch(
|
|||||||
class="layout-el layout-el--page-break"
|
class="layout-el layout-el--page-break"
|
||||||
:style="elStyle(el)"
|
:style="elStyle(el)"
|
||||||
>
|
>
|
||||||
<div style="border-top: 1px dashed #9ca3af; width: 100%; height: 0;" />
|
<div style="border-top: 1px dashed #9ca3af; width: 100%; height: 0" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Container -->
|
<!-- Container -->
|
||||||
@@ -200,13 +216,27 @@ watch(
|
|||||||
}"
|
}"
|
||||||
:style="{ ...elStyle(el), ...containerStyle(el) }"
|
:style="{ ...elStyle(el), ...containerStyle(el) }"
|
||||||
>
|
>
|
||||||
<span v-if="el.id === 'header' || el.id.startsWith('header_p')" class="layout-el__section-label">Üst Bilgi</span>
|
<span
|
||||||
<span v-else-if="el.id === 'footer' || el.id.startsWith('footer_p')" class="layout-el__section-label">Alt Bilgi</span>
|
v-if="el.id === 'header' || el.id.startsWith('header_p')"
|
||||||
|
class="layout-el__section-label"
|
||||||
|
>Üst Bilgi</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else-if="el.id === 'footer' || el.id.startsWith('footer_p')"
|
||||||
|
class="layout-el__section-label"
|
||||||
|
>Alt Bilgi</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Static text / Text / Page number -->
|
<!-- Static text / Text / Page number -->
|
||||||
<div
|
<div
|
||||||
v-else-if="el.element_type === 'static_text' || el.element_type === 'text' || el.element_type === 'page_number' || el.element_type === 'current_date' || el.element_type === 'calculated_text'"
|
v-else-if="
|
||||||
|
el.element_type === 'static_text' ||
|
||||||
|
el.element_type === 'text' ||
|
||||||
|
el.element_type === 'page_number' ||
|
||||||
|
el.element_type === 'current_date' ||
|
||||||
|
el.element_type === 'calculated_text'
|
||||||
|
"
|
||||||
class="layout-el layout-el--text"
|
class="layout-el layout-el--text"
|
||||||
:style="{ ...elStyle(el), ...textStyle(el) }"
|
:style="{ ...elStyle(el), ...textStyle(el) }"
|
||||||
>
|
>
|
||||||
@@ -231,7 +261,11 @@ watch(
|
|||||||
<img
|
<img
|
||||||
v-if="el.content?.type === 'image' && el.content.src"
|
v-if="el.content?.type === 'image' && el.content.src"
|
||||||
:src="el.content.src"
|
:src="el.content.src"
|
||||||
:style="{ width: '100%', height: '100%', objectFit: (el.style.objectFit || 'fill') as CSSProperties['objectFit'] }"
|
:style="{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: (el.style.objectFit || 'fill') as CSSProperties['objectFit'],
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
<div v-else class="layout-el__placeholder">Görsel</div>
|
<div v-else class="layout-el__placeholder">Görsel</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,7 +282,10 @@ watch(
|
|||||||
data-barcode
|
data-barcode
|
||||||
:data-format="el.content.format"
|
:data-format="el.content.format"
|
||||||
:data-value="el.content.value"
|
:data-value="el.content.value"
|
||||||
:data-include-text="el.style.barcodeIncludeText ?? (el.content.format === 'ean13' || el.content.format === 'ean8')"
|
:data-include-text="
|
||||||
|
el.style.barcodeIncludeText ??
|
||||||
|
(el.content.format === 'ean13' || el.content.format === 'ean8')
|
||||||
|
"
|
||||||
:data-el-w="el.width_mm"
|
:data-el-w="el.width_mm"
|
||||||
:data-el-h="el.height_mm"
|
:data-el-h="el.height_mm"
|
||||||
:style="{ width: '100%', height: '100%', display: 'block' }"
|
:style="{ width: '100%', height: '100%', display: 'block' }"
|
||||||
@@ -264,16 +301,24 @@ watch(
|
|||||||
:style="elStyle(el)"
|
:style="elStyle(el)"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 20 20" :style="{ width: '100%', height: '100%' }">
|
<svg viewBox="0 0 20 20" :style="{ width: '100%', height: '100%' }">
|
||||||
<rect x="1" y="1" width="18" height="18" fill="none"
|
<rect
|
||||||
|
x="1"
|
||||||
|
y="1"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
fill="none"
|
||||||
:stroke="el.style.borderColor ?? '#333'"
|
:stroke="el.style.borderColor ?? '#333'"
|
||||||
:stroke-width="el.style.borderWidth ? el.style.borderWidth * 3 : 1.5" />
|
:stroke-width="el.style.borderWidth ? el.style.borderWidth * 3 : 1.5"
|
||||||
<path v-if="el.content?.type === 'checkbox' && el.content.checked"
|
/>
|
||||||
|
<path
|
||||||
|
v-if="el.content?.type === 'checkbox' && el.content.checked"
|
||||||
d="M4 10 L8 15 L16 5"
|
d="M4 10 L8 15 L16 5"
|
||||||
fill="none"
|
fill="none"
|
||||||
:stroke="el.style.color ?? '#000'"
|
:stroke="el.style.color ?? '#000'"
|
||||||
stroke-width="2.5"
|
stroke-width="2.5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -293,7 +338,8 @@ watch(
|
|||||||
fontFamily: span.fontFamily || undefined,
|
fontFamily: span.fontFamily || undefined,
|
||||||
color: span.color || undefined,
|
color: span.color || undefined,
|
||||||
}"
|
}"
|
||||||
>{{ span.text }}</span>
|
>{{ span.text }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -313,13 +359,24 @@ watch(
|
|||||||
<div
|
<div
|
||||||
v-if="el.content?.type === 'chart' && el.content.svg"
|
v-if="el.content?.type === 'chart' && el.content.svg"
|
||||||
v-html="el.content.svg"
|
v-html="el.content.svg"
|
||||||
style="width: 100%; height: 100%;"
|
style="width: 100%; height: 100%"
|
||||||
/>
|
/>
|
||||||
<div v-else class="layout-el__placeholder" :style="{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', color: '#94a3b8', fontSize: '12px' }">
|
<div
|
||||||
|
v-else
|
||||||
|
class="layout-el__placeholder"
|
||||||
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
color: '#94a3b8',
|
||||||
|
fontSize: '12px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
Grafik
|
Grafik
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ const RULER_SIZE = computed(() => props.rulerSize ?? 20)
|
|||||||
const hCanvas = ref<HTMLCanvasElement | null>(null)
|
const hCanvas = ref<HTMLCanvasElement | null>(null)
|
||||||
const vCanvas = ref<HTMLCanvasElement | null>(null)
|
const vCanvas = ref<HTMLCanvasElement | null>(null)
|
||||||
|
|
||||||
function drawRuler(
|
function drawRuler(canvas: HTMLCanvasElement | null, direction: 'horizontal' | 'vertical') {
|
||||||
canvas: HTMLCanvasElement | null,
|
|
||||||
direction: 'horizontal' | 'vertical',
|
|
||||||
) {
|
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
@@ -79,9 +76,10 @@ function drawTicks(
|
|||||||
// EditorCanvas sayfayı ortalar, ruler da buna uymalı
|
// EditorCanvas sayfayı ortalar, ruler da buna uymalı
|
||||||
// Yatay: canvas ortası - sayfa genişliği/2
|
// Yatay: canvas ortası - sayfa genişliği/2
|
||||||
// Sayfanın canvas üzerindeki orijin px'i
|
// Sayfanın canvas üzerindeki orijin px'i
|
||||||
const canvasCenter = direction === 'horizontal'
|
const canvasCenter =
|
||||||
? (length / 2) // flex centering approximation
|
direction === 'horizontal'
|
||||||
: 40 // EditorCanvas padding-top: 40px
|
? length / 2 // flex centering approximation
|
||||||
|
: 40 // EditorCanvas padding-top: 40px
|
||||||
|
|
||||||
const pageStartPx = canvasCenter - (pageMm * s) / 2 + pan
|
const pageStartPx = canvasCenter - (pageMm * s) / 2 + pan
|
||||||
|
|
||||||
@@ -188,16 +186,8 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ruler-corner" :style="{ width: `${RULER_SIZE}px`, height: `${RULER_SIZE}px` }" />
|
<div class="ruler-corner" :style="{ width: `${RULER_SIZE}px`, height: `${RULER_SIZE}px` }" />
|
||||||
<canvas
|
<canvas ref="hCanvas" class="ruler-h" :style="{ height: `${RULER_SIZE}px` }" />
|
||||||
ref="hCanvas"
|
<canvas ref="vCanvas" class="ruler-v" :style="{ width: `${RULER_SIZE}px` }" />
|
||||||
class="ruler-h"
|
|
||||||
:style="{ height: `${RULER_SIZE}px` }"
|
|
||||||
/>
|
|
||||||
<canvas
|
|
||||||
ref="vCanvas"
|
|
||||||
class="ruler-v"
|
|
||||||
:style="{ width: `${RULER_SIZE}px` }"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -55,21 +55,36 @@ const elementTypeLabel = computed(() => {
|
|||||||
if (el.id === 'header') return 'Üst Bilgi'
|
if (el.id === 'header') return 'Üst Bilgi'
|
||||||
if (el.id === 'footer') return 'Alt Bilgi'
|
if (el.id === 'footer') return 'Alt Bilgi'
|
||||||
return 'Container'
|
return 'Container'
|
||||||
case 'static_text': return 'Metin'
|
case 'static_text':
|
||||||
case 'text': return 'Metin'
|
return 'Metin'
|
||||||
case 'line': return 'Cizgi'
|
case 'text':
|
||||||
case 'repeating_table': return 'Tablo'
|
return 'Metin'
|
||||||
case 'image': return 'Gorsel'
|
case 'line':
|
||||||
case 'page_number': return 'Sayfa No'
|
return 'Cizgi'
|
||||||
case 'barcode': return 'Barkod'
|
case 'repeating_table':
|
||||||
case 'checkbox': return 'Onay Kutusu'
|
return 'Tablo'
|
||||||
case 'shape': return 'Sekil'
|
case 'image':
|
||||||
case 'current_date': return 'Tarih'
|
return 'Gorsel'
|
||||||
case 'calculated_text': return 'Hesaplanan Metin'
|
case 'page_number':
|
||||||
case 'rich_text': return 'Zengin Metin'
|
return 'Sayfa No'
|
||||||
case 'page_break': return 'Sayfa Sonu'
|
case 'barcode':
|
||||||
case 'chart': return 'Grafik'
|
return 'Barkod'
|
||||||
default: return 'Eleman'
|
case 'checkbox':
|
||||||
|
return 'Onay Kutusu'
|
||||||
|
case 'shape':
|
||||||
|
return 'Sekil'
|
||||||
|
case 'current_date':
|
||||||
|
return 'Tarih'
|
||||||
|
case 'calculated_text':
|
||||||
|
return 'Hesaplanan Metin'
|
||||||
|
case 'rich_text':
|
||||||
|
return 'Zengin Metin'
|
||||||
|
case 'page_break':
|
||||||
|
return 'Sayfa Sonu'
|
||||||
|
case 'chart':
|
||||||
|
return 'Grafik'
|
||||||
|
default:
|
||||||
|
return 'Eleman'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -105,12 +120,12 @@ function deleteSelected() {
|
|||||||
<div class="properties-panel">
|
<div class="properties-panel">
|
||||||
<div v-if="multipleSelected" class="properties-panel__empty">
|
<div v-if="multipleSelected" class="properties-panel__empty">
|
||||||
{{ editorStore.selectedElementIds.size }} eleman secili
|
{{ editorStore.selectedElementIds.size }} eleman secili
|
||||||
<button class="prop-delete-btn" style="margin-top: 12px" @click="deleteSelected">Secilenleri Sil</button>
|
<button class="prop-delete-btn" style="margin-top: 12px" @click="deleteSelected">
|
||||||
|
Secilenleri Sil
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!selectedElement" class="properties-panel__empty">
|
<div v-else-if="!selectedElement" class="properties-panel__empty">Bir eleman secin</div>
|
||||||
Bir eleman secin
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@@ -134,68 +149,87 @@ function deleteSelected() {
|
|||||||
|
|
||||||
<TextProperties
|
<TextProperties
|
||||||
v-if="selectedElement.type === 'static_text' || selectedElement.type === 'text'"
|
v-if="selectedElement.type === 'static_text' || selectedElement.type === 'text'"
|
||||||
:element="selectedElement" />
|
:element="selectedElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<LineProperties
|
<LineProperties
|
||||||
v-if="selectedElement.type === 'line'"
|
v-if="selectedElement.type === 'line'"
|
||||||
:element="(selectedElement as LineElement)" />
|
:element="selectedElement as LineElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<ImageProperties
|
<ImageProperties
|
||||||
v-if="selectedElement.type === 'image'"
|
v-if="selectedElement.type === 'image'"
|
||||||
:element="(selectedElement as ImageElement)" />
|
:element="selectedElement as ImageElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<PageNumberProperties
|
<PageNumberProperties
|
||||||
v-if="selectedElement.type === 'page_number'"
|
v-if="selectedElement.type === 'page_number'"
|
||||||
:element="(selectedElement as PageNumberElement)" />
|
:element="selectedElement as PageNumberElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<BarcodeProperties
|
<BarcodeProperties
|
||||||
v-if="selectedElement.type === 'barcode'"
|
v-if="selectedElement.type === 'barcode'"
|
||||||
:element="(selectedElement as BarcodeElement)" />
|
:element="selectedElement as BarcodeElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<CurrentDateProperties
|
<CurrentDateProperties
|
||||||
v-if="selectedElement.type === 'current_date'"
|
v-if="selectedElement.type === 'current_date'"
|
||||||
:element="(selectedElement as CurrentDateElement)" />
|
:element="selectedElement as CurrentDateElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<CheckboxProperties
|
<CheckboxProperties
|
||||||
v-if="selectedElement.type === 'checkbox'"
|
v-if="selectedElement.type === 'checkbox'"
|
||||||
:element="(selectedElement as CheckboxElement)" />
|
:element="selectedElement as CheckboxElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<CalculatedTextProperties
|
<CalculatedTextProperties
|
||||||
v-if="selectedElement.type === 'calculated_text'"
|
v-if="selectedElement.type === 'calculated_text'"
|
||||||
:element="(selectedElement as CalculatedTextElement)" />
|
:element="selectedElement as CalculatedTextElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<RichTextProperties
|
<RichTextProperties
|
||||||
v-if="selectedElement.type === 'rich_text'"
|
v-if="selectedElement.type === 'rich_text'"
|
||||||
:element="(selectedElement as RichTextElement)" />
|
:element="selectedElement as RichTextElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<ShapeProperties
|
<ShapeProperties
|
||||||
v-if="selectedElement.type === 'shape'"
|
v-if="selectedElement.type === 'shape'"
|
||||||
:element="(selectedElement as ShapeElement)" />
|
:element="selectedElement as ShapeElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<ContainerProperties
|
<ContainerProperties
|
||||||
v-if="isContainer(selectedElement)"
|
v-if="isContainer(selectedElement)"
|
||||||
:element="(selectedElement as ContainerElement)" />
|
:element="selectedElement as ContainerElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<RepeatingTableProperties
|
<RepeatingTableProperties
|
||||||
v-if="selectedElement.type === 'repeating_table'"
|
v-if="selectedElement.type === 'repeating_table'"
|
||||||
:element="(selectedElement as RepeatingTableElement)" />
|
:element="selectedElement as RepeatingTableElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<ChartProperties
|
<ChartProperties
|
||||||
v-if="selectedElement.type === 'chart'"
|
v-if="selectedElement.type === 'chart'"
|
||||||
:element="(selectedElement as ChartElement)" />
|
:element="selectedElement as ChartElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Header/Footer toggles for root element -->
|
<!-- Header/Footer toggles for root element -->
|
||||||
<div v-if="selectedElement.id === 'root'" class="prop-section">
|
<div v-if="selectedElement.id === 'root'" class="prop-section">
|
||||||
<div class="prop-section__title">Sayfa Ust/Alt Bilgi</div>
|
<div class="prop-section__title">Sayfa Ust/Alt Bilgi</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Ust Bilgi (Header)</label>
|
<label class="prop-label">Ust Bilgi (Header)</label>
|
||||||
<input type="checkbox" :checked="!!templateStore.template.header"
|
<input
|
||||||
@change="toggleHeader" />
|
type="checkbox"
|
||||||
|
:checked="!!templateStore.template.header"
|
||||||
|
@change="toggleHeader"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Alt Bilgi (Footer)</label>
|
<label class="prop-label">Alt Bilgi (Footer)</label>
|
||||||
<input type="checkbox" :checked="!!templateStore.template.footer"
|
<input
|
||||||
@change="toggleFooter" />
|
type="checkbox"
|
||||||
|
:checked="!!templateStore.template.footer"
|
||||||
|
@change="toggleFooter"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import { sz } from '../../core/types'
|
|||||||
import { useEditorStore } from '../../stores/editor'
|
import { useEditorStore } from '../../stores/editor'
|
||||||
import { useSchemaStore } from '../../stores/schema'
|
import { useSchemaStore } from '../../stores/schema'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(
|
||||||
node: SchemaNode
|
defineProps<{
|
||||||
depth?: number
|
node: SchemaNode
|
||||||
}>(), {
|
depth?: number
|
||||||
depth: 0,
|
}>(),
|
||||||
})
|
{
|
||||||
|
depth: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const editorStore = useEditorStore()
|
const editorStore = useEditorStore()
|
||||||
const schemaStore = useSchemaStore()
|
const schemaStore = useSchemaStore()
|
||||||
@@ -68,7 +71,7 @@ function createBoundTextElement(node: SchemaNode): TemplateElement {
|
|||||||
|
|
||||||
function createBoundTableElement(node: SchemaNode): RepeatingTableElement {
|
function createBoundTableElement(node: SchemaNode): RepeatingTableElement {
|
||||||
const itemFields = schemaStore.getArrayItemFields(node.path)
|
const itemFields = schemaStore.getArrayItemFields(node.path)
|
||||||
const columns: TableColumn[] = itemFields.map(field => ({
|
const columns: TableColumn[] = itemFields.map((field) => ({
|
||||||
id: `col_${(++colIdCounter).toString(36)}`,
|
id: `col_${(++colIdCounter).toString(36)}`,
|
||||||
field: field.key,
|
field: field.key,
|
||||||
title: field.title,
|
title: field.title,
|
||||||
@@ -108,9 +111,7 @@ function onDragEnd() {
|
|||||||
editorStore.endDragNewElement()
|
editorStore.endDragNewElement()
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayChildren = isArray
|
const displayChildren = isArray ? (props.node.itemProperties ?? []) : props.node.children
|
||||||
? (props.node.itemProperties ?? [])
|
|
||||||
: props.node.children
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -131,7 +132,11 @@ const displayChildren = isArray
|
|||||||
@dragstart="onDragStart"
|
@dragstart="onDragStart"
|
||||||
@dragend="onDragEnd"
|
@dragend="onDragEnd"
|
||||||
>
|
>
|
||||||
<span v-if="hasChildren" class="schema-node__arrow" :class="{ 'schema-node__arrow--expanded': expanded }">
|
<span
|
||||||
|
v-if="hasChildren"
|
||||||
|
class="schema-node__arrow"
|
||||||
|
:class="{ 'schema-node__arrow--expanded': expanded }"
|
||||||
|
>
|
||||||
▶
|
▶
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="schema-node__arrow-placeholder" />
|
<span v-else class="schema-node__arrow-placeholder" />
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useEditorStore } from '../../stores/editor'
|
import { useEditorStore } from '../../stores/editor'
|
||||||
import { useSchemaStore } from '../../stores/schema'
|
import { useSchemaStore } from '../../stores/schema'
|
||||||
import type { TemplateElement, RepeatingTableElement, TableColumn, ImageElement, PageNumberElement, BarcodeElement, PageBreakElement, CurrentDateElement, ShapeElement, CheckboxElement, CalculatedTextElement, RichTextElement, ChartElement } from '../../core/types'
|
import type {
|
||||||
|
TemplateElement,
|
||||||
|
RepeatingTableElement,
|
||||||
|
TableColumn,
|
||||||
|
ImageElement,
|
||||||
|
PageNumberElement,
|
||||||
|
BarcodeElement,
|
||||||
|
PageBreakElement,
|
||||||
|
CurrentDateElement,
|
||||||
|
ShapeElement,
|
||||||
|
CheckboxElement,
|
||||||
|
CalculatedTextElement,
|
||||||
|
RichTextElement,
|
||||||
|
ChartElement,
|
||||||
|
} from '../../core/types'
|
||||||
import { sz } from '../../core/types'
|
import { sz } from '../../core/types'
|
||||||
import { schemaFormatToFormatType, defaultAlignForSchema } from '../../core/schema-parser'
|
import { schemaFormatToFormatType, defaultAlignForSchema } from '../../core/schema-parser'
|
||||||
|
|
||||||
@@ -88,7 +102,7 @@ const tools: ToolItem[] = [
|
|||||||
if (firstArray) {
|
if (firstArray) {
|
||||||
dataPath = firstArray.path
|
dataPath = firstArray.path
|
||||||
const itemFields = schemaStore.getArrayItemFields(firstArray.path)
|
const itemFields = schemaStore.getArrayItemFields(firstArray.path)
|
||||||
columns = itemFields.map(field => ({
|
columns = itemFields.map((field) => ({
|
||||||
id: nextId('col'),
|
id: nextId('col'),
|
||||||
field: field.key,
|
field: field.key,
|
||||||
title: field.title,
|
title: field.title,
|
||||||
@@ -212,8 +226,8 @@ const tools: ToolItem[] = [
|
|||||||
if (firstArray) {
|
if (firstArray) {
|
||||||
dataPath = firstArray.path
|
dataPath = firstArray.path
|
||||||
const itemFields = schemaStore.getArrayItemFields(firstArray.path)
|
const itemFields = schemaStore.getArrayItemFields(firstArray.path)
|
||||||
const stringField = itemFields.find(f => f.type === 'string')
|
const stringField = itemFields.find((f) => f.type === 'string')
|
||||||
const numberField = itemFields.find(f => f.type === 'number' || f.type === 'integer')
|
const numberField = itemFields.find((f) => f.type === 'number' || f.type === 'integer')
|
||||||
categoryField = stringField?.key ?? itemFields[0]?.key ?? ''
|
categoryField = stringField?.key ?? itemFields[0]?.key ?? ''
|
||||||
valueField = numberField?.key ?? itemFields[1]?.key ?? ''
|
valueField = numberField?.key ?? itemFields[1]?.key ?? ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function validateBarcode(format: BarcodeFormat, value: string): boolean {
|
|||||||
case 'code39':
|
case 'code39':
|
||||||
return /^[A-Z0-9\-. $/+%]+$/i.test(value)
|
return /^[A-Z0-9\-. $/+%]+$/i.test(value)
|
||||||
case 'code128':
|
case 'code128':
|
||||||
return value.length > 0 && [...value].every(c => c.charCodeAt(0) < 128)
|
return value.length > 0 && [...value].every((c) => c.charCodeAt(0) < 128)
|
||||||
case 'qr':
|
case 'qr':
|
||||||
return value.length > 0
|
return value.length > 0
|
||||||
default:
|
default:
|
||||||
@@ -61,10 +61,14 @@ function validateBarcode(format: BarcodeFormat, value: string): boolean {
|
|||||||
const barcodeInputValue = ref('')
|
const barcodeInputValue = ref('')
|
||||||
const barcodeInputInvalid = ref(false)
|
const barcodeInputInvalid = ref(false)
|
||||||
|
|
||||||
watch(() => props.element.value ?? '', (val) => {
|
watch(
|
||||||
barcodeInputValue.value = val
|
() => props.element.value ?? '',
|
||||||
barcodeInputInvalid.value = false
|
(val) => {
|
||||||
}, { immediate: true })
|
barcodeInputValue.value = val
|
||||||
|
barcodeInputInvalid.value = false
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
function onBarcodeValueInput(e: Event) {
|
function onBarcodeValueInput(e: Event) {
|
||||||
const val = (e.target as HTMLInputElement).value
|
const val = (e.target as HTMLInputElement).value
|
||||||
@@ -96,9 +100,13 @@ function onBarcodeFormatChange(newFormat: BarcodeFormat) {
|
|||||||
<div class="prop-section__title">Barkod Ayarlari</div>
|
<div class="prop-section__title">Barkod Ayarlari</div>
|
||||||
<div class="prop-row" data-tip="Barkod formati">
|
<div class="prop-row" data-tip="Barkod formati">
|
||||||
<label class="prop-label">Format</label>
|
<label class="prop-label">Format</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.format"
|
:value="element.format"
|
||||||
@change="(e) => onBarcodeFormatChange((e.target as HTMLSelectElement).value as BarcodeFormat)">
|
@change="
|
||||||
|
(e) => onBarcodeFormatChange((e.target as HTMLSelectElement).value as BarcodeFormat)
|
||||||
|
"
|
||||||
|
>
|
||||||
<option value="qr">QR Kod</option>
|
<option value="qr">QR Kod</option>
|
||||||
<option value="ean13">EAN-13</option>
|
<option value="ean13">EAN-13</option>
|
||||||
<option value="ean8">EAN-8</option>
|
<option value="ean8">EAN-8</option>
|
||||||
@@ -108,44 +116,70 @@ function onBarcodeFormatChange(newFormat: BarcodeFormat) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Barkod icerigi — formata uygun olmali">
|
<div class="prop-row" data-tip="Barkod icerigi — formata uygun olmali">
|
||||||
<label class="prop-label">Deger</label>
|
<label class="prop-label">Deger</label>
|
||||||
<input class="prop-input" type="text"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="text"
|
||||||
:class="{ 'prop-input--invalid': barcodeInputInvalid }"
|
:class="{ 'prop-input--invalid': barcodeInputInvalid }"
|
||||||
:value="barcodeInputValue"
|
:value="barcodeInputValue"
|
||||||
@input="onBarcodeValueInput" />
|
@input="onBarcodeValueInput"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Barkod cizgi/modül rengi">
|
<div class="prop-row" data-tip="Barkod cizgi/modül rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<div class="prop-row-inline">
|
<div class="prop-row-inline">
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.color ?? '#000000'"
|
:value="element.style.color ?? '#000000'"
|
||||||
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)"
|
||||||
<button v-if="element.style.color" class="prop-clear" @click="updateStyle('color', undefined)">x</button>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="element.style.color"
|
||||||
|
class="prop-clear"
|
||||||
|
@click="updateStyle('color', undefined)"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="element.format !== 'qr'" class="prop-row" data-tip="Barkod altinda degeri metin olarak goster">
|
<div
|
||||||
|
v-if="element.format !== 'qr'"
|
||||||
|
class="prop-row"
|
||||||
|
data-tip="Barkod altinda degeri metin olarak goster"
|
||||||
|
>
|
||||||
<label class="prop-label">Metin Goster</label>
|
<label class="prop-label">Metin Goster</label>
|
||||||
<input type="checkbox"
|
<input
|
||||||
:checked="element.style.includeText ?? (element.format === 'ean13' || element.format === 'ean8')"
|
type="checkbox"
|
||||||
@change="(e) => updateStyle('includeText', (e.target as HTMLInputElement).checked)" />
|
:checked="
|
||||||
|
element.style.includeText ?? (element.format === 'ean13' || element.format === 'ean8')
|
||||||
|
"
|
||||||
|
@change="(e) => updateStyle('includeText', (e.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="schemaStore.scalarFields.length > 0" class="prop-row" data-tip="Schema'dan dinamik veri baglama">
|
<div
|
||||||
|
v-if="schemaStore.scalarFields.length > 0"
|
||||||
|
class="prop-row"
|
||||||
|
data-tip="Schema'dan dinamik veri baglama"
|
||||||
|
>
|
||||||
<label class="prop-label">Veri Baglama</label>
|
<label class="prop-label">Veri Baglama</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.binding?.path ?? ''"
|
:value="element.binding?.path ?? ''"
|
||||||
@change="(e) => {
|
@change="
|
||||||
const val = (e.target as HTMLSelectElement).value
|
(e) => {
|
||||||
if (val) {
|
const val = (e.target as HTMLSelectElement).value
|
||||||
update({ binding: { type: 'scalar', path: val } } as any)
|
if (val) {
|
||||||
} else {
|
update({ binding: { type: 'scalar', path: val } } as any)
|
||||||
update({ binding: undefined } as any)
|
} else {
|
||||||
|
update({ binding: undefined } as any)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}">
|
"
|
||||||
|
>
|
||||||
<option value="">Yok (statik deger)</option>
|
<option value="">Yok (statik deger)</option>
|
||||||
<option
|
<option v-for="field in schemaStore.scalarFields" :key="field.path" :value="field.path">
|
||||||
v-for="field in schemaStore.scalarFields"
|
{{ field.title }} ({{ field.path }})
|
||||||
:key="field.path"
|
</option>
|
||||||
:value="field.path"
|
|
||||||
>{{ field.title }} ({{ field.path }})</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,18 +27,26 @@ function onExpressionChange(value: string) {
|
|||||||
<template>
|
<template>
|
||||||
<div class="prop-section">
|
<div class="prop-section">
|
||||||
<div class="prop-section__title">Hesaplanan Metin</div>
|
<div class="prop-section__title">Hesaplanan Metin</div>
|
||||||
<div class="prop-row-stack" data-tip="Hesaplama ifadesi (orn: toplamlar.kdv + toplamlar.araToplam)">
|
<div
|
||||||
|
class="prop-row-stack"
|
||||||
|
data-tip="Hesaplama ifadesi (orn: toplamlar.kdv + toplamlar.araToplam)"
|
||||||
|
>
|
||||||
<label class="prop-label">Ifade</label>
|
<label class="prop-label">Ifade</label>
|
||||||
<DexprEditor
|
<DexprEditor
|
||||||
:model-value="element.expression"
|
:model-value="element.expression"
|
||||||
@update:model-value="onExpressionChange"
|
@update:model-value="onExpressionChange"
|
||||||
placeholder="toplamlar.kdv + toplamlar.araToplam" />
|
placeholder="toplamlar.kdv + toplamlar.araToplam"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Sonucun gosterim formati">
|
<div class="prop-row" data-tip="Sonucun gosterim formati">
|
||||||
<label class="prop-label">Format</label>
|
<label class="prop-label">Format</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.format ?? ''"
|
:value="element.format ?? ''"
|
||||||
@change="(e) => update({ format: (e.target as HTMLSelectElement).value || undefined } as any)">
|
@change="
|
||||||
|
(e) => update({ format: (e.target as HTMLSelectElement).value || undefined } as any)
|
||||||
|
"
|
||||||
|
>
|
||||||
<option value="">Yok</option>
|
<option value="">Yok</option>
|
||||||
<option value="currency">Para Birimi</option>
|
<option value="currency">Para Birimi</option>
|
||||||
<option value="number">Sayi</option>
|
<option value="number">Sayi</option>
|
||||||
@@ -47,30 +55,44 @@ function onExpressionChange(value: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
||||||
<label class="prop-label">Boyut (pt)</label>
|
<label class="prop-label">Boyut (pt)</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.style as TextStyle).fontSize ?? 11"
|
:value="(element.style as TextStyle).fontSize ?? 11"
|
||||||
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
|
@input="
|
||||||
|
(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metin rengi">
|
<div class="prop-row" data-tip="Metin rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="(element.style as TextStyle).color ?? '#000000'"
|
:value="(element.style as TextStyle).color ?? '#000000'"
|
||||||
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Yazi tipi kalinligi">
|
<div class="prop-row" data-tip="Yazi tipi kalinligi">
|
||||||
<label class="prop-label">Kalinlik</label>
|
<label class="prop-label">Kalinlik</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
|
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
|
||||||
@change="(e) => updateStyle('fontWeight', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('fontWeight', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="normal">Normal</option>
|
<option value="normal">Normal</option>
|
||||||
<option value="bold">Kalin</option>
|
<option value="bold">Kalin</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
||||||
<label class="prop-label">Hizalama</label>
|
<label class="prop-label">Hizalama</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(element.style as TextStyle).align ?? 'left'"
|
:value="(element.style as TextStyle).align ?? 'left'"
|
||||||
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="left">Sol</option>
|
<option value="left">Sol</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ const itemFields = computed(() => {
|
|||||||
return schemaStore.getArrayItemFields(path)
|
return schemaStore.getArrayItemFields(path)
|
||||||
})
|
})
|
||||||
|
|
||||||
const stringFields = computed(() => itemFields.value.filter(f => f.type === 'string'))
|
const stringFields = computed(() => itemFields.value.filter((f) => f.type === 'string'))
|
||||||
const numberFields = computed(() => itemFields.value.filter(f => f.type === 'number' || f.type === 'integer'))
|
const numberFields = computed(() =>
|
||||||
|
itemFields.value.filter((f) => f.type === 'number' || f.type === 'integer'),
|
||||||
|
)
|
||||||
|
|
||||||
function updateDataSource(path: string) {
|
function updateDataSource(path: string) {
|
||||||
const fields = schemaStore.getArrayItemFields(path)
|
const fields = schemaStore.getArrayItemFields(path)
|
||||||
const strField = fields.find(f => f.type === 'string')
|
const strField = fields.find((f) => f.type === 'string')
|
||||||
const numField = fields.find(f => f.type === 'number' || f.type === 'integer')
|
const numField = fields.find((f) => f.type === 'number' || f.type === 'integer')
|
||||||
update({
|
update({
|
||||||
dataSource: { type: 'array', path },
|
dataSource: { type: 'array', path },
|
||||||
categoryField: strField?.key ?? fields[0]?.key ?? '',
|
categoryField: strField?.key ?? fields[0]?.key ?? '',
|
||||||
@@ -73,7 +75,9 @@ const hasGroup = computed(() => !!props.element.groupField)
|
|||||||
|
|
||||||
// Renk paleti (default 6 renk)
|
// Renk paleti (default 6 renk)
|
||||||
const colorList = computed(() => {
|
const colorList = computed(() => {
|
||||||
return props.element.style.colors ?? ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899']
|
return (
|
||||||
|
props.element.style.colors ?? ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899']
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateColor(index: number, value: string) {
|
function updateColor(index: number, value: string) {
|
||||||
@@ -99,7 +103,11 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section">
|
<div class="prop-section">
|
||||||
<div class="prop-section__title">Grafik Tipi</div>
|
<div class="prop-section__title">Grafik Tipi</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<select class="prop-input prop-select" :value="element.chartType" @change="update({ chartType: ($event.target as HTMLSelectElement).value as ChartType })">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.chartType"
|
||||||
|
@change="update({ chartType: ($event.target as HTMLSelectElement).value as ChartType })"
|
||||||
|
>
|
||||||
<option value="bar">Bar</option>
|
<option value="bar">Bar</option>
|
||||||
<option value="line">Line</option>
|
<option value="line">Line</option>
|
||||||
<option value="pie">Pie</option>
|
<option value="pie">Pie</option>
|
||||||
@@ -112,33 +120,61 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Veri Kaynagi</div>
|
<div class="prop-section__title">Veri Kaynagi</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Array</label>
|
<label class="prop-label">Array</label>
|
||||||
<select class="prop-input prop-select" :value="element.dataSource?.path ?? ''" @change="updateDataSource(($event.target as HTMLSelectElement).value)">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.dataSource?.path ?? ''"
|
||||||
|
@change="updateDataSource(($event.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="" disabled>Sec...</option>
|
<option value="" disabled>Sec...</option>
|
||||||
<option v-for="arr in arrayFields" :key="arr.path" :value="arr.path">{{ arr.title || arr.path }}</option>
|
<option v-for="arr in arrayFields" :key="arr.path" :value="arr.path">
|
||||||
|
{{ arr.title || arr.path }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Kategori</label>
|
<label class="prop-label">Kategori</label>
|
||||||
<select class="prop-input prop-select" :value="element.categoryField" @change="update({ categoryField: ($event.target as HTMLSelectElement).value })">
|
<select
|
||||||
<option v-for="f in itemFields" :key="f.key" :value="f.key">{{ f.title || f.key }}</option>
|
class="prop-input prop-select"
|
||||||
|
:value="element.categoryField"
|
||||||
|
@change="update({ categoryField: ($event.target as HTMLSelectElement).value })"
|
||||||
|
>
|
||||||
|
<option v-for="f in itemFields" :key="f.key" :value="f.key">
|
||||||
|
{{ f.title || f.key }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Deger</label>
|
<label class="prop-label">Deger</label>
|
||||||
<select class="prop-input prop-select" :value="element.valueField" @change="update({ valueField: ($event.target as HTMLSelectElement).value })">
|
<select
|
||||||
<option v-for="f in numberFields" :key="f.key" :value="f.key">{{ f.title || f.key }}</option>
|
class="prop-input prop-select"
|
||||||
|
:value="element.valueField"
|
||||||
|
@change="update({ valueField: ($event.target as HTMLSelectElement).value })"
|
||||||
|
>
|
||||||
|
<option v-for="f in numberFields" :key="f.key" :value="f.key">
|
||||||
|
{{ f.title || f.key }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Gruplama</label>
|
<label class="prop-label">Gruplama</label>
|
||||||
<select class="prop-input prop-select" :value="element.groupField ?? ''" @change="update({ groupField: ($event.target as HTMLSelectElement).value || undefined })">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.groupField ?? ''"
|
||||||
|
@change="update({ groupField: ($event.target as HTMLSelectElement).value || undefined })"
|
||||||
|
>
|
||||||
<option value="">Yok</option>
|
<option value="">Yok</option>
|
||||||
<option v-for="f in stringFields" :key="f.key" :value="f.key">{{ f.title || f.key }}</option>
|
<option v-for="f in stringFields" :key="f.key" :value="f.key">
|
||||||
|
{{ f.title || f.key }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasGroup && !isPie" class="prop-row">
|
<div v-if="hasGroup && !isPie" class="prop-row">
|
||||||
<label class="prop-label">Grup Modu</label>
|
<label class="prop-label">Grup Modu</label>
|
||||||
<select class="prop-input prop-select" :value="element.groupMode ?? 'grouped'" @change="update({ groupMode: ($event.target as HTMLSelectElement).value as GroupMode })">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.groupMode ?? 'grouped'"
|
||||||
|
@change="update({ groupMode: ($event.target as HTMLSelectElement).value as GroupMode })"
|
||||||
|
>
|
||||||
<option value="grouped">Yan Yana</option>
|
<option value="grouped">Yan Yana</option>
|
||||||
<option value="stacked">Yigin</option>
|
<option value="stacked">Yigin</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -150,19 +186,40 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Baslik</div>
|
<div class="prop-section__title">Baslik</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Metin</label>
|
<label class="prop-label">Metin</label>
|
||||||
<input class="prop-input" type="text" :value="element.title?.text ?? ''" @change="updateTitle('text', ($event.target as HTMLInputElement).value)" placeholder="Grafik basligi">
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="text"
|
||||||
|
:value="element.title?.text ?? ''"
|
||||||
|
@change="updateTitle('text', ($event.target as HTMLInputElement).value)"
|
||||||
|
placeholder="Grafik basligi"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" v-if="element.title?.text">
|
<div class="prop-row" v-if="element.title?.text">
|
||||||
<label class="prop-label">Boyut</label>
|
<label class="prop-label">Boyut</label>
|
||||||
<input class="prop-input prop-input--sm" type="number" :value="element.title?.fontSize ?? 4" step="0.5" @change="updateTitle('fontSize', parseFloat(($event.target as HTMLInputElement).value))">
|
<input
|
||||||
|
class="prop-input prop-input--sm"
|
||||||
|
type="number"
|
||||||
|
:value="element.title?.fontSize ?? 4"
|
||||||
|
step="0.5"
|
||||||
|
@change="updateTitle('fontSize', parseFloat(($event.target as HTMLInputElement).value))"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" v-if="element.title?.text">
|
<div class="prop-row" v-if="element.title?.text">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-color" type="color" :value="element.title?.color ?? '#333333'" @input="updateTitle('color', ($event.target as HTMLInputElement).value)">
|
<input
|
||||||
|
class="prop-color"
|
||||||
|
type="color"
|
||||||
|
:value="element.title?.color ?? '#333333'"
|
||||||
|
@input="updateTitle('color', ($event.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" v-if="element.title?.text">
|
<div class="prop-row" v-if="element.title?.text">
|
||||||
<label class="prop-label">Hiza</label>
|
<label class="prop-label">Hiza</label>
|
||||||
<select class="prop-input prop-select" :value="element.title?.align ?? 'center'" @change="updateTitle('align', ($event.target as HTMLSelectElement).value)">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.title?.align ?? 'center'"
|
||||||
|
@change="updateTitle('align', ($event.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="left">Sol</option>
|
<option value="left">Sol</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
@@ -175,12 +232,20 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Gosterge</div>
|
<div class="prop-section__title">Gosterge</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Goster</label>
|
<label class="prop-label">Goster</label>
|
||||||
<input type="checkbox" :checked="element.legend?.show ?? false" @change="updateLegend('show', ($event.target as HTMLInputElement).checked)">
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="element.legend?.show ?? false"
|
||||||
|
@change="updateLegend('show', ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="element.legend?.show">
|
<template v-if="element.legend?.show">
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Konum</label>
|
<label class="prop-label">Konum</label>
|
||||||
<select class="prop-input prop-select" :value="element.legend?.position ?? 'bottom'" @change="updateLegend('position', ($event.target as HTMLSelectElement).value)">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.legend?.position ?? 'bottom'"
|
||||||
|
@change="updateLegend('position', ($event.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="top">Ust</option>
|
<option value="top">Ust</option>
|
||||||
<option value="bottom">Alt</option>
|
<option value="bottom">Alt</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
@@ -188,7 +253,15 @@ function removeColor(index: number) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Boyut</label>
|
<label class="prop-label">Boyut</label>
|
||||||
<input class="prop-input prop-input--sm" type="number" :value="element.legend?.fontSize ?? 2.8" step="0.2" @change="updateLegend('fontSize', parseFloat(($event.target as HTMLInputElement).value))">
|
<input
|
||||||
|
class="prop-input prop-input--sm"
|
||||||
|
type="number"
|
||||||
|
:value="element.legend?.fontSize ?? 2.8"
|
||||||
|
step="0.2"
|
||||||
|
@change="
|
||||||
|
updateLegend('fontSize', parseFloat(($event.target as HTMLInputElement).value))
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,16 +271,33 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Etiketler</div>
|
<div class="prop-section__title">Etiketler</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Goster</label>
|
<label class="prop-label">Goster</label>
|
||||||
<input type="checkbox" :checked="element.labels?.show ?? false" @change="updateLabels('show', ($event.target as HTMLInputElement).checked)">
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="element.labels?.show ?? false"
|
||||||
|
@change="updateLabels('show', ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="element.labels?.show">
|
<template v-if="element.labels?.show">
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Boyut</label>
|
<label class="prop-label">Boyut</label>
|
||||||
<input class="prop-input prop-input--sm" type="number" :value="element.labels?.fontSize ?? 2.2" step="0.2" @change="updateLabels('fontSize', parseFloat(($event.target as HTMLInputElement).value))">
|
<input
|
||||||
|
class="prop-input prop-input--sm"
|
||||||
|
type="number"
|
||||||
|
:value="element.labels?.fontSize ?? 2.2"
|
||||||
|
step="0.2"
|
||||||
|
@change="
|
||||||
|
updateLabels('fontSize', parseFloat(($event.target as HTMLInputElement).value))
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-color" type="color" :value="element.labels?.color ?? '#333333'" @input="updateLabels('color', ($event.target as HTMLInputElement).value)">
|
<input
|
||||||
|
class="prop-color"
|
||||||
|
type="color"
|
||||||
|
:value="element.labels?.color ?? '#333333'"
|
||||||
|
@input="updateLabels('color', ($event.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,19 +307,40 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Eksenler</div>
|
<div class="prop-section__title">Eksenler</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">X Etiketi</label>
|
<label class="prop-label">X Etiketi</label>
|
||||||
<input class="prop-input" type="text" :value="element.axis?.xLabel ?? ''" @change="updateAxis('xLabel', ($event.target as HTMLInputElement).value || undefined)" placeholder="X ekseni">
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="text"
|
||||||
|
:value="element.axis?.xLabel ?? ''"
|
||||||
|
@change="updateAxis('xLabel', ($event.target as HTMLInputElement).value || undefined)"
|
||||||
|
placeholder="X ekseni"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Y Etiketi</label>
|
<label class="prop-label">Y Etiketi</label>
|
||||||
<input class="prop-input" type="text" :value="element.axis?.yLabel ?? ''" @change="updateAxis('yLabel', ($event.target as HTMLInputElement).value || undefined)" placeholder="Y ekseni">
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="text"
|
||||||
|
:value="element.axis?.yLabel ?? ''"
|
||||||
|
@change="updateAxis('yLabel', ($event.target as HTMLInputElement).value || undefined)"
|
||||||
|
placeholder="Y ekseni"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Izgara</label>
|
<label class="prop-label">Izgara</label>
|
||||||
<input type="checkbox" :checked="element.axis?.showGrid ?? true" @change="updateAxis('showGrid', ($event.target as HTMLInputElement).checked)">
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="element.axis?.showGrid ?? true"
|
||||||
|
@change="updateAxis('showGrid', ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" v-if="element.axis?.showGrid !== false">
|
<div class="prop-row" v-if="element.axis?.showGrid !== false">
|
||||||
<label class="prop-label">Izgara Renk</label>
|
<label class="prop-label">Izgara Renk</label>
|
||||||
<input class="prop-color" type="color" :value="element.axis?.gridColor ?? '#E5E7EB'" @input="updateAxis('gridColor', ($event.target as HTMLInputElement).value)">
|
<input
|
||||||
|
class="prop-color"
|
||||||
|
type="color"
|
||||||
|
:value="element.axis?.gridColor ?? '#E5E7EB'"
|
||||||
|
@input="updateAxis('gridColor', ($event.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -238,14 +349,26 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Stil</div>
|
<div class="prop-section__title">Stil</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Arka Plan</label>
|
<label class="prop-label">Arka Plan</label>
|
||||||
<input class="prop-color" type="color" :value="element.style.backgroundColor ?? '#FFFFFF'" @input="updateStyle('backgroundColor', ($event.target as HTMLInputElement).value)">
|
<input
|
||||||
|
class="prop-color"
|
||||||
|
type="color"
|
||||||
|
:value="element.style.backgroundColor ?? '#FFFFFF'"
|
||||||
|
@input="updateStyle('backgroundColor', ($event.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Renk Paleti -->
|
<!-- Renk Paleti -->
|
||||||
<div class="prop-section__subtitle">Renk Paleti</div>
|
<div class="prop-section__subtitle">Renk Paleti</div>
|
||||||
<div v-for="(color, i) in colorList" :key="i" class="prop-row">
|
<div v-for="(color, i) in colorList" :key="i" class="prop-row">
|
||||||
<input class="prop-color" type="color" :value="color" @input="updateColor(i, ($event.target as HTMLInputElement).value)">
|
<input
|
||||||
<button class="prop-btn-sm prop-btn-sm--danger" @click="removeColor(i)" title="Kaldir">×</button>
|
class="prop-color"
|
||||||
|
type="color"
|
||||||
|
:value="color"
|
||||||
|
@input="updateColor(i, ($event.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
|
<button class="prop-btn-sm prop-btn-sm--danger" @click="removeColor(i)" title="Kaldir">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="prop-btn-sm" @click="addColor">+ Renk Ekle</button>
|
<button class="prop-btn-sm" @click="addColor">+ Renk Ekle</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -255,7 +378,15 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Bar Ayarlari</div>
|
<div class="prop-section__title">Bar Ayarlari</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Bar Boslugu</label>
|
<label class="prop-label">Bar Boslugu</label>
|
||||||
<input class="prop-input prop-input--sm" type="number" :value="element.style.barGap ?? 0.2" step="0.05" min="0" max="0.8" @change="updateStyle('barGap', parseFloat(($event.target as HTMLInputElement).value))">
|
<input
|
||||||
|
class="prop-input prop-input--sm"
|
||||||
|
type="number"
|
||||||
|
:value="element.style.barGap ?? 0.2"
|
||||||
|
step="0.05"
|
||||||
|
min="0"
|
||||||
|
max="0.8"
|
||||||
|
@change="updateStyle('barGap', parseFloat(($event.target as HTMLInputElement).value))"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -263,11 +394,22 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Line Ayarlari</div>
|
<div class="prop-section__title">Line Ayarlari</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Cizgi Kalinligi</label>
|
<label class="prop-label">Cizgi Kalinligi</label>
|
||||||
<input class="prop-input prop-input--sm" type="number" :value="element.style.lineWidth ?? 0.5" step="0.1" min="0.1" @change="updateStyle('lineWidth', parseFloat(($event.target as HTMLInputElement).value))">
|
<input
|
||||||
|
class="prop-input prop-input--sm"
|
||||||
|
type="number"
|
||||||
|
:value="element.style.lineWidth ?? 0.5"
|
||||||
|
step="0.1"
|
||||||
|
min="0.1"
|
||||||
|
@change="updateStyle('lineWidth', parseFloat(($event.target as HTMLInputElement).value))"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Noktalar</label>
|
<label class="prop-label">Noktalar</label>
|
||||||
<input type="checkbox" :checked="element.style.showPoints ?? true" @change="updateStyle('showPoints', ($event.target as HTMLInputElement).checked)">
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="element.style.showPoints ?? true"
|
||||||
|
@change="updateStyle('showPoints', ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -275,11 +417,19 @@ function removeColor(index: number) {
|
|||||||
<div class="prop-section__title">Pie Ayarlari</div>
|
<div class="prop-section__title">Pie Ayarlari</div>
|
||||||
<div class="prop-row">
|
<div class="prop-row">
|
||||||
<label class="prop-label">Ic Yaricap</label>
|
<label class="prop-label">Ic Yaricap</label>
|
||||||
<input class="prop-input prop-input--sm" type="number" :value="element.style.innerRadius ?? 0" step="0.05" min="0" max="0.9" @change="updateStyle('innerRadius', parseFloat(($event.target as HTMLInputElement).value))">
|
<input
|
||||||
</div>
|
class="prop-input prop-input--sm"
|
||||||
<div class="prop-row" style="font-size: 11px; color: #94a3b8;">
|
type="number"
|
||||||
0 = Pie, >0 = Donut
|
:value="element.style.innerRadius ?? 0"
|
||||||
|
step="0.05"
|
||||||
|
min="0"
|
||||||
|
max="0.9"
|
||||||
|
@change="
|
||||||
|
updateStyle('innerRadius', parseFloat(($event.target as HTMLInputElement).value))
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="prop-row" style="font-size: 11px; color: #94a3b8">0 = Pie, >0 = Donut</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,27 +24,40 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-section__title">Onay Kutusu</div>
|
<div class="prop-section__title">Onay Kutusu</div>
|
||||||
<div v-if="!element.binding" class="prop-row" data-tip="Onay kutusunun varsayilan durumu">
|
<div v-if="!element.binding" class="prop-row" data-tip="Onay kutusunun varsayilan durumu">
|
||||||
<label class="prop-label">Isaretli</label>
|
<label class="prop-label">Isaretli</label>
|
||||||
<input type="checkbox"
|
<input
|
||||||
|
type="checkbox"
|
||||||
:checked="element.checked ?? false"
|
:checked="element.checked ?? false"
|
||||||
@change="(e) => update({ checked: (e.target as HTMLInputElement).checked } as any)" />
|
@change="(e) => update({ checked: (e.target as HTMLInputElement).checked } as any)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Onay kutusu boyutu (mm)">
|
<div class="prop-row" data-tip="Onay kutusu boyutu (mm)">
|
||||||
<label class="prop-label">Boyut (mm)</label>
|
<label class="prop-label">Boyut (mm)</label>
|
||||||
<input class="prop-input" type="number" step="0.5" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="1"
|
||||||
:value="element.style.size ?? 4"
|
:value="element.style.size ?? 4"
|
||||||
@input="(e) => updateStyle('size', parseFloat((e.target as HTMLInputElement).value) || 4)" />
|
@input="(e) => updateStyle('size', parseFloat((e.target as HTMLInputElement).value) || 4)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Isaret (tik) rengi">
|
<div class="prop-row" data-tip="Isaret (tik) rengi">
|
||||||
<label class="prop-label">Isaret Rengi</label>
|
<label class="prop-label">Isaret Rengi</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.checkColor ?? '#000000'"
|
:value="element.style.checkColor ?? '#000000'"
|
||||||
@input="(e) => updateStyle('checkColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('checkColor', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kutu kenarlik rengi">
|
<div class="prop-row" data-tip="Kutu kenarlik rengi">
|
||||||
<label class="prop-label">Kenar Rengi</label>
|
<label class="prop-label">Kenar Rengi</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.borderColor ?? '#333333'"
|
:value="element.style.borderColor ?? '#333333'"
|
||||||
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -25,24 +25,37 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-section__title">Container Ayarlari</div>
|
<div class="prop-section__title">Container Ayarlari</div>
|
||||||
<div class="prop-row" data-tip="Cocuk elemanlarin dizilim yonu">
|
<div class="prop-row" data-tip="Cocuk elemanlarin dizilim yonu">
|
||||||
<label class="prop-label">Yon</label>
|
<label class="prop-label">Yon</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.direction"
|
:value="element.direction"
|
||||||
@change="(e) => update({ direction: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ direction: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="column">Dikey</option>
|
<option value="column">Dikey</option>
|
||||||
<option value="row">Yatay</option>
|
<option value="row">Yatay</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Cocuk elemanlar arasi bosluk (mm)">
|
<div class="prop-row" data-tip="Cocuk elemanlar arasi bosluk (mm)">
|
||||||
<label class="prop-label">Bosluk (mm)</label>
|
<label class="prop-label">Bosluk (mm)</label>
|
||||||
<input class="prop-input" type="number" step="1" min="0"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
:value="element.gap"
|
:value="element.gap"
|
||||||
@input="(e) => update({ gap: parseFloat((e.target as HTMLInputElement).value) || 0 } as any)" />
|
@input="
|
||||||
|
(e) => update({ gap: parseFloat((e.target as HTMLInputElement).value) || 0 } as any)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Cocuklarin cross-axis hizalamasi">
|
<div class="prop-row" data-tip="Cocuklarin cross-axis hizalamasi">
|
||||||
<label class="prop-label">{{ element.direction === 'column' ? 'Yatay Hizalama' : 'Dikey Hizalama' }}</label>
|
<label class="prop-label">{{
|
||||||
<select class="prop-input prop-select"
|
element.direction === 'column' ? 'Yatay Hizalama' : 'Dikey Hizalama'
|
||||||
|
}}</label>
|
||||||
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.align"
|
:value="element.align"
|
||||||
@change="(e) => update({ align: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ align: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="start">{{ element.direction === 'column' ? 'Sol' : 'Ust' }}</option>
|
<option value="start">{{ element.direction === 'column' ? 'Sol' : 'Ust' }}</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="end">{{ element.direction === 'column' ? 'Sag' : 'Alt' }}</option>
|
<option value="end">{{ element.direction === 'column' ? 'Sag' : 'Alt' }}</option>
|
||||||
@@ -50,10 +63,14 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Cocuklarin main-axis dagilimi">
|
<div class="prop-row" data-tip="Cocuklarin main-axis dagilimi">
|
||||||
<label class="prop-label">{{ element.direction === 'column' ? 'Dikey Dagilim' : 'Yatay Dagilim' }}</label>
|
<label class="prop-label">{{
|
||||||
<select class="prop-input prop-select"
|
element.direction === 'column' ? 'Dikey Dagilim' : 'Yatay Dagilim'
|
||||||
|
}}</label>
|
||||||
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.justify"
|
:value="element.justify"
|
||||||
@change="(e) => update({ justify: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ justify: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="start">{{ element.direction === 'column' ? 'Ust' : 'Sol' }}</option>
|
<option value="start">{{ element.direction === 'column' ? 'Ust' : 'Sol' }}</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="end">{{ element.direction === 'column' ? 'Alt' : 'Sag' }}</option>
|
<option value="end">{{ element.direction === 'column' ? 'Alt' : 'Sag' }}</option>
|
||||||
@@ -72,9 +89,11 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
|
|
||||||
<div class="prop-row" data-tip="Sayfa sonunda bolunmeyi kontrol eder">
|
<div class="prop-row" data-tip="Sayfa sonunda bolunmeyi kontrol eder">
|
||||||
<label class="prop-label">Sayfa Bolme</label>
|
<label class="prop-label">Sayfa Bolme</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.breakInside ?? 'auto'"
|
:value="element.breakInside ?? 'auto'"
|
||||||
@change="(e) => update({ breakInside: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ breakInside: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="auto">Izin Ver</option>
|
<option value="auto">Izin Ver</option>
|
||||||
<option value="avoid">Bolme</option>
|
<option value="avoid">Bolme</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -84,32 +103,59 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-row" data-tip="Container arka plan rengi">
|
<div class="prop-row" data-tip="Container arka plan rengi">
|
||||||
<label class="prop-label">Arka plan</label>
|
<label class="prop-label">Arka plan</label>
|
||||||
<div class="prop-row-inline">
|
<div class="prop-row-inline">
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.backgroundColor ?? '#ffffff'"
|
:value="element.style.backgroundColor ?? '#ffffff'"
|
||||||
@input="(e) => updateStyle('backgroundColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('backgroundColor', (e.target as HTMLInputElement).value)"
|
||||||
<button v-if="element.style.backgroundColor" class="prop-clear" @click="updateStyle('backgroundColor', undefined)">x</button>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="element.style.backgroundColor"
|
||||||
|
class="prop-clear"
|
||||||
|
@click="updateStyle('backgroundColor', undefined)"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kenarlik kalinligi (mm)">
|
<div class="prop-row" data-tip="Kenarlik kalinligi (mm)">
|
||||||
<label class="prop-label">Kenarlik (mm)</label>
|
<label class="prop-label">Kenarlik (mm)</label>
|
||||||
<input class="prop-input" type="number" step="0.1" min="0"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
:value="element.style.borderWidth ?? 0"
|
:value="element.style.borderWidth ?? 0"
|
||||||
@input="(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kenarlik cizgisi rengi">
|
<div class="prop-row" data-tip="Kenarlik cizgisi rengi">
|
||||||
<label class="prop-label">Kenarlik rengi</label>
|
<label class="prop-label">Kenarlik rengi</label>
|
||||||
<div class="prop-row-inline">
|
<div class="prop-row-inline">
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.borderColor ?? '#000000'"
|
:value="element.style.borderColor ?? '#000000'"
|
||||||
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)"
|
||||||
<button v-if="element.style.borderColor" class="prop-clear" @click="updateStyle('borderColor', undefined)">x</button>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="element.style.borderColor"
|
||||||
|
class="prop-clear"
|
||||||
|
@click="updateStyle('borderColor', undefined)"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kenarlik cizgi stili">
|
<div class="prop-row" data-tip="Kenarlik cizgi stili">
|
||||||
<label class="prop-label">Kenarlik stili</label>
|
<label class="prop-label">Kenarlik stili</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.style.borderStyle ?? 'solid'"
|
:value="element.style.borderStyle ?? 'solid'"
|
||||||
@change="(e) => updateStyle('borderStyle', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('borderStyle', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="solid">Duz</option>
|
<option value="solid">Duz</option>
|
||||||
<option value="dashed">Kesikli</option>
|
<option value="dashed">Kesikli</option>
|
||||||
<option value="dotted">Noktali</option>
|
<option value="dotted">Noktali</option>
|
||||||
@@ -117,9 +163,16 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kose yuvarlakligi (mm)">
|
<div class="prop-row" data-tip="Kose yuvarlakligi (mm)">
|
||||||
<label class="prop-label">Radius (mm)</label>
|
<label class="prop-label">Radius (mm)</label>
|
||||||
<input class="prop-input" type="number" step="0.5" min="0"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="0"
|
||||||
:value="element.style.borderRadius ?? 0"
|
:value="element.style.borderRadius ?? 0"
|
||||||
@input="(e) => updateStyle('borderRadius', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) => updateStyle('borderRadius', parseFloat((e.target as HTMLInputElement).value) || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-section__title">Tarih</div>
|
<div class="prop-section__title">Tarih</div>
|
||||||
<div class="prop-row" data-tip="Tarih gosterim formati">
|
<div class="prop-row" data-tip="Tarih gosterim formati">
|
||||||
<label class="prop-label">Format</label>
|
<label class="prop-label">Format</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.format ?? 'DD.MM.YYYY'"
|
:value="element.format ?? 'DD.MM.YYYY'"
|
||||||
@change="(e) => update({ format: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ format: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="DD.MM.YYYY">30.03.2026</option>
|
<option value="DD.MM.YYYY">30.03.2026</option>
|
||||||
<option value="DD/MM/YYYY">30/03/2026</option>
|
<option value="DD/MM/YYYY">30/03/2026</option>
|
||||||
<option value="YYYY-MM-DD">2026-03-30</option>
|
<option value="YYYY-MM-DD">2026-03-30</option>
|
||||||
@@ -35,21 +37,33 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
||||||
<label class="prop-label">Boyut (pt)</label>
|
<label class="prop-label">Boyut (pt)</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.style as TextStyle).fontSize ?? 10"
|
:value="(element.style as TextStyle).fontSize ?? 10"
|
||||||
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
|
@input="
|
||||||
|
(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metin rengi">
|
<div class="prop-row" data-tip="Metin rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="(element.style as TextStyle).color ?? '#666666'"
|
:value="(element.style as TextStyle).color ?? '#666666'"
|
||||||
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
||||||
<label class="prop-label">Hizalama</label>
|
<label class="prop-label">Hizalama</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(element.style as TextStyle).align ?? 'left'"
|
:value="(element.style as TextStyle).align ?? 'left'"
|
||||||
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="left">Sol</option>
|
<option value="left">Sol</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ function setMode(mode: 'static' | 'dynamic') {
|
|||||||
update({ binding: undefined } as Partial<TemplateElement>)
|
update({ binding: undefined } as Partial<TemplateElement>)
|
||||||
} else {
|
} else {
|
||||||
// Dinamik moda geç — ilk uygun alanı seç veya boş bırak
|
// Dinamik moda geç — ilk uygun alanı seç veya boş bırak
|
||||||
const imageFields = schemaStore.scalarFields.filter(f => f.format === 'image' || f.type === 'string')
|
const imageFields = schemaStore.scalarFields.filter(
|
||||||
|
(f) => f.format === 'image' || f.type === 'string',
|
||||||
|
)
|
||||||
const path = imageFields.length > 0 ? imageFields[0].path : ''
|
const path = imageFields.length > 0 ? imageFields[0].path : ''
|
||||||
update({ src: undefined, binding: { type: 'scalar', path } } as Partial<TemplateElement>)
|
update({ src: undefined, binding: { type: 'scalar', path } } as Partial<TemplateElement>)
|
||||||
}
|
}
|
||||||
@@ -52,7 +54,7 @@ function setBindingPath(path: string) {
|
|||||||
|
|
||||||
/** Schema'dan görsel olabilecek alanlar (format: image veya string) */
|
/** Schema'dan görsel olabilecek alanlar (format: image veya string) */
|
||||||
const imageScalarFields = computed(() => {
|
const imageScalarFields = computed(() => {
|
||||||
return schemaStore.scalarFields.filter(f => f.format === 'image' || f.type === 'string')
|
return schemaStore.scalarFields.filter((f) => f.format === 'image' || f.type === 'string')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -64,8 +66,20 @@ const imageScalarFields = computed(() => {
|
|||||||
<div class="prop-row" data-tip="Gorsel kaynagi: dosya veya veri alanından">
|
<div class="prop-row" data-tip="Gorsel kaynagi: dosya veya veri alanından">
|
||||||
<label class="prop-label">Mod</label>
|
<label class="prop-label">Mod</label>
|
||||||
<div class="prop-toggle-group">
|
<div class="prop-toggle-group">
|
||||||
<button class="prop-toggle-btn" :class="{ 'prop-toggle-btn--active': !isDynamic }" @click="setMode('static')">Statik</button>
|
<button
|
||||||
<button class="prop-toggle-btn" :class="{ 'prop-toggle-btn--active': isDynamic }" @click="setMode('dynamic')">Dinamik</button>
|
class="prop-toggle-btn"
|
||||||
|
:class="{ 'prop-toggle-btn--active': !isDynamic }"
|
||||||
|
@click="setMode('static')"
|
||||||
|
>
|
||||||
|
Statik
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="prop-toggle-btn"
|
||||||
|
:class="{ 'prop-toggle-btn--active': isDynamic }"
|
||||||
|
@click="setMode('dynamic')"
|
||||||
|
>
|
||||||
|
Dinamik
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -84,7 +98,9 @@ const imageScalarFields = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="element.src" class="prop-row" data-tip="Gorseli kaldirmak icin tiklayin">
|
<div v-if="element.src" class="prop-row" data-tip="Gorseli kaldirmak icin tiklayin">
|
||||||
<label class="prop-label"></label>
|
<label class="prop-label"></label>
|
||||||
<button class="prop-clear" @click="update({ src: undefined } as any)">Gorseli kaldir</button>
|
<button class="prop-clear" @click="update({ src: undefined } as any)">
|
||||||
|
Gorseli kaldir
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -92,15 +108,15 @@ const imageScalarFields = computed(() => {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="prop-row" data-tip="Gorsel URL'sinin gelecegi veri alani">
|
<div class="prop-row" data-tip="Gorsel URL'sinin gelecegi veri alani">
|
||||||
<label class="prop-label">Veri Alani</label>
|
<label class="prop-label">Veri Alani</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.binding?.path ?? ''"
|
:value="element.binding?.path ?? ''"
|
||||||
@change="(e) => setBindingPath((e.target as HTMLSelectElement).value)">
|
@change="(e) => setBindingPath((e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="" disabled>Secin...</option>
|
<option value="" disabled>Secin...</option>
|
||||||
<option
|
<option v-for="field in imageScalarFields" :key="field.path" :value="field.path">
|
||||||
v-for="field in imageScalarFields"
|
{{ field.title }} ({{ field.path }})
|
||||||
:key="field.path"
|
</option>
|
||||||
:value="field.path"
|
|
||||||
>{{ field.title }} ({{ field.path }})</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="element.binding?.path" class="prop-row">
|
<div v-if="element.binding?.path" class="prop-row">
|
||||||
@@ -112,9 +128,11 @@ const imageScalarFields = computed(() => {
|
|||||||
<!-- Sığdırma modu (ortak) -->
|
<!-- Sığdırma modu (ortak) -->
|
||||||
<div class="prop-row" data-tip="Gorselin alana sigdirma modu">
|
<div class="prop-row" data-tip="Gorselin alana sigdirma modu">
|
||||||
<label class="prop-label">Sigdirma</label>
|
<label class="prop-label">Sigdirma</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.style.objectFit ?? 'contain'"
|
:value="element.style.objectFit ?? 'contain'"
|
||||||
@change="(e) => updateStyle('objectFit', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('objectFit', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="contain">Sigdir</option>
|
<option value="contain">Sigdir</option>
|
||||||
<option value="cover">Kap</option>
|
<option value="cover">Kap</option>
|
||||||
<option value="stretch">Esnet</option>
|
<option value="stretch">Esnet</option>
|
||||||
@@ -137,7 +155,9 @@ const imageScalarFields = computed(() => {
|
|||||||
color: #64748b;
|
color: #64748b;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.1s, color 0.1s;
|
transition:
|
||||||
|
background 0.1s,
|
||||||
|
color 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prop-toggle-btn:first-child {
|
.prop-toggle-btn:first-child {
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ const editorStore = useEditorStore()
|
|||||||
function updateStyle(key: string, value: unknown) {
|
function updateStyle(key: string, value: unknown) {
|
||||||
const id = editorStore.selectedElementId
|
const id = editorStore.selectedElementId
|
||||||
if (!id) return
|
if (!id) return
|
||||||
templateStore.updateElement(id, { style: { ...props.element.style, [key]: value } } as Partial<TemplateElement>)
|
templateStore.updateElement(id, {
|
||||||
|
style: { ...props.element.style, [key]: value },
|
||||||
|
} as Partial<TemplateElement>)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -20,15 +22,25 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-section__title">Cizgi Stili</div>
|
<div class="prop-section__title">Cizgi Stili</div>
|
||||||
<div class="prop-row" data-tip="Cizgi kalinligi (mm)">
|
<div class="prop-row" data-tip="Cizgi kalinligi (mm)">
|
||||||
<label class="prop-label">Kalinlik (mm)</label>
|
<label class="prop-label">Kalinlik (mm)</label>
|
||||||
<input class="prop-input" type="number" step="0.1" min="0.1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0.1"
|
||||||
:value="element.style.strokeWidth ?? 0.5"
|
:value="element.style.strokeWidth ?? 0.5"
|
||||||
@input="(e) => updateStyle('strokeWidth', parseFloat((e.target as HTMLInputElement).value) || 0.5)" />
|
@input="
|
||||||
|
(e) => updateStyle('strokeWidth', parseFloat((e.target as HTMLInputElement).value) || 0.5)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Cizgi rengi">
|
<div class="prop-row" data-tip="Cizgi rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.strokeColor ?? '#000000'"
|
:value="element.style.strokeColor ?? '#000000'"
|
||||||
@input="(e) => updateStyle('strokeColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('strokeColor', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -22,10 +22,42 @@ function onInput(side: 'top' | 'right' | 'bottom' | 'left', e: Event) {
|
|||||||
<div class="pb">
|
<div class="pb">
|
||||||
<span class="pb__label">Padding</span>
|
<span class="pb__label">Padding</span>
|
||||||
<div class="pb__box">
|
<div class="pb__box">
|
||||||
<input class="pb__in pb__in--t" type="number" step="1" min="0" :value="props.top" @input="(e) => onInput('top', e)" data-tip="Ust bosluk (mm)" />
|
<input
|
||||||
<input class="pb__in pb__in--r" type="number" step="1" min="0" :value="props.right" @input="(e) => onInput('right', e)" data-tip="Sag bosluk (mm)" />
|
class="pb__in pb__in--t"
|
||||||
<input class="pb__in pb__in--b" type="number" step="1" min="0" :value="props.bottom" @input="(e) => onInput('bottom', e)" data-tip="Alt bosluk (mm)" />
|
type="number"
|
||||||
<input class="pb__in pb__in--l" type="number" step="1" min="0" :value="props.left" @input="(e) => onInput('left', e)" data-tip="Sol bosluk (mm)" />
|
step="1"
|
||||||
|
min="0"
|
||||||
|
:value="props.top"
|
||||||
|
@input="(e) => onInput('top', e)"
|
||||||
|
data-tip="Ust bosluk (mm)"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="pb__in pb__in--r"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
:value="props.right"
|
||||||
|
@input="(e) => onInput('right', e)"
|
||||||
|
data-tip="Sag bosluk (mm)"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="pb__in pb__in--b"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
:value="props.bottom"
|
||||||
|
@input="(e) => onInput('bottom', e)"
|
||||||
|
data-tip="Alt bosluk (mm)"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="pb__in pb__in--l"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
:value="props.left"
|
||||||
|
@input="(e) => onInput('left', e)"
|
||||||
|
data-tip="Sol bosluk (mm)"
|
||||||
|
/>
|
||||||
<div class="pb__center" />
|
<div class="pb__center" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,11 +119,32 @@ function onInput(side: 'top' | 'right' | 'bottom' | 'left', e: Event) {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pb__in:hover { background: #f1f5f9; }
|
.pb__in:hover {
|
||||||
.pb__in:focus { background: white; box-shadow: 0 0 0 1px #93c5fd; }
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
.pb__in:focus {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 0 1px #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
.pb__in--t { top: 1px; left: 50%; transform: translateX(-50%); }
|
.pb__in--t {
|
||||||
.pb__in--b { bottom: 1px; left: 50%; transform: translateX(-50%); }
|
top: 1px;
|
||||||
.pb__in--l { left: 2px; top: 50%; transform: translateY(-50%); }
|
left: 50%;
|
||||||
.pb__in--r { right: 2px; top: 50%; transform: translateY(-50%); }
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.pb__in--b {
|
||||||
|
bottom: 1px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.pb__in--l {
|
||||||
|
left: 2px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.pb__in--r {
|
||||||
|
right: 2px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-section__title">Sayfa Numarasi</div>
|
<div class="prop-section__title">Sayfa Numarasi</div>
|
||||||
<div class="prop-row" data-tip="Sayfa numarasi gosterim formati">
|
<div class="prop-row" data-tip="Sayfa numarasi gosterim formati">
|
||||||
<label class="prop-label">Format</label>
|
<label class="prop-label">Format</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.format ?? '{current} / {total}'"
|
:value="element.format ?? '{current} / {total}'"
|
||||||
@change="(e) => update({ format: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ format: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="{current} / {total}">1 / 5</option>
|
<option value="{current} / {total}">1 / 5</option>
|
||||||
<option value="{current}">1</option>
|
<option value="{current}">1</option>
|
||||||
<option value="Sayfa {current}">Sayfa 1</option>
|
<option value="Sayfa {current}">Sayfa 1</option>
|
||||||
@@ -35,21 +37,33 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
||||||
<label class="prop-label">Boyut (pt)</label>
|
<label class="prop-label">Boyut (pt)</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.style as TextStyle).fontSize ?? 10"
|
:value="(element.style as TextStyle).fontSize ?? 10"
|
||||||
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
|
@input="
|
||||||
|
(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metin rengi">
|
<div class="prop-row" data-tip="Metin rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="(element.style as TextStyle).color ?? '#666666'"
|
:value="(element.style as TextStyle).color ?? '#666666'"
|
||||||
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
||||||
<label class="prop-label">Hizalama</label>
|
<label class="prop-label">Hizalama</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(element.style as TextStyle).align ?? 'center'"
|
:value="(element.style as TextStyle).align ?? 'center'"
|
||||||
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="left">Sol</option>
|
<option value="left">Sol</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ function togglePositioning() {
|
|||||||
<div class="prop-section__title">Pozisyon</div>
|
<div class="prop-section__title">Pozisyon</div>
|
||||||
<div class="prop-row" data-tip="Flow: otomatik dizilim, Absolute: sabit konum">
|
<div class="prop-row" data-tip="Flow: otomatik dizilim, Absolute: sabit konum">
|
||||||
<label class="prop-label">Mod</label>
|
<label class="prop-label">Mod</label>
|
||||||
<select class="prop-input prop-select" :value="element.position.type" @change="togglePositioning">
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
|
:value="element.position.type"
|
||||||
|
@change="togglePositioning"
|
||||||
|
>
|
||||||
<option value="flow">Flow</option>
|
<option value="flow">Flow</option>
|
||||||
<option value="absolute">Absolute</option>
|
<option value="absolute">Absolute</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -28,15 +32,37 @@ function togglePositioning() {
|
|||||||
<template v-if="element.position.type === 'absolute'">
|
<template v-if="element.position.type === 'absolute'">
|
||||||
<div class="prop-row" data-tip="Yatay pozisyon — parent sol kenardan uzaklik (mm)">
|
<div class="prop-row" data-tip="Yatay pozisyon — parent sol kenardan uzaklik (mm)">
|
||||||
<label class="prop-label">X (mm)</label>
|
<label class="prop-label">X (mm)</label>
|
||||||
<input class="prop-input" type="number" step="0.5"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
:value="element.position.x"
|
: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 })" />
|
@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>
|
||||||
<div class="prop-row" data-tip="Dikey pozisyon — parent ust kenardan uzaklik (mm)">
|
<div class="prop-row" data-tip="Dikey pozisyon — parent ust kenardan uzaklik (mm)">
|
||||||
<label class="prop-label">Y (mm)</label>
|
<label class="prop-label">Y (mm)</label>
|
||||||
<input class="prop-input" type="number" step="0.5"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
:value="element.position.y"
|
: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 })" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
templateStore.updateElementPosition(element.id, {
|
||||||
|
type: 'absolute',
|
||||||
|
x: (element.position as any).x ?? 0,
|
||||||
|
y: parseFloat((e.target as HTMLInputElement).value) || 0,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import { useEditorStore } from '../../stores/editor'
|
|||||||
import { useSchemaStore } from '../../stores/schema'
|
import { useSchemaStore } from '../../stores/schema'
|
||||||
import { sz } from '../../core/types'
|
import { sz } from '../../core/types'
|
||||||
import { schemaFormatToFormatType, defaultAlignForSchema } from '../../core/schema-parser'
|
import { schemaFormatToFormatType, defaultAlignForSchema } from '../../core/schema-parser'
|
||||||
import type { RepeatingTableElement, TableColumn, FormatType, TemplateElement } from '../../core/types'
|
import type {
|
||||||
|
RepeatingTableElement,
|
||||||
|
TableColumn,
|
||||||
|
FormatType,
|
||||||
|
TemplateElement,
|
||||||
|
} from '../../core/types'
|
||||||
import '../../styles/properties.css'
|
import '../../styles/properties.css'
|
||||||
|
|
||||||
const props = defineProps<{ element: RepeatingTableElement }>()
|
const props = defineProps<{ element: RepeatingTableElement }>()
|
||||||
@@ -27,7 +32,7 @@ function nextColId() {
|
|||||||
function updateTableDataSource(path: string) {
|
function updateTableDataSource(path: string) {
|
||||||
const itemFields = schemaStore.getArrayItemFields(path)
|
const itemFields = schemaStore.getArrayItemFields(path)
|
||||||
if (itemFields.length > 0) {
|
if (itemFields.length > 0) {
|
||||||
const columns: TableColumn[] = itemFields.map(field => ({
|
const columns: TableColumn[] = itemFields.map((field) => ({
|
||||||
id: nextColId(),
|
id: nextColId(),
|
||||||
field: field.key,
|
field: field.key,
|
||||||
title: field.title,
|
title: field.title,
|
||||||
@@ -51,7 +56,7 @@ function updateTableStyle(key: string, value: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateColumn(colId: string, updates: Partial<TableColumn>) {
|
function updateColumn(colId: string, updates: Partial<TableColumn>) {
|
||||||
const columns = props.element.columns.map(c => c.id === colId ? { ...c, ...updates } : c)
|
const columns = props.element.columns.map((c) => (c.id === colId ? { ...c, ...updates } : c))
|
||||||
update({ columns } as Partial<TemplateElement>)
|
update({ columns } as Partial<TemplateElement>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +72,14 @@ function addColumn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeColumn(colId: string) {
|
function removeColumn(colId: string) {
|
||||||
update({ columns: props.element.columns.filter(c => c.id !== colId) } as Partial<TemplateElement>)
|
update({
|
||||||
|
columns: props.element.columns.filter((c) => c.id !== colId),
|
||||||
|
} as Partial<TemplateElement>)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveColumn(colId: string, direction: -1 | 1) {
|
function moveColumn(colId: string, direction: -1 | 1) {
|
||||||
const cols = [...props.element.columns]
|
const cols = [...props.element.columns]
|
||||||
const idx = cols.findIndex(c => c.id === colId)
|
const idx = cols.findIndex((c) => c.id === colId)
|
||||||
const newIdx = idx + direction
|
const newIdx = idx + direction
|
||||||
if (newIdx < 0 || newIdx >= cols.length) return
|
if (newIdx < 0 || newIdx >= cols.length) return
|
||||||
;[cols[idx], cols[newIdx]] = [cols[newIdx], cols[idx]]
|
;[cols[idx], cols[newIdx]] = [cols[newIdx], cols[idx]]
|
||||||
@@ -90,15 +97,15 @@ const tableItemFields = computed(() => {
|
|||||||
<div class="prop-section__title">Veri Kaynagi</div>
|
<div class="prop-section__title">Veri Kaynagi</div>
|
||||||
<div class="prop-row" data-tip="Tablonun baglanacagi array veri kaynagi">
|
<div class="prop-row" data-tip="Tablonun baglanacagi array veri kaynagi">
|
||||||
<label class="prop-label">Kaynak</label>
|
<label class="prop-label">Kaynak</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.dataSource.path"
|
:value="element.dataSource.path"
|
||||||
@change="(e) => updateTableDataSource((e.target as HTMLSelectElement).value)">
|
@change="(e) => updateTableDataSource((e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="" disabled>Secin...</option>
|
<option value="" disabled>Secin...</option>
|
||||||
<option
|
<option v-for="arr in schemaStore.arrayFields" :key="arr.path" :value="arr.path">
|
||||||
v-for="arr in schemaStore.arrayFields"
|
{{ arr.title }} ({{ arr.path }})
|
||||||
:key="arr.path"
|
</option>
|
||||||
:value="arr.path"
|
|
||||||
>{{ arr.title }} ({{ arr.path }})</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,26 +116,41 @@ const tableItemFields = computed(() => {
|
|||||||
Sutunlar
|
Sutunlar
|
||||||
<button class="prop-add-btn" @click="addColumn">+</button>
|
<button class="prop-add-btn" @click="addColumn">+</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-for="col in element.columns" :key="col.id" class="tbl-col">
|
||||||
v-for="col in element.columns"
|
|
||||||
:key="col.id"
|
|
||||||
class="tbl-col"
|
|
||||||
>
|
|
||||||
<!-- Row 1: title + actions -->
|
<!-- Row 1: title + actions -->
|
||||||
<div class="tbl-col__head">
|
<div class="tbl-col__head">
|
||||||
<input class="tbl-col__title" type="text" :value="col.title"
|
<input
|
||||||
|
class="tbl-col__title"
|
||||||
|
type="text"
|
||||||
|
:value="col.title"
|
||||||
@change="(e) => updateColumn(col.id, { title: (e.target as HTMLInputElement).value })"
|
@change="(e) => updateColumn(col.id, { title: (e.target as HTMLInputElement).value })"
|
||||||
:placeholder="col.field"
|
:placeholder="col.field"
|
||||||
data-tip="Sutun basligi" />
|
data-tip="Sutun basligi"
|
||||||
|
/>
|
||||||
<div class="tbl-col__actions">
|
<div class="tbl-col__actions">
|
||||||
<button class="tbl-col__act" @click="moveColumn(col.id, -1)" data-tip="Yukari tasi">
|
<button class="tbl-col__act" @click="moveColumn(col.id, -1)" data-tip="Yukari tasi">
|
||||||
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M5 2L2 6h6L5 2z" fill="currentColor"/></svg>
|
<svg width="10" height="10" viewBox="0 0 10 10">
|
||||||
|
<path d="M5 2L2 6h6L5 2z" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="tbl-col__act" @click="moveColumn(col.id, 1)" data-tip="Asagi tasi">
|
<button class="tbl-col__act" @click="moveColumn(col.id, 1)" data-tip="Asagi tasi">
|
||||||
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M5 8L2 4h6L5 8z" fill="currentColor"/></svg>
|
<svg width="10" height="10" viewBox="0 0 10 10">
|
||||||
|
<path d="M5 8L2 4h6L5 8z" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="tbl-col__act tbl-col__act--del" @click="removeColumn(col.id)" data-tip="Sutunu sil">
|
<button
|
||||||
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M2 2l6 6M8 2l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
class="tbl-col__act tbl-col__act--del"
|
||||||
|
@click="removeColumn(col.id)"
|
||||||
|
data-tip="Sutunu sil"
|
||||||
|
>
|
||||||
|
<svg width="10" height="10" viewBox="0 0 10 10">
|
||||||
|
<path
|
||||||
|
d="M2 2l6 6M8 2l-6 6"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,37 +158,148 @@ const tableItemFields = computed(() => {
|
|||||||
<!-- Row 2: field + align + format + width compact -->
|
<!-- Row 2: field + align + format + width compact -->
|
||||||
<div class="tbl-col__controls">
|
<div class="tbl-col__controls">
|
||||||
<!-- Field -->
|
<!-- Field -->
|
||||||
<select v-if="tableItemFields.length > 0" class="tbl-col__field" :value="col.field" data-tip="Veri alani"
|
<select
|
||||||
@change="(e) => {
|
v-if="tableItemFields.length > 0"
|
||||||
const field = (e.target as HTMLSelectElement).value
|
class="tbl-col__field"
|
||||||
const node = tableItemFields.find(f => f.key === field)
|
:value="col.field"
|
||||||
if (node) {
|
data-tip="Veri alani"
|
||||||
updateColumn(col.id, {
|
@change="
|
||||||
field,
|
(e) => {
|
||||||
title: node.title,
|
const field = (e.target as HTMLSelectElement).value
|
||||||
align: defaultAlignForSchema(node),
|
const node = tableItemFields.find((f) => f.key === field)
|
||||||
format: schemaFormatToFormatType(node.format),
|
if (node) {
|
||||||
})
|
updateColumn(col.id, {
|
||||||
} else {
|
field,
|
||||||
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.key }}</option>
|
<option v-for="f in tableItemFields" :key="f.key" :value="f.key">{{ f.key }}</option>
|
||||||
</select>
|
</select>
|
||||||
<input v-else class="tbl-col__field" type="text" :value="col.field"
|
<input
|
||||||
|
v-else
|
||||||
|
class="tbl-col__field"
|
||||||
|
type="text"
|
||||||
|
:value="col.field"
|
||||||
@change="(e) => updateColumn(col.id, { field: (e.target as HTMLInputElement).value })"
|
@change="(e) => updateColumn(col.id, { field: (e.target as HTMLInputElement).value })"
|
||||||
data-tip="Veri alani" />
|
data-tip="Veri alani"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Alignment icons -->
|
<!-- Alignment icons -->
|
||||||
<div class="tbl-col__align">
|
<div class="tbl-col__align">
|
||||||
<button class="tbl-col__align-btn" :class="{ 'tbl-col__align-btn--on': col.align === 'left' }" @click="updateColumn(col.id, { align: 'left' })" data-tip="Sola hizala">
|
<button
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="1" y1="3" x2="11" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="1" y1="6" x2="8" y2="6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="1" y1="9" x2="10" y2="9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
class="tbl-col__align-btn"
|
||||||
|
:class="{ 'tbl-col__align-btn--on': col.align === 'left' }"
|
||||||
|
@click="updateColumn(col.id, { align: 'left' })"
|
||||||
|
data-tip="Sola hizala"
|
||||||
|
>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12">
|
||||||
|
<line
|
||||||
|
x1="1"
|
||||||
|
y1="3"
|
||||||
|
x2="11"
|
||||||
|
y2="3"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="1"
|
||||||
|
y1="6"
|
||||||
|
x2="8"
|
||||||
|
y2="6"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="1"
|
||||||
|
y1="9"
|
||||||
|
x2="10"
|
||||||
|
y2="9"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="tbl-col__align-btn" :class="{ 'tbl-col__align-btn--on': col.align === 'center' }" @click="updateColumn(col.id, { align: 'center' })" data-tip="Ortala">
|
<button
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="1" y1="3" x2="11" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="2.5" y1="6" x2="9.5" y2="6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="1.5" y1="9" x2="10.5" y2="9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
class="tbl-col__align-btn"
|
||||||
|
:class="{ 'tbl-col__align-btn--on': col.align === 'center' }"
|
||||||
|
@click="updateColumn(col.id, { align: 'center' })"
|
||||||
|
data-tip="Ortala"
|
||||||
|
>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12">
|
||||||
|
<line
|
||||||
|
x1="1"
|
||||||
|
y1="3"
|
||||||
|
x2="11"
|
||||||
|
y2="3"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="2.5"
|
||||||
|
y1="6"
|
||||||
|
x2="9.5"
|
||||||
|
y2="6"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="1.5"
|
||||||
|
y1="9"
|
||||||
|
x2="10.5"
|
||||||
|
y2="9"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="tbl-col__align-btn" :class="{ 'tbl-col__align-btn--on': col.align === 'right' }" @click="updateColumn(col.id, { align: 'right' })" data-tip="Saga hizala">
|
<button
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="1" y1="3" x2="11" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="4" y1="6" x2="11" y2="6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><line x1="2" y1="9" x2="11" y2="9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
class="tbl-col__align-btn"
|
||||||
|
:class="{ 'tbl-col__align-btn--on': col.align === 'right' }"
|
||||||
|
@click="updateColumn(col.id, { align: 'right' })"
|
||||||
|
data-tip="Saga hizala"
|
||||||
|
>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12">
|
||||||
|
<line
|
||||||
|
x1="1"
|
||||||
|
y1="3"
|
||||||
|
x2="11"
|
||||||
|
y2="3"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="4"
|
||||||
|
y1="6"
|
||||||
|
x2="11"
|
||||||
|
y2="6"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="2"
|
||||||
|
y1="9"
|
||||||
|
x2="11"
|
||||||
|
y2="9"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,8 +307,18 @@ const tableItemFields = computed(() => {
|
|||||||
<!-- Row 3: format + width -->
|
<!-- Row 3: format + width -->
|
||||||
<div class="tbl-col__extra" data-tip="Veri gosterim formati">
|
<div class="tbl-col__extra" data-tip="Veri gosterim formati">
|
||||||
<label class="tbl-col__elabel">Format</label>
|
<label class="tbl-col__elabel">Format</label>
|
||||||
<select class="tbl-col__fmt" :value="col.format ?? ''"
|
<select
|
||||||
@change="(e) => updateColumn(col.id, { format: ((e.target as HTMLSelectElement).value || undefined) as FormatType | undefined })">
|
class="tbl-col__fmt"
|
||||||
|
:value="col.format ?? ''"
|
||||||
|
@change="
|
||||||
|
(e) =>
|
||||||
|
updateColumn(col.id, {
|
||||||
|
format: ((e.target as HTMLSelectElement).value || undefined) as
|
||||||
|
| FormatType
|
||||||
|
| undefined,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
<option value="">Yok</option>
|
<option value="">Yok</option>
|
||||||
<option value="currency">Para birimi</option>
|
<option value="currency">Para birimi</option>
|
||||||
<option value="number">Sayi</option>
|
<option value="number">Sayi</option>
|
||||||
@@ -185,22 +328,45 @@ const tableItemFields = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="tbl-col__extra" data-tip="Sutun genislik modu">
|
<div class="tbl-col__extra" data-tip="Sutun genislik modu">
|
||||||
<label class="tbl-col__elabel">Genislik</label>
|
<label class="tbl-col__elabel">Genislik</label>
|
||||||
<select class="tbl-col__wtype" :value="col.width.type"
|
<select
|
||||||
@change="(e) => {
|
class="tbl-col__wtype"
|
||||||
const t = (e.target as HTMLSelectElement).value
|
:value="col.width.type"
|
||||||
if (t === 'auto') updateColumn(col.id, { width: { type: 'auto' } })
|
@change="
|
||||||
else if (t === 'fr') updateColumn(col.id, { width: { type: 'fr', value: 1 } })
|
(e) => {
|
||||||
else updateColumn(col.id, { width: { type: 'fixed', value: 30 } })
|
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="auto">Otomatik</option>
|
||||||
<option value="fixed">Sabit (mm)</option>
|
<option value="fixed">Sabit (mm)</option>
|
||||||
<option value="fr">Oran (fr)</option>
|
<option value="fr">Oran (fr)</option>
|
||||||
</select>
|
</select>
|
||||||
<span v-if="col.width.type === 'fixed' || col.width.type === 'fr'" class="ts-tip-wrap" :data-tip="col.width.type === 'fixed' ? 'Sabit genislik (mm)' : 'Oran degeri (fr)'">
|
<span
|
||||||
<input class="tbl-col__wval" type="number" step="1"
|
v-if="col.width.type === 'fixed' || col.width.type === 'fr'"
|
||||||
|
class="ts-tip-wrap"
|
||||||
|
:data-tip="col.width.type === 'fixed' ? 'Sabit genislik (mm)' : 'Oran degeri (fr)'"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="tbl-col__wval"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
:min="col.width.type === 'fixed' ? 5 : 1"
|
:min="col.width.type === 'fixed' ? 5 : 1"
|
||||||
:value="(col.width as any).value"
|
:value="(col.width as any).value"
|
||||||
@change="(e) => updateColumn(col.id, { width: { type: col.width.type, value: parseFloat((e.target as HTMLInputElement).value) || (col.width.type === 'fixed' ? 30 : 1) } as any })" />
|
@change="
|
||||||
|
(e) =>
|
||||||
|
updateColumn(col.id, {
|
||||||
|
width: {
|
||||||
|
type: col.width.type,
|
||||||
|
value:
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) ||
|
||||||
|
(col.width.type === 'fixed' ? 30 : 1),
|
||||||
|
} as any,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,15 +382,36 @@ const tableItemFields = computed(() => {
|
|||||||
<div class="ts-val ts-val--pair">
|
<div class="ts-val ts-val--pair">
|
||||||
<span class="ts-sep">Icerik</span>
|
<span class="ts-sep">Icerik</span>
|
||||||
<span class="ts-tip-wrap" data-tip="Icerik yazi boyutu (pt)">
|
<span class="ts-tip-wrap" data-tip="Icerik yazi boyutu (pt)">
|
||||||
<input class="ts-num" type="number" step="1" min="6" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="6"
|
||||||
|
max="99"
|
||||||
:value="element.style.fontSize ?? 10"
|
:value="element.style.fontSize ?? 10"
|
||||||
@input="(e) => updateTableStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 10)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="ts-sep">Header</span>
|
<span class="ts-sep">Header</span>
|
||||||
<span class="ts-tip-wrap" data-tip="Header yazi boyutu (pt)">
|
<span class="ts-tip-wrap" data-tip="Header yazi boyutu (pt)">
|
||||||
<input class="ts-num" type="number" step="1" min="6" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="6"
|
||||||
|
max="99"
|
||||||
:value="element.style.headerFontSize ?? element.style.fontSize ?? 10"
|
:value="element.style.headerFontSize ?? element.style.fontSize ?? 10"
|
||||||
@input="(e) => updateTableStyle('headerFontSize', parseFloat((e.target as HTMLInputElement).value) || 10)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle(
|
||||||
|
'headerFontSize',
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) || 10,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -232,23 +419,38 @@ const tableItemFields = computed(() => {
|
|||||||
<label class="ts-lbl" data-tip="Header, metin ve zebra satirlari renkleri">Renkler</label>
|
<label class="ts-lbl" data-tip="Header, metin ve zebra satirlari renkleri">Renkler</label>
|
||||||
<div class="ts-val ts-val--colors">
|
<div class="ts-val ts-val--colors">
|
||||||
<div class="ts-color-item" data-tip="Header arkaplan rengi">
|
<div class="ts-color-item" data-tip="Header arkaplan rengi">
|
||||||
<input class="ts-swatch" type="color"
|
<input
|
||||||
|
class="ts-swatch"
|
||||||
|
type="color"
|
||||||
:value="element.style.headerBg ?? '#f0f0f0'"
|
:value="element.style.headerBg ?? '#f0f0f0'"
|
||||||
@input="(e) => updateTableStyle('headerBg', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateTableStyle('headerBg', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
<span class="ts-clbl">Arkaplan</span>
|
<span class="ts-clbl">Arkaplan</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ts-color-item" data-tip="Header metin rengi">
|
<div class="ts-color-item" data-tip="Header metin rengi">
|
||||||
<input class="ts-swatch" type="color"
|
<input
|
||||||
|
class="ts-swatch"
|
||||||
|
type="color"
|
||||||
:value="element.style.headerColor ?? '#000000'"
|
:value="element.style.headerColor ?? '#000000'"
|
||||||
@input="(e) => updateTableStyle('headerColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateTableStyle('headerColor', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
<span class="ts-clbl">Metin</span>
|
<span class="ts-clbl">Metin</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ts-color-item" data-tip="Zebra satir rengi — tek satirlar">
|
<div class="ts-color-item" data-tip="Zebra satir rengi — tek satirlar">
|
||||||
<div class="ts-swatch-wrap">
|
<div class="ts-swatch-wrap">
|
||||||
<input class="ts-swatch" type="color"
|
<input
|
||||||
|
class="ts-swatch"
|
||||||
|
type="color"
|
||||||
:value="element.style.zebraOdd ?? '#fafafa'"
|
:value="element.style.zebraOdd ?? '#fafafa'"
|
||||||
@input="(e) => updateTableStyle('zebraOdd', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateTableStyle('zebraOdd', (e.target as HTMLInputElement).value)"
|
||||||
<button v-if="element.style.zebraOdd" class="ts-swatch-clr" @click="updateTableStyle('zebraOdd', undefined)">×</button>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="element.style.zebraOdd"
|
||||||
|
class="ts-swatch-clr"
|
||||||
|
@click="updateTableStyle('zebraOdd', undefined)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="ts-clbl">Zebra</span>
|
<span class="ts-clbl">Zebra</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -258,15 +460,36 @@ const tableItemFields = computed(() => {
|
|||||||
<label class="ts-lbl" data-tip="Tablo kenarlik rengi ve kalinligi">Kenarlik</label>
|
<label class="ts-lbl" data-tip="Tablo kenarlik rengi ve kalinligi">Kenarlik</label>
|
||||||
<div class="ts-val ts-val--pair">
|
<div class="ts-val ts-val--pair">
|
||||||
<div class="ts-swatch-wrap" data-tip="Kenarlik rengi">
|
<div class="ts-swatch-wrap" data-tip="Kenarlik rengi">
|
||||||
<input class="ts-swatch" type="color"
|
<input
|
||||||
|
class="ts-swatch"
|
||||||
|
type="color"
|
||||||
:value="element.style.borderColor ?? '#cccccc'"
|
:value="element.style.borderColor ?? '#cccccc'"
|
||||||
@input="(e) => updateTableStyle('borderColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateTableStyle('borderColor', (e.target as HTMLInputElement).value)"
|
||||||
<button v-if="element.style.borderColor" class="ts-swatch-clr" @click="updateTableStyle('borderColor', undefined)">×</button>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="element.style.borderColor"
|
||||||
|
class="ts-swatch-clr"
|
||||||
|
@click="updateTableStyle('borderColor', undefined)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="ts-tip-wrap" data-tip="Kenarlik kalinligi (mm)">
|
<span class="ts-tip-wrap" data-tip="Kenarlik kalinligi (mm)">
|
||||||
<input class="ts-num" type="number" step="0.1" min="0" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
max="99"
|
||||||
:value="element.style.borderWidth ?? 0.5"
|
:value="element.style.borderWidth ?? 0.5"
|
||||||
@input="(e) => updateTableStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle(
|
||||||
|
'borderWidth',
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) || 0,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="ts-unit">mm</span>
|
<span class="ts-unit">mm</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,42 +499,96 @@ const tableItemFields = computed(() => {
|
|||||||
<div class="ts-val ts-val--pair">
|
<div class="ts-val ts-val--pair">
|
||||||
<span class="ts-pad-icon" data-tip="Yatay bosluk (mm)">↔</span>
|
<span class="ts-pad-icon" data-tip="Yatay bosluk (mm)">↔</span>
|
||||||
<span class="ts-tip-wrap" data-tip="Yatay ic bosluk (mm)">
|
<span class="ts-tip-wrap" data-tip="Yatay ic bosluk (mm)">
|
||||||
<input class="ts-num" type="number" step="0.5" min="0" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="0"
|
||||||
|
max="99"
|
||||||
:value="element.style.cellPaddingH ?? 2"
|
:value="element.style.cellPaddingH ?? 2"
|
||||||
@input="(e) => updateTableStyle('cellPaddingH', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle(
|
||||||
|
'cellPaddingH',
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) || 0,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="ts-pad-icon" data-tip="Dikey bosluk (mm)">↕</span>
|
<span class="ts-pad-icon" data-tip="Dikey bosluk (mm)">↕</span>
|
||||||
<span class="ts-tip-wrap" data-tip="Dikey ic bosluk (mm)">
|
<span class="ts-tip-wrap" data-tip="Dikey ic bosluk (mm)">
|
||||||
<input class="ts-num" type="number" step="0.5" min="0" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="0"
|
||||||
|
max="99"
|
||||||
:value="element.style.cellPaddingV ?? 1"
|
:value="element.style.cellPaddingV ?? 1"
|
||||||
@input="(e) => updateTableStyle('cellPaddingV', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle(
|
||||||
|
'cellPaddingV',
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) || 0,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Header padding -->
|
<!-- Header padding -->
|
||||||
<label class="ts-lbl" data-tip="Header hucre bosluklari — yatay ve dikey (mm)">Header bosluk</label>
|
<label class="ts-lbl" data-tip="Header hucre bosluklari — yatay ve dikey (mm)"
|
||||||
|
>Header bosluk</label
|
||||||
|
>
|
||||||
<div class="ts-val ts-val--pair">
|
<div class="ts-val ts-val--pair">
|
||||||
<span class="ts-pad-icon" data-tip="Yatay bosluk (mm)">↔</span>
|
<span class="ts-pad-icon" data-tip="Yatay bosluk (mm)">↔</span>
|
||||||
<span class="ts-tip-wrap" data-tip="Header yatay bosluk (mm)">
|
<span class="ts-tip-wrap" data-tip="Header yatay bosluk (mm)">
|
||||||
<input class="ts-num" type="number" step="0.5" min="0" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="0"
|
||||||
|
max="99"
|
||||||
:value="element.style.headerPaddingH ?? element.style.cellPaddingH ?? 2"
|
:value="element.style.headerPaddingH ?? element.style.cellPaddingH ?? 2"
|
||||||
@input="(e) => updateTableStyle('headerPaddingH', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle(
|
||||||
|
'headerPaddingH',
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) || 0,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="ts-pad-icon" data-tip="Dikey bosluk (mm)">↕</span>
|
<span class="ts-pad-icon" data-tip="Dikey bosluk (mm)">↕</span>
|
||||||
<span class="ts-tip-wrap" data-tip="Header dikey bosluk (mm)">
|
<span class="ts-tip-wrap" data-tip="Header dikey bosluk (mm)">
|
||||||
<input class="ts-num" type="number" step="0.5" min="0" max="99"
|
<input
|
||||||
|
class="ts-num"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="0"
|
||||||
|
max="99"
|
||||||
:value="element.style.headerPaddingV ?? element.style.cellPaddingV ?? 1"
|
:value="element.style.headerPaddingV ?? element.style.cellPaddingV ?? 1"
|
||||||
@input="(e) => updateTableStyle('headerPaddingV', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateTableStyle(
|
||||||
|
'headerPaddingV',
|
||||||
|
parseFloat((e.target as HTMLInputElement).value) || 0,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Repeat header -->
|
<!-- Repeat header -->
|
||||||
<label class="ts-lbl" data-tip="Cok sayfali tablolarda header'i her sayfada tekrarla">Header tekrarla</label>
|
<label class="ts-lbl" data-tip="Cok sayfali tablolarda header'i her sayfada tekrarla"
|
||||||
|
>Header tekrarla</label
|
||||||
|
>
|
||||||
<div class="ts-val">
|
<div class="ts-val">
|
||||||
<label class="ts-toggle">
|
<label class="ts-toggle">
|
||||||
<input type="checkbox"
|
<input
|
||||||
|
type="checkbox"
|
||||||
:checked="element.repeatHeader !== false"
|
:checked="element.repeatHeader !== false"
|
||||||
@change="(e) => update({ repeatHeader: (e.target as HTMLInputElement).checked } as any)" />
|
@change="(e) => update({ repeatHeader: (e.target as HTMLInputElement).checked } as any)"
|
||||||
|
/>
|
||||||
<span class="ts-toggle__track"></span>
|
<span class="ts-toggle__track"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -666,7 +943,7 @@ const tableItemFields = computed(() => {
|
|||||||
background: white;
|
background: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: transform 0.15s;
|
transition: transform 0.15s;
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ts-toggle input:checked + .ts-toggle__track {
|
.ts-toggle input:checked + .ts-toggle__track {
|
||||||
|
|||||||
@@ -46,21 +46,33 @@ function removeSpan(index: number) {
|
|||||||
<div class="prop-section__title">Varsayilan Stil</div>
|
<div class="prop-section__title">Varsayilan Stil</div>
|
||||||
<div class="prop-row" data-tip="Varsayilan yazi tipi boyutu (point)">
|
<div class="prop-row" data-tip="Varsayilan yazi tipi boyutu (point)">
|
||||||
<label class="prop-label">Boyut (pt)</label>
|
<label class="prop-label">Boyut (pt)</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="element.style.fontSize ?? 11"
|
:value="element.style.fontSize ?? 11"
|
||||||
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
|
@input="
|
||||||
|
(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Varsayilan metin rengi">
|
<div class="prop-row" data-tip="Varsayilan metin rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.color ?? '#000000'"
|
:value="element.style.color ?? '#000000'"
|
||||||
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
||||||
<label class="prop-label">Hizalama</label>
|
<label class="prop-label">Hizalama</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.style.align ?? 'left'"
|
:value="element.style.align ?? 'left'"
|
||||||
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="left">Sol</option>
|
<option value="left">Sol</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
@@ -82,33 +94,49 @@ function removeSpan(index: number) {
|
|||||||
class="prop-span-card__remove"
|
class="prop-span-card__remove"
|
||||||
@click="removeSpan(idx)"
|
@click="removeSpan(idx)"
|
||||||
title="Sil"
|
title="Sil"
|
||||||
>×</button>
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prop-row" data-tip="Span metin icerigi">
|
<div class="prop-row" data-tip="Span metin icerigi">
|
||||||
<label class="prop-label">Metin</label>
|
<label class="prop-label">Metin</label>
|
||||||
<input class="prop-input" type="text"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="text"
|
||||||
:value="span.text ?? ''"
|
:value="span.text ?? ''"
|
||||||
@input="(e) => updateSpan(idx, { text: (e.target as HTMLInputElement).value })" />
|
@input="(e) => updateSpan(idx, { text: (e.target as HTMLInputElement).value })"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Span yazi boyutu — bos birakilirsa varsayilan kullanilir">
|
<div class="prop-row" data-tip="Span yazi boyutu — bos birakilirsa varsayilan kullanilir">
|
||||||
<label class="prop-label">Boyut</label>
|
<label class="prop-label">Boyut</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(span.style as TextStyle).fontSize ?? ''"
|
:value="(span.style as TextStyle).fontSize ?? ''"
|
||||||
placeholder="varsayilan"
|
placeholder="varsayilan"
|
||||||
@input="(e) => {
|
@input="
|
||||||
const v = (e.target as HTMLInputElement).value
|
(e) => {
|
||||||
updateSpanStyle(idx, 'fontSize', v ? parseFloat(v) : undefined)
|
const v = (e.target as HTMLInputElement).value
|
||||||
}" />
|
updateSpanStyle(idx, 'fontSize', v ? parseFloat(v) : undefined)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Span yazi kalinligi">
|
<div class="prop-row" data-tip="Span yazi kalinligi">
|
||||||
<label class="prop-label">Kalinlik</label>
|
<label class="prop-label">Kalinlik</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(span.style as TextStyle).fontWeight ?? ''"
|
:value="(span.style as TextStyle).fontWeight ?? ''"
|
||||||
@change="(e) => {
|
@change="
|
||||||
const v = (e.target as HTMLSelectElement).value
|
(e) => {
|
||||||
updateSpanStyle(idx, 'fontWeight', v || undefined)
|
const v = (e.target as HTMLSelectElement).value
|
||||||
}">
|
updateSpanStyle(idx, 'fontWeight', v || undefined)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
<option value="">Varsayilan</option>
|
<option value="">Varsayilan</option>
|
||||||
<option value="normal">Normal</option>
|
<option value="normal">Normal</option>
|
||||||
<option value="bold">Kalin</option>
|
<option value="bold">Kalin</option>
|
||||||
@@ -116,9 +144,12 @@ function removeSpan(index: number) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Span metin rengi">
|
<div class="prop-row" data-tip="Span metin rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="(span.style as TextStyle).color ?? element.style.color ?? '#000000'"
|
:value="(span.style as TextStyle).color ?? element.style.color ?? '#000000'"
|
||||||
@input="(e) => updateSpanStyle(idx, 'color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateSpanStyle(idx, 'color', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
<div class="prop-section__title">Sekil</div>
|
<div class="prop-section__title">Sekil</div>
|
||||||
<div class="prop-row" data-tip="Sekil tipi">
|
<div class="prop-row" data-tip="Sekil tipi">
|
||||||
<label class="prop-label">Tip</label>
|
<label class="prop-label">Tip</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.shapeType"
|
:value="element.shapeType"
|
||||||
@change="(e) => update({ shapeType: (e.target as HTMLSelectElement).value } as any)">
|
@change="(e) => update({ shapeType: (e.target as HTMLSelectElement).value } as any)"
|
||||||
|
>
|
||||||
<option value="rectangle">Dikdortgen</option>
|
<option value="rectangle">Dikdortgen</option>
|
||||||
<option value="rounded_rectangle">Yuvarlak Dikdortgen</option>
|
<option value="rounded_rectangle">Yuvarlak Dikdortgen</option>
|
||||||
<option value="ellipse">Elips</option>
|
<option value="ellipse">Elips</option>
|
||||||
@@ -34,27 +36,51 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Sekil arka plan rengi">
|
<div class="prop-row" data-tip="Sekil arka plan rengi">
|
||||||
<label class="prop-label">Arka Plan</label>
|
<label class="prop-label">Arka Plan</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.backgroundColor ?? '#f0f0f0'"
|
:value="element.style.backgroundColor ?? '#f0f0f0'"
|
||||||
@input="(e) => updateStyle('backgroundColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('backgroundColor', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kenarlik cizgisi rengi">
|
<div class="prop-row" data-tip="Kenarlik cizgisi rengi">
|
||||||
<label class="prop-label">Kenar Rengi</label>
|
<label class="prop-label">Kenar Rengi</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="element.style.borderColor ?? '#333333'"
|
:value="element.style.borderColor ?? '#333333'"
|
||||||
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('borderColor', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Kenarlik cizgi kalinligi (mm)">
|
<div class="prop-row" data-tip="Kenarlik cizgi kalinligi (mm)">
|
||||||
<label class="prop-label">Kenar Kalinligi</label>
|
<label class="prop-label">Kenar Kalinligi</label>
|
||||||
<input class="prop-input" type="number" step="0.25" min="0"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.25"
|
||||||
|
min="0"
|
||||||
:value="element.style.borderWidth ?? 0.5"
|
:value="element.style.borderWidth ?? 0.5"
|
||||||
@input="(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) => updateStyle('borderWidth', parseFloat((e.target as HTMLInputElement).value) || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="element.shapeType === 'rounded_rectangle'" class="prop-row" data-tip="Kose yuvarlakligi (mm)">
|
<div
|
||||||
|
v-if="element.shapeType === 'rounded_rectangle'"
|
||||||
|
class="prop-row"
|
||||||
|
data-tip="Kose yuvarlakligi (mm)"
|
||||||
|
>
|
||||||
<label class="prop-label">Kose Yuvarlakligi</label>
|
<label class="prop-label">Kose Yuvarlakligi</label>
|
||||||
<input class="prop-input" type="number" step="0.5" min="0"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="0.5"
|
||||||
|
min="0"
|
||||||
:value="element.style.borderRadius ?? 2"
|
:value="element.style.borderRadius ?? 2"
|
||||||
@input="(e) => updateStyle('borderRadius', parseFloat((e.target as HTMLInputElement).value) || 0)" />
|
@input="
|
||||||
|
(e) => updateStyle('borderRadius', parseFloat((e.target as HTMLInputElement).value) || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -16,52 +16,105 @@ function updateSize(axis: 'width' | 'height', sv: SizeValue) {
|
|||||||
<div class="prop-section__title">Boyut</div>
|
<div class="prop-section__title">Boyut</div>
|
||||||
<div class="prop-row" data-tip="Genislik boyutlandirma modu">
|
<div class="prop-row" data-tip="Genislik boyutlandirma modu">
|
||||||
<label class="prop-label">Genislik</label>
|
<label class="prop-label">Genislik</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.size.width.type"
|
:value="element.size.width.type"
|
||||||
@change="(e) => {
|
@change="
|
||||||
const t = (e.target as HTMLSelectElement).value
|
(e) => {
|
||||||
if (t === 'auto') updateSize('width', { type: 'auto' })
|
const t = (e.target as HTMLSelectElement).value
|
||||||
else if (t === 'fr') updateSize('width', { type: 'fr', value: 1 })
|
if (t === 'auto') updateSize('width', { type: 'auto' })
|
||||||
else updateSize('width', { type: 'fixed', value: 50 })
|
else if (t === 'fr') updateSize('width', { type: 'fr', value: 1 })
|
||||||
}">
|
else updateSize('width', { type: 'fixed', value: 50 })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
<option value="auto">Otomatik</option>
|
<option value="auto">Otomatik</option>
|
||||||
<option value="fixed">Sabit (mm)</option>
|
<option value="fixed">Sabit (mm)</option>
|
||||||
<option value="fr">Oran (fr)</option>
|
<option value="fr">Oran (fr)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="element.size.width.type === 'fixed'" class="prop-row" data-tip="Sabit genislik degeri (mm)">
|
<div
|
||||||
|
v-if="element.size.width.type === 'fixed'"
|
||||||
|
class="prop-row"
|
||||||
|
data-tip="Sabit genislik degeri (mm)"
|
||||||
|
>
|
||||||
<label class="prop-label">mm</label>
|
<label class="prop-label">mm</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.size.width as any).value"
|
:value="(element.size.width as any).value"
|
||||||
@input="(e) => updateSize('width', { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 10 })" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateSize('width', {
|
||||||
|
type: 'fixed',
|
||||||
|
value: parseFloat((e.target as HTMLInputElement).value) || 10,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="element.size.width.type === 'fr'" class="prop-row" data-tip="Kalan alani oransal doldurma degeri">
|
<div
|
||||||
|
v-if="element.size.width.type === 'fr'"
|
||||||
|
class="prop-row"
|
||||||
|
data-tip="Kalan alani oransal doldurma degeri"
|
||||||
|
>
|
||||||
<label class="prop-label">fr</label>
|
<label class="prop-label">fr</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.size.width as any).value"
|
:value="(element.size.width as any).value"
|
||||||
@input="(e) => updateSize('width', { type: 'fr', value: parseFloat((e.target as HTMLInputElement).value) || 1 })" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateSize('width', {
|
||||||
|
type: 'fr',
|
||||||
|
value: parseFloat((e.target as HTMLInputElement).value) || 1,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prop-row" data-tip="Yukseklik boyutlandirma modu">
|
<div class="prop-row" data-tip="Yukseklik boyutlandirma modu">
|
||||||
<label class="prop-label">Yukseklik</label>
|
<label class="prop-label">Yukseklik</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="element.size.height.type"
|
:value="element.size.height.type"
|
||||||
@change="(e) => {
|
@change="
|
||||||
const t = (e.target as HTMLSelectElement).value
|
(e) => {
|
||||||
if (t === 'auto') updateSize('height', { type: 'auto' })
|
const t = (e.target as HTMLSelectElement).value
|
||||||
else if (t === 'fr') updateSize('height', { type: 'fr', value: 1 })
|
if (t === 'auto') updateSize('height', { type: 'auto' })
|
||||||
else updateSize('height', { type: 'fixed', value: 20 })
|
else if (t === 'fr') updateSize('height', { type: 'fr', value: 1 })
|
||||||
}">
|
else updateSize('height', { type: 'fixed', value: 20 })
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
<option value="auto">Otomatik</option>
|
<option value="auto">Otomatik</option>
|
||||||
<option value="fixed">Sabit (mm)</option>
|
<option value="fixed">Sabit (mm)</option>
|
||||||
<option value="fr">Oran (fr)</option>
|
<option value="fr">Oran (fr)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="element.size.height.type === 'fixed'" class="prop-row" data-tip="Sabit yukseklik degeri (mm)">
|
<div
|
||||||
|
v-if="element.size.height.type === 'fixed'"
|
||||||
|
class="prop-row"
|
||||||
|
data-tip="Sabit yukseklik degeri (mm)"
|
||||||
|
>
|
||||||
<label class="prop-label">mm</label>
|
<label class="prop-label">mm</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.size.height as any).value"
|
:value="(element.size.height as any).value"
|
||||||
@input="(e) => updateSize('height', { type: 'fixed', value: parseFloat((e.target as HTMLInputElement).value) || 10 })" />
|
@input="
|
||||||
|
(e) =>
|
||||||
|
updateSize('height', {
|
||||||
|
type: 'fixed',
|
||||||
|
value: parseFloat((e.target as HTMLInputElement).value) || 10,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -25,37 +25,54 @@ function updateStyle(key: string, value: unknown) {
|
|||||||
|
|
||||||
<div v-if="element.type === 'static_text'" class="prop-row" data-tip="Sabit metin icerigi">
|
<div v-if="element.type === 'static_text'" class="prop-row" data-tip="Sabit metin icerigi">
|
||||||
<label class="prop-label">Metin</label>
|
<label class="prop-label">Metin</label>
|
||||||
<input class="prop-input" type="text"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="text"
|
||||||
:value="(element as StaticTextElement).content"
|
:value="(element as StaticTextElement).content"
|
||||||
@input="(e) => update({ content: (e.target as HTMLInputElement).value } as any)" />
|
@input="(e) => update({ content: (e.target as HTMLInputElement).value } as any)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
<div class="prop-row" data-tip="Yazi tipi boyutu (point)">
|
||||||
<label class="prop-label">Boyut (pt)</label>
|
<label class="prop-label">Boyut (pt)</label>
|
||||||
<input class="prop-input" type="number" step="1" min="1"
|
<input
|
||||||
|
class="prop-input"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
:value="(element.style as TextStyle).fontSize ?? 11"
|
:value="(element.style as TextStyle).fontSize ?? 11"
|
||||||
@input="(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)" />
|
@input="
|
||||||
|
(e) => updateStyle('fontSize', parseFloat((e.target as HTMLInputElement).value) || 11)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Yazi tipi kalinligi">
|
<div class="prop-row" data-tip="Yazi tipi kalinligi">
|
||||||
<label class="prop-label">Kalinlik</label>
|
<label class="prop-label">Kalinlik</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
|
:value="(element.style as TextStyle).fontWeight ?? 'normal'"
|
||||||
@change="(e) => updateStyle('fontWeight', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('fontWeight', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="normal">Normal</option>
|
<option value="normal">Normal</option>
|
||||||
<option value="bold">Kalin</option>
|
<option value="bold">Kalin</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metin rengi">
|
<div class="prop-row" data-tip="Metin rengi">
|
||||||
<label class="prop-label">Renk</label>
|
<label class="prop-label">Renk</label>
|
||||||
<input class="prop-input prop-color" type="color"
|
<input
|
||||||
|
class="prop-input prop-color"
|
||||||
|
type="color"
|
||||||
:value="(element.style as TextStyle).color ?? '#000000'"
|
:value="(element.style as TextStyle).color ?? '#000000'"
|
||||||
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)" />
|
@input="(e) => updateStyle('color', (e.target as HTMLInputElement).value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
<div class="prop-row" data-tip="Metnin yatay hizalamasi">
|
||||||
<label class="prop-label">Hizalama</label>
|
<label class="prop-label">Hizalama</label>
|
||||||
<select class="prop-input prop-select"
|
<select
|
||||||
|
class="prop-input prop-select"
|
||||||
:value="(element.style as TextStyle).align ?? 'left'"
|
:value="(element.style as TextStyle).align ?? 'left'"
|
||||||
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)">
|
@change="(e) => updateStyle('align', (e.target as HTMLSelectElement).value)"
|
||||||
|
>
|
||||||
<option value="left">Sol</option>
|
<option value="left">Sol</option>
|
||||||
<option value="center">Orta</option>
|
<option value="center">Orta</option>
|
||||||
<option value="right">Sag</option>
|
<option value="right">Sag</option>
|
||||||
|
|||||||
@@ -121,11 +121,20 @@ export function useLayoutEngine(
|
|||||||
|
|
||||||
// --- Barcode üretimi (WASM üzerinden) ---
|
// --- Barcode üretimi (WASM üzerinden) ---
|
||||||
let barcodeReqId = 0
|
let barcodeReqId = 0
|
||||||
const barcodeCallbacks = new Map<number, (result: { width: number; height: number; rgba: ArrayBuffer } | null) => void>()
|
const barcodeCallbacks = new Map<
|
||||||
|
number,
|
||||||
|
(result: { width: number; height: number; rgba: ArrayBuffer } | null) => void
|
||||||
|
>()
|
||||||
|
|
||||||
function generateBarcode(format: string, value: string, width: number, height: number, includeText: boolean = false): Promise<{ width: number; height: number; rgba: ArrayBuffer } | null> {
|
function generateBarcode(
|
||||||
|
format: string,
|
||||||
|
value: string,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
includeText: boolean = false,
|
||||||
|
): Promise<{ width: number; height: number; rgba: ArrayBuffer } | null> {
|
||||||
if (!worker) initWorker()
|
if (!worker) initWorker()
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
barcodeReqId++
|
barcodeReqId++
|
||||||
const id = barcodeReqId
|
const id = barcodeReqId
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@@ -140,11 +149,17 @@ export function useLayoutEngine(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBarcodeResponse(msg: Extract<WorkerResponse, { type: 'barcode-result' } | { type: 'barcode-error' }>) {
|
function handleBarcodeResponse(
|
||||||
|
msg: Extract<WorkerResponse, { type: 'barcode-result' } | { type: 'barcode-error' }>,
|
||||||
|
) {
|
||||||
const cb = barcodeCallbacks.get(msg.id)
|
const cb = barcodeCallbacks.get(msg.id)
|
||||||
if (cb) {
|
if (cb) {
|
||||||
barcodeCallbacks.delete(msg.id)
|
barcodeCallbacks.delete(msg.id)
|
||||||
cb(msg.type === 'barcode-result' ? { width: msg.width, height: msg.height, rgba: msg.rgba } : null)
|
cb(
|
||||||
|
msg.type === 'barcode-result'
|
||||||
|
? { width: msg.width, height: msg.height, rgba: msg.rgba }
|
||||||
|
: null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export interface SnapResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EdgeSet {
|
interface EdgeSet {
|
||||||
verticals: number[] // x positions in mm (left, right, center of elements + page)
|
verticals: number[] // x positions in mm (left, right, center of elements + page)
|
||||||
horizontals: number[] // y positions in mm (top, bottom, center of elements + page)
|
horizontals: number[] // y positions in mm (top, bottom, center of elements + page)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ export function useSnapGuides() {
|
|||||||
layoutMap: Record<string, ElementLayout>,
|
layoutMap: Record<string, ElementLayout>,
|
||||||
excludeId: string,
|
excludeId: string,
|
||||||
pageWidth: number,
|
pageWidth: number,
|
||||||
pageHeight: number
|
pageHeight: number,
|
||||||
) {
|
) {
|
||||||
const verticals: number[] = [0, pageWidth / 2, pageWidth] // page edges + center
|
const verticals: number[] = [0, pageWidth / 2, pageWidth] // page edges + center
|
||||||
const horizontals: number[] = [0, pageHeight / 2, pageHeight]
|
const horizontals: number[] = [0, pageHeight / 2, pageHeight]
|
||||||
|
|
||||||
for (const [id, el] of Object.entries(layoutMap)) {
|
for (const [id, el] of Object.entries(layoutMap)) {
|
||||||
@@ -48,7 +48,7 @@ export function useSnapGuides() {
|
|||||||
proposedX_mm: number,
|
proposedX_mm: number,
|
||||||
proposedY_mm: number,
|
proposedY_mm: number,
|
||||||
width_mm: number,
|
width_mm: number,
|
||||||
height_mm: number
|
height_mm: number,
|
||||||
): SnapResult {
|
): SnapResult {
|
||||||
if (!cachedEdges) {
|
if (!cachedEdges) {
|
||||||
return { snappedX_mm: proposedX_mm, snappedY_mm: proposedY_mm, guides: [] }
|
return { snappedX_mm: proposedX_mm, snappedY_mm: proposedY_mm, guides: [] }
|
||||||
@@ -132,13 +132,12 @@ export function useSnapGuides() {
|
|||||||
/** Calculate snap for resize edge */
|
/** Calculate snap for resize edge */
|
||||||
function calculateResizeSnap(
|
function calculateResizeSnap(
|
||||||
edge: 'left' | 'right' | 'top' | 'bottom',
|
edge: 'left' | 'right' | 'top' | 'bottom',
|
||||||
proposedValue_mm: number
|
proposedValue_mm: number,
|
||||||
): number {
|
): number {
|
||||||
if (!cachedEdges) return proposedValue_mm
|
if (!cachedEdges) return proposedValue_mm
|
||||||
|
|
||||||
const targets = (edge === 'left' || edge === 'right')
|
const targets =
|
||||||
? cachedEdges.verticals
|
edge === 'left' || edge === 'right' ? cachedEdges.verticals : cachedEdges.horizontals
|
||||||
: cachedEdges.horizontals
|
|
||||||
|
|
||||||
const guides: SnapGuide[] = []
|
const guides: SnapGuide[] = []
|
||||||
let snapped = proposedValue_mm
|
let snapped = proposedValue_mm
|
||||||
@@ -154,7 +153,7 @@ export function useSnapGuides() {
|
|||||||
|
|
||||||
if (snapped !== proposedValue_mm) {
|
if (snapped !== proposedValue_mm) {
|
||||||
guides.push({
|
guides.push({
|
||||||
type: (edge === 'left' || edge === 'right') ? 'vertical' : 'horizontal',
|
type: edge === 'left' || edge === 'right' ? 'vertical' : 'horizontal',
|
||||||
position_mm: snapped,
|
position_mm: snapped,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function useUndoRedo<T>(source: Ref<T>, maxHistory = 50) {
|
|||||||
redoStack.value = []
|
redoStack.value = []
|
||||||
}, 300)
|
}, 300)
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
function undo() {
|
function undo() {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ describe('findScalarFields', () => {
|
|||||||
// firma.unvan, firma.vergiNo, fatura.no, fatura.tutar, fatura.tarih = 5
|
// firma.unvan, firma.vergiNo, fatura.no, fatura.tutar, fatura.tarih = 5
|
||||||
expect(scalars).toHaveLength(5)
|
expect(scalars).toHaveLength(5)
|
||||||
|
|
||||||
const paths = scalars.map(s => s.path)
|
const paths = scalars.map((s) => s.path)
|
||||||
expect(paths).toContain('firma.unvan')
|
expect(paths).toContain('firma.unvan')
|
||||||
expect(paths).toContain('firma.vergiNo')
|
expect(paths).toContain('firma.vergiNo')
|
||||||
expect(paths).toContain('fatura.no')
|
expect(paths).toContain('fatura.no')
|
||||||
@@ -159,7 +159,7 @@ describe('findScalarFields', () => {
|
|||||||
it('does not include object or array nodes', () => {
|
it('does not include object or array nodes', () => {
|
||||||
const tree = parseSchema(testSchema)
|
const tree = parseSchema(testSchema)
|
||||||
const scalars = findScalarFields(tree)
|
const scalars = findScalarFields(tree)
|
||||||
const types = scalars.map(s => s.type)
|
const types = scalars.map((s) => s.type)
|
||||||
|
|
||||||
expect(types).not.toContain('object')
|
expect(types).not.toContain('object')
|
||||||
expect(types).not.toContain('array')
|
expect(types).not.toContain('array')
|
||||||
@@ -195,17 +195,38 @@ describe('defaultAlignForSchema', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns right for currency format', () => {
|
it('returns right for currency format', () => {
|
||||||
const node: SchemaNode = { path: 'x', key: 'x', title: 'X', type: 'string', format: 'currency', children: [] }
|
const node: SchemaNode = {
|
||||||
|
path: 'x',
|
||||||
|
key: 'x',
|
||||||
|
title: 'X',
|
||||||
|
type: 'string',
|
||||||
|
format: 'currency',
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
expect(defaultAlignForSchema(node)).toBe('right')
|
expect(defaultAlignForSchema(node)).toBe('right')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns right for percentage format', () => {
|
it('returns right for percentage format', () => {
|
||||||
const node: SchemaNode = { path: 'x', key: 'x', title: 'X', type: 'string', format: 'percentage', children: [] }
|
const node: SchemaNode = {
|
||||||
|
path: 'x',
|
||||||
|
key: 'x',
|
||||||
|
title: 'X',
|
||||||
|
type: 'string',
|
||||||
|
format: 'percentage',
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
expect(defaultAlignForSchema(node)).toBe('right')
|
expect(defaultAlignForSchema(node)).toBe('right')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns center for date format', () => {
|
it('returns center for date format', () => {
|
||||||
const node: SchemaNode = { path: 'x', key: 'x', title: 'X', type: 'string', format: 'date', children: [] }
|
const node: SchemaNode = {
|
||||||
|
path: 'x',
|
||||||
|
key: 'x',
|
||||||
|
title: 'X',
|
||||||
|
type: 'string',
|
||||||
|
format: 'date',
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
expect(defaultAlignForSchema(node)).toBe('center')
|
expect(defaultAlignForSchema(node)).toBe('center')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,13 @@ function mockColumnValue(field: string, format: string | undefined, index: numbe
|
|||||||
const lower = field.toLowerCase()
|
const lower = field.toLowerCase()
|
||||||
if (lower.includes('sira') || lower.includes('no') || lower === 'id') return index + 1
|
if (lower.includes('sira') || lower.includes('no') || lower === 'id') return index + 1
|
||||||
if (lower.includes('miktar') || lower.includes('adet')) return [2, 1, 5][index % 3]
|
if (lower.includes('miktar') || lower.includes('adet')) return [2, 1, 5][index % 3]
|
||||||
if (lower.includes('fiyat') || lower.includes('tutar') || lower.includes('toplam')) return [1500, 2750, 500][index % 3]
|
if (lower.includes('fiyat') || lower.includes('tutar') || lower.includes('toplam'))
|
||||||
|
return [1500, 2750, 500][index % 3]
|
||||||
if (lower.includes('birim')) return ['Adet', 'Saat', 'Adet'][index % 3]
|
if (lower.includes('birim')) return ['Adet', 'Saat', 'Adet'][index % 3]
|
||||||
if (lower.includes('tarih') || lower.includes('date')) return ['2026-01-15', '2026-02-20', '2026-03-10'][index % 3]
|
if (lower.includes('tarih') || lower.includes('date'))
|
||||||
if (lower.includes('ad') || lower.includes('isim') || lower.includes('name')) return ['Kalem A', 'Kalem B', 'Kalem C'][index % 3]
|
return ['2026-01-15', '2026-02-20', '2026-03-10'][index % 3]
|
||||||
|
if (lower.includes('ad') || lower.includes('isim') || lower.includes('name'))
|
||||||
|
return ['Kalem A', 'Kalem B', 'Kalem C'][index % 3]
|
||||||
|
|
||||||
return `Ornek ${index + 1}`
|
return `Ornek ${index + 1}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface SchemaNode {
|
export interface SchemaNode {
|
||||||
path: string // Noktalı yol — ör: "firma.unvan"
|
path: string // Noktalı yol — ör: "firma.unvan"
|
||||||
key: string // Son segment — ör: "unvan"
|
key: string // Son segment — ör: "unvan"
|
||||||
title: string // Görüntüleme adı — schema'daki "title" veya key
|
title: string // Görüntüleme adı — schema'daki "title" veya key
|
||||||
type: 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean'
|
type: 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean'
|
||||||
format?: string // "currency", "date", "percentage", "image" vs.
|
format?: string // "currency", "date", "percentage", "image" vs.
|
||||||
children: SchemaNode[]
|
children: SchemaNode[]
|
||||||
/** Sadece array tipi için: array item'larının alt alanları */
|
/** Sadece array tipi için: array item'larının alt alanları */
|
||||||
itemProperties?: SchemaNode[]
|
itemProperties?: SchemaNode[]
|
||||||
@@ -73,7 +73,12 @@ export function findArrayFields(node: SchemaNode): SchemaNode[] {
|
|||||||
/** Schema ağacından tüm scalar alanları bulur (metin binding için) */
|
/** Schema ağacından tüm scalar alanları bulur (metin binding için) */
|
||||||
export function findScalarFields(node: SchemaNode): SchemaNode[] {
|
export function findScalarFields(node: SchemaNode): SchemaNode[] {
|
||||||
const result: SchemaNode[] = []
|
const result: SchemaNode[] = []
|
||||||
if (node.type === 'string' || node.type === 'number' || node.type === 'integer' || node.type === 'boolean') {
|
if (
|
||||||
|
node.type === 'string' ||
|
||||||
|
node.type === 'number' ||
|
||||||
|
node.type === 'integer' ||
|
||||||
|
node.type === 'boolean'
|
||||||
|
) {
|
||||||
result.push(node)
|
result.push(node)
|
||||||
}
|
}
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
@@ -83,13 +88,19 @@ export function findScalarFields(node: SchemaNode): SchemaNode[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Format tipinden FormatType'a dönüşüm */
|
/** Format tipinden FormatType'a dönüşüm */
|
||||||
export function schemaFormatToFormatType(format?: string): 'currency' | 'date' | 'percentage' | 'number' | undefined {
|
export function schemaFormatToFormatType(
|
||||||
|
format?: string,
|
||||||
|
): 'currency' | 'date' | 'percentage' | 'number' | undefined {
|
||||||
if (!format) return undefined
|
if (!format) return undefined
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'currency': return 'currency'
|
case 'currency':
|
||||||
case 'date': return 'date'
|
return 'currency'
|
||||||
case 'percentage': return 'percentage'
|
case 'date':
|
||||||
default: return undefined
|
return 'date'
|
||||||
|
case 'percentage':
|
||||||
|
return 'percentage'
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export interface ShapeElement extends BaseElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CheckboxStyle {
|
export interface CheckboxStyle {
|
||||||
size?: number // mm — kare boyutu
|
size?: number // mm — kare boyutu
|
||||||
checkColor?: string // checkmark rengi
|
checkColor?: string // checkmark rengi
|
||||||
borderColor?: string
|
borderColor?: string
|
||||||
borderWidth?: number
|
borderWidth?: number
|
||||||
@@ -251,11 +251,11 @@ export interface ChartAxis {
|
|||||||
export interface ChartStyle {
|
export interface ChartStyle {
|
||||||
colors?: string[]
|
colors?: string[]
|
||||||
backgroundColor?: string
|
backgroundColor?: string
|
||||||
barGap?: number // 0.0-1.0
|
barGap?: number // 0.0-1.0
|
||||||
lineWidth?: number // mm
|
lineWidth?: number // mm
|
||||||
showPoints?: boolean
|
showPoints?: boolean
|
||||||
curveType?: 'linear' | 'smooth'
|
curveType?: 'linear' | 'smooth'
|
||||||
innerRadius?: number // 0=pie, >0=donut (0-0.9)
|
innerRadius?: number // 0=pie, >0=donut (0-0.9)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChartElement extends BaseElement {
|
export interface ChartElement extends BaseElement {
|
||||||
@@ -293,7 +293,21 @@ export interface RepeatingTableElement extends BaseElement {
|
|||||||
repeatHeader?: boolean
|
repeatHeader?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LeafElement = StaticTextElement | TextElement | LineElement | RepeatingTableElement | ImageElement | PageNumberElement | BarcodeElement | PageBreakElement | CurrentDateElement | ShapeElement | CheckboxElement | CalculatedTextElement | RichTextElement | ChartElement
|
export type LeafElement =
|
||||||
|
| StaticTextElement
|
||||||
|
| TextElement
|
||||||
|
| LineElement
|
||||||
|
| RepeatingTableElement
|
||||||
|
| ImageElement
|
||||||
|
| PageNumberElement
|
||||||
|
| BarcodeElement
|
||||||
|
| PageBreakElement
|
||||||
|
| CurrentDateElement
|
||||||
|
| ShapeElement
|
||||||
|
| CheckboxElement
|
||||||
|
| CalculatedTextElement
|
||||||
|
| RichTextElement
|
||||||
|
| ChartElement
|
||||||
export type TemplateElement = LeafElement | ContainerElement
|
export type TemplateElement = LeafElement | ContainerElement
|
||||||
|
|
||||||
// --- Template ---
|
// --- Template ---
|
||||||
@@ -330,10 +344,7 @@ export function isLeaf(el: TemplateElement): el is LeafElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Ağaçta bir element'i ID ile bulur */
|
/** Ağaçta bir element'i ID ile bulur */
|
||||||
export function findElementById(
|
export function findElementById(root: ContainerElement, id: string): TemplateElement | undefined {
|
||||||
root: ContainerElement,
|
|
||||||
id: string
|
|
||||||
): TemplateElement | undefined {
|
|
||||||
if (root.id === id) return root
|
if (root.id === id) return root
|
||||||
for (const child of root.children) {
|
for (const child of root.children) {
|
||||||
if (child.id === id) return child
|
if (child.id === id) return child
|
||||||
@@ -346,10 +357,7 @@ export function findElementById(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Bir element'in parent container'ını bulur */
|
/** Bir element'in parent container'ını bulur */
|
||||||
export function findParent(
|
export function findParent(root: ContainerElement, id: string): ContainerElement | undefined {
|
||||||
root: ContainerElement,
|
|
||||||
id: string
|
|
||||||
): ContainerElement | undefined {
|
|
||||||
for (const child of root.children) {
|
for (const child of root.children) {
|
||||||
if (child.id === id) return root
|
if (child.id === id) return root
|
||||||
if (isContainer(child)) {
|
if (isContainer(child)) {
|
||||||
|
|||||||
@@ -58,23 +58,39 @@ let installed = false
|
|||||||
export function setupTooltips() {
|
export function setupTooltips() {
|
||||||
if (installed) return
|
if (installed) return
|
||||||
installed = true
|
installed = true
|
||||||
document.addEventListener('pointerenter', (e) => {
|
document.addEventListener(
|
||||||
const target = closest(e.target)
|
'pointerenter',
|
||||||
if (target) show(target)
|
(e) => {
|
||||||
}, true)
|
const target = closest(e.target)
|
||||||
|
if (target) show(target)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
document.addEventListener('pointerleave', (e) => {
|
document.addEventListener(
|
||||||
const target = closest(e.target)
|
'pointerleave',
|
||||||
if (target && target === currentTarget) hide()
|
(e) => {
|
||||||
}, true)
|
const target = closest(e.target)
|
||||||
|
if (target && target === currentTarget) hide()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
document.addEventListener('focusin', (e) => {
|
document.addEventListener(
|
||||||
const target = closest(e.target)
|
'focusin',
|
||||||
if (target) show(target)
|
(e) => {
|
||||||
}, true)
|
const target = closest(e.target)
|
||||||
|
if (target) show(target)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
document.addEventListener('focusout', (e) => {
|
document.addEventListener(
|
||||||
const target = closest(e.target)
|
'focusout',
|
||||||
if (target && target === currentTarget) hide()
|
(e) => {
|
||||||
}, true)
|
const target = closest(e.target)
|
||||||
|
if (target && target === currentTarget) hide()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,18 @@ export interface DreportEditorConfig {
|
|||||||
apiBaseUrl?: string
|
apiBaseUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(
|
||||||
schema: JsonSchema
|
defineProps<{
|
||||||
modelValue: Template
|
schema: JsonSchema
|
||||||
data?: Record<string, unknown>
|
modelValue: Template
|
||||||
config?: DreportEditorConfig
|
data?: Record<string, unknown>
|
||||||
handleErrors?: boolean
|
config?: DreportEditorConfig
|
||||||
}>(), {
|
handleErrors?: boolean
|
||||||
handleErrors: true,
|
}>(),
|
||||||
})
|
{
|
||||||
|
handleErrors: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: Template]
|
'update:modelValue': [value: Template]
|
||||||
@@ -45,34 +48,55 @@ onMounted(() => {
|
|||||||
schemaStore.setSchema(props.schema)
|
schemaStore.setSchema(props.schema)
|
||||||
syncing = true
|
syncing = true
|
||||||
templateStore.template = JSON.parse(JSON.stringify(props.modelValue))
|
templateStore.template = JSON.parse(JSON.stringify(props.modelValue))
|
||||||
nextTick(() => { syncing = false })
|
nextTick(() => {
|
||||||
|
syncing = false
|
||||||
|
})
|
||||||
templateStore.setOverrideData(props.data ?? null)
|
templateStore.setOverrideData(props.data ?? null)
|
||||||
setupTooltips()
|
setupTooltips()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.schema, (val) => {
|
watch(
|
||||||
schemaStore.setSchema(val)
|
() => props.schema,
|
||||||
}, { deep: true })
|
(val) => {
|
||||||
|
schemaStore.setSchema(val)
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
watch(() => props.data, (val) => {
|
watch(
|
||||||
templateStore.setOverrideData(val ?? null)
|
() => props.data,
|
||||||
}, { deep: true })
|
(val) => {
|
||||||
|
templateStore.setOverrideData(val ?? null)
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
// Template: prop → store (only on reference change from parent)
|
// Template: prop → store (only on reference change from parent)
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(
|
||||||
if (syncing) return
|
() => props.modelValue,
|
||||||
syncing = true
|
(val) => {
|
||||||
templateStore.template = JSON.parse(JSON.stringify(val))
|
if (syncing) return
|
||||||
nextTick(() => { syncing = false })
|
syncing = true
|
||||||
})
|
templateStore.template = JSON.parse(JSON.stringify(val))
|
||||||
|
nextTick(() => {
|
||||||
|
syncing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Template: store → emit
|
// Template: store → emit
|
||||||
watch(() => templateStore.template, (val) => {
|
watch(
|
||||||
if (syncing) return
|
() => templateStore.template,
|
||||||
syncing = true
|
(val) => {
|
||||||
emit('update:modelValue', JSON.parse(JSON.stringify(val)))
|
if (syncing) return
|
||||||
nextTick(() => { syncing = false })
|
syncing = true
|
||||||
}, { deep: true })
|
emit('update:modelValue', JSON.parse(JSON.stringify(val)))
|
||||||
|
nextTick(() => {
|
||||||
|
syncing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
// --- Error forwarding ---
|
// --- Error forwarding ---
|
||||||
|
|
||||||
@@ -85,7 +109,8 @@ function onCompileError(error: string | null) {
|
|||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
const target = e.target as HTMLElement
|
const target = e.target as HTMLElement
|
||||||
const tag = target?.tagName
|
const tag = target?.tagName
|
||||||
const isInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target?.isContentEditable
|
const isInput =
|
||||||
|
tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target?.isContentEditable
|
||||||
|
|
||||||
// Delete / Backspace — çoklu seçim desteği
|
// Delete / Backspace — çoklu seçim desteği
|
||||||
if ((e.key === 'Delete' || e.key === 'Backspace') && editorStore.selectedElementIds.size > 0) {
|
if ((e.key === 'Delete' || e.key === 'Backspace') && editorStore.selectedElementIds.size > 0) {
|
||||||
@@ -116,7 +141,11 @@ function onKeyDown(e: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Z-Order kısayolları
|
// Z-Order kısayolları
|
||||||
if ((e.ctrlKey || e.metaKey) && editorStore.selectedElementId && editorStore.selectedElementId !== 'root') {
|
if (
|
||||||
|
(e.ctrlKey || e.metaKey) &&
|
||||||
|
editorStore.selectedElementId &&
|
||||||
|
editorStore.selectedElementId !== 'root'
|
||||||
|
) {
|
||||||
if (e.key === ']' && e.shiftKey) {
|
if (e.key === ']' && e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
templateStore.bringToFront(editorStore.selectedElementId)
|
templateStore.bringToFront(editorStore.selectedElementId)
|
||||||
@@ -202,8 +231,20 @@ defineExpose({
|
|||||||
<div class="dreport-editor">
|
<div class="dreport-editor">
|
||||||
<aside class="dreport-editor__sidebar dreport-editor__sidebar--left">
|
<aside class="dreport-editor__sidebar dreport-editor__sidebar--left">
|
||||||
<div class="sidebar-tabs">
|
<div class="sidebar-tabs">
|
||||||
<button class="sidebar-tab" :class="{ 'sidebar-tab--active': leftTab === 'tools' }" @click="leftTab = 'tools'">Araclar</button>
|
<button
|
||||||
<button class="sidebar-tab" :class="{ 'sidebar-tab--active': leftTab === 'schema' }" @click="leftTab = 'schema'">Schema</button>
|
class="sidebar-tab"
|
||||||
|
:class="{ 'sidebar-tab--active': leftTab === 'tools' }"
|
||||||
|
@click="leftTab = 'tools'"
|
||||||
|
>
|
||||||
|
Araclar
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="sidebar-tab"
|
||||||
|
:class="{ 'sidebar-tab--active': leftTab === 'schema' }"
|
||||||
|
@click="leftTab = 'schema'"
|
||||||
|
>
|
||||||
|
Schema
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ToolboxPanel v-if="leftTab === 'tools'" />
|
<ToolboxPanel v-if="leftTab === 'tools'" />
|
||||||
<SchemaTreePanel v-else />
|
<SchemaTreePanel v-else />
|
||||||
|
|||||||
@@ -24,13 +24,17 @@ const template = ref<Template>({
|
|||||||
page: { width: 210, height: 297 },
|
page: { width: 210, height: 297 },
|
||||||
fonts: ['Noto Sans'],
|
fonts: ['Noto Sans'],
|
||||||
root: {
|
root: {
|
||||||
id: 'root', type: 'container',
|
id: 'root',
|
||||||
|
type: 'container',
|
||||||
position: { type: 'flow' },
|
position: { type: 'flow' },
|
||||||
size: { width: { type: 'auto' }, height: { type: 'auto' } },
|
size: { width: { type: 'auto' }, height: { type: 'auto' } },
|
||||||
direction: 'column', gap: 0,
|
direction: 'column',
|
||||||
|
gap: 0,
|
||||||
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||||
align: 'stretch', justify: 'start',
|
align: 'stretch',
|
||||||
style: {}, children: [],
|
justify: 'start',
|
||||||
|
style: {},
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const data = ref<Record<string, unknown>>({})
|
const data = ref<Record<string, unknown>>({})
|
||||||
@@ -81,11 +85,7 @@ onMounted(() => {
|
|||||||
:data-render-ready="ready || undefined"
|
:data-render-ready="ready || undefined"
|
||||||
:data-render-error="errorMsg || undefined"
|
:data-render-error="errorMsg || undefined"
|
||||||
>
|
>
|
||||||
<LayoutRenderer
|
<LayoutRenderer v-if="layout" :layout="layout" :scale="SCALE" />
|
||||||
v-if="layout"
|
|
||||||
:layout="layout"
|
|
||||||
:scale="SCALE"
|
|
||||||
/>
|
|
||||||
<div v-else-if="errorMsg" class="error">{{ errorMsg }}</div>
|
<div v-else-if="errorMsg" class="error">{{ errorMsg }}</div>
|
||||||
<div v-else class="loading">Computing layout...</div>
|
<div v-else class="loading">Computing layout...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|||||||
import { setActivePinia, createPinia } from 'pinia'
|
import { setActivePinia, createPinia } from 'pinia'
|
||||||
import { useTemplateStore } from '../template'
|
import { useTemplateStore } from '../template'
|
||||||
import { useEditorStore } from '../editor'
|
import { useEditorStore } from '../editor'
|
||||||
import type { Template, StaticTextElement, ContainerElement, ImageElement, TemplateElement } from '../../core/types'
|
import type {
|
||||||
|
Template,
|
||||||
|
StaticTextElement,
|
||||||
|
ContainerElement,
|
||||||
|
ImageElement,
|
||||||
|
TemplateElement,
|
||||||
|
} from '../../core/types'
|
||||||
import { sz } from '../../core/types'
|
import { sz } from '../../core/types'
|
||||||
|
|
||||||
function createTestTemplate(): Template {
|
function createTestTemplate(): Template {
|
||||||
@@ -392,52 +398,52 @@ describe('3.2 Z-Order controls', () => {
|
|||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
// Sıra: [a, b, c] → bringForward(a) → [b, a, c]
|
// Sıra: [a, b, c] → bringForward(a) → [b, a, c]
|
||||||
store.bringForward('a')
|
store.bringForward('a')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['b', 'a', 'c'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['b', 'a', 'c'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendBackward moves element one step down', () => {
|
it('sendBackward moves element one step down', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
// Sıra: [a, b, c] → sendBackward(c) → [a, c, b]
|
// Sıra: [a, b, c] → sendBackward(c) → [a, c, b]
|
||||||
store.sendBackward('c')
|
store.sendBackward('c')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['a', 'c', 'b'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['a', 'c', 'b'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('bringToFront moves element to end', () => {
|
it('bringToFront moves element to end', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
// Sıra: [a, b, c] → bringToFront(a) → [b, c, a]
|
// Sıra: [a, b, c] → bringToFront(a) → [b, c, a]
|
||||||
store.bringToFront('a')
|
store.bringToFront('a')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['b', 'c', 'a'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['b', 'c', 'a'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendToBack moves element to beginning', () => {
|
it('sendToBack moves element to beginning', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
// Sıra: [a, b, c] → sendToBack(c) → [c, a, b]
|
// Sıra: [a, b, c] → sendToBack(c) → [c, a, b]
|
||||||
store.sendToBack('c')
|
store.sendToBack('c')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['c', 'a', 'b'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['c', 'a', 'b'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('bringForward on last element is no-op', () => {
|
it('bringForward on last element is no-op', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
store.bringForward('c')
|
store.bringForward('c')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['a', 'b', 'c'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['a', 'b', 'c'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendBackward on first element is no-op', () => {
|
it('sendBackward on first element is no-op', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
store.sendBackward('a')
|
store.sendBackward('a')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['a', 'b', 'c'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['a', 'b', 'c'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('bringToFront on last element is no-op', () => {
|
it('bringToFront on last element is no-op', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
store.bringToFront('c')
|
store.bringToFront('c')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['a', 'b', 'c'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['a', 'b', 'c'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sendToBack on first element is no-op', () => {
|
it('sendToBack on first element is no-op', () => {
|
||||||
const store = setupThreeElements()
|
const store = setupThreeElements()
|
||||||
store.sendToBack('a')
|
store.sendToBack('a')
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['a', 'b', 'c'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['a', 'b', 'c'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ describe('useTemplateStore', () => {
|
|||||||
store.addChild('root', createTextElement('b', 'B'))
|
store.addChild('root', createTextElement('b', 'B'))
|
||||||
store.addChild('root', createTextElement('c', 'C'), 1)
|
store.addChild('root', createTextElement('c', 'C'), 1)
|
||||||
|
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['a', 'c', 'b'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['a', 'c', 'b'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('removeElement removes element', () => {
|
it('removeElement removes element', () => {
|
||||||
@@ -133,7 +133,7 @@ describe('useTemplateStore', () => {
|
|||||||
|
|
||||||
store.reorderChild('root', 0, 2)
|
store.reorderChild('root', 0, 2)
|
||||||
|
|
||||||
expect(store.template.root.children.map(c => c.id)).toEqual(['b', 'c', 'a'])
|
expect(store.template.root.children.map((c) => c.id)).toEqual(['b', 'c', 'a'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('exportTemplate returns valid JSON', () => {
|
it('exportTemplate returns valid JSON', () => {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import type { Template, TemplateElement, ContainerElement, SizeConstraint, PositionMode } from '../core/types'
|
import type {
|
||||||
|
Template,
|
||||||
|
TemplateElement,
|
||||||
|
ContainerElement,
|
||||||
|
SizeConstraint,
|
||||||
|
PositionMode,
|
||||||
|
} from '../core/types'
|
||||||
import { findElementById, findParent, isContainer, sz } from '../core/types'
|
import { findElementById, findParent, isContainer, sz } from '../core/types'
|
||||||
import { generateMockData } from '../core/mock-data-generator'
|
import { generateMockData } from '../core/mock-data-generator'
|
||||||
import { useUndoRedo } from '../composables/useUndoRedo'
|
import { useUndoRedo } from '../composables/useUndoRedo'
|
||||||
@@ -132,7 +138,7 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
function removeElement(elementId: string) {
|
function removeElement(elementId: string) {
|
||||||
const parent = getParent(elementId)
|
const parent = getParent(elementId)
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
const idx = parent.children.findIndex(c => c.id === elementId)
|
const idx = parent.children.findIndex((c) => c.id === elementId)
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
parent.children.splice(idx, 1)
|
parent.children.splice(idx, 1)
|
||||||
bumpLayoutVersion()
|
bumpLayoutVersion()
|
||||||
@@ -146,7 +152,7 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
// Ağaçtan kaldır (bump'sız)
|
// Ağaçtan kaldır (bump'sız)
|
||||||
const parent = getParent(elementId)
|
const parent = getParent(elementId)
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const idx = parent.children.findIndex(c => c.id === elementId)
|
const idx = parent.children.findIndex((c) => c.id === elementId)
|
||||||
if (idx !== -1) parent.children.splice(idx, 1)
|
if (idx !== -1) parent.children.splice(idx, 1)
|
||||||
}
|
}
|
||||||
// Hedef container'a ekle (bump'sız)
|
// Hedef container'a ekle (bump'sız)
|
||||||
@@ -202,7 +208,7 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
function bringForward(elementId: string) {
|
function bringForward(elementId: string) {
|
||||||
const parent = getParent(elementId)
|
const parent = getParent(elementId)
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
const idx = parent.children.findIndex(c => c.id === elementId)
|
const idx = parent.children.findIndex((c) => c.id === elementId)
|
||||||
if (idx < 0 || idx >= parent.children.length - 1) return
|
if (idx < 0 || idx >= parent.children.length - 1) return
|
||||||
reorderChild(parent.id, idx, idx + 1)
|
reorderChild(parent.id, idx, idx + 1)
|
||||||
}
|
}
|
||||||
@@ -211,7 +217,7 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
function sendBackward(elementId: string) {
|
function sendBackward(elementId: string) {
|
||||||
const parent = getParent(elementId)
|
const parent = getParent(elementId)
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
const idx = parent.children.findIndex(c => c.id === elementId)
|
const idx = parent.children.findIndex((c) => c.id === elementId)
|
||||||
if (idx <= 0) return
|
if (idx <= 0) return
|
||||||
reorderChild(parent.id, idx, idx - 1)
|
reorderChild(parent.id, idx, idx - 1)
|
||||||
}
|
}
|
||||||
@@ -220,7 +226,7 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
function bringToFront(elementId: string) {
|
function bringToFront(elementId: string) {
|
||||||
const parent = getParent(elementId)
|
const parent = getParent(elementId)
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
const idx = parent.children.findIndex(c => c.id === elementId)
|
const idx = parent.children.findIndex((c) => c.id === elementId)
|
||||||
if (idx < 0 || idx >= parent.children.length - 1) return
|
if (idx < 0 || idx >= parent.children.length - 1) return
|
||||||
reorderChild(parent.id, idx, parent.children.length - 1)
|
reorderChild(parent.id, idx, parent.children.length - 1)
|
||||||
}
|
}
|
||||||
@@ -229,7 +235,7 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
function sendToBack(elementId: string) {
|
function sendToBack(elementId: string) {
|
||||||
const parent = getParent(elementId)
|
const parent = getParent(elementId)
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
const idx = parent.children.findIndex(c => c.id === elementId)
|
const idx = parent.children.findIndex((c) => c.id === elementId)
|
||||||
if (idx <= 0) return
|
if (idx <= 0) return
|
||||||
reorderChild(parent.id, idx, 0)
|
reorderChild(parent.id, idx, 0)
|
||||||
}
|
}
|
||||||
@@ -251,7 +257,11 @@ export const useTemplateStore = defineStore('template', () => {
|
|||||||
if (!parsed.root || parsed.root.type !== 'container') {
|
if (!parsed.root || parsed.root.type !== 'container') {
|
||||||
throw new Error('Geçersiz şablon: root alanı eksik veya container değil')
|
throw new Error('Geçersiz şablon: root alanı eksik veya container değil')
|
||||||
}
|
}
|
||||||
if (!parsed.page || typeof parsed.page.width !== 'number' || typeof parsed.page.height !== 'number') {
|
if (
|
||||||
|
!parsed.page ||
|
||||||
|
typeof parsed.page.width !== 'number' ||
|
||||||
|
typeof parsed.page.height !== 'number'
|
||||||
|
) {
|
||||||
throw new Error('Geçersiz şablon: page alanı eksik veya geçersiz')
|
throw new Error('Geçersiz şablon: page alanı eksik veya geçersiz')
|
||||||
}
|
}
|
||||||
template.value = parsed
|
template.value = parsed
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
/// Template JSON + Data JSON → Layout WASM → LayoutResult
|
/// Template JSON + Data JSON → Layout WASM → LayoutResult
|
||||||
/// Font loading is dynamic — fetches from backend API based on template needs.
|
/// Font loading is dynamic — fetches from backend API based on template needs.
|
||||||
|
|
||||||
import init, { loadFonts, addFonts, computeLayout, generateBarcode } from '../core/wasm-layout/dreport_layout.js'
|
import init, {
|
||||||
|
loadFonts,
|
||||||
|
addFonts,
|
||||||
|
computeLayout,
|
||||||
|
generateBarcode,
|
||||||
|
} from '../core/wasm-layout/dreport_layout.js'
|
||||||
import type { LayoutResult } from '../core/layout-types'
|
import type { LayoutResult } from '../core/layout-types'
|
||||||
|
|
||||||
let initPromise: Promise<void> | null = null
|
let initPromise: Promise<void> | null = null
|
||||||
@@ -35,7 +40,9 @@ async function doInit() {
|
|||||||
fontCatalog = await res.json()
|
fontCatalog = await res.json()
|
||||||
console.log(`[layout-worker] Font kataloğu yüklendi (${fontCatalog.length} aile)`)
|
console.log(`[layout-worker] Font kataloğu yüklendi (${fontCatalog.length} aile)`)
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[layout-worker] Font kataloğu alınamadı (HTTP ${res.status}), static fallback deneniyor`)
|
console.warn(
|
||||||
|
`[layout-worker] Font kataloğu alınamadı (HTTP ${res.status}), static fallback deneniyor`,
|
||||||
|
)
|
||||||
await loadStaticFallback()
|
await loadStaticFallback()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +75,7 @@ async function loadStaticFallback() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
buffers.push(new Uint8Array(await res.arrayBuffer()))
|
buffers.push(new Uint8Array(await res.arrayBuffer()))
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (buffers.length > 0) {
|
if (buffers.length > 0) {
|
||||||
@@ -81,13 +88,13 @@ async function loadStaticFallback() {
|
|||||||
|
|
||||||
/** Load all variants of given families from the API into WASM */
|
/** Load all variants of given families from the API into WASM */
|
||||||
async function ensureFamiliesLoaded(families: string[]): Promise<void> {
|
async function ensureFamiliesLoaded(families: string[]): Promise<void> {
|
||||||
const toLoad = families.filter(f => !loadedFamilies.has(f.toLowerCase()))
|
const toLoad = families.filter((f) => !loadedFamilies.has(f.toLowerCase()))
|
||||||
if (toLoad.length === 0) return
|
if (toLoad.length === 0) return
|
||||||
|
|
||||||
const buffers: Uint8Array[] = []
|
const buffers: Uint8Array[] = []
|
||||||
|
|
||||||
for (const family of toLoad) {
|
for (const family of toLoad) {
|
||||||
const info = fontCatalog.find(f => f.family.toLowerCase() === family.toLowerCase())
|
const info = fontCatalog.find((f) => f.family.toLowerCase() === family.toLowerCase())
|
||||||
if (!info) {
|
if (!info) {
|
||||||
console.warn(`[layout-worker] Font ailesi bulunamadı: ${family}`)
|
console.warn(`[layout-worker] Font ailesi bulunamadı: ${family}`)
|
||||||
continue
|
continue
|
||||||
@@ -132,7 +139,15 @@ function ensureInit(): Promise<void> {
|
|||||||
|
|
||||||
type WorkerMessage =
|
type WorkerMessage =
|
||||||
| { type: 'compile'; templateJson: string; dataJson: string; id: number }
|
| { type: 'compile'; templateJson: string; dataJson: string; id: number }
|
||||||
| { type: 'barcode'; format: string; value: string; width: number; height: number; includeText: boolean; id: number }
|
| {
|
||||||
|
type: 'barcode'
|
||||||
|
format: string
|
||||||
|
value: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
includeText: boolean
|
||||||
|
id: number
|
||||||
|
}
|
||||||
| { type: 'configure'; fontApiBase?: string }
|
| { type: 'configure'; fontApiBase?: string }
|
||||||
|
|
||||||
self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
|
self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user