Files
Void-Homelab/lib/api/routes/dashboard.js
root 600057582e feat(sacred-valley): hybrid free/snap canvas + blank & blackflame cards (2.8.0)
Replace masonry grid with an absolute-positioned 12-col canvas: drag to
move, corner to resize, per-card free/overlap toggle (Alt = no-snap).
Geometry persisted (migration 027: dashboard_layout.geom + extras).
Two new addable decorative cards: blank spacer + animated blackflame
(canvas particle flame). Old layout auto-migrates by flow-placement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:33:45 +10:00

40 lines
1.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Router } from 'express';
import { z } from 'zod';
import { validate } from '../validate.js';
import { asyncWrap } from '../errors.js';
import { requireOwner } from '../cap.js';
import * as repo from '../../db/repos/dashboard_layout.js';
export const router = Router();
router.use(requireOwner);
const geomCell = z.object({
x: z.number(), y: z.number(),
w: z.number().positive(), h: z.number().positive(),
free: z.boolean().optional()
});
const layoutSchema = z.object({
card_order: z.array(z.string()).default([]),
hidden: z.array(z.string()).default([]),
// Per-card width: an integer column span 112 (legacy 's'|'m'|'l' still accepted).
sizes: z.record(z.union([z.number().int().min(1).max(12), z.enum(['s', 'm', 'l'])])).default({}),
// Hybrid-canvas geometry, keyed by card id → {x,y,w,h,free} in 12-col grid units.
geom: z.record(geomCell).default({}),
// User-added decorative card instances that must survive reloads.
extras: z.array(z.object({
id: z.string().min(1).max(64),
type: z.enum(['blank', 'blackflame'])
})).default([])
});
router.get('/layout', asyncWrap(async (_req, res) => {
res.json(await repo.get());
}));
router.put('/layout',
validate({ body: layoutSchema }),
asyncWrap(async (req, res) => {
res.json(await repo.put(req.body));
})
);