diff --git a/display/middleware/src/modules/display/layout-manager.ts b/display/middleware/src/modules/display/layout-manager.ts new file mode 100644 index 0000000..64d8e72 --- /dev/null +++ b/display/middleware/src/modules/display/layout-manager.ts @@ -0,0 +1,211 @@ +import { getDb } from '../../db/index'; + +export interface Layout { + id: number; + name: string; + type: string; + width: number; + height: number; + grid_cols: number | null; + grid_rows: number | null; + created_at: string; +} + +export interface LayoutWithElements extends Layout { + elements: LayoutElement[]; +} + +export interface LayoutElement { + id: number; + layout_id: number; + datapoint_id: number | null; + type: string; + x: number; + y: number; + width: number | null; + height: number | null; + font_size: number; + font_weight: string; + text_align: string; + icon: string | null; + style: string; + static_text: string | null; + grid_col: number | null; + grid_row: number | null; + sort_order: number; +} + +export interface CreateLayoutData { + name: string; + type?: string; + width?: number; + height?: number; + grid_cols?: number; + grid_rows?: number; +} + +export interface UpdateLayoutData { + name?: string; + type?: string; + width?: number; + height?: number; + grid_cols?: number | null; + grid_rows?: number | null; +} + +export interface CreateElementData { + datapoint_id?: number; + type: string; + x?: number; + y?: number; + width?: number; + height?: number; + font_size?: number; + font_weight?: string; + text_align?: string; + icon?: string; + style?: string; + static_text?: string; + grid_col?: number; + grid_row?: number; + sort_order?: number; +} + +export type UpdateElementData = Partial; + +export function getAllLayouts(): Layout[] { + const db = getDb(); + return db.prepare('SELECT * FROM layouts ORDER BY id ASC').all() as Layout[]; +} + +export function getLayoutById(id: number): LayoutWithElements | undefined { + const db = getDb(); + const layout = db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined; + if (!layout) return undefined; + + const elements = db + .prepare('SELECT * FROM layout_elements WHERE layout_id = ? ORDER BY sort_order ASC, id ASC') + .all(id) as LayoutElement[]; + + return { ...layout, elements }; +} + +export function createLayout(data: CreateLayoutData): Layout { + const db = getDb(); + const result = db.run( + `INSERT INTO layouts (name, type, width, height, grid_cols, grid_rows) + VALUES (?, ?, ?, ?, ?, ?)`, + [ + data.name, + data.type ?? 'free', + data.width ?? 800, + data.height ?? 480, + data.grid_cols ?? null, + data.grid_rows ?? null, + ] + ); + const id = result.lastInsertRowid as number; + return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout; +} + +export function updateLayout(id: number, data: UpdateLayoutData): Layout | undefined { + const db = getDb(); + const fields: string[] = []; + const values: unknown[] = []; + + if (data.name !== undefined) { fields.push('name = ?'); values.push(data.name); } + if (data.type !== undefined) { fields.push('type = ?'); values.push(data.type); } + if (data.width !== undefined) { fields.push('width = ?'); values.push(data.width); } + if (data.height !== undefined) { fields.push('height = ?'); values.push(data.height); } + if (data.grid_cols !== undefined) { fields.push('grid_cols = ?'); values.push(data.grid_cols); } + if (data.grid_rows !== undefined) { fields.push('grid_rows = ?'); values.push(data.grid_rows); } + + if (fields.length === 0) { + return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined; + } + + values.push(id); + db.run(`UPDATE layouts SET ${fields.join(', ')} WHERE id = ?`, values); + return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined; +} + +export function deleteLayout(id: number): boolean { + const db = getDb(); + const result = db.run('DELETE FROM layouts WHERE id = ?', [id]); + return (result.changes as number) > 0; +} + +export function addElement(layoutId: number, data: CreateElementData): LayoutElement { + const db = getDb(); + const result = db.run( + `INSERT INTO layout_elements + (layout_id, datapoint_id, type, x, y, width, height, font_size, font_weight, + text_align, icon, style, static_text, grid_col, grid_row, sort_order) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + layoutId, + data.datapoint_id ?? null, + data.type, + data.x ?? 0, + data.y ?? 0, + data.width ?? null, + data.height ?? null, + data.font_size ?? 24, + data.font_weight ?? 'normal', + data.text_align ?? 'left', + data.icon ?? null, + data.style ?? 'plain', + data.static_text ?? null, + data.grid_col ?? null, + data.grid_row ?? null, + data.sort_order ?? 0, + ] + ); + const id = result.lastInsertRowid as number; + return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement; +} + +export function updateElement(id: number, data: UpdateElementData): LayoutElement | undefined { + const db = getDb(); + const fields: string[] = []; + const values: unknown[] = []; + + const fieldMap: Record = { + datapoint_id: 'datapoint_id', + type: 'type', + x: 'x', + y: 'y', + width: 'width', + height: 'height', + font_size: 'font_size', + font_weight: 'font_weight', + text_align: 'text_align', + icon: 'icon', + style: 'style', + static_text: 'static_text', + grid_col: 'grid_col', + grid_row: 'grid_row', + sort_order: 'sort_order', + }; + + for (const [key, col] of Object.entries(fieldMap)) { + if (Object.prototype.hasOwnProperty.call(data, key)) { + fields.push(`${col} = ?`); + values.push((data as Record)[key]); + } + } + + if (fields.length === 0) { + return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement | undefined; + } + + values.push(id); + db.run(`UPDATE layout_elements SET ${fields.join(', ')} WHERE id = ?`, values); + return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement | undefined; +} + +export function deleteElement(id: number): boolean { + const db = getDb(); + const result = db.run('DELETE FROM layout_elements WHERE id = ?', [id]); + return (result.changes as number) > 0; +}