update canvas.html

This commit is contained in:
xsxx03-art
2026-06-04 19:32:28 +08:00
parent 23d631c4f0
commit 866bc1de18
+54 -20
View File
@@ -1622,7 +1622,7 @@ Organization : OptiHK Limited
// Displays a category icon with cached loading and graceful failure behavior.
const IconImg = memo(({ category, containerStyle }) => {
const IconImg = memo(({ category, containerStyle, objectFit: imgObjectFit }) => {
const [src, setSrc] = useState(() => {
if (!category) return undefined;
const cache = fetchIcon(category);
@@ -1671,7 +1671,7 @@ Organization : OptiHK Limited
style={{
width: '100%',
height: '100%',
objectFit: 'fill',
objectFit: imgObjectFit || 'fill',
pointerEvents: 'none',
}}
onError={(e) => {
@@ -1721,7 +1721,7 @@ Organization : OptiHK Limited
};
const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
const portHandles = useMemo(
() => buildPortHandles(data.ports, { rotation: 0, flip: Boolean(data.flip), flop: Boolean(data.flop), boxSize: componentSize }),
() => buildPortHandles(data.ports, { rotation: data.rotation || 0, flip: Boolean(data.flip), flop: Boolean(data.flop), boxSize: componentSize }),
[data.ports, data.rotation, data.flip, data.flop, componentSize]
);
const portDirectionMap = useMemo(
@@ -1735,17 +1735,16 @@ Organization : OptiHK Limited
const iconSize = createComponentSymbolMetrics(componentSize);
const portLabelStyle = (portHandle) => {
const base = { ...portHandle.style };
const unrotate = `rotate(${-(data.rotation || 0)}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`;
if (portHandle.position === 'left') {
return { ...base, left: 'auto', right: 'calc(100% + 8px)', transform: `translateY(-50%) ${unrotate}`, textAlign: 'right' };
return { ...base, left: 'auto', right: 'calc(100% + 8px)', transform: 'translateY(-50%)', textAlign: 'right' };
}
if (portHandle.position === 'right') {
return { ...base, left: 'calc(100% + 8px)', right: 'auto', transform: `translateY(-50%) ${unrotate}`, textAlign: 'left' };
return { ...base, left: 'calc(100% + 8px)', right: 'auto', transform: 'translateY(-50%)', textAlign: 'left' };
}
if (portHandle.position === 'top') {
return { ...base, top: 'auto', bottom: 'calc(100% + 8px)', transform: `translateX(-50%) ${unrotate}`, textAlign: 'center' };
return { ...base, top: 'auto', bottom: 'calc(100% + 8px)', transform: 'translateX(-50%)', textAlign: 'center' };
}
return { ...base, top: 'calc(100% + 8px)', bottom: 'auto', transform: `translateX(-50%) ${unrotate}`, textAlign: 'center' };
return { ...base, top: 'calc(100% + 8px)', bottom: 'auto', transform: 'translateX(-50%)', textAlign: 'center' };
};
return (
@@ -1759,19 +1758,18 @@ Organization : OptiHK Limited
<span title={data.componentName}>{data.componentName}</span>
)}
</div>
<div style={{
position: 'relative',
width: componentSize.width,
transform: componentVisualTransform,
transformOrigin: 'center center',
}}>
<div
className="component-visual-body"
style={{
width: componentSize.width,
height: visualSize.height,
minHeight: visualSize.height,
overflow: 'hidden',
...(visualSize.height < 50 && !isAnchorElement ? { padding: '2px 4px' } : {}),
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)',
boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)',
transform: componentVisualTransform,
transformOrigin: 'center center',
...(isBasicCompactComponent ? {
padding: 0,
display: 'flex',
@@ -1794,8 +1792,8 @@ Organization : OptiHK Limited
) : (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}>
{!data.hideIcon && data.category && (
<div style={{ width: iconSize.width, height: iconSize.height }}>
<IconImg category={data.category} />
<div style={{ maxWidth: iconSize.width, maxHeight: iconSize.height, width: '100%', aspectRatio: `${iconSize.width}/${iconSize.height}`, overflow: 'hidden' }}>
<IconImg category={data.category} objectFit="contain" />
</div>
)}
{!data.category && <div style={{ width: iconSize.width, height: iconSize.height, borderRadius: 4, border: '1px solid var(--border-strong)', background: 'rgba(148, 163, 184, 0.08)' }} />}
@@ -1806,8 +1804,8 @@ Organization : OptiHK Limited
<div style={{
position: 'absolute',
top: 0, left: 0,
width: '100%',
height: '100%',
width: componentSize.width,
height: visualSize.height,
pointerEvents: 'none'
}}>
{portHandles.map((portHandle) => (
@@ -1838,7 +1836,6 @@ Organization : OptiHK Limited
</React.Fragment>
))}
</div>
</div>
);
}, (prevProps, nextProps) => {
return (
@@ -5065,6 +5062,35 @@ Organization : OptiHK Limited
return boxSize ? { ...node, data: { ...node.data, boxSize } } : node;
})
}));
// Pre-fetch PDK component metadata so nodes render with correct boxSize immediately.
const allNodes = cellPages.flatMap(page => page.nodes);
const pdkNames = [...new Set(allNodes
.filter(n => n.data?.componentName && !n.data?.elementType
&& !isForgeComponent(n.data.componentName)
&& !isBasicComponent(n.data.componentName))
.map(n => n.data.componentName))];
if (pdkNames.length > 0) {
const metaResults = await Promise.all(
pdkNames.map(name => loadComponentMetadata(name).catch(() => null))
);
const metaMap = new Map(
pdkNames.filter((_, i) => metaResults[i]).map((name, i) => [name, metaResults[i]])
);
for (const page of cellPages) {
page.nodes = page.nodes.map(node => {
const metadata = metaMap.get(node.data?.componentName);
if (!metadata) return node;
const sz = normalizeBoxSize(metadata);
return {
...node,
position: clampPositionToCanvas(node.position, page.canvasSize || DEFAULT_CANVAS_SIZE, sz),
data: { ...node.data, boxSize: sz, ports: metadata.pins || metadata.ports || {}, foundry: metadata.foundry || '', process: metadata.process || '' }
};
});
}
}
const loadedProjectPage = cellPages.find(page => page.type === 'project' && page.name === currentProjectName);
const nonProjectPages = cellPages.filter(page => page !== loadedProjectPage);
const resolvedProjectPage = loadedProjectPage || projectPage;
@@ -5189,12 +5215,20 @@ Organization : OptiHK Limited
};
})
})));
// Force React Flow to re-measure nodes whose boxSize / ports have changed.
requestAnimationFrame(() => {
const updatedIds = results.filter(r => r.metadata).map(r => r.nodeId);
if (updatedIds.length > 0 && reactFlowInstance.updateNodeInternals) {
reactFlowInstance.updateNodeInternals(updatedIds);
}
});
});
return () => {
cancelled = true;
};
}, [pages, loadComponentMetadata]);
}, [pages, loadComponentMetadata, reactFlowInstance]);
const openTabs = useMemo(() => pages.filter(page => !page.isClosed), [pages]);