feat: layout export/import as JSON files
Export saves layout + elements with datapoint names for re-linking. Import creates new layout and re-links datapoints by name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,113 @@ router.delete('/:id', (req, res): void => {
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Export / Import ----
|
||||
|
||||
// GET /api/layouts/:id/export — export layout with elements as JSON file
|
||||
router.get('/:id/export', (req, res): void => {
|
||||
try {
|
||||
const layout = getLayoutById(parseInt(req.params.id));
|
||||
if (!layout) { res.status(404).json({ error: 'Layout not found' }); return; }
|
||||
|
||||
const exportData = {
|
||||
_format: 'epaper-layout-v1',
|
||||
name: layout.name,
|
||||
type: layout.type,
|
||||
width: layout.width,
|
||||
height: layout.height,
|
||||
grid_cols: layout.grid_cols,
|
||||
grid_rows: layout.grid_rows,
|
||||
elements: layout.elements.map((el: any) => ({
|
||||
type: el.type,
|
||||
x: el.x,
|
||||
y: el.y,
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
font_size: el.font_size,
|
||||
font_weight: el.font_weight,
|
||||
text_align: el.text_align,
|
||||
icon: el.icon,
|
||||
style: el.style,
|
||||
static_text: el.static_text,
|
||||
grid_col: el.grid_col,
|
||||
grid_row: el.grid_row,
|
||||
sort_order: el.sort_order,
|
||||
// Store datapoint name for re-linking on import
|
||||
datapoint_name: el.datapoint_id
|
||||
? (require('../../modules/datapoints/datapoint-manager').getDatapointById(el.datapoint_id)?.name || null)
|
||||
: null,
|
||||
})),
|
||||
};
|
||||
|
||||
const filename = `layout-${layout.name.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`;
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.json(exportData);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/layouts/import — import layout from JSON
|
||||
router.post('/import', (req, res): void => {
|
||||
try {
|
||||
const data = req.body;
|
||||
if (!data._format || !data._format.startsWith('epaper-layout')) {
|
||||
res.status(400).json({ error: 'Ungueltiges Layout-Format. Erwartet: epaper-layout-v1' });
|
||||
return;
|
||||
}
|
||||
if (!data.name || !data.elements) {
|
||||
res.status(400).json({ error: 'name und elements sind erforderlich' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Create layout
|
||||
const layout = createLayout({
|
||||
name: data.name + ' (Import)',
|
||||
type: data.type || 'free',
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
grid_cols: data.grid_cols,
|
||||
grid_rows: data.grid_rows,
|
||||
});
|
||||
|
||||
const { getDatapointByName } = require('../../modules/datapoints/datapoint-manager');
|
||||
|
||||
// Add elements
|
||||
for (const el of data.elements) {
|
||||
// Try to re-link datapoint by name
|
||||
let datapointId = null;
|
||||
if (el.datapoint_name) {
|
||||
const dp = getDatapointByName ? getDatapointByName(el.datapoint_name) : null;
|
||||
if (dp) datapointId = dp.id;
|
||||
}
|
||||
|
||||
addElement(layout.id, {
|
||||
type: el.type || 'label',
|
||||
x: el.x || 0,
|
||||
y: el.y || 0,
|
||||
width: el.width || null,
|
||||
height: el.height || null,
|
||||
font_size: el.font_size || 24,
|
||||
font_weight: el.font_weight || 'normal',
|
||||
text_align: el.text_align || 'left',
|
||||
icon: el.icon || null,
|
||||
style: el.style || 'plain',
|
||||
static_text: el.static_text || null,
|
||||
grid_col: el.grid_col || null,
|
||||
grid_row: el.grid_row || null,
|
||||
sort_order: el.sort_order || 0,
|
||||
datapoint_id: datapointId,
|
||||
});
|
||||
}
|
||||
|
||||
const result = getLayoutById(layout.id);
|
||||
res.status(201).json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Elements ----
|
||||
|
||||
// POST /api/layouts/:layoutId/elements
|
||||
|
||||
@@ -118,6 +118,11 @@ export function createDatapointFromPg(
|
||||
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint;
|
||||
}
|
||||
|
||||
export function getDatapointByName(name: string): Datapoint | undefined {
|
||||
const db = getDb();
|
||||
return db.prepare('SELECT * FROM datapoints WHERE name = ?').get(name) as Datapoint | undefined;
|
||||
}
|
||||
|
||||
export function deleteDatapoint(id: number): boolean {
|
||||
const db = getDb();
|
||||
const result = db.prepare('DELETE FROM datapoints WHERE id = ?').run(id);
|
||||
|
||||
Reference in New Issue
Block a user