feat: resolve topology reference candidates
This commit is contained in:
21
src/App.tsx
21
src/App.tsx
@@ -452,7 +452,16 @@ function PropertyEditor({ facade, objectId, property, showNotice }: { facade: Bi
|
||||
editor = <select className="property-control property-link-control" value={String(property.value ?? '')} aria-label={property.label} onChange={(event) => commit(event.target.value || null)}><option value="">None</option>{ids.filter((id) => id !== objectId).map((id) => <option value={id} key={id}>{document.tree.find((item) => item.id === id)?.label ?? id}</option>)}</select>
|
||||
} else if (property.type === 'App::PropertyLinkSub') {
|
||||
const ref = property.value && typeof property.value === 'object' && !Array.isArray(property.value) && 'schemaVersion' in property.value ? property.value : null
|
||||
editor = ref ? <div className="property-link-sub"><span title={ref.persistentId}>{ref.objectId} / {ref.kind} · {ref.status}</span><IconButton icon={X} label="Clear subshape reference" onClick={() => commit(null)} /></div> : <span className="property-readonly">None</span>
|
||||
const source = ref ? facade.app.document.getObject(ref.objectId) : null
|
||||
const candidates = ref ? (ref.candidates?.length ? ref.candidates : source?.topology?.entries.filter((entry) => entry.ref.kind === ref.kind && entry.ref.status !== 'deleted').map((entry) => entry.ref.persistentId) ?? []) : []
|
||||
const replace = (candidatePersistentId: string) => {
|
||||
if (!candidatePersistentId) return
|
||||
try {
|
||||
facade.app.document.resolveTopologyReference({ ownerObjectId: objectId, referenceName: property.name, candidatePersistentId })
|
||||
void facade.app.document.recomputeAsync().then((result) => showNotice(result.status === 'completed' ? `${property.label} reference replaced` : `Reference replaced; recompute ${result.status}`))
|
||||
} catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
|
||||
}
|
||||
editor = ref ? <div className="property-link-sub"><span title={ref.persistentId}>{ref.objectId} / {ref.kind} · {ref.status}</span>{(ref.status === 'ambiguous' || ref.status === 'deleted') && candidates.length > 0 ? <select className="property-control property-topology-candidates" aria-label={`${property.label} replacement`} defaultValue="" onChange={(event) => replace(event.target.value)}><option value="">Replace</option>{candidates.map((candidate) => <option value={candidate} key={candidate}>{candidate}</option>)}</select> : null}<IconButton icon={X} label="Clear subshape reference" onClick={() => commit(null)} /></div> : <span className="property-readonly">None</span>
|
||||
} else if (property.type === 'App::PropertyPlacement') {
|
||||
const placement = property.value as PlacementValue
|
||||
const updatePlacement = (path: 'position' | 'axis' | 'angle', key: 'x' | 'y' | 'z' | null, next: number) => {
|
||||
@@ -670,12 +679,22 @@ function DiagnosticsPage({ onNavigate, showNotice, facade }: { onNavigate: (page
|
||||
|
||||
function DocumentDiagnosticNode({ node, facade, showNotice, child = false }: { node: DiagnosticTreeNode; facade: BitBybitWebCadFacade; showNotice: (message: string) => void; child?: boolean }) {
|
||||
const diagnostic = node.diagnostic
|
||||
const topologyRepair = diagnostic.topologyRepair
|
||||
const [topologyCandidate, setTopologyCandidate] = useState(topologyRepair?.candidates[0] ?? '')
|
||||
const runRepair = (actionId: 'select-object' | 'recompute-root' | 'suppress-root') => {
|
||||
void facade.diagnostics.repair(diagnostic.id, actionId).then((result) => showNotice(result.message))
|
||||
}
|
||||
const replaceTopologyReference = () => {
|
||||
if (!topologyRepair || !topologyCandidate) return
|
||||
try {
|
||||
facade.app.document.resolveTopologyReference({ ...topologyRepair, candidatePersistentId: topologyCandidate })
|
||||
void facade.app.document.recomputeAsync().then((result) => showNotice(result.status === 'completed' ? 'Topology reference replaced' : `Reference replaced; recompute ${result.status}`))
|
||||
} catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
|
||||
}
|
||||
return <div className={`document-diagnostic ${child ? 'is-child' : ''}`}>
|
||||
<div className="document-diagnostic-main"><span className={`diagnostic-severity ${diagnostic.severity}`}><AlertTriangle size={14} /></span><div><strong>{diagnostic.code}</strong><span>{diagnostic.message}</span><small>{diagnostic.objectId || diagnostic.source}{diagnostic.dependencyPath && diagnostic.dependencyPath.length > 1 ? ` · ${diagnostic.dependencyPath.join(' → ')}` : ''}{diagnostic.generation ? ` · generation ${diagnostic.generation}` : ''}</small></div></div>
|
||||
{diagnostic.repairActions?.length ? <div className="diagnostic-actions">{diagnostic.repairActions.map((action) => <button key={action.id} className="button button-quiet" disabled={!action.enabled} title={action.reason || action.label} onClick={() => runRepair(action.id)}>{action.id === 'select-object' ? <Search size={13} /> : action.id === 'recompute-root' ? <RefreshCw size={13} /> : <Pause size={13} />}{action.label}</button>)}</div> : null}
|
||||
{topologyRepair ? <div className="topology-repair"><select className="property-control" aria-label="Replacement subshape" value={topologyCandidate} onChange={(event) => setTopologyCandidate(event.target.value)}>{topologyRepair.candidates.map((candidate) => <option value={candidate} key={candidate}>{candidate}</option>)}</select><button className="button button-quiet" onClick={replaceTopologyReference} disabled={!topologyCandidate}><Check size={13} />Replace reference</button></div> : null}
|
||||
{node.children.length > 0 ? <div className="diagnostic-children">{node.children.map((entry) => <DocumentDiagnosticNode key={entry.diagnostic.id} node={entry} facade={facade} showNotice={showNotice} child />)}</div> : null}
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user