优化参数表样式与科学计数法支持

This commit is contained in:
ljz committed 2026-08-03 23:51:27 +08:00
1 parent 046aa49814
commit 32f21f08ff
6 files changed
+547 -64

No files matched your search

+145 -26
View File
@@ -100,6 +100,7 @@ import {
import {
evaluateParameterExpression,
isParameterExpressionValue,
isScientificNotationValue,
} from "./parameterExpression";
import {
WorkspaceViewTabs,
@@ -108,6 +109,10 @@ import {
type ParameterValue = number | string;
type UnitQuantity = string;
type ParameterScientificNotation = {
text: string;
unit: string;
};
type PortKind = "physical" | "signal";
type PortNominalRole = "inlet" | "outlet" | "bidirectional" | "input" | "output";
type PortSide = "left" | "right";
@@ -255,6 +260,7 @@ type SimulationNodeData = {
ports: PortDefinition[];
parameters: Record<string, ParameterValue>;
parameterUnits: Record<string, string>;
parameterScientificNotation: Record<string, ParameterScientificNotation>;
rotation: NodeRotation;
mirrored: boolean;
portConnectionStates?: Record<string, PortConnectionState>;
@@ -288,8 +294,12 @@ type ProjectNodePayload = {
x: number;
y: number;
};
data: Omit<SimulationNodeData, "parameterUnits"> & {
data: Omit<
SimulationNodeData,
"parameterUnits" | "parameterScientificNotation"
> & {
parameterUnits?: Record<string, string>;
parameterScientificNotation?: Record<string, ParameterScientificNotation>;
};
};
@@ -1199,6 +1209,7 @@ function buildNodeData(definition: ComponentDefinition): SimulationNodeData {
ports: definition.ports.map((port) => ({ ...port })),
parameters: defaultParameters(definition),
parameterUnits: defaultParameterUnits(definition),
parameterScientificNotation: {},
rotation: 0,
mirrored: false,
};
@@ -2709,6 +2720,7 @@ function DeferredNumberInput({
<input
aria-invalid={invalid}
aria-label={ariaLabel}
className="parameter-number-input"
inputMode="text"
onBlur={() => {
editingRef.current = false;
@@ -3971,34 +3983,62 @@ function FlowWorkbench() {
const unit = findUnitOption(definition, selectedUnit);
const normalizedDisplayValue = displayValue.trim();
const numericValue = Number(normalizedDisplayValue);
const baseValue =
normalizedDisplayValue === "" || !Number.isFinite(numericValue)
? normalizedDisplayValue
: unit.toBase(numericValue);
const hasFiniteNumericValue =
normalizedDisplayValue !== "" && Number.isFinite(numericValue);
const convertedBaseValue = hasFiniteNumericValue
? unit.toBase(numericValue)
: numericValue;
const baseValue = hasFiniteNumericValue
? convertedBaseValue
: normalizedDisplayValue;
const scientificNotation =
hasFiniteNumericValue &&
Number.isFinite(convertedBaseValue) &&
isScientificNotationValue(normalizedDisplayValue)
? { text: normalizedDisplayValue, unit: selectedUnit }
: null;
updateSelectedNode((node) => ({
...node,
data: {
...node.data,
parameters: {
...node.data.parameters,
[key]: baseValue,
updateSelectedNode((node) => {
const parameterScientificNotation = {
...node.data.parameterScientificNotation,
};
if (scientificNotation) {
parameterScientificNotation[key] = scientificNotation;
} else {
delete parameterScientificNotation[key];
}
return {
...node,
data: {
...node.data,
parameters: {
...node.data.parameters,
[key]: baseValue,
},
parameterScientificNotation,
},
},
}));
};
});
};
const updateSelectedChoice = (key: string, value: number) => {
updateSelectedNode((node) => ({
...node,
data: {
...node.data,
parameters: {
...node.data.parameters,
[key]: value,
updateSelectedNode((node) => {
const parameterScientificNotation = {
...node.data.parameterScientificNotation,
};
delete parameterScientificNotation[key];
return {
...node,
data: {
...node.data,
parameters: {
...node.data.parameters,
[key]: value,
},
parameterScientificNotation,
},
},
}));
};
});
};
const updateSelectedParameterUnit = (key: string, unit: string) => {
@@ -4181,7 +4221,9 @@ function FlowWorkbench() {
key === "method"
? value
: normalizedValue !== "" && Number.isFinite(numericValue)
? numericValue
? isScientificNotationValue(normalizedValue)
? normalizedValue
: numericValue
: normalizedValue,
}));
};
@@ -4961,6 +5003,7 @@ function FlowWorkbench() {
baseValue,
definition,
selectedUnit,
selectedNode.data.parameterScientificNotation[key],
);
const mediumDefinitionIndex =
isAmesimGasMediumDefinition(selectedDefinition) && key === "gi";
@@ -5249,7 +5292,7 @@ function FlowWorkbench() {
];
const parameterTableAvailableWidth = Math.max(
0,
modelingPaneLayout.propertiesWidth - 24,
modelingPaneLayout.propertiesWidth,
);
return (
@@ -5900,6 +5943,9 @@ function buildProjectPayload(
ports: node.data.ports.map((port) => ({ ...port })),
parameters: { ...node.data.parameters },
parameterUnits: { ...node.data.parameterUnits },
parameterScientificNotation: {
...node.data.parameterScientificNotation,
},
rotation: normalizeNodeRotation(node.data.rotation),
mirrored: Boolean(node.data.mirrored),
},
@@ -5915,6 +5961,33 @@ function buildProjectPayload(
};
}
function normalizeParameterScientificNotation(
value: unknown,
): Record<string, ParameterScientificNotation> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return {};
}
const normalized: Record<string, ParameterScientificNotation> = {};
Object.entries(value as Record<string, unknown>).forEach(([key, entry]) => {
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
return;
}
const candidate = entry as Record<string, unknown>;
if (
typeof candidate.text !== "string" ||
typeof candidate.unit !== "string" ||
!isScientificNotationValue(candidate.text)
) {
return;
}
normalized[key] = {
text: candidate.text.trim(),
unit: candidate.unit,
};
});
return normalized;
}
function normalizeLoadedNode(
node: ProjectNodePayload,
componentDefinitions: ComponentDefinition[],
@@ -5935,6 +6008,9 @@ function normalizeLoadedNode(
const parameterUnits = definition
? { ...defaultParameterUnits(definition), ...(node.data.parameterUnits ?? {}) }
: { ...(node.data.parameterUnits ?? {}) };
const parameterScientificNotation = normalizeParameterScientificNotation(
node.data.parameterScientificNotation,
);
return {
id: node.id,
@@ -5953,6 +6029,7 @@ function normalizeLoadedNode(
ports: normalizeLoadedPorts(node.data.ports, definition),
parameters,
parameterUnits,
parameterScientificNotation,
rotation: normalizeNodeRotation(node.data.rotation),
mirrored: Boolean(node.data.mirrored),
},
@@ -6190,6 +6267,7 @@ function displayParameterValue(
baseValue: ParameterValue,
definition: ParameterDefinition,
selectedUnit: string,
scientificNotation?: ParameterScientificNotation,
) {
if (typeof baseValue === "string" && baseValue.trim() === "") {
return "";
@@ -6198,7 +6276,16 @@ function displayParameterValue(
if (!Number.isFinite(numericValue)) {
return String(baseValue);
}
return formatEditableNumber(findUnitOption(definition, selectedUnit).fromBase(numericValue));
const displayValue = findUnitOption(definition, selectedUnit).fromBase(numericValue);
if (
scientificNotation &&
isScientificNotationValue(scientificNotation.text)
) {
return scientificNotation.unit === selectedUnit
? scientificNotation.text.trim()
: formatScientificEditableNumber(displayValue, scientificNotation.text);
}
return formatEditableNumber(displayValue);
}
function formatEditableNumber(value: number) {
@@ -6208,6 +6295,38 @@ function formatEditableNumber(value: number) {
return Number(value.toPrecision(12)).toString();
}
const SCIENTIFIC_NOTATION_STYLE_PATTERN =
/^[+-]?(?:\d+(?:\.(\d*))?|\.(\d+))([eE])([+-]?)(\d+)$/;
function formatScientificEditableNumber(value: number, notationHint: string) {
if (!Number.isFinite(value)) {
return "";
}
const style = SCIENTIFIC_NOTATION_STYLE_PATTERN.exec(notationHint.trim());
if (!style) {
return Number(value.toPrecision(12)).toExponential();
}
const minimumFractionDigits = (style[1] ?? style[2] ?? "").length;
const exponentMarker = style[3];
const showPositiveExponentSign = style[4] === "+";
const roundedDisplayValue = Number(value.toPrecision(15));
const [rawMantissa, rawExponent] = roundedDisplayValue.toExponential().split("e");
const [wholePart, rawFraction = ""] = rawMantissa.split(".");
let fraction = rawFraction;
while (fraction.endsWith("0") && fraction.length > minimumFractionDigits) {
fraction = fraction.slice(0, -1);
}
if (fraction.length < minimumFractionDigits) {
fraction = fraction.padEnd(minimumFractionDigits, "0");
}
const exponent = Number(rawExponent);
const exponentSign = exponent < 0 ? "-" : showPositiveExponentSign ? "+" : "";
const exponentDigits = String(Math.abs(exponent));
return `${wholePart}${fraction ? `.${fraction}` : ""}${exponentMarker}${exponentSign}${exponentDigits}`;
}
function parameterMetadataValue(
value: ParameterValue,
definition: ParameterDefinition,
+42 -4
View File
@@ -2,6 +2,7 @@ import {
Fragment,
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
@@ -173,7 +174,15 @@ export function ParameterTable({
availableWidth,
onColumnLayoutChange,
}: ParameterTableProps) {
const effectiveLayout = effectiveColumnLayout(columnLayout, availableWidth);
const tableWrapRef = useRef<HTMLDivElement>(null);
const [measuredAvailableWidth, setMeasuredAvailableWidth] = useState<
number | null
>(null);
const resolvedAvailableWidth = measuredAvailableWidth ?? availableWidth;
const effectiveLayout = effectiveColumnLayout(
columnLayout,
resolvedAvailableWidth,
);
const columnDragRef = useRef<ColumnDragState | null>(null);
const helpPreviewTimerRef = useRef<number | null>(null);
const helpPreviewAnchorRef = useRef<HelpPreviewAnchor | null>(null);
@@ -181,6 +190,26 @@ export function ParameterTable({
useState<ColumnName | null>(null);
const [helpPreview, setHelpPreview] = useState<HelpPreviewState | null>(null);
useLayoutEffect(() => {
const wrap = tableWrapRef.current;
if (!wrap) {
return;
}
const measureAvailableWidth = () => {
const nextWidth = wrap.clientWidth;
if (nextWidth <= 0) {
return;
}
setMeasuredAvailableWidth((currentWidth) =>
currentWidth === nextWidth ? currentWidth : nextWidth,
);
};
measureAvailableWidth();
const observer = new ResizeObserver(measureAvailableWidth);
observer.observe(wrap);
return () => observer.disconnect();
}, []);
const dismissHelpPreview = useCallback(() => {
if (helpPreviewTimerRef.current !== null) {
window.clearTimeout(helpPreviewTimerRef.current);
@@ -258,7 +287,9 @@ export function ParameterTable({
column === "name"
? { ...startLayout, nameWidth: startLayout.nameWidth + boundaryDelta }
: { ...startLayout, unitWidth: startLayout.unitWidth - boundaryDelta };
onColumnLayoutChange(effectiveColumnLayout(proposed, availableWidth));
onColumnLayoutChange(
effectiveColumnLayout(proposed, resolvedAvailableWidth),
);
};
const startColumnResize = (
@@ -311,11 +342,17 @@ export function ParameterTable({
const nameColumnMaximum = Math.max(
MIN_NAME_WIDTH,
Math.min(MAX_NAME_WIDTH, availableWidth - MIN_VALUE_WIDTH - effectiveLayout.unitWidth),
Math.min(
MAX_NAME_WIDTH,
resolvedAvailableWidth - MIN_VALUE_WIDTH - effectiveLayout.unitWidth,
),
);
const unitColumnMaximum = Math.max(
MIN_UNIT_WIDTH,
Math.min(MAX_UNIT_WIDTH, availableWidth - MIN_VALUE_WIDTH - effectiveLayout.nameWidth),
Math.min(
MAX_UNIT_WIDTH,
resolvedAvailableWidth - MIN_VALUE_WIDTH - effectiveLayout.nameWidth,
),
);
const tableStyle = {
"--parameter-name-width": `${effectiveLayout.nameWidth}px`,
@@ -369,6 +406,7 @@ export function ParameterTable({
<div
className={`parameter-table-wrap ${activeColumnResize ? `resizing-${activeColumnResize}` : ""}`}
data-resizing-column={activeColumnResize ?? undefined}
ref={tableWrapRef}
>
<table aria-label={ariaLabel} className="parameter-table" style={tableStyle}>
<colgroup>
+8
View File
@@ -13,6 +13,14 @@ const NUMERIC_LITERAL_PATTERN =
const UNSIGNED_NUMBER_PREFIX =
/^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
export function isScientificNotationValue(value: number | string): value is string {
if (typeof value !== "string") {
return false;
}
const trimmed = value.trim();
return NUMERIC_LITERAL_PATTERN.test(trimmed) && /[eE]/.test(trimmed);
}
type TokenKind =
| "number"
| "identifier"
+92 -34
View File
@@ -447,6 +447,17 @@ textarea {
font-weight: 700;
}
.properties .panel-title {
display: flex;
align-items: center;
min-height: 32px;
padding: 0 10px;
border-bottom-color: #c7cdd4;
color: #293642;
background: #f2f4f6;
font-size: 12px;
}
.palette-list {
display: block;
padding: 12px;
@@ -461,13 +472,13 @@ textarea {
.parameter-table-wrap {
width: 100%;
min-width: 0;
padding: 0 12px 12px;
overflow-x: hidden;
padding: 0;
overflow-x: clip;
}
.parameter-table-wrap.parameter-form {
display: block;
padding: 0 12px 12px;
padding: 0;
}
.parameter-table {
@@ -475,27 +486,30 @@ textarea {
--parameter-unit-width: 72px;
width: 100%;
min-width: 0;
border: 1px solid #cbd5df;
border: 0;
border-spacing: 0;
border-radius: 4px;
border-radius: 0;
color: #263442;
background: #ffffff;
table-layout: fixed;
font-size: 11px;
font-size: 12px;
}
.parameter-table-head .parameter-table-cell {
position: relative;
position: sticky;
z-index: 1;
height: 30px;
padding: 0 7px;
top: 0;
height: 28px;
padding: 0 8px;
overflow: visible;
border-bottom-color: #bfcbd7;
color: #475569;
background: #edf1f5;
font-size: 10px;
border-right-color: #c4cad1;
border-bottom-color: #b9c1c9;
color: #35414c;
background: #e3e6e9;
box-shadow: 0 1px 0 rgba(51, 65, 85, 0.06);
font-size: 11px;
font-weight: 700;
line-height: 30px;
line-height: 27px;
}
.parameter-table-head .parameter-table-name-cell {
@@ -507,16 +521,21 @@ textarea {
}
.parameter-table-row {
height: 36px;
height: 32px;
}
.parameter-table-head-row {
height: 28px;
}
.parameter-table-cell {
min-width: 0;
height: 36px;
padding: 2px 6px;
height: 32px;
box-sizing: border-box;
padding: 1px 7px;
overflow: hidden;
border-right: 1px solid #d9e0e7;
border-bottom: 1px solid #d9e0e7;
border-right: 1px solid #d5d9de;
border-bottom: 1px solid #e1e4e7;
vertical-align: middle;
}
@@ -524,8 +543,22 @@ textarea {
border-right: 0;
}
.parameter-table
> tbody
> .parameter-table-row:nth-child(even)
> .parameter-table-cell {
background: #f3f4f5;
}
.parameter-table > tbody > .parameter-table-row:hover > .parameter-table-cell {
background: #f8fbfe;
background: #eaf3f8;
}
.parameter-table
> tbody
> .parameter-table-row:focus-within
> .parameter-table-cell {
background: #deedf5;
}
.parameter-table-row:focus-visible {
@@ -537,16 +570,21 @@ textarea {
.parameter-table-name-cell {
overflow: hidden;
color: #344256;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
}
.parameter-table > tbody .parameter-table-name-cell {
font-weight: 400;
}
.parameter-value-cell,
.parameter-table-value-cell,
.parameter-unit-cell,
.parameter-table-unit-cell {
min-width: 0;
padding: 2px;
padding: 1px 3px;
}
.parameter-unit-cell,
@@ -561,30 +599,44 @@ textarea {
width: 100%;
min-width: 0;
max-width: 100%;
height: 30px;
height: 28px;
box-sizing: border-box;
margin: 0;
padding: 0 6px;
border: 1px solid #d5dde6;
border-radius: 2px;
border: 1px solid transparent;
border-radius: 1px;
outline: 0;
color: #1f2933;
background: #ffffff;
font-size: 11px;
background: transparent;
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.parameter-table-cell select {
padding-right: 4px;
}
.parameter-table > tbody .parameter-table-value-cell,
.parameter-table > tbody .parameter-table-value-cell input,
.parameter-table > tbody .parameter-table-value-cell select {
text-align: right;
}
.parameter-table > tbody .parameter-table-value-cell select {
text-align-last: right;
}
.parameter-table-cell input:hover:not(:disabled),
.parameter-table-cell select:hover:not(:disabled) {
border-color: #aebdcb;
border-color: #aebbc7;
background: rgba(255, 255, 255, 0.72);
}
.parameter-table-cell input:focus,
.parameter-table-cell select:focus {
border-color: #1d6fb8;
outline: 0;
background: #ffffff;
box-shadow: inset 0 0 0 1px #1d6fb8;
}
@@ -597,24 +649,27 @@ textarea {
.parameter-table-row.read-only .parameter-value-cell,
.parameter-table-row.read-only .parameter-table-value-cell,
.parameter-table-cell input:read-only {
background: #f4f6f8;
background: transparent;
}
.parameter-read-only-value,
.parameter-table-read-only-value {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
min-width: 0;
height: 30px;
padding: 0 7px;
height: 28px;
box-sizing: border-box;
padding: 0 6px;
overflow: hidden;
border: 1px solid #d5dde6;
border-radius: 2px;
border: 0;
border-radius: 0;
color: #526172;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
background: #f4f6f8;
background: transparent;
font-variant-numeric: tabular-nums;
}
@@ -628,7 +683,10 @@ textarea {
content: "—";
}
.parameter-table-row.invalid > .parameter-table-cell {
.parameter-table
> tbody
> .parameter-table-row.invalid
> .parameter-table-cell {
background: #fff8f7;
}