41 lines
3.1 KiB
JavaScript
41 lines
3.1 KiB
JavaScript
// Recover inert geometric SVG attributes already retained by REA; no target access.
|
|
import fs from 'node:fs/promises'
|
|
import crypto from 'node:crypto'
|
|
const reference = JSON.parse(await fs.readFile('.tmp/rea-visual-reference/layout.json', 'utf8'))
|
|
const evidence = JSON.parse(await fs.readFile(reference.archive, 'utf8'))
|
|
if (evidence.evidence_id !== reference.evidenceId || JSON.stringify(evidence.normalized_result.visuals.layout) !== JSON.stringify(reference.layout)) throw new Error('Visual export does not match archived REA Evidence')
|
|
const document = reference.layout.documents[0]
|
|
const nodes = new Map([...document.nodes, ...document.svg_nodes].map(n => [n.node_index, n]))
|
|
function ancestor(node, matcher) {
|
|
for (let p = node.parent_index; nodes.has(p); p = nodes.get(p).parent_index) if (matcher(nodes.get(p).attributes?.class ?? '')) return true
|
|
return false
|
|
}
|
|
const svg = document.svg_nodes.filter(n => n.node_name.toLowerCase() === 'svg')
|
|
const specs = [
|
|
['RuntimeSendIcon', svg.filter(n => ancestor(n, c => c.split(' ').includes('cr-send-button__icon')))],
|
|
['RuntimeMicrophoneIcon', svg.filter(n => ancestor(n, c => c.split(' ').includes('cr-voice-trigger')))],
|
|
['RuntimePermissionIcon', svg.filter(n => ancestor(n, c => c.split(' ').includes('cr-permission-setting')) && ancestor(n, c => c.split(' ').includes('cr-input-footer-item__icon')))],
|
|
['RuntimeRegenerateIcon', svg.filter(n => ancestor(n, c => c.includes('_actionButton_')) && n.attributes.viewBox === '0 0 14 14')],
|
|
]
|
|
function subtree(node) {
|
|
return { ...node, children: document.svg_nodes.filter(child => child.parent_index === node.node_index).map(subtree) }
|
|
}
|
|
function jsx(node, root = false) {
|
|
const tag = node.node_name.toLowerCase() === 'clippath' ? 'clipPath' : node.node_name
|
|
const attrs = Object.entries(node.attributes).filter(([key]) => key !== 'class').map(([key, value]) => `${key.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}={${JSON.stringify(value)}}`).join(' ')
|
|
return `<${tag} ${attrs}${root ? ' aria-hidden="true" focusable="false" {...props}' : ''}>${node.children.map(child => jsx(child)).join('')}</${tag}>`
|
|
}
|
|
let output = `// Generated from REA visual Evidence ${reference.evidenceId}; SVG geometry only.\nimport type { SVGProps } from 'react'\n`
|
|
const records = []
|
|
for (const [name, matches] of specs) {
|
|
if (matches.length !== 1) throw new Error(`${name}: expected one captured SVG, found ${matches.length}`)
|
|
const tree = subtree(matches[0])
|
|
const hash = crypto.createHash('sha256').update(JSON.stringify(tree)).digest('hex')
|
|
output += `\n// Captured node ${tree.node_index}; geometry SHA-256 ${hash}.\nexport function ${name}(props: SVGProps<SVGSVGElement>) { return (${jsx(tree, true)}) }\n`
|
|
records.push({ name, nodeIndex: tree.node_index, sha256: hash })
|
|
}
|
|
const destination = 'src/renderer/src/workbuddy/message-actions/RuntimeIcons.tsx'
|
|
await fs.writeFile(destination, output)
|
|
await fs.writeFile('.tmp/rea-visual-reference/icon-projection.json', JSON.stringify({ evidenceId: reference.evidenceId, destination, records }, null, 2) + '\n')
|
|
console.log(JSON.stringify({ evidenceId: reference.evidenceId, destination, icons: records.length }))
|