mirror of
https://github.com/duhanbalci/dreport.git
synced 2026-07-02 02:49:16 +00:00
refactor & improvements
This commit is contained in:
178
frontend/src/composables/useSnapGuides.ts
Normal file
178
frontend/src/composables/useSnapGuides.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { ref } from 'vue'
|
||||
import type { ElementLayout } from '../core/layout-types'
|
||||
|
||||
export interface SnapGuide {
|
||||
type: 'vertical' | 'horizontal'
|
||||
position_mm: number
|
||||
}
|
||||
|
||||
export interface SnapResult {
|
||||
snappedX_mm: number
|
||||
snappedY_mm: number
|
||||
guides: SnapGuide[]
|
||||
}
|
||||
|
||||
interface EdgeSet {
|
||||
verticals: number[] // x positions in mm (left, right, center of elements + page)
|
||||
horizontals: number[] // y positions in mm (top, bottom, center of elements + page)
|
||||
}
|
||||
|
||||
export function useSnapGuides() {
|
||||
const SNAP_THRESHOLD_MM = 1.5
|
||||
const activeGuides = ref<SnapGuide[]>([])
|
||||
let cachedEdges: EdgeSet | null = null
|
||||
|
||||
/** Collect edges from all elements except the one being dragged. Call once on drag start. */
|
||||
function collectEdges(
|
||||
layoutMap: Record<string, ElementLayout>,
|
||||
excludeId: string,
|
||||
pageWidth: number,
|
||||
pageHeight: number
|
||||
) {
|
||||
const verticals: number[] = [0, pageWidth / 2, pageWidth] // page edges + center
|
||||
const horizontals: number[] = [0, pageHeight / 2, pageHeight]
|
||||
|
||||
for (const [id, el] of Object.entries(layoutMap)) {
|
||||
if (id === excludeId) continue
|
||||
// Left, center, right
|
||||
verticals.push(el.x_mm, el.x_mm + el.width_mm / 2, el.x_mm + el.width_mm)
|
||||
// Top, center, bottom
|
||||
horizontals.push(el.y_mm, el.y_mm + el.height_mm / 2, el.y_mm + el.height_mm)
|
||||
}
|
||||
|
||||
cachedEdges = { verticals, horizontals }
|
||||
}
|
||||
|
||||
/** Calculate snap for a dragged element. Returns adjusted position + active guides. */
|
||||
function calculateSnap(
|
||||
proposedX_mm: number,
|
||||
proposedY_mm: number,
|
||||
width_mm: number,
|
||||
height_mm: number
|
||||
): SnapResult {
|
||||
if (!cachedEdges) {
|
||||
return { snappedX_mm: proposedX_mm, snappedY_mm: proposedY_mm, guides: [] }
|
||||
}
|
||||
|
||||
const guides: SnapGuide[] = []
|
||||
let snappedX = proposedX_mm
|
||||
let snappedY = proposedY_mm
|
||||
|
||||
// Element edges to check
|
||||
const myLeft = proposedX_mm
|
||||
const myCenter = proposedX_mm + width_mm / 2
|
||||
const myRight = proposedX_mm + width_mm
|
||||
|
||||
// Find closest vertical snap
|
||||
let bestVDist = SNAP_THRESHOLD_MM
|
||||
let bestVSnap: { edge: number; offset: number } | null = null
|
||||
|
||||
for (const v of cachedEdges.verticals) {
|
||||
// Check left edge
|
||||
const dLeft = Math.abs(myLeft - v)
|
||||
if (dLeft < bestVDist) {
|
||||
bestVDist = dLeft
|
||||
bestVSnap = { edge: v, offset: 0 }
|
||||
}
|
||||
// Check center
|
||||
const dCenter = Math.abs(myCenter - v)
|
||||
if (dCenter < bestVDist) {
|
||||
bestVDist = dCenter
|
||||
bestVSnap = { edge: v, offset: width_mm / 2 }
|
||||
}
|
||||
// Check right edge
|
||||
const dRight = Math.abs(myRight - v)
|
||||
if (dRight < bestVDist) {
|
||||
bestVDist = dRight
|
||||
bestVSnap = { edge: v, offset: width_mm }
|
||||
}
|
||||
}
|
||||
|
||||
if (bestVSnap) {
|
||||
snappedX = bestVSnap.edge - bestVSnap.offset
|
||||
guides.push({ type: 'vertical', position_mm: bestVSnap.edge })
|
||||
}
|
||||
|
||||
// Element edges to check (Y axis)
|
||||
const myTop = proposedY_mm
|
||||
const myMiddle = proposedY_mm + height_mm / 2
|
||||
const myBottom = proposedY_mm + height_mm
|
||||
|
||||
// Find closest horizontal snap
|
||||
let bestHDist = SNAP_THRESHOLD_MM
|
||||
let bestHSnap: { edge: number; offset: number } | null = null
|
||||
|
||||
for (const h of cachedEdges.horizontals) {
|
||||
const dTop = Math.abs(myTop - h)
|
||||
if (dTop < bestHDist) {
|
||||
bestHDist = dTop
|
||||
bestHSnap = { edge: h, offset: 0 }
|
||||
}
|
||||
const dMiddle = Math.abs(myMiddle - h)
|
||||
if (dMiddle < bestHDist) {
|
||||
bestHDist = dMiddle
|
||||
bestHSnap = { edge: h, offset: height_mm / 2 }
|
||||
}
|
||||
const dBottom = Math.abs(myBottom - h)
|
||||
if (dBottom < bestHDist) {
|
||||
bestHDist = dBottom
|
||||
bestHSnap = { edge: h, offset: height_mm }
|
||||
}
|
||||
}
|
||||
|
||||
if (bestHSnap) {
|
||||
snappedY = bestHSnap.edge - bestHSnap.offset
|
||||
guides.push({ type: 'horizontal', position_mm: bestHSnap.edge })
|
||||
}
|
||||
|
||||
activeGuides.value = guides
|
||||
return { snappedX_mm: snappedX, snappedY_mm: snappedY, guides }
|
||||
}
|
||||
|
||||
/** Calculate snap for resize edge */
|
||||
function calculateResizeSnap(
|
||||
edge: 'left' | 'right' | 'top' | 'bottom',
|
||||
proposedValue_mm: number
|
||||
): number {
|
||||
if (!cachedEdges) return proposedValue_mm
|
||||
|
||||
const targets = (edge === 'left' || edge === 'right')
|
||||
? cachedEdges.verticals
|
||||
: cachedEdges.horizontals
|
||||
|
||||
const guides: SnapGuide[] = []
|
||||
let snapped = proposedValue_mm
|
||||
|
||||
let bestDist = SNAP_THRESHOLD_MM
|
||||
for (const t of targets) {
|
||||
const d = Math.abs(proposedValue_mm - t)
|
||||
if (d < bestDist) {
|
||||
bestDist = d
|
||||
snapped = t
|
||||
}
|
||||
}
|
||||
|
||||
if (snapped !== proposedValue_mm) {
|
||||
guides.push({
|
||||
type: (edge === 'left' || edge === 'right') ? 'vertical' : 'horizontal',
|
||||
position_mm: snapped,
|
||||
})
|
||||
}
|
||||
|
||||
activeGuides.value = guides
|
||||
return snapped
|
||||
}
|
||||
|
||||
function clearGuides() {
|
||||
activeGuides.value = []
|
||||
cachedEdges = null
|
||||
}
|
||||
|
||||
return {
|
||||
activeGuides,
|
||||
collectEdges,
|
||||
calculateSnap,
|
||||
calculateResizeSnap,
|
||||
clearGuides,
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { ref, watch, type Ref } from 'vue'
|
||||
import type { ElementLayout } from '../core/template-to-typst'
|
||||
import type { Template } from '../core/types'
|
||||
|
||||
export function useTypstCompiler(
|
||||
template: Ref<Template>,
|
||||
data: Ref<Record<string, unknown>>,
|
||||
) {
|
||||
const svg = ref<string | null>(null)
|
||||
const error = ref<string | null>(null)
|
||||
const compiling = ref(false)
|
||||
const layout = ref<Record<string, ElementLayout>>({})
|
||||
|
||||
let worker: Worker | null = null
|
||||
let requestId = 0
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function initWorker() {
|
||||
worker = new Worker(new URL('../workers/typst.worker.ts', import.meta.url), {
|
||||
type: 'module',
|
||||
})
|
||||
|
||||
worker.onmessage = (e: MessageEvent<{
|
||||
type: string
|
||||
svg?: string
|
||||
layout?: Record<string, ElementLayout>
|
||||
error?: string
|
||||
id: number
|
||||
}>) => {
|
||||
const data = e.data
|
||||
if (data.id !== requestId) return
|
||||
|
||||
compiling.value = false
|
||||
if (data.type === 'result') {
|
||||
svg.value = data.svg ?? null
|
||||
layout.value = data.layout ?? {}
|
||||
error.value = null
|
||||
} else if (data.type === 'error') {
|
||||
error.value = data.error ?? 'Bilinmeyen derleme hatası'
|
||||
}
|
||||
}
|
||||
|
||||
worker.onerror = () => {
|
||||
compiling.value = false
|
||||
error.value = 'Worker hatası — yeniden başlatılıyor'
|
||||
worker?.terminate()
|
||||
worker = null
|
||||
setTimeout(initWorker, 500)
|
||||
}
|
||||
}
|
||||
|
||||
function compile() {
|
||||
if (!worker) initWorker()
|
||||
requestId++
|
||||
compiling.value = true
|
||||
worker!.postMessage({
|
||||
type: 'compile',
|
||||
templateJson: JSON.stringify(template.value),
|
||||
dataJson: JSON.stringify(data.value),
|
||||
id: requestId,
|
||||
})
|
||||
}
|
||||
|
||||
// template veya data değiştiğinde yeniden derle
|
||||
watch(
|
||||
[template, data],
|
||||
() => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
compile()
|
||||
}, 200)
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
function dispose() {
|
||||
worker?.terminate()
|
||||
worker = null
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
}
|
||||
|
||||
return {
|
||||
svg,
|
||||
error,
|
||||
compiling,
|
||||
layout,
|
||||
compile,
|
||||
dispose,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user