123 lines
4.4 KiB
JavaScript
123 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Apply editor JSON edits to space-route HTML.
|
||
* Usage: node src/scripts/apply-route-edits.mjs output/html/space-route-edits.json
|
||
*/
|
||
import { readFileSync, writeFileSync } from 'fs';
|
||
import { join, dirname } from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const ROOT = join(__dirname, '..', '..');
|
||
|
||
const editsPath = process.argv[2] || join(ROOT, 'output', 'html', 'space-route-edits.json');
|
||
const edits = JSON.parse(readFileSync(editsPath, 'utf-8'));
|
||
const htmlPath = join(ROOT, 'output', 'html', edits.file);
|
||
let html = readFileSync(htmlPath, 'utf-8');
|
||
|
||
// Split HTML into pages
|
||
const pageRegex = /<div class="w-\[210mm\] h-\[297mm\][^>]*>/g;
|
||
const pageStarts = [];
|
||
let m;
|
||
while ((m = pageRegex.exec(html)) !== null) {
|
||
pageStarts.push(m.index);
|
||
}
|
||
pageStarts.push(html.length);
|
||
|
||
function getPageHtml(pageIndex) {
|
||
return html.slice(pageStarts[pageIndex], pageStarts[pageIndex + 1]);
|
||
}
|
||
|
||
function setPageHtml(pageIndex, newPageHtml) {
|
||
html = html.slice(0, pageStarts[pageIndex]) + newPageHtml + html.slice(pageStarts[pageIndex + 1]);
|
||
// Recalculate starts
|
||
const diff = newPageHtml.length - (pageStarts[pageIndex + 1] - pageStarts[pageIndex]);
|
||
for (let i = pageIndex + 1; i < pageStarts.length; i++) {
|
||
pageStarts[i] += diff;
|
||
}
|
||
}
|
||
|
||
for (const pageEdit of edits.pages) {
|
||
const pi = pageEdit.page - 1;
|
||
let pageHtml = getPageHtml(pi);
|
||
console.log(`Applying page ${pageEdit.page}...`);
|
||
|
||
// Apply object edits
|
||
if (pageEdit.objects) {
|
||
for (const obj of pageEdit.objects) {
|
||
// Find the img with matching data-node-id and data-type on this page
|
||
const nodeId = obj.nodeId;
|
||
const type = obj.type;
|
||
|
||
// Build transform string
|
||
let transform = '';
|
||
if (obj.rotate) transform += `rotate(${obj.rotate}deg)`;
|
||
if (obj.flipH) transform += ` scaleX(-1)`;
|
||
transform = transform.trim();
|
||
const transformAttr = transform ? `transform:${transform};` : '';
|
||
|
||
const w = parseFloat(obj.w);
|
||
const h = parseFloat(obj.h);
|
||
const left = parseFloat(obj.left);
|
||
const top = parseFloat(obj.top);
|
||
|
||
// Match the img element by data-node-id and data-type
|
||
const imgRegex = new RegExp(
|
||
`(<img[^>]*data-node-id="${nodeId}"[^>]*data-type="${type}"[^>]*style=")([^"]*)(")`,
|
||
'g'
|
||
);
|
||
|
||
const newStyle = `left:${left.toFixed(1)}mm;top:${top.toFixed(1)}mm;width:${w}mm;height:${h}mm;margin-left:-${w/2}mm;margin-top:-${h/2}mm;z-index:4;${transformAttr}`;
|
||
|
||
const before = pageHtml;
|
||
pageHtml = pageHtml.replace(imgRegex, `$1${newStyle}$3`);
|
||
if (pageHtml === before) {
|
||
// Try alternate order (data-type before data-node-id)
|
||
const imgRegex2 = new RegExp(
|
||
`(<img[^>]*data-type="${type}"[^>]*data-node-id="${nodeId}"[^>]*style=")([^"]*)(")`,
|
||
'g'
|
||
);
|
||
pageHtml = pageHtml.replace(imgRegex2, `$1${newStyle}$3`);
|
||
}
|
||
console.log(` obj node:${nodeId} type:${type} → ${left.toFixed(1)},${top.toFixed(1)} ${w}×${h}mm${obj.rotate ? ' rot:'+obj.rotate+'°' : ''}${obj.flipH ? ' FLIP' : ''}`);
|
||
}
|
||
}
|
||
|
||
// Apply node edits
|
||
if (pageEdit.nodes) {
|
||
for (const nd of pageEdit.nodes) {
|
||
const nodeId = nd.nodeId;
|
||
const left = parseFloat(nd.left);
|
||
const top = parseFloat(nd.top);
|
||
|
||
// Update node div position
|
||
const nodeRegex = new RegExp(
|
||
`(<div[^>]*data-node-id="${nodeId}"[^>]*style=")(left:[^;]*;top:[^;]*)`,
|
||
'g'
|
||
);
|
||
pageHtml = pageHtml.replace(nodeRegex, `$1left:${left.toFixed(1)}mm;top:${top.toFixed(1)}mm`);
|
||
|
||
// Update all edges connected to this node
|
||
// Edge format: data-edge="A-B" where A or B matches nodeId
|
||
// Update x1,y1 if nodeId is the first, x2,y2 if second
|
||
const edgeRegex1 = new RegExp(
|
||
`(<line data-edge="${nodeId}-\\d+"[^>]*)(x1="[^"]*")(\\s*)(y1="[^"]*")`,
|
||
'g'
|
||
);
|
||
pageHtml = pageHtml.replace(edgeRegex1, `$1x1="${left.toFixed(1)}mm"$3y1="${top.toFixed(1)}mm"`);
|
||
|
||
const edgeRegex2 = new RegExp(
|
||
`(<line data-edge="\\d+-${nodeId}"[^>]*)(x2="[^"]*")(\\s*)(y2="[^"]*")`,
|
||
'g'
|
||
);
|
||
pageHtml = pageHtml.replace(edgeRegex2, `$1x2="${left.toFixed(1)}mm"$3y2="${top.toFixed(1)}mm"`);
|
||
}
|
||
console.log(` ${pageEdit.nodes.length} nodes updated`);
|
||
}
|
||
|
||
setPageHtml(pi, pageHtml);
|
||
}
|
||
|
||
writeFileSync(htmlPath, html, 'utf-8');
|
||
console.log(`\nWritten to ${htmlPath}`);
|