P4-02 P6-02: add metadata-driven property editor

This commit is contained in:
2026-08-02 07:53:35 -04:00
parent 8d8d1b7c26
commit 23b7f58df5
10 changed files with 250 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
import sqlite3InitModule, { type Database, type Sqlite3Static } from '@sqlite.org/sqlite-wasm'
import { PROJECT_SCHEMA_MIGRATIONS, PROJECT_SCHEMA_VERSION } from './projectSchema'
import type { DocumentSnapshot, ModelTreeItem, PersistenceCapabilities, ProjectResource } from './types'
import type { DocumentObjectSnapshot, DocumentSnapshot, ModelTreeItem, ObjectPropertySnapshot, PersistenceCapabilities, ProjectResource } from './types'
type PersistenceRequest =
| { id: number; type: 'initialize' }
@@ -87,6 +87,7 @@ const saveDocument = (document: DocumentSnapshot) => {
const parentByChild = new Map<string, string>()
for (const item of document.tree) for (const childId of item.children || []) parentByChild.set(childId, item.id)
document.tree.forEach((item, ordinal) => database?.exec({ sql: 'INSERT INTO objects(id, document_id, parent_id, label, object_type, state, detail, children_json, ordinal) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)', bind: [item.id, document.id, parentByChild.get(item.id) || null, item.label, item.type, item.state || null, item.detail || null, JSON.stringify(item.children || []), ordinal] }))
for (const object of document.objects) for (const property of object.properties) database.exec({ sql: 'INSERT INTO object_properties(document_id, object_id, name, value_json, property_type, updated_at) VALUES(?, ?, ?, ?, ?, ?)', bind: [document.id, object.id, property.name, JSON.stringify(property), property.type, now] })
database.exec('COMMIT;')
} catch (error) {
database.exec('ROLLBACK;')
@@ -101,6 +102,19 @@ const loadDocument = (documentId: string): DocumentSnapshot | null => {
const row = documents[0]
if (!row) return null
const objects = database.exec({ sql: 'SELECT id, label, object_type, state, detail, children_json FROM objects WHERE document_id = ? ORDER BY ordinal', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number | null>>
const propertyRows = database.exec({ sql: 'SELECT object_id, value_json FROM object_properties WHERE document_id = ? ORDER BY object_id, name', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
const propertiesByObject = new Map<string, ObjectPropertySnapshot[]>()
for (const propertyRow of propertyRows) {
const properties = propertiesByObject.get(String(propertyRow.object_id)) ?? []
properties.push(JSON.parse(String(propertyRow.value_json)) as ObjectPropertySnapshot)
propertiesByObject.set(String(propertyRow.object_id), properties)
}
const tree: ModelTreeItem[] = objects.map((object) => ({ id: String(object.id), label: String(object.label), type: String(object.object_type) as ModelTreeItem['type'], state: object.state ? String(object.state) as ModelTreeItem['state'] : undefined, detail: object.detail ? String(object.detail) : undefined, children: object.children_json ? JSON.parse(String(object.children_json)) as string[] : undefined }))
const objectSnapshots: DocumentObjectSnapshot[] = tree.map((item) => {
const properties = propertiesByObject.get(item.id) ?? []
const typeId = properties.find((property) => property.name === 'TypeId')?.value
return { id: item.id, typeId: typeof typeId === 'string' ? typeId : item.type, properties }
})
return {
id: String(row.id),
label: String(row.label),
@@ -108,7 +122,8 @@ const loadDocument = (documentId: string): DocumentSnapshot | null => {
dirty: Boolean(row.dirty),
readOnly: Boolean(row.read_only),
units: String(row.units),
tree: objects.map((object) => ({ id: String(object.id), label: String(object.label), type: String(object.object_type) as ModelTreeItem['type'], state: object.state ? String(object.state) as ModelTreeItem['state'] : undefined, detail: object.detail ? String(object.detail) : undefined, children: object.children_json ? JSON.parse(String(object.children_json)) as string[] : undefined })),
tree,
objects: objectSnapshots,
}
}