完善端口标号与MECMAS21动态图标
This commit is contained in:
1 parent
df1d676131
commit
09778972a6
7 files changed
+780
-60
No files matched your search
+227
-2
@@ -456,6 +456,13 @@ const NODE_SYMBOL_HEIGHT = 84;
|
||||
const SYMBOL_VIEWBOX_WIDTH = 64;
|
||||
const SYMBOL_VIEWBOX_HEIGHT = 48;
|
||||
const SYMBOL_ENVELOPE_PADDING_PX = 2;
|
||||
const PORT_INDEX_OUTSET_PX = 18;
|
||||
const PORT_INDEX_TANGENT_OFFSET_PX = 12;
|
||||
const PORT_INDEX_LABEL_HEIGHT_PX = 20;
|
||||
const PORT_INDEX_LABEL_MIN_WIDTH_PX = 20;
|
||||
const PORT_INDEX_LABEL_CHARACTER_WIDTH_PX = 10;
|
||||
const PORT_INDEX_LABEL_GAP_PX = 1;
|
||||
const PORT_INDEX_LABEL_LANE_GAP_PX = 2;
|
||||
const CONTACT_SNAP_RADIUS_PX = 12;
|
||||
const CONTACT_DISCONNECT_DISTANCE_PX = 8;
|
||||
const CONTACT_GEOMETRY_TOLERANCE = 0.5;
|
||||
@@ -1585,6 +1592,13 @@ function SimulationComponentNode({ id, data, selected }: NodeProps<SimulationNod
|
||||
mirrored,
|
||||
portAnchors,
|
||||
);
|
||||
const portIndexLabels = selected
|
||||
? portIndexLabelPlacements(data.ports, portPlacements, rotation)
|
||||
: [];
|
||||
const symbolVariantKey =
|
||||
symbol === "amesim_mecmas21"
|
||||
? `${data.parameters.useFriction ?? ""}:${data.parameters.stoptype ?? ""}`
|
||||
: "";
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!hasDedicatedSymbol) {
|
||||
@@ -1605,7 +1619,7 @@ function SimulationComponentNode({ id, data, selected }: NodeProps<SimulationNod
|
||||
? currentEnvelope
|
||||
: nextEnvelope,
|
||||
);
|
||||
}, [hasDedicatedSymbol, symbol]);
|
||||
}, [hasDedicatedSymbol, symbol, symbolVariantKey]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(id);
|
||||
@@ -1669,10 +1683,26 @@ function SimulationComponentNode({ id, data, selected }: NodeProps<SimulationNod
|
||||
height: symbolEnvelope.height,
|
||||
}}
|
||||
/>
|
||||
<ComponentSymbol symbol={symbol} surface="canvas" />
|
||||
<ComponentSymbol
|
||||
parameters={data.parameters}
|
||||
symbol={symbol}
|
||||
surface="canvas"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{portIndexLabels.map((label) => (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="port-index-label"
|
||||
data-port-index={label.text}
|
||||
data-port-name={label.portName}
|
||||
key={label.portName}
|
||||
style={{ left: label.x, top: label.y }}
|
||||
>
|
||||
{label.text}
|
||||
</span>
|
||||
))}
|
||||
<div className="node-label">{data.label}</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1760,6 +1790,199 @@ function transformedPortPlacements(
|
||||
});
|
||||
}
|
||||
|
||||
type TransformedPortPlacement = ReturnType<
|
||||
typeof transformedPortPlacements
|
||||
>[number];
|
||||
|
||||
type PortIndexLabelPlacement = {
|
||||
order: number;
|
||||
portName: string;
|
||||
position: Position;
|
||||
text: string;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
function portIndexText(port: PortDefinition, index: number) {
|
||||
const numericSuffix = port.name.match(/^port_([1-9]\d*)$/)?.[1];
|
||||
if (numericSuffix !== undefined) {
|
||||
const parsed = Number(numericSuffix);
|
||||
if (Number.isSafeInteger(parsed)) {
|
||||
return String(parsed);
|
||||
}
|
||||
}
|
||||
return String(index + 1);
|
||||
}
|
||||
|
||||
function portIndexLabelWidth(text: string) {
|
||||
return Math.max(
|
||||
PORT_INDEX_LABEL_MIN_WIDTH_PX,
|
||||
text.length * PORT_INDEX_LABEL_CHARACTER_WIDTH_PX + 4,
|
||||
);
|
||||
}
|
||||
|
||||
function packPortIndexLabelLane(
|
||||
labels: PortIndexLabelPlacement[],
|
||||
position: Position,
|
||||
axisLength: number,
|
||||
) {
|
||||
const verticalEdge = position === Position.Left || position === Position.Right;
|
||||
const coordinate = (label: PortIndexLabelPlacement) =>
|
||||
verticalEdge ? label.y : label.x;
|
||||
const extent = (label: PortIndexLabelPlacement) =>
|
||||
verticalEdge ? PORT_INDEX_LABEL_HEIGHT_PX : portIndexLabelWidth(label.text);
|
||||
const sorted = [...labels].sort(
|
||||
(first, second) =>
|
||||
coordinate(first) - coordinate(second) || first.order - second.order,
|
||||
);
|
||||
if (sorted.length < 2) {
|
||||
return sorted;
|
||||
}
|
||||
|
||||
const packedCoordinates: number[] = [];
|
||||
for (const [index, label] of sorted.entries()) {
|
||||
const target = coordinate(label);
|
||||
if (index === 0) {
|
||||
packedCoordinates.push(target);
|
||||
continue;
|
||||
}
|
||||
const previous = sorted[index - 1];
|
||||
const minimumCoordinate =
|
||||
packedCoordinates[index - 1] +
|
||||
extent(previous) / 2 +
|
||||
extent(label) / 2 +
|
||||
PORT_INDEX_LABEL_GAP_PX;
|
||||
packedCoordinates.push(Math.max(target, minimumCoordinate));
|
||||
}
|
||||
|
||||
const idealShift =
|
||||
sorted.reduce(
|
||||
(sum, label, index) => sum + coordinate(label) - packedCoordinates[index],
|
||||
0,
|
||||
) / sorted.length;
|
||||
const minimumShift = extent(sorted[0]) / 2 - packedCoordinates[0];
|
||||
const maximumShift =
|
||||
axisLength -
|
||||
extent(sorted[sorted.length - 1]) / 2 -
|
||||
packedCoordinates[packedCoordinates.length - 1];
|
||||
const shift =
|
||||
minimumShift <= maximumShift
|
||||
? Math.min(Math.max(idealShift, minimumShift), maximumShift)
|
||||
: idealShift;
|
||||
|
||||
return sorted.map((label, index) => {
|
||||
const adjustedCoordinate = packedCoordinates[index] + shift;
|
||||
return verticalEdge
|
||||
? { ...label, y: adjustedCoordinate }
|
||||
: { ...label, x: adjustedCoordinate };
|
||||
});
|
||||
}
|
||||
|
||||
function offsetPortIndexLabelOutward(
|
||||
label: PortIndexLabelPlacement,
|
||||
distance: number,
|
||||
) {
|
||||
if (label.position === Position.Left) {
|
||||
return { ...label, x: label.x - distance };
|
||||
}
|
||||
if (label.position === Position.Right) {
|
||||
return { ...label, x: label.x + distance };
|
||||
}
|
||||
if (label.position === Position.Top) {
|
||||
return { ...label, y: label.y - distance };
|
||||
}
|
||||
return { ...label, y: label.y + distance };
|
||||
}
|
||||
|
||||
function spreadPortIndexLabels(
|
||||
labels: PortIndexLabelPlacement[],
|
||||
position: Position,
|
||||
axisLength: number,
|
||||
) {
|
||||
if (labels.length === 0) {
|
||||
return labels;
|
||||
}
|
||||
const verticalEdge = position === Position.Left || position === Position.Right;
|
||||
const coordinate = (label: PortIndexLabelPlacement) =>
|
||||
verticalEdge ? label.y : label.x;
|
||||
const extent = (label: PortIndexLabelPlacement) =>
|
||||
verticalEdge ? PORT_INDEX_LABEL_HEIGHT_PX : portIndexLabelWidth(label.text);
|
||||
const normalExtent = (label: PortIndexLabelPlacement) =>
|
||||
verticalEdge ? portIndexLabelWidth(label.text) : PORT_INDEX_LABEL_HEIGHT_PX;
|
||||
const sorted = [...labels].sort(
|
||||
(first, second) =>
|
||||
coordinate(first) - coordinate(second) || first.order - second.order,
|
||||
);
|
||||
const requiredSpan =
|
||||
sorted.reduce((sum, label) => sum + extent(label), 0) +
|
||||
PORT_INDEX_LABEL_GAP_PX * Math.max(0, sorted.length - 1);
|
||||
const laneCount = Math.min(
|
||||
sorted.length,
|
||||
Math.max(1, Math.ceil(requiredSpan / axisLength)),
|
||||
);
|
||||
const lanes = Array.from(
|
||||
{ length: laneCount },
|
||||
() => [] as PortIndexLabelPlacement[],
|
||||
);
|
||||
sorted.forEach((label, index) => lanes[index % laneCount].push(label));
|
||||
const laneStep =
|
||||
Math.max(...sorted.map(normalExtent)) + PORT_INDEX_LABEL_LANE_GAP_PX;
|
||||
|
||||
return lanes.flatMap((lane, laneIndex) =>
|
||||
packPortIndexLabelLane(lane, position, axisLength).map((label) =>
|
||||
offsetPortIndexLabelOutward(
|
||||
label,
|
||||
PORT_INDEX_OUTSET_PX + laneIndex * laneStep,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function portIndexLabelPlacements(
|
||||
ports: PortDefinition[],
|
||||
placements: TransformedPortPlacement[],
|
||||
rotation: NodeRotation,
|
||||
) {
|
||||
const indexByPortName = new Map(
|
||||
ports.map((port, index) => [port.name, index] as const),
|
||||
);
|
||||
const labels = placements.map(({ port, position, x, y }, order) => {
|
||||
const index = indexByPortName.get(port.name) ?? order;
|
||||
const label: PortIndexLabelPlacement = {
|
||||
order,
|
||||
portName: port.name,
|
||||
position,
|
||||
text: portIndexText(port, index),
|
||||
x,
|
||||
y,
|
||||
};
|
||||
if (position === Position.Left || position === Position.Right) {
|
||||
label.y -= PORT_INDEX_TANGENT_OFFSET_PX;
|
||||
} else {
|
||||
label.x += PORT_INDEX_TANGENT_OFFSET_PX;
|
||||
}
|
||||
return label;
|
||||
});
|
||||
const renderedSize = nodeFrameDimensions(rotation);
|
||||
const spreadByOrder = new Map<number, PortIndexLabelPlacement>();
|
||||
for (const position of [
|
||||
Position.Left,
|
||||
Position.Right,
|
||||
Position.Top,
|
||||
Position.Bottom,
|
||||
]) {
|
||||
const group = labels.filter((label) => label.position === position);
|
||||
const axisLength =
|
||||
position === Position.Left || position === Position.Right
|
||||
? renderedSize.height
|
||||
: renderedSize.width;
|
||||
for (const label of spreadPortIndexLabels(group, position, axisLength)) {
|
||||
spreadByOrder.set(label.order, label);
|
||||
}
|
||||
}
|
||||
return labels.map((label) => spreadByOrder.get(label.order) ?? label);
|
||||
}
|
||||
|
||||
function rotatePoint(x: number, y: number, rotation: NodeRotation): [number, number] {
|
||||
if (rotation === 90) {
|
||||
return [-y, x];
|
||||
@@ -5749,6 +5972,7 @@ function FlowWorkbench() {
|
||||
>
|
||||
<span className="palette-icon">
|
||||
<ComponentSymbol
|
||||
parameters={defaultParameters(definition)}
|
||||
surface="palette"
|
||||
symbol={definition.symbol}
|
||||
/>
|
||||
@@ -6038,6 +6262,7 @@ function FlowWorkbench() {
|
||||
>
|
||||
<span className="palette-icon-preview-symbol">
|
||||
<ComponentSymbol
|
||||
parameters={defaultParameters(paletteIconPreview.definition)}
|
||||
surface="palette"
|
||||
symbol={paletteIconPreview.definition.symbol}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user