完善建模交互、组件图标与系统协议
This commit is contained in:
1 parent
456c29b3b6
commit
6572defaa4
66 files changed
+10067
-4163
No files matched your search
+1952
-1234
File diff suppressed because it is too large.
Load diff
@@ -10,7 +10,11 @@ import {
|
||||
AmesimForcSymbol,
|
||||
FORC_PORT_ANCHORS,
|
||||
AmesimLmechn1Symbol,
|
||||
lmechn1RightPortCount,
|
||||
lmechn1SymbolMetrics,
|
||||
LSTP00A_PORT_ANCHORS,
|
||||
MECMAS21_PORT_ANCHORS,
|
||||
PNRP17_PORT_ANCHORS,
|
||||
AmesimPnrp17Symbol,
|
||||
AmesimLstp00aSymbol,
|
||||
AmesimMecmas21Symbol,
|
||||
@@ -29,6 +33,9 @@ import {
|
||||
AmesimPnor001Symbol,
|
||||
AmesimPnvo001FixedSymbol,
|
||||
AmesimPnvo001SignalSymbol,
|
||||
P4NODE2_PORT_ANCHORS,
|
||||
PIPE_NETWORK_PORT_ANCHORS,
|
||||
PN3NODE2_PORT_ANCHORS,
|
||||
PNVO001_PORT_ANCHORS,
|
||||
} from "./componentSymbols/pneumatic";
|
||||
import {
|
||||
@@ -126,18 +133,6 @@ function sameViewBox(
|
||||
);
|
||||
}
|
||||
|
||||
const LEGACY_HORIZONTAL_PORT_ANCHORS: ComponentSymbolPortAnchors = {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
};
|
||||
const LEGACY_PNVO001_SIGNAL_PORT_ANCHORS: ComponentSymbolPortAnchors = {
|
||||
left: [
|
||||
{ x: 26, y: 46 },
|
||||
{ x: 2, y: 24 },
|
||||
],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
};
|
||||
|
||||
type CustomSymbolLayout = {
|
||||
viewBox?: ComponentSymbolViewBox;
|
||||
symbolSize: ComponentSymbolSize;
|
||||
@@ -186,6 +181,30 @@ function makePortlessSymbolLayout(
|
||||
};
|
||||
}
|
||||
|
||||
function lmechn1CanvasLayout(
|
||||
parameters?: ComponentSymbolParameters,
|
||||
): ComponentSymbolLayout {
|
||||
const metrics = lmechn1SymbolMetrics(parameters);
|
||||
const symbolHeight = metrics.viewBoxHeight * 2;
|
||||
return makeSymbolLayout(
|
||||
"custom",
|
||||
{
|
||||
left: [{ x: 2, y: metrics.viewBoxHeight / 2 }],
|
||||
right: metrics.rightPortRows.map((y) => ({ x: 62, y })),
|
||||
},
|
||||
{
|
||||
viewBox: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 64,
|
||||
height: metrics.viewBoxHeight,
|
||||
},
|
||||
symbolSize: { width: 128, height: symbolHeight },
|
||||
nodeSize: { width: 148, height: symbolHeight },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function CylinderSymbol() {
|
||||
return (
|
||||
<SymbolSvg>
|
||||
@@ -377,7 +396,6 @@ const symbolRegistry = {
|
||||
render: AmesimPnpl01Symbol,
|
||||
layout: {
|
||||
...makeSymbolLayout("compact", { left: [PNPL01_PORT_ANCHOR] }),
|
||||
legacyPortAnchors: { left: [{ x: 2, y: 24 }] },
|
||||
},
|
||||
},
|
||||
amesim_pnch023: {
|
||||
@@ -388,10 +406,6 @@ const symbolRegistry = {
|
||||
left: [PNCH023_PORT_ANCHORS.left],
|
||||
right: [PNCH023_PORT_ANCHORS.right],
|
||||
}),
|
||||
legacyPortAnchors: {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
},
|
||||
},
|
||||
},
|
||||
amesim_pnch012: {
|
||||
@@ -409,16 +423,6 @@ const symbolRegistry = {
|
||||
PNCH012_PORT_ANCHORS.bottom,
|
||||
],
|
||||
}),
|
||||
legacyPortAnchors: {
|
||||
left: [
|
||||
{ x: 2, y: 16 },
|
||||
{ x: 2, y: 32 },
|
||||
],
|
||||
right: [
|
||||
{ x: 62, y: 16 },
|
||||
{ x: 62, y: 32 },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
amesim_pnor001: {
|
||||
@@ -429,7 +433,6 @@ const symbolRegistry = {
|
||||
left: [PNVO001_PORT_ANCHORS.physicalLeft],
|
||||
right: [PNVO001_PORT_ANCHORS.physicalRight],
|
||||
}),
|
||||
legacyPortAnchors: LEGACY_HORIZONTAL_PORT_ANCHORS,
|
||||
},
|
||||
},
|
||||
amesim_pnvo001_fixed: {
|
||||
@@ -440,7 +443,6 @@ const symbolRegistry = {
|
||||
left: [PNVO001_PORT_ANCHORS.physicalLeft],
|
||||
right: [PNVO001_PORT_ANCHORS.physicalRight],
|
||||
}),
|
||||
legacyPortAnchors: LEGACY_HORIZONTAL_PORT_ANCHORS,
|
||||
},
|
||||
},
|
||||
amesim_pnvo001: {
|
||||
@@ -454,49 +456,50 @@ const symbolRegistry = {
|
||||
],
|
||||
right: [PNVO001_PORT_ANCHORS.physicalRight],
|
||||
}),
|
||||
legacyPortAnchors: LEGACY_PNVO001_SIGNAL_PORT_ANCHORS,
|
||||
},
|
||||
},
|
||||
amesim_pnl00r: {
|
||||
presentation: "framed",
|
||||
render: AmesimPnl00rSymbol,
|
||||
layout: makeSymbolLayout("compact", {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
layout: makeSymbolLayout("standard", {
|
||||
left: [PIPE_NETWORK_PORT_ANCHORS.left],
|
||||
right: [PIPE_NETWORK_PORT_ANCHORS.right],
|
||||
}),
|
||||
},
|
||||
amesim_pnl0001: {
|
||||
presentation: "framed",
|
||||
render: AmesimPnl0001Symbol,
|
||||
layout: makeSymbolLayout("small", {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
layout: makeSymbolLayout("standard", {
|
||||
left: [PIPE_NETWORK_PORT_ANCHORS.left],
|
||||
right: [PIPE_NETWORK_PORT_ANCHORS.right],
|
||||
}),
|
||||
},
|
||||
amesim_pnl0002: {
|
||||
presentation: "framed",
|
||||
render: AmesimPnl0002Symbol,
|
||||
layout: makeSymbolLayout("small", {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
layout: makeSymbolLayout("standard", {
|
||||
left: [PIPE_NETWORK_PORT_ANCHORS.left],
|
||||
right: [PIPE_NETWORK_PORT_ANCHORS.right],
|
||||
}),
|
||||
},
|
||||
amesim_pnl0003: {
|
||||
presentation: "framed",
|
||||
render: AmesimPnl0003Symbol,
|
||||
layout: makeSymbolLayout("small", {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
layout: makeSymbolLayout("standard", {
|
||||
left: [PIPE_NETWORK_PORT_ANCHORS.left],
|
||||
right: [PIPE_NETWORK_PORT_ANCHORS.right],
|
||||
}),
|
||||
},
|
||||
amesim_pn3node2: {
|
||||
presentation: "framed",
|
||||
render: AmesimPn3Node2Symbol,
|
||||
layout: makeSymbolLayout("compact", {
|
||||
left: [{ x: 3, y: 24 }],
|
||||
layout: makeSymbolLayout("small", {
|
||||
// 目录中 port_1 属于 left 组,但参考图把它显示在顶部;
|
||||
// port_2 是粗线汇流支路,固定显示在左侧。
|
||||
left: [PN3NODE2_PORT_ANCHORS.port1],
|
||||
right: [
|
||||
{ x: 61, y: 12 },
|
||||
{ x: 61, y: 36 },
|
||||
PN3NODE2_PORT_ANCHORS.port2,
|
||||
PN3NODE2_PORT_ANCHORS.port3,
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -504,11 +507,11 @@ const symbolRegistry = {
|
||||
presentation: "framed",
|
||||
render: AmesimP4Node2Symbol,
|
||||
layout: makeSymbolLayout("small", {
|
||||
left: [{ x: 3, y: 24 }],
|
||||
left: [P4NODE2_PORT_ANCHORS.port1],
|
||||
right: [
|
||||
{ x: 61, y: 8 },
|
||||
{ x: 61, y: 24 },
|
||||
{ x: 61, y: 40 },
|
||||
P4NODE2_PORT_ANCHORS.port2,
|
||||
P4NODE2_PORT_ANCHORS.port3,
|
||||
P4NODE2_PORT_ANCHORS.port4,
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -517,7 +520,6 @@ const symbolRegistry = {
|
||||
render: AmesimStep0Symbol,
|
||||
layout: {
|
||||
...makeSymbolLayout("standard", { right: [SIGNAL_OUTPUT_PORT_ANCHOR] }),
|
||||
legacyPortAnchors: { right: [{ x: 60, y: 24 }] },
|
||||
},
|
||||
},
|
||||
amesim_ud00: {
|
||||
@@ -525,7 +527,6 @@ const symbolRegistry = {
|
||||
render: AmesimUd00Symbol,
|
||||
layout: {
|
||||
...makeSymbolLayout("standard", { right: [SIGNAL_OUTPUT_PORT_ANCHOR] }),
|
||||
legacyPortAnchors: { right: [{ x: 60, y: 24 }] },
|
||||
},
|
||||
},
|
||||
amesim_f000: {
|
||||
@@ -533,7 +534,6 @@ const symbolRegistry = {
|
||||
render: AmesimF000Symbol,
|
||||
layout: {
|
||||
...makeSymbolLayout("compact", { right: [F000_PORT_ANCHOR] }),
|
||||
legacyPortAnchors: { right: [{ x: 62, y: 24 }] },
|
||||
},
|
||||
},
|
||||
amesim_forc: {
|
||||
@@ -544,7 +544,6 @@ const symbolRegistry = {
|
||||
left: [FORC_PORT_ANCHORS.left],
|
||||
right: [FORC_PORT_ANCHORS.right],
|
||||
}),
|
||||
legacyPortAnchors: LEGACY_HORIZONTAL_PORT_ANCHORS,
|
||||
},
|
||||
},
|
||||
amesim_mecmas21: {
|
||||
@@ -555,18 +554,19 @@ const symbolRegistry = {
|
||||
left: [MECMAS21_PORT_ANCHORS.left],
|
||||
right: [MECMAS21_PORT_ANCHORS.right],
|
||||
}),
|
||||
legacyPortAnchors: LEGACY_HORIZONTAL_PORT_ANCHORS,
|
||||
},
|
||||
variantKey: (parameters) =>
|
||||
`${parameters?.useFriction ?? ""}:${parameters?.stoptype ?? ""}`,
|
||||
`${parameters?.useFriction ?? 2}:${parameters?.stoptype ?? 1}`,
|
||||
},
|
||||
amesim_lstp00a: {
|
||||
presentation: "framed",
|
||||
render: AmesimLstp00aSymbol,
|
||||
layout: makeSymbolLayout("standard", {
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
}),
|
||||
layout: {
|
||||
...makeSymbolLayout("standard", {
|
||||
left: [LSTP00A_PORT_ANCHORS.left],
|
||||
right: [LSTP00A_PORT_ANCHORS.right],
|
||||
}),
|
||||
},
|
||||
},
|
||||
amesim_lmechn1: {
|
||||
presentation: "framed",
|
||||
@@ -574,14 +574,16 @@ const symbolRegistry = {
|
||||
layout: makeSymbolLayout(
|
||||
"custom",
|
||||
{
|
||||
left: [7, 12, 17, 22, 27, 32, 37, 42].map((y) => ({ x: 5, y })),
|
||||
right: [{ x: 59, y: 24 }],
|
||||
left: [{ x: 2, y: 24 }],
|
||||
right: [{ x: 62, y: 24 }],
|
||||
},
|
||||
{
|
||||
symbolSize: { width: 128, height: 128 },
|
||||
nodeSize: { width: 148, height: 128 },
|
||||
},
|
||||
),
|
||||
layoutForParameters: lmechn1CanvasLayout,
|
||||
variantKey: (parameters) => String(lmechn1RightPortCount(parameters)),
|
||||
},
|
||||
amesim_pnrp17: {
|
||||
presentation: "framed",
|
||||
@@ -589,13 +591,13 @@ const symbolRegistry = {
|
||||
// PNRP17 的当前 112×112 画布作为 standard 档位基准。
|
||||
layout: makeSymbolLayout("standard", {
|
||||
left: [
|
||||
{ x: 25.5, y: 48 },
|
||||
{ x: 2, y: 4.4 },
|
||||
{ x: 2, y: 24.1 },
|
||||
PNRP17_PORT_ANCHORS.bottom,
|
||||
PNRP17_PORT_ANCHORS.upperLeft,
|
||||
PNRP17_PORT_ANCHORS.middleLeft,
|
||||
],
|
||||
right: [
|
||||
{ x: 62, y: 4.4 },
|
||||
{ x: 62, y: 24.1 },
|
||||
PNRP17_PORT_ANCHORS.upperRight,
|
||||
PNRP17_PORT_ANCHORS.middleRight,
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -635,10 +637,15 @@ export function componentSymbolPresentation(
|
||||
: "framed";
|
||||
}
|
||||
|
||||
export function componentSymbolLayout(symbol: string): ComponentSymbolLayout {
|
||||
return isRegisteredComponentSymbol(symbol)
|
||||
? symbolRegistry[symbol].layout
|
||||
: fallbackComponentSymbolLayout;
|
||||
export function componentSymbolLayout(
|
||||
symbol: string,
|
||||
parameters?: ComponentSymbolParameters,
|
||||
): ComponentSymbolLayout {
|
||||
if (!isRegisteredComponentSymbol(symbol)) {
|
||||
return fallbackComponentSymbolLayout;
|
||||
}
|
||||
const definition: SymbolDefinition = symbolRegistry[symbol];
|
||||
return definition.layoutForParameters?.(parameters) ?? definition.layout;
|
||||
}
|
||||
|
||||
export function componentSymbolPortAnchors(
|
||||
@@ -669,7 +676,10 @@ export function ComponentSymbol({
|
||||
: "generic";
|
||||
const definition: SymbolDefinition = symbolRegistry[resolvedSymbol];
|
||||
const SymbolRenderer = definition.render;
|
||||
const { layout } = definition;
|
||||
const layout =
|
||||
surface === "canvas"
|
||||
? definition.layoutForParameters?.(parameters) ?? definition.layout
|
||||
: definition.layout;
|
||||
const rootRef = useRef<HTMLSpanElement>(null);
|
||||
const [paletteViewBox, setPaletteViewBox] = useState<ComponentSymbolViewBox>(
|
||||
layout.viewBox,
|
||||
@@ -737,7 +747,7 @@ export function ComponentSymbol({
|
||||
ref={rootRef}
|
||||
>
|
||||
<SymbolViewBoxProvider viewBox={renderedViewBox}>
|
||||
<SymbolRenderer parameters={parameters} />
|
||||
<SymbolRenderer parameters={parameters} surface={surface} />
|
||||
</SymbolViewBoxProvider>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,9 @@ import {
|
||||
} from "@xyflow/react";
|
||||
|
||||
export const CONTACT_AWARE_EDGE_TYPE = "contact-aware";
|
||||
// React Flow 在 Loose 模式下会把重叠 Handle 的边端点分别放在圆点两侧,
|
||||
// 因此两个端口中心完全重合时,edge props 仍会相差约一个 Handle 直径。
|
||||
export const CONTACT_EDGE_SCREEN_TOLERANCE = 12;
|
||||
|
||||
export function ContactAwareEdge({
|
||||
data,
|
||||
id,
|
||||
interactionWidth,
|
||||
markerEnd,
|
||||
@@ -23,31 +21,9 @@ export function ContactAwareEdge({
|
||||
targetX,
|
||||
targetY,
|
||||
}: EdgeProps) {
|
||||
const distance = Math.hypot(targetX - sourceX, targetY - sourceY);
|
||||
if (distance <= CONTACT_EDGE_SCREEN_TOLERANCE) {
|
||||
const centerX = (sourceX + targetX) / 2;
|
||||
const centerY = (sourceY + targetY) / 2;
|
||||
return (
|
||||
<>
|
||||
<circle
|
||||
aria-label="接触连接"
|
||||
className="contact-edge-hit"
|
||||
cx={centerX}
|
||||
cy={centerY}
|
||||
data-contact-edge-id={id}
|
||||
r={12}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
/>
|
||||
<circle
|
||||
aria-hidden="true"
|
||||
className="contact-edge-marker"
|
||||
cx={centerX}
|
||||
cy={centerY}
|
||||
r={3.5}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const isContactEdge = data?.isContactEdge === true;
|
||||
if (isContactEdge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [edgePath] = getSmoothStepPath({
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { CSSProperties, PointerEvent as ReactPointerEvent } from "react";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Square,
|
||||
SquareTerminal,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
|
||||
type DockedConsoleMode = "minimized" | "normal" | "maximized";
|
||||
type DockedConsoleLevel = "info" | "success" | "warning" | "error";
|
||||
|
||||
type DockedConsoleEntry = {
|
||||
id: number;
|
||||
timestamp: string;
|
||||
level: DockedConsoleLevel;
|
||||
message: string;
|
||||
format: "message" | "xml";
|
||||
};
|
||||
|
||||
type DockedConsoleProgress = {
|
||||
state: "idle" | "running" | "success" | "warning" | "error";
|
||||
percent: number;
|
||||
message: string;
|
||||
startTime: number;
|
||||
simulatedTime: number;
|
||||
totalTime: number;
|
||||
};
|
||||
|
||||
type DockedSimulationConsoleProps = {
|
||||
entries: DockedConsoleEntry[];
|
||||
mode: DockedConsoleMode;
|
||||
progress: DockedConsoleProgress;
|
||||
onClear: () => void;
|
||||
onModeChange: (mode: DockedConsoleMode) => void;
|
||||
onStop: () => void;
|
||||
stopPending: boolean;
|
||||
};
|
||||
|
||||
type ResizeSession = {
|
||||
pointerId: number;
|
||||
startHeight: number;
|
||||
startY: number;
|
||||
};
|
||||
|
||||
const CONSOLE_DOCK_HEIGHT_KEY =
|
||||
"system-simulation-flow:modeling-console-dock-height";
|
||||
const DEFAULT_CONSOLE_DOCK_HEIGHT = 180;
|
||||
const MIN_CONSOLE_DOCK_HEIGHT = 120;
|
||||
const MAX_CONSOLE_DOCK_HEIGHT = 420;
|
||||
const MIN_MODELING_CANVAS_HEIGHT = 240;
|
||||
const COLLAPSED_CONSOLE_DOCK_HEIGHT = 40;
|
||||
|
||||
const consoleLevelLabels: Record<DockedConsoleLevel, string> = {
|
||||
info: "信息",
|
||||
success: "完成",
|
||||
warning: "警告",
|
||||
error: "错误",
|
||||
};
|
||||
|
||||
function simulationProgressClockText(progress: DockedConsoleProgress) {
|
||||
const simulatedTime = Number.isFinite(progress.simulatedTime)
|
||||
? progress.simulatedTime
|
||||
: progress.startTime;
|
||||
const totalTime = Number.isFinite(progress.totalTime)
|
||||
? progress.totalTime
|
||||
: progress.startTime;
|
||||
return `${simulatedTime.toFixed(2)} s / ${totalTime.toFixed(2)} s`;
|
||||
}
|
||||
|
||||
function maximumConsoleDockHeight(hostHeight = window.innerHeight) {
|
||||
return Math.max(
|
||||
MIN_CONSOLE_DOCK_HEIGHT,
|
||||
Math.min(
|
||||
MAX_CONSOLE_DOCK_HEIGHT,
|
||||
Math.round(hostHeight * 0.48),
|
||||
Math.max(MIN_CONSOLE_DOCK_HEIGHT, hostHeight - MIN_MODELING_CANVAS_HEIGHT),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function clampConsoleDockHeight(height: number, hostHeight = window.innerHeight) {
|
||||
return Math.min(
|
||||
maximumConsoleDockHeight(hostHeight),
|
||||
Math.max(MIN_CONSOLE_DOCK_HEIGHT, Math.round(height)),
|
||||
);
|
||||
}
|
||||
|
||||
function loadConsoleDockHeight() {
|
||||
try {
|
||||
const stored = Number(localStorage.getItem(CONSOLE_DOCK_HEIGHT_KEY));
|
||||
return Number.isFinite(stored) && stored > 0
|
||||
? clampConsoleDockHeight(stored)
|
||||
: DEFAULT_CONSOLE_DOCK_HEIGHT;
|
||||
} catch {
|
||||
return DEFAULT_CONSOLE_DOCK_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
function storeConsoleDockHeight(height: number) {
|
||||
try {
|
||||
localStorage.setItem(CONSOLE_DOCK_HEIGHT_KEY, String(height));
|
||||
} catch {
|
||||
// 本地存储不可用时仍可在当前页面调整高度。
|
||||
}
|
||||
}
|
||||
|
||||
function collapsedSummary(
|
||||
entries: DockedConsoleEntry[],
|
||||
progress: DockedConsoleProgress,
|
||||
) {
|
||||
if (progress.state === "running") {
|
||||
return `${progress.message} · ${simulationProgressClockText(progress)} · ${progress.percent}%`;
|
||||
}
|
||||
const latestEntry = entries.at(-1);
|
||||
if (!latestEntry) {
|
||||
return progress.message;
|
||||
}
|
||||
return latestEntry.format === "xml"
|
||||
? "系统 XML 已生成"
|
||||
: latestEntry.message.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
export function DockedSimulationConsole({
|
||||
entries,
|
||||
mode,
|
||||
progress,
|
||||
onClear,
|
||||
onModeChange,
|
||||
onStop,
|
||||
stopPending,
|
||||
}: DockedSimulationConsoleProps) {
|
||||
const dockRef = useRef<HTMLElement>(null);
|
||||
const logRef = useRef<HTMLDivElement>(null);
|
||||
const resizeSessionRef = useRef<ResizeSession | null>(null);
|
||||
const [height, setHeight] = useState(loadConsoleDockHeight);
|
||||
const [hostHeight, setHostHeight] = useState(() => window.innerHeight);
|
||||
const [resizing, setResizing] = useState(false);
|
||||
const collapsed = mode === "minimized";
|
||||
const canStop = progress.state === "running" && !stopPending;
|
||||
const summary = useMemo(
|
||||
() => collapsedSummary(entries, progress),
|
||||
[entries, progress],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!collapsed && logRef.current) {
|
||||
logRef.current.scrollTop = logRef.current.scrollHeight;
|
||||
}
|
||||
}, [collapsed, entries]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!collapsed) {
|
||||
storeConsoleDockHeight(height);
|
||||
}
|
||||
}, [collapsed, height]);
|
||||
|
||||
useEffect(() => {
|
||||
const host = dockRef.current?.parentElement;
|
||||
const updateHostHeight = () => {
|
||||
const nextHostHeight = host?.clientHeight ?? window.innerHeight;
|
||||
setHostHeight(nextHostHeight);
|
||||
setHeight((current) => clampConsoleDockHeight(current, nextHostHeight));
|
||||
};
|
||||
updateHostHeight();
|
||||
const observer =
|
||||
host && typeof ResizeObserver !== "undefined"
|
||||
? new ResizeObserver(updateHostHeight)
|
||||
: null;
|
||||
if (host) {
|
||||
observer?.observe(host);
|
||||
}
|
||||
window.addEventListener("resize", updateHostHeight);
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
window.removeEventListener("resize", updateHostHeight);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const beginResize = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
if (collapsed || event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
resizeSessionRef.current = {
|
||||
pointerId: event.pointerId,
|
||||
startHeight: height,
|
||||
startY: event.clientY,
|
||||
};
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
setResizing(true);
|
||||
};
|
||||
|
||||
const resize = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
const session = resizeSessionRef.current;
|
||||
if (!session || session.pointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
setHeight(
|
||||
clampConsoleDockHeight(
|
||||
session.startHeight + session.startY - event.clientY,
|
||||
hostHeight,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const finishResize = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
const session = resizeSessionRef.current;
|
||||
if (!session || session.pointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
resizeSessionRef.current = null;
|
||||
setResizing(false);
|
||||
};
|
||||
|
||||
const consoleStyle = {
|
||||
"--simulation-console-dock-height": `${
|
||||
collapsed ? COLLAPSED_CONSOLE_DOCK_HEIGHT : height
|
||||
}px`,
|
||||
} as CSSProperties;
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label="仿真控制台"
|
||||
className={`simulation-console-dock ${collapsed ? "minimized" : "normal"} ${
|
||||
resizing ? "resizing" : ""
|
||||
}`}
|
||||
data-editor-shortcuts-ignore="true"
|
||||
ref={dockRef}
|
||||
style={consoleStyle}
|
||||
>
|
||||
{!collapsed ? (
|
||||
<div
|
||||
aria-label="调整仿真控制台高度"
|
||||
aria-orientation="horizontal"
|
||||
aria-valuemax={maximumConsoleDockHeight(hostHeight)}
|
||||
aria-valuemin={MIN_CONSOLE_DOCK_HEIGHT}
|
||||
aria-valuenow={height}
|
||||
className="simulation-console-dock-resizer"
|
||||
onDoubleClick={() => setHeight(DEFAULT_CONSOLE_DOCK_HEIGHT)}
|
||||
onKeyDown={(event) => {
|
||||
const nextHeight =
|
||||
event.key === "ArrowUp"
|
||||
? height + 20
|
||||
: event.key === "ArrowDown"
|
||||
? height - 20
|
||||
: event.key === "Home"
|
||||
? MIN_CONSOLE_DOCK_HEIGHT
|
||||
: event.key === "End"
|
||||
? maximumConsoleDockHeight(hostHeight)
|
||||
: null;
|
||||
if (nextHeight === null) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
setHeight(clampConsoleDockHeight(nextHeight, hostHeight));
|
||||
}}
|
||||
onLostPointerCapture={finishResize}
|
||||
onPointerCancel={finishResize}
|
||||
onPointerDown={beginResize}
|
||||
onPointerMove={resize}
|
||||
onPointerUp={finishResize}
|
||||
role="separator"
|
||||
tabIndex={0}
|
||||
title="拖动调整控制台高度,双击恢复默认高度"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<header className="simulation-console-dock-header">
|
||||
<div className="simulation-console-dock-title">
|
||||
<SquareTerminal aria-hidden="true" size={17} />
|
||||
<strong>仿真控制台</strong>
|
||||
<span className="simulation-console-dock-summary" title={summary}>
|
||||
{summary}
|
||||
</span>
|
||||
</div>
|
||||
<div className="simulation-console-dock-actions">
|
||||
<button
|
||||
aria-label={stopPending ? "正在停止仿真" : "停止仿真"}
|
||||
className={canStop ? "active" : ""}
|
||||
disabled={!canStop}
|
||||
onClick={onStop}
|
||||
title={canStop ? "停止当前仿真" : "当前没有可停止的仿真"}
|
||||
type="button"
|
||||
>
|
||||
<Square aria-hidden="true" fill="currentColor" size={13} strokeWidth={1.8} />
|
||||
</button>
|
||||
{!collapsed ? (
|
||||
<button
|
||||
aria-label="清空控制台"
|
||||
disabled={entries.length === 0}
|
||||
onClick={onClear}
|
||||
title="清空控制台"
|
||||
type="button"
|
||||
>
|
||||
<Trash2 aria-hidden="true" size={16} />
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
aria-label={collapsed ? "展开仿真控制台" : "收起仿真控制台"}
|
||||
onClick={() => onModeChange(collapsed ? "normal" : "minimized")}
|
||||
title={collapsed ? "展开仿真控制台" : "收起仿真控制台"}
|
||||
type="button"
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronUp aria-hidden="true" size={17} />
|
||||
) : (
|
||||
<ChevronDown aria-hidden="true" size={17} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{!collapsed ? (
|
||||
<>
|
||||
<div className={`simulation-console-dock-progress ${progress.state}`}>
|
||||
<div className="simulation-console-dock-progress-label">
|
||||
<span>{progress.message}</span>
|
||||
<span>{simulationProgressClockText(progress)}</span>
|
||||
<strong>{progress.percent}%</strong>
|
||||
</div>
|
||||
<div
|
||||
aria-label={`仿真进度 ${progress.percent}%,${simulationProgressClockText(progress)}`}
|
||||
aria-valuemax={100}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={progress.percent}
|
||||
className="simulation-console-dock-progress-track"
|
||||
role="progressbar"
|
||||
>
|
||||
<span style={{ width: `${progress.percent}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="控制台日志"
|
||||
className="simulation-console-dock-log"
|
||||
ref={logRef}
|
||||
role="region"
|
||||
>
|
||||
{entries.length === 0 ? (
|
||||
<div className="simulation-console-dock-empty">暂无仿真消息</div>
|
||||
) : (
|
||||
entries.map((entry) => (
|
||||
<div
|
||||
className={`simulation-console-dock-entry ${entry.level} ${entry.format}`}
|
||||
data-entry-id={entry.id}
|
||||
key={entry.id}
|
||||
>
|
||||
<time>{entry.timestamp}</time>
|
||||
<span className="simulation-console-dock-entry-level">
|
||||
{consoleLevelLabels[entry.level]}
|
||||
</span>
|
||||
{entry.format === "xml" ? (
|
||||
<pre
|
||||
aria-label="生成的系统 XML"
|
||||
className="simulation-console-dock-entry-xml"
|
||||
>
|
||||
{entry.message}
|
||||
</pre>
|
||||
) : (
|
||||
<span className="simulation-console-dock-entry-message">
|
||||
{entry.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -77,8 +77,11 @@ type ResultProjectEdge = {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
sourceHandle: string | null;
|
||||
targetHandle: string | null;
|
||||
sourceHandle: string;
|
||||
targetHandle: string;
|
||||
data: {
|
||||
isContactEdge: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type ResultSimulationConfig = {
|
||||
@@ -131,8 +134,8 @@ export type SimulationResultsSnapshot = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
project: {
|
||||
projectSchemaVersion: 1;
|
||||
name: string;
|
||||
presentationLayoutVersion?: 1 | null;
|
||||
nodes: ResultProjectNode[];
|
||||
edges: ResultProjectEdge[];
|
||||
simulation: ResultSimulationConfig;
|
||||
@@ -384,7 +387,18 @@ export function SimulationResultsView({
|
||||
};
|
||||
clampLayoutToWorkbench();
|
||||
window.addEventListener("resize", clampLayoutToWorkbench);
|
||||
return () => window.removeEventListener("resize", clampLayoutToWorkbench);
|
||||
const workbench = workbenchRef.current;
|
||||
const observer =
|
||||
workbench && typeof ResizeObserver !== "undefined"
|
||||
? new ResizeObserver(clampLayoutToWorkbench)
|
||||
: null;
|
||||
if (workbench) {
|
||||
observer?.observe(workbench);
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener("resize", clampLayoutToWorkbench);
|
||||
observer?.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1138,6 +1152,7 @@ export function SimulationResultsView({
|
||||
nodes={readOnlyNodes}
|
||||
nodesConnectable={false}
|
||||
nodesDraggable={false}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
nodesFocusable
|
||||
onNodeClick={(_, node) => setSelectedNodeId(node.id)}
|
||||
onPaneClick={() => setSelectedNodeId(null)}
|
||||
@@ -3910,10 +3925,8 @@ function isSimulationResultsSnapshotValue(
|
||||
const result = value.result;
|
||||
if (
|
||||
!isRecord(project) ||
|
||||
project.projectSchemaVersion !== 1 ||
|
||||
typeof project.name !== "string" ||
|
||||
(project.presentationLayoutVersion !== undefined &&
|
||||
project.presentationLayoutVersion !== null &&
|
||||
project.presentationLayoutVersion !== 1) ||
|
||||
!Array.isArray(project.nodes) ||
|
||||
!project.nodes.every(isResultProjectNode) ||
|
||||
!Array.isArray(project.edges) ||
|
||||
@@ -3954,6 +3967,7 @@ function isResultProjectNode(value: unknown): value is ResultProjectNode {
|
||||
typeof data.componentType === "string" &&
|
||||
typeof data.modelType === "string" &&
|
||||
Array.isArray(data.ports) &&
|
||||
data.ports.every(isResultPortDefinition) &&
|
||||
isRecord(data.parameters) &&
|
||||
(data.parameterUnits === undefined || isRecord(data.parameterUnits)) &&
|
||||
[0, 90, 180, 270].includes(Number(data.rotation)) &&
|
||||
@@ -3961,14 +3975,32 @@ function isResultProjectNode(value: unknown): value is ResultProjectNode {
|
||||
);
|
||||
}
|
||||
|
||||
function isResultPortDefinition(value: unknown): value is ResultPortDefinition {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
typeof value.name === "string" &&
|
||||
value.name.length > 0 &&
|
||||
typeof value.kind === "string" &&
|
||||
typeof value.domain === "string" &&
|
||||
typeof value.nominalRole === "string" &&
|
||||
(value.positiveFlowDirection === undefined ||
|
||||
typeof value.positiveFlowDirection === "string") &&
|
||||
(value.side === "left" || value.side === "right")
|
||||
);
|
||||
}
|
||||
|
||||
function isResultProjectEdge(value: unknown): value is ResultProjectEdge {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
typeof value.id === "string" &&
|
||||
typeof value.source === "string" &&
|
||||
typeof value.target === "string" &&
|
||||
(value.sourceHandle === null || typeof value.sourceHandle === "string") &&
|
||||
(value.targetHandle === null || typeof value.targetHandle === "string")
|
||||
typeof value.sourceHandle === "string" &&
|
||||
value.sourceHandle.length > 0 &&
|
||||
typeof value.targetHandle === "string" &&
|
||||
value.targetHandle.length > 0 &&
|
||||
isRecord(value.data) &&
|
||||
typeof value.data.isContactEdge === "boolean"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,12 +63,44 @@ export const MECMAS21_PORT_ANCHORS = {
|
||||
y: 24,
|
||||
},
|
||||
} as const;
|
||||
const LSTP00A_STROKE = "#00af00";
|
||||
const LSTP00A_STROKE_WIDTH = 3;
|
||||
const LSTP00A_CONTENT_SCALE = (64 * 0.65) / 60;
|
||||
const LSTP00A_CONTENT_CENTER = { x: 32, y: 24 } as const;
|
||||
const LSTP00A_CONTENT_TRANSFORM = `translate(${LSTP00A_CONTENT_CENTER.x} ${LSTP00A_CONTENT_CENTER.y}) scale(${LSTP00A_CONTENT_SCALE}) translate(${-LSTP00A_CONTENT_CENTER.x} ${-LSTP00A_CONTENT_CENTER.y})`;
|
||||
export const LSTP00A_PORT_ANCHORS = {
|
||||
left: {
|
||||
x:
|
||||
LSTP00A_CONTENT_CENTER.x +
|
||||
(2 - LSTP00A_CONTENT_CENTER.x) * LSTP00A_CONTENT_SCALE,
|
||||
y: LSTP00A_CONTENT_CENTER.y,
|
||||
},
|
||||
right: {
|
||||
x:
|
||||
LSTP00A_CONTENT_CENTER.x +
|
||||
(62 - LSTP00A_CONTENT_CENTER.x) * LSTP00A_CONTENT_SCALE,
|
||||
y: LSTP00A_CONTENT_CENTER.y,
|
||||
},
|
||||
} as const;
|
||||
const PNRP17_STROKE = "#8b134f";
|
||||
const PNRP17_FILL = "#cf9db6";
|
||||
const PNRP17_LIGHT_FILL = "#f5ebf0";
|
||||
// PNRP17 使用最终像素线宽;几何缩放不再改变参考图中的粗细层级。
|
||||
const PNRP17_PRIMARY_STROKE_WIDTH = 2;
|
||||
const PNRP17_DETAIL_STROKE_WIDTH = 1.5;
|
||||
const PNRP17_PRIMARY_STROKE_WIDTH = 2.1;
|
||||
const PNRP17_DETAIL_STROKE_WIDTH = 1.9;
|
||||
const PNRP17_VERTICAL_CENTER = 24.5;
|
||||
const PNRP17_VERTICAL_SCALE = 1.21;
|
||||
const PNRP17_CONTENT_TRANSFORM = `translate(0 ${PNRP17_VERTICAL_CENTER}) scale(1 ${PNRP17_VERTICAL_SCALE}) translate(0 ${-PNRP17_VERTICAL_CENTER})`;
|
||||
const pnrp17ScaledY = (y: number) =>
|
||||
PNRP17_VERTICAL_CENTER +
|
||||
(y - PNRP17_VERTICAL_CENTER) * PNRP17_VERTICAL_SCALE;
|
||||
export const PNRP17_PORT_ANCHORS = {
|
||||
bottom: { x: 25.5, y: pnrp17ScaledY(48) },
|
||||
upperLeft: { x: 2, y: pnrp17ScaledY(4.4) },
|
||||
middleLeft: { x: 2, y: pnrp17ScaledY(24.1) },
|
||||
upperRight: { x: 62, y: pnrp17ScaledY(4.4) },
|
||||
middleRight: { x: 62, y: pnrp17ScaledY(24.1) },
|
||||
} as const;
|
||||
|
||||
const mechanicalStroke = {
|
||||
fill: "none",
|
||||
@@ -200,8 +232,8 @@ export function AmesimForcSymbol() {
|
||||
export function AmesimMecmas21Symbol({
|
||||
parameters,
|
||||
}: ComponentSymbolRendererProps) {
|
||||
const frictionEnabled = Number(parameters?.useFriction) === 2;
|
||||
const stopType = Number(parameters?.stoptype);
|
||||
const frictionEnabled = Number(parameters?.useFriction ?? 2) === 2;
|
||||
const stopType = Number(parameters?.stoptype ?? 1);
|
||||
const limitsEnabled = stopType === 1 || stopType === 2 || stopType === 3;
|
||||
const showGuides = frictionEnabled || limitsEnabled;
|
||||
const variant = frictionEnabled
|
||||
@@ -342,159 +374,336 @@ export function AmesimMecmas21Symbol({
|
||||
export function AmesimLstp00aSymbol() {
|
||||
return (
|
||||
<SymbolSvg className="component-symbol-amesim-lstp00a" domain="mechanical">
|
||||
<path d="M2 24h8M54 24h8" {...mechanicalStroke} />
|
||||
<g transform="rotate(90 32 24)">
|
||||
<path d="M32 2v7M32 39v7" {...mechanicalStroke} />
|
||||
<path d="M27 9V5h10v4M27 39v4h10v-4" {...mechanicalStroke} />
|
||||
<rect
|
||||
fill={SYMBOL_FILL}
|
||||
height="3"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeWidth="1.6"
|
||||
width="46"
|
||||
x="9"
|
||||
y="10"
|
||||
/>
|
||||
<rect
|
||||
fill={SYMBOL_FILL}
|
||||
height="3"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeWidth="1.6"
|
||||
width="46"
|
||||
x="9"
|
||||
y="31"
|
||||
/>
|
||||
<rect
|
||||
fill={SYMBOL_FILL}
|
||||
height="3"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeWidth="1.6"
|
||||
width="46"
|
||||
x="9"
|
||||
y="39"
|
||||
<g
|
||||
data-lstp00a-part="symbol"
|
||||
fill="#ffffff"
|
||||
stroke={LSTP00A_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={LSTP00A_STROKE_WIDTH}
|
||||
transform={LSTP00A_CONTENT_TRANSFORM}
|
||||
>
|
||||
<path
|
||||
d="M2 24H7.5"
|
||||
data-lstp00a-part="left-extension"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M14 13v4c0 2 12 2 12 4s-12 2-12 4 12 2 12 6"
|
||||
d="M56.5 24H62"
|
||||
data-lstp00a-part="right-extension"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
data-lstp00a-part="left-socket"
|
||||
d="M13.75 19.75H6.25V28.25H13.75"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
data-lstp00a-part="right-socket"
|
||||
d="M49.75 19.75H57.25V28.25H49.75"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M28.75 17.2C31.2 16 32.6 0 35 0c1.4 0 2.2 17.2 3.7 17.2 2.8 0 3.8-16 7.3-16"
|
||||
data-lstp00a-part="spring"
|
||||
fill="none"
|
||||
stroke={SYMBOL_ACCENT}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path d="M36 18v-5M36 18h14v6H36v7M40 22h6" {...mechanicalStroke} />
|
||||
<path
|
||||
d="M28.75 39H36M41 39H46M36 31V47M33.75 27H41V50.5H33.75"
|
||||
data-lstp00a-part="lower-link"
|
||||
fill="none"
|
||||
/>
|
||||
<rect
|
||||
data-lstp00a-part="left-rail"
|
||||
height="53"
|
||||
width="3.75"
|
||||
x="13.75"
|
||||
y="-2.5"
|
||||
/>
|
||||
<rect
|
||||
data-lstp00a-part="middle-rail"
|
||||
height="53"
|
||||
width="3.75"
|
||||
x="25"
|
||||
y="-2.5"
|
||||
/>
|
||||
<rect
|
||||
data-lstp00a-part="right-rail"
|
||||
height="53"
|
||||
width="3.75"
|
||||
x="46"
|
||||
y="-2.5"
|
||||
/>
|
||||
</g>
|
||||
</SymbolSvg>
|
||||
);
|
||||
}
|
||||
|
||||
export function AmesimLmechn1Symbol() {
|
||||
const leftPortRows = [7, 12, 17, 22, 27, 32, 37, 42];
|
||||
export const LMECHN1_DEFAULT_RIGHT_PORT_COUNT = 2;
|
||||
export const LMECHN1_MAX_RIGHT_PORT_COUNT = 20;
|
||||
const LMECHN1_PALETTE_RIGHT_PORT_COUNT = 5;
|
||||
const LMECHN1_PORT_ROW_SPACING = 13;
|
||||
const LMECHN1_BODY_LEFT = 18;
|
||||
const LMECHN1_BODY_RIGHT = 46;
|
||||
const LMECHN1_SOCKET_WIDTH = 6;
|
||||
const LMECHN1_SOCKET_HALF_HEIGHT = 2.5;
|
||||
const LMECHN1_LEFT_SOCKET_OUTER_X = LMECHN1_BODY_LEFT - LMECHN1_SOCKET_WIDTH;
|
||||
const LMECHN1_RIGHT_SOCKET_OUTER_X = LMECHN1_BODY_RIGHT + LMECHN1_SOCKET_WIDTH;
|
||||
|
||||
export function lmechn1RightPortCount(
|
||||
parameters?: ComponentSymbolRendererProps["parameters"],
|
||||
) {
|
||||
const candidate = Number(parameters?.v1);
|
||||
if (!Number.isInteger(candidate)) {
|
||||
return LMECHN1_DEFAULT_RIGHT_PORT_COUNT;
|
||||
}
|
||||
return Math.min(
|
||||
LMECHN1_MAX_RIGHT_PORT_COUNT,
|
||||
Math.max(1, candidate),
|
||||
);
|
||||
}
|
||||
|
||||
export function lmechn1SymbolMetrics(
|
||||
parameters?: ComponentSymbolRendererProps["parameters"],
|
||||
) {
|
||||
const rightPortCount = lmechn1RightPortCount(parameters);
|
||||
const requestedBodyHeight = Math.max(
|
||||
28,
|
||||
12 + (rightPortCount - 1) * LMECHN1_PORT_ROW_SPACING,
|
||||
);
|
||||
const viewBoxHeight = Math.max(48, requestedBodyHeight + 8);
|
||||
const bodyHeight = Math.min(requestedBodyHeight, viewBoxHeight - 8);
|
||||
const bodyTop = (viewBoxHeight - bodyHeight) / 2;
|
||||
const rightPortSpan = (rightPortCount - 1) * LMECHN1_PORT_ROW_SPACING;
|
||||
const firstPortY = (viewBoxHeight - rightPortSpan) / 2;
|
||||
const rightPortRows = Array.from(
|
||||
{ length: rightPortCount },
|
||||
(_, index) => firstPortY + index * LMECHN1_PORT_ROW_SPACING,
|
||||
);
|
||||
return {
|
||||
bodyHeight,
|
||||
bodyTop,
|
||||
rightPortCount,
|
||||
rightPortRows,
|
||||
viewBoxHeight,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function AmesimLmechn1Symbol({
|
||||
parameters,
|
||||
surface,
|
||||
}: ComponentSymbolRendererProps) {
|
||||
// 组件库始终展示一个固定、易辨识的标准图;工作区才按实际端口数展开。
|
||||
const metrics = lmechn1SymbolMetrics(
|
||||
surface === "palette"
|
||||
? { ...parameters, v1: LMECHN1_PALETTE_RIGHT_PORT_COUNT }
|
||||
: parameters,
|
||||
);
|
||||
const centerY = metrics.viewBoxHeight / 2;
|
||||
return (
|
||||
<SymbolSvg className="component-symbol-amesim-lmechn1" domain="mechanical">
|
||||
<rect
|
||||
data-lmechn1-part="body"
|
||||
fill={SYMBOL_FILL}
|
||||
height="44"
|
||||
height={metrics.bodyHeight}
|
||||
rx="1"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeWidth="1.8"
|
||||
width="12"
|
||||
x="27"
|
||||
y="2"
|
||||
strokeWidth="2.5"
|
||||
width={LMECHN1_BODY_RIGHT - LMECHN1_BODY_LEFT}
|
||||
x={LMECHN1_BODY_LEFT}
|
||||
y={metrics.bodyTop}
|
||||
/>
|
||||
{leftPortRows.map((y) => (
|
||||
<path
|
||||
d={`M5 ${y}h22`}
|
||||
key={y}
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="round"
|
||||
strokeWidth="1.4"
|
||||
/>
|
||||
{metrics.rightPortRows.map((y, index) => (
|
||||
<g data-lmechn1-port={`port_${index + 1}`} key={index}>
|
||||
<path
|
||||
d={`M${LMECHN1_RIGHT_SOCKET_OUTER_X} ${y}H62`}
|
||||
data-lmechn1-part="right-port-line"
|
||||
fill="none"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d={`M${LMECHN1_BODY_RIGHT} ${y - LMECHN1_SOCKET_HALF_HEIGHT}H${LMECHN1_RIGHT_SOCKET_OUTER_X}V${y + LMECHN1_SOCKET_HALF_HEIGHT}H${LMECHN1_BODY_RIGHT}`}
|
||||
data-lmechn1-part="right-port-socket"
|
||||
fill="none"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
<path d="M39 24h20" stroke={SYMBOL_STROKE} strokeWidth="1.8" />
|
||||
<circle cx="33" cy="24" fill={SYMBOL_ACCENT} r="2.2" />
|
||||
<g data-lmechn1-part="reference-port">
|
||||
<path
|
||||
d={`M2 ${centerY}H${LMECHN1_LEFT_SOCKET_OUTER_X}`}
|
||||
data-lmechn1-part="reference-port-line"
|
||||
fill="none"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d={`M${LMECHN1_BODY_LEFT} ${centerY - LMECHN1_SOCKET_HALF_HEIGHT}H${LMECHN1_LEFT_SOCKET_OUTER_X}V${centerY + LMECHN1_SOCKET_HALF_HEIGHT}H${LMECHN1_BODY_LEFT}`}
|
||||
data-lmechn1-part="reference-port-socket"
|
||||
fill="none"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</g>
|
||||
<text
|
||||
data-lmechn1-part="label"
|
||||
fill={SYMBOL_STROKE}
|
||||
fontFamily="Arial, sans-serif"
|
||||
fontSize="6.2"
|
||||
fontWeight="600"
|
||||
stroke="none"
|
||||
textAnchor="middle"
|
||||
x="32"
|
||||
y={centerY - 6.2}
|
||||
>
|
||||
<tspan x="32">linear</tspan>
|
||||
<tspan dy="6.5" x="32">node</tspan>
|
||||
<tspan dy="6.5" x="32">(displ.)</tspan>
|
||||
</text>
|
||||
</SymbolSvg>
|
||||
);
|
||||
}
|
||||
|
||||
function Pnrp17ArrowPair({ centerY }: { centerY: number }) {
|
||||
return (
|
||||
<g
|
||||
data-pnrp17-part="chamber-arrows"
|
||||
fill="none"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_DETAIL_STROKE_WIDTH}
|
||||
transform={`translate(0 ${centerY})`}
|
||||
>
|
||||
<g data-pnrp17-arrow="left" transform="translate(18.2 0)">
|
||||
<path d="M9.1 0H3.9" />
|
||||
<path d="M3.9 -2.5 0 0 3.9 2.5Z" fill="#ffffff" />
|
||||
</g>
|
||||
<g
|
||||
data-pnrp17-arrow="right"
|
||||
transform="translate(38.8 0) scale(-1 1)"
|
||||
>
|
||||
<path d="M9.1 0H3.9" />
|
||||
<path d="M3.9 -2.5 0 0 3.9 2.5Z" fill="#ffffff" />
|
||||
</g>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export function AmesimPnrp17Symbol() {
|
||||
return (
|
||||
<SymbolSvg className="component-symbol-amesim-pnrp17" domain="mechanical">
|
||||
<path
|
||||
d="M13.4 1.1H51.4V3h4.1v2.9h-4.1v1.9H18.2v13.8h-10V7h5.2V5.9H9.3V3h4.1Z"
|
||||
fill={PNRP17_FILL}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<path
|
||||
d="M8.2 26.7h12.7v13.8h2v6.7H8.2Z"
|
||||
fill={PNRP17_FILL}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<path
|
||||
d="M28 40.5h28.5v6.7H28Z"
|
||||
fill={PNRP17_FILL}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<rect
|
||||
fill={PNRP17_LIGHT_FILL}
|
||||
height="34"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
width="7.7"
|
||||
x="40.8"
|
||||
y="7.2"
|
||||
/>
|
||||
<rect fill={PNRP17_STROKE} height="34" width="1.8" x="38.8" y="7.2" />
|
||||
<rect
|
||||
fill={PNRP17_LIGHT_FILL}
|
||||
height="2.8"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
width="31.5"
|
||||
x="9.3"
|
||||
y="22.7"
|
||||
/>
|
||||
<rect
|
||||
fill={PNRP17_LIGHT_FILL}
|
||||
height="2.8"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
width="7.7"
|
||||
x="47.8"
|
||||
y="22.7"
|
||||
/>
|
||||
<path
|
||||
d="M2 4.4h7.3m46.2 0H62M2 24.1h7.3m46.2 0H62M25.5 43.4V48"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
{[15.3, 33].map((centerY) => (
|
||||
<g
|
||||
<g data-pnrp17-part="symbol" transform={PNRP17_CONTENT_TRANSFORM}>
|
||||
<path
|
||||
data-pnrp17-part="upper-shell"
|
||||
d="M13.4 1.1H51.4V3h4.1v2.9h-4.1v1.9H18.2V21.1H3.3V7.3H13.4V5.9H9.3V3h4.1Z"
|
||||
fill={PNRP17_FILL}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<rect
|
||||
data-pnrp17-part="upper-left-wall"
|
||||
fill={PNRP17_STROKE}
|
||||
height="13.8"
|
||||
width="2.9"
|
||||
x="15.3"
|
||||
y="7.3"
|
||||
/>
|
||||
<path
|
||||
data-pnrp17-part="lower-left-shell"
|
||||
d="M3.3 27H18.2V40.7H20V46.6H3.3Z"
|
||||
fill={PNRP17_FILL}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<rect
|
||||
data-pnrp17-part="lower-left-wall"
|
||||
fill={PNRP17_STROKE}
|
||||
height="13.7"
|
||||
width="2.9"
|
||||
x="15.3"
|
||||
y="27"
|
||||
/>
|
||||
<path
|
||||
data-pnrp17-part="lower-base"
|
||||
d="M28 40.5h28.5v6.7H28Z"
|
||||
fill={PNRP17_FILL}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<rect
|
||||
data-pnrp17-part="piston-sleeve"
|
||||
fill={PNRP17_LIGHT_FILL}
|
||||
height="34"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
width="7.7"
|
||||
x="40.8"
|
||||
y="7.2"
|
||||
/>
|
||||
<rect
|
||||
data-pnrp17-part="piston-face"
|
||||
fill={PNRP17_STROKE}
|
||||
height="34"
|
||||
width="1.8"
|
||||
x="38.8"
|
||||
y="7.2"
|
||||
/>
|
||||
<rect
|
||||
data-pnrp17-part="horizontal-rod-left"
|
||||
fill={PNRP17_LIGHT_FILL}
|
||||
height="2.8"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
width="31.5"
|
||||
x="9.3"
|
||||
y="22.7"
|
||||
/>
|
||||
<rect
|
||||
data-pnrp17-part="horizontal-rod-right"
|
||||
fill={PNRP17_LIGHT_FILL}
|
||||
height="2.8"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
width="7.7"
|
||||
x="47.8"
|
||||
y="22.7"
|
||||
/>
|
||||
<path
|
||||
data-pnrp17-part="horizontal-extensions"
|
||||
d="M2 4.4h7.3m46.2 0H62M2 24.1h7.3m46.2 0H62"
|
||||
fill="none"
|
||||
key={centerY}
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeLinejoin="miter"
|
||||
strokeWidth={PNRP17_DETAIL_STROKE_WIDTH}
|
||||
>
|
||||
<path d={`M28.8 ${centerY}h-3.4`} />
|
||||
<path
|
||||
d={`M25.4 ${centerY - 2.5} 21.5 ${centerY}l3.9 2.5Z`}
|
||||
fill="#ffffff"
|
||||
/>
|
||||
<path d={`M30.9 ${centerY}h3.4`} />
|
||||
<path
|
||||
d={`M34.3 ${centerY - 2.5} 38.3 ${centerY} 34.3 ${centerY + 2.5}Z`}
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<path
|
||||
data-pnrp17-part="bottom-port"
|
||||
d="M25.5 43.4V48"
|
||||
fill="none"
|
||||
stroke={PNRP17_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth={PNRP17_PRIMARY_STROKE_WIDTH}
|
||||
/>
|
||||
<Pnrp17ArrowPair centerY={15.15} />
|
||||
<Pnrp17ArrowPair centerY={32.9} />
|
||||
</g>
|
||||
</SymbolSvg>
|
||||
);
|
||||
}
|
||||
@@ -118,6 +118,26 @@ export function AmesimPnvo001SignalSymbol() {
|
||||
|
||||
type PipeSection = "C" | "R";
|
||||
|
||||
const PIPE_NETWORK_CONTENT_CENTER = { x: 32, y: 24 } as const;
|
||||
const PIPE_NETWORK_CONTENT_SCALE = 0.64;
|
||||
const PIPE_NETWORK_CONTENT_TRANSFORM = `translate(${PIPE_NETWORK_CONTENT_CENTER.x} ${PIPE_NETWORK_CONTENT_CENTER.y}) scale(${PIPE_NETWORK_CONTENT_SCALE}) translate(${-PIPE_NETWORK_CONTENT_CENTER.x} ${-PIPE_NETWORK_CONTENT_CENTER.y})`;
|
||||
|
||||
function scaledPipeNetworkPoint(x: number, y: number) {
|
||||
return {
|
||||
x:
|
||||
PIPE_NETWORK_CONTENT_CENTER.x +
|
||||
(x - PIPE_NETWORK_CONTENT_CENTER.x) * PIPE_NETWORK_CONTENT_SCALE,
|
||||
y:
|
||||
PIPE_NETWORK_CONTENT_CENTER.y +
|
||||
(y - PIPE_NETWORK_CONTENT_CENTER.y) * PIPE_NETWORK_CONTENT_SCALE,
|
||||
};
|
||||
}
|
||||
|
||||
export const PIPE_NETWORK_PORT_ANCHORS = {
|
||||
left: scaledPipeNetworkPoint(2, 24),
|
||||
right: scaledPipeNetworkPoint(62, 24),
|
||||
} as const;
|
||||
|
||||
function PipeNetworkBody({ sections }: { sections: readonly PipeSection[] }) {
|
||||
const left = 9;
|
||||
const right = 55;
|
||||
@@ -126,7 +146,10 @@ function PipeNetworkBody({ sections }: { sections: readonly PipeSection[] }) {
|
||||
const sectionWidth = (right - left) / sections.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<g
|
||||
data-pipe-network-content="true"
|
||||
transform={PIPE_NETWORK_CONTENT_TRANSFORM}
|
||||
>
|
||||
<path
|
||||
d="M2 24h7M55 24h7"
|
||||
stroke={SYMBOL_STROKE}
|
||||
@@ -169,7 +192,7 @@ function PipeNetworkBody({ sections }: { sections: readonly PipeSection[] }) {
|
||||
{section}
|
||||
</text>
|
||||
))}
|
||||
</>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -209,34 +232,50 @@ export function AmesimPnl0003Symbol() {
|
||||
);
|
||||
}
|
||||
|
||||
function JunctionTerminal({ x, y }: { x: number; y: number }) {
|
||||
return (
|
||||
<circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
fill={SYMBOL_FILL}
|
||||
r="2"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeWidth="1.3"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const JUNCTION_STROKE = "#8b134f";
|
||||
const JUNCTION_FINE_STROKE_WIDTH = 1.5;
|
||||
const JUNCTION_COLLECTOR_STROKE_WIDTH = 4;
|
||||
const JUNCTION_CENTER_RADIUS = 2.75;
|
||||
|
||||
export const PN3NODE2_PORT_ANCHORS = {
|
||||
port1: { x: 36.6, y: 11.2 },
|
||||
port2: { x: 24.6, y: 24 },
|
||||
port3: { x: 36.6, y: 36.8 },
|
||||
} as const;
|
||||
|
||||
export const P4NODE2_PORT_ANCHORS = {
|
||||
port1: { x: 32, y: 11.2 },
|
||||
port2: { x: 19.2, y: 24 },
|
||||
port3: { x: 44.8, y: 24 },
|
||||
port4: { x: 32, y: 36.8 },
|
||||
} as const;
|
||||
|
||||
/** Three-port pneumatic junction with one inlet-side and two outlet-side ports. */
|
||||
export function AmesimPn3Node2Symbol() {
|
||||
return (
|
||||
<SymbolSvg className="component-symbol-amesim-pn3node2-svg" domain="pneumatic">
|
||||
<path
|
||||
d="M4 24h25M29 24C41 24 42 12 60 12M29 24c12 0 13 12 31 12"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={PNEUMATIC_STROKE_WIDTH}
|
||||
d="M36.6 11.2V36.8M24.6 24H27.5"
|
||||
data-junction-part="fine-branches"
|
||||
stroke={JUNCTION_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth={JUNCTION_FINE_STROKE_WIDTH}
|
||||
/>
|
||||
<path
|
||||
d="M27.5 24H36.6"
|
||||
data-junction-part="collector-branch"
|
||||
data-port-name="port_2"
|
||||
stroke={JUNCTION_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth={JUNCTION_COLLECTOR_STROKE_WIDTH}
|
||||
/>
|
||||
<circle
|
||||
cx="36.6"
|
||||
cy="24"
|
||||
data-junction-part="center"
|
||||
fill={JUNCTION_STROKE}
|
||||
r={JUNCTION_CENTER_RADIUS}
|
||||
/>
|
||||
<circle cx="29" cy="24" fill={SYMBOL_ACCENT} r="3" />
|
||||
<JunctionTerminal x={3} y={24} />
|
||||
<JunctionTerminal x={61} y={12} />
|
||||
<JunctionTerminal x={61} y={36} />
|
||||
</SymbolSvg>
|
||||
);
|
||||
}
|
||||
@@ -246,17 +285,27 @@ export function AmesimP4Node2Symbol() {
|
||||
return (
|
||||
<SymbolSvg className="component-symbol-amesim-p4node2-svg" domain="pneumatic">
|
||||
<path
|
||||
d="M4 24h24M28 24C40 24 42 8 60 8M28 24h32M28 24c12 0 14 16 32 16"
|
||||
stroke={SYMBOL_STROKE}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={PNEUMATIC_STROKE_WIDTH}
|
||||
d="M32 11.2V36.8M32 24H44.8M19.2 24H22.1"
|
||||
data-junction-part="fine-branches"
|
||||
stroke={JUNCTION_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth={JUNCTION_FINE_STROKE_WIDTH}
|
||||
/>
|
||||
<path
|
||||
d="M22.1 24H32"
|
||||
data-junction-part="collector-branch"
|
||||
data-port-name="port_2"
|
||||
stroke={JUNCTION_STROKE}
|
||||
strokeLinecap="butt"
|
||||
strokeWidth={JUNCTION_COLLECTOR_STROKE_WIDTH}
|
||||
/>
|
||||
<circle
|
||||
cx="32"
|
||||
cy="24"
|
||||
data-junction-part="center"
|
||||
fill={JUNCTION_STROKE}
|
||||
r={JUNCTION_CENTER_RADIUS}
|
||||
/>
|
||||
<circle cx="28" cy="24" fill={SYMBOL_ACCENT} r="3" />
|
||||
<JunctionTerminal x={3} y={24} />
|
||||
<JunctionTerminal x={61} y={8} />
|
||||
<JunctionTerminal x={61} y={24} />
|
||||
<JunctionTerminal x={61} y={40} />
|
||||
</SymbolSvg>
|
||||
);
|
||||
}
|
||||
@@ -42,8 +42,6 @@ export type ComponentSymbolLayout = {
|
||||
symbolSize: ComponentSymbolSize;
|
||||
nodeSize: ComponentSymbolSize;
|
||||
portAnchors?: ComponentSymbolPortAnchors;
|
||||
/** 仅用于识别 presentationLayoutVersion 之前保存的固定尺寸接触连接。 */
|
||||
legacyPortAnchors?: ComponentSymbolPortAnchors;
|
||||
};
|
||||
|
||||
export type ComponentSymbolParameters = Readonly<
|
||||
@@ -52,11 +50,15 @@ export type ComponentSymbolParameters = Readonly<
|
||||
|
||||
export type ComponentSymbolRendererProps = {
|
||||
parameters?: ComponentSymbolParameters;
|
||||
surface?: "palette" | "canvas";
|
||||
};
|
||||
|
||||
export type SymbolDefinition = {
|
||||
presentation: ComponentSymbolPresentation;
|
||||
render: ComponentType<ComponentSymbolRendererProps>;
|
||||
layout: ComponentSymbolLayout;
|
||||
layoutForParameters?: (
|
||||
parameters?: ComponentSymbolParameters,
|
||||
) => ComponentSymbolLayout;
|
||||
variantKey?: (parameters?: ComponentSymbolParameters) => string;
|
||||
};
|
||||
+536
-171
@@ -460,7 +460,7 @@ textarea {
|
||||
|
||||
.palette-list {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.parameter-form {
|
||||
@@ -965,46 +965,14 @@ textarea {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.palette-display-mode {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 1px;
|
||||
padding: 1px;
|
||||
border: 1px solid #cad5e1;
|
||||
border-radius: 5px;
|
||||
background: #f4f7fa;
|
||||
}
|
||||
|
||||
.palette-display-mode-button {
|
||||
display: grid;
|
||||
width: 23px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
place-items: center;
|
||||
color: #64748b;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.palette-display-mode-button:hover {
|
||||
color: #155a94;
|
||||
background: #e6f0f8;
|
||||
}
|
||||
|
||||
.palette-display-mode-button:focus-visible {
|
||||
outline: 2px solid #1d6fb8;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.palette-display-mode-button[aria-pressed="true"] {
|
||||
color: #155a94;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.16);
|
||||
.palette-panel-title > span {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.catalog-source {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
@@ -1014,19 +982,54 @@ textarea {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.catalog-source > span {
|
||||
.catalog-source-dot {
|
||||
flex: 0 0 auto;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #d49b16;
|
||||
}
|
||||
|
||||
.catalog-source.ready > span {
|
||||
.catalog-source.ready > .catalog-source-dot {
|
||||
background: #198754;
|
||||
}
|
||||
|
||||
.catalog-source.fallback > span {
|
||||
background: #b45309;
|
||||
.catalog-source.error {
|
||||
color: #9f1d1d;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.catalog-source.error > .catalog-source-dot {
|
||||
background: #c53030;
|
||||
}
|
||||
|
||||
.catalog-source-tooltip {
|
||||
position: absolute;
|
||||
top: calc(100% + 7px);
|
||||
right: 0;
|
||||
z-index: 30;
|
||||
width: 250px;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #e3a4a4;
|
||||
border-radius: 5px;
|
||||
color: #7f1d1d;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
white-space: normal;
|
||||
background: #fff7f7;
|
||||
box-shadow: 0 6px 16px rgba(75, 20, 20, 0.18);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease, visibility 120ms ease;
|
||||
}
|
||||
|
||||
.catalog-source.error:hover .catalog-source-tooltip,
|
||||
.catalog-source.error:focus-visible .catalog-source-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.palette-library + .palette-library {
|
||||
@@ -1039,18 +1042,24 @@ textarea {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.palette-library-heading strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #17212b;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.palette-library-heading small {
|
||||
color: #64748b;
|
||||
font-size: 10px;
|
||||
font-size: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -1061,8 +1070,12 @@ textarea {
|
||||
.palette-category-title {
|
||||
margin-bottom: 6px;
|
||||
color: #526172;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.palette-category-title > span {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.palette-collapse-toggle {
|
||||
@@ -1107,10 +1120,6 @@ textarea {
|
||||
|
||||
.palette-category-items {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-category-items {
|
||||
grid-template-columns: repeat(auto-fill, 76px);
|
||||
gap: 6px;
|
||||
justify-content: start;
|
||||
@@ -1126,15 +1135,19 @@ textarea {
|
||||
|
||||
.palette-item {
|
||||
display: grid;
|
||||
grid-template-columns: 36px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 52px;
|
||||
padding: 8px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: 48px 16px;
|
||||
gap: 4px;
|
||||
width: 76px;
|
||||
height: 82px;
|
||||
min-height: 82px;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 5px;
|
||||
border: 1px solid #d6dee9;
|
||||
border-radius: 6px;
|
||||
place-items: center;
|
||||
color: #22303f;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
cursor: grab;
|
||||
transition: border-color 120ms ease, box-shadow 120ms ease,
|
||||
@@ -1159,34 +1172,36 @@ textarea {
|
||||
}
|
||||
|
||||
.palette-item-copy {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.palette-item-copy strong {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.palette-item small {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
overflow: hidden;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.palette-icon {
|
||||
display: grid;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex: 0 0 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.palette-canvas-drag-templates {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.component-symbol {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
@@ -1252,51 +1267,6 @@ textarea {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-list {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-item {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: 48px 16px;
|
||||
gap: 4px;
|
||||
width: 76px;
|
||||
height: 82px;
|
||||
min-height: 82px;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 5px;
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-item-copy {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-item-copy strong {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-item-copy small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.palette[data-view-mode="icons"] .palette-icon {
|
||||
flex: 0 0 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.palette-icon-preview {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
@@ -1360,6 +1330,99 @@ textarea {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.modeling-canvas-stack {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbars {
|
||||
--flow-viewport-scrollbar-size: 12px;
|
||||
position: absolute;
|
||||
z-index: 7;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
overflow: scroll;
|
||||
background: #e5eaf0;
|
||||
outline: none;
|
||||
overscroll-behavior: contain;
|
||||
pointer-events: auto;
|
||||
scrollbar-color: #8493a5 #e5eaf0;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar-horizontal {
|
||||
right: var(--flow-viewport-scrollbar-size);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: var(--flow-viewport-scrollbar-size);
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar-vertical {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: var(--flow-viewport-scrollbar-size);
|
||||
width: var(--flow-viewport-scrollbar-size);
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar-spacer {
|
||||
display: block;
|
||||
min-width: 1px;
|
||||
min-height: 1px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar-corner {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: var(--flow-viewport-scrollbar-size);
|
||||
height: var(--flow-viewport-scrollbar-size);
|
||||
background: #e5eaf0;
|
||||
box-shadow: inset 1px 1px #d5dce5;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar:focus-visible {
|
||||
box-shadow: inset 0 0 0 2px #2f72ad;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar::-webkit-scrollbar-track {
|
||||
background: #e5eaf0;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar::-webkit-scrollbar-thumb {
|
||||
min-width: 28px;
|
||||
min-height: 28px;
|
||||
border: 2px solid #e5eaf0;
|
||||
border-radius: 999px;
|
||||
background: #8493a5;
|
||||
}
|
||||
|
||||
.flow-viewport-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #65768a;
|
||||
}
|
||||
|
||||
.flow-canvas .react-flow__panel.bottom {
|
||||
bottom: 14px;
|
||||
}
|
||||
|
||||
.recovery-banner button {
|
||||
min-width: 48px;
|
||||
height: 28px;
|
||||
@@ -1461,6 +1524,41 @@ textarea {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.flow-canvas .react-flow__node.pending-paste-node,
|
||||
.flow-canvas .react-flow__node.pending-paste-node .sim-node,
|
||||
.flow-canvas .react-flow__node.pending-paste-node .node-symbol-envelope,
|
||||
.flow-canvas .react-flow__node.pending-paste-node .port-handle {
|
||||
cursor: none;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.flow-canvas .react-flow__node.pending-paste-node {
|
||||
opacity: 0.72;
|
||||
filter: drop-shadow(0 0 4px rgba(29, 111, 184, 0.5));
|
||||
}
|
||||
|
||||
.flow-canvas .react-flow__edge.pending-paste-edge {
|
||||
opacity: 0.72;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flow-canvas .react-flow__edge.pending-paste-edge .react-flow__edge-path {
|
||||
stroke: #1d6fb8;
|
||||
stroke-dasharray: 5 4;
|
||||
}
|
||||
|
||||
.pending-paste-hint {
|
||||
padding: 7px 11px;
|
||||
border: 1px solid #76a8cf;
|
||||
border-radius: 5px;
|
||||
color: #174f7a;
|
||||
background: rgba(239, 248, 255, 0.96);
|
||||
box-shadow: 0 2px 8px rgba(23, 79, 122, 0.15);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__node.validation-error .sim-node-frame {
|
||||
border-color: #c63b32;
|
||||
box-shadow: 0 0 0 3px rgba(198, 59, 50, 0.14);
|
||||
@@ -1575,26 +1673,57 @@ textarea {
|
||||
}
|
||||
|
||||
.port-handle {
|
||||
--port-handle-visual-size: 6px;
|
||||
z-index: 3;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
width: var(--port-handle-visual-size);
|
||||
min-width: var(--port-handle-visual-size);
|
||||
max-width: var(--port-handle-visual-size);
|
||||
height: var(--port-handle-visual-size);
|
||||
min-height: var(--port-handle-visual-size);
|
||||
max-height: var(--port-handle-visual-size);
|
||||
aspect-ratio: 1 / 1;
|
||||
box-sizing: border-box;
|
||||
border: 1.5px solid var(--port-handle-color, #475569);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.82);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
opacity: 1;
|
||||
transition: opacity 100ms ease, border-color 100ms ease,
|
||||
background 100ms ease, box-shadow 100ms ease;
|
||||
transition: opacity 100ms ease;
|
||||
}
|
||||
|
||||
.port-handle::before {
|
||||
position: absolute;
|
||||
inset: -4.5px;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
content: "";
|
||||
pointer-events: inherit;
|
||||
}
|
||||
|
||||
.port-handle-physical {
|
||||
--port-handle-color: #a12b70;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.port-handle-signal {
|
||||
--port-handle-color: #dc2626;
|
||||
border-radius: 2px;
|
||||
border-radius: 1.5px;
|
||||
}
|
||||
|
||||
.port-handle-glyph {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
transition: filter 100ms ease;
|
||||
}
|
||||
|
||||
.port-handle-glyph-shape {
|
||||
fill: var(--port-handle-fill, #ffffff);
|
||||
stroke: var(--port-handle-color, #475569);
|
||||
stroke-width: 1;
|
||||
transition: fill 100ms ease, stroke 100ms ease;
|
||||
}
|
||||
|
||||
.port-handle.port-connection-connected {
|
||||
@@ -1602,18 +1731,46 @@ textarea {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.model-contract-warning {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
top: -9px;
|
||||
right: -9px;
|
||||
display: grid;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
color: #ffffff;
|
||||
background: #c53b32;
|
||||
box-shadow: 0 1px 4px rgba(127, 29, 29, 0.42);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
pointer-events: auto;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.sim-node.model-contract-incompatible .component-symbol-svg {
|
||||
filter: drop-shadow(0 0 3px rgba(198, 59, 50, 0.72));
|
||||
}
|
||||
|
||||
.port-handle.port-connection-origin {
|
||||
border-color: #d97706;
|
||||
background: #fff7d6;
|
||||
box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.24),
|
||||
0 0 8px rgba(217, 119, 6, 0.42);
|
||||
--port-handle-color: #d97706;
|
||||
--port-handle-fill: #fff7d6;
|
||||
}
|
||||
|
||||
.port-handle.port-connection-origin .port-handle-glyph {
|
||||
filter: drop-shadow(0 0 4px rgba(245, 158, 11, 0.46));
|
||||
}
|
||||
|
||||
.port-handle.port-connection-compatible {
|
||||
border-color: #15803d;
|
||||
background: #dcfce7;
|
||||
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.24),
|
||||
0 0 9px rgba(21, 128, 61, 0.48);
|
||||
--port-handle-color: #15803d;
|
||||
--port-handle-fill: #dcfce7;
|
||||
}
|
||||
|
||||
.port-handle.port-connection-compatible .port-handle-glyph {
|
||||
filter: drop-shadow(0 0 4px rgba(21, 128, 61, 0.5));
|
||||
}
|
||||
|
||||
.port-handle.port-connection-incompatible {
|
||||
@@ -1669,38 +1826,6 @@ textarea {
|
||||
filter: drop-shadow(0 0 2px rgba(29, 111, 184, 0.42));
|
||||
}
|
||||
|
||||
.contact-edge-hit {
|
||||
cursor: pointer;
|
||||
fill: transparent;
|
||||
pointer-events: all;
|
||||
stroke: transparent;
|
||||
}
|
||||
|
||||
.contact-edge-marker {
|
||||
fill: #ffffff;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
stroke: #64748b;
|
||||
stroke-width: 1.5;
|
||||
transition: opacity 120ms ease-out, fill 120ms ease-out, stroke 120ms ease-out;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.react-flow__edge.editor-edge-contact:hover .contact-edge-marker,
|
||||
.react-flow__edge.editor-edge-contact.selected .contact-edge-marker,
|
||||
.react-flow__edge.editor-edge-contact.editor-edge-selected .contact-edge-marker,
|
||||
.contact-edge-hit:focus-visible + .contact-edge-marker {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.react-flow__edge.editor-edge-contact.selected .contact-edge-marker,
|
||||
.react-flow__edge.editor-edge-contact.editor-edge-selected .contact-edge-marker,
|
||||
.contact-edge-hit:focus-visible + .contact-edge-marker {
|
||||
fill: #dbeafe;
|
||||
stroke: #1d6fb8;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -2150,15 +2275,6 @@ textarea {
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.results-system-canvas .contact-edge-hit {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.results-system-canvas .contact-edge-marker {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.results-chart-workspace {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
@@ -2844,6 +2960,255 @@ textarea {
|
||||
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.22);
|
||||
}
|
||||
|
||||
/* 功能 4 的审阅版使用独立 dock 前缀;旧浮动控制台样式保留,便于快速回退。 */
|
||||
.simulation-console-dock {
|
||||
position: relative;
|
||||
z-index: 30;
|
||||
display: grid;
|
||||
grid-template-rows: 38px 50px minmax(0, 1fr);
|
||||
width: 100%;
|
||||
height: var(--simulation-console-dock-height, 180px);
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
border-top: 1px solid #718297;
|
||||
color: #263442;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 -3px 12px rgba(15, 23, 42, 0.14);
|
||||
}
|
||||
|
||||
.simulation-console-dock.minimized {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.simulation-console-dock.resizing {
|
||||
cursor: row-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.simulation-console-dock-resizer {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: -4px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 8px;
|
||||
cursor: row-resize;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.simulation-console-dock-resizer::after {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background: transparent;
|
||||
content: "";
|
||||
transition: background 120ms ease;
|
||||
}
|
||||
|
||||
.simulation-console-dock-resizer:hover::after,
|
||||
.simulation-console-dock-resizer:focus-visible::after,
|
||||
.simulation-console-dock.resizing .simulation-console-dock-resizer::after {
|
||||
background: #2f72ad;
|
||||
}
|
||||
|
||||
.simulation-console-dock-resizer:focus-visible {
|
||||
outline: 2px solid #2f72ad;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.simulation-console-dock-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding: 0 10px 0 12px;
|
||||
border-bottom: 1px solid #d4dde7;
|
||||
background: #f4f7f9;
|
||||
}
|
||||
|
||||
.simulation-console-dock.minimized .simulation-console-dock-header {
|
||||
height: 40px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.simulation-console-dock-title,
|
||||
.simulation-console-dock-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.simulation-console-dock-title {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.simulation-console-dock-title strong {
|
||||
flex: 0 0 auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.simulation-console-dock-summary {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #657487;
|
||||
font-size: 11px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.simulation-console-dock-actions {
|
||||
flex: 0 0 auto;
|
||||
gap: 2px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.simulation-console-dock-actions button {
|
||||
display: grid;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
color: #526172;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.simulation-console-dock-actions button:hover:not(:disabled) {
|
||||
border-color: #bdc9d5;
|
||||
color: #155a94;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.simulation-console-dock-actions button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.38;
|
||||
}
|
||||
|
||||
.simulation-console-dock-actions button.active {
|
||||
border-color: #e2aaa5;
|
||||
color: #b42318;
|
||||
background: #fff1f0;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress {
|
||||
padding: 8px 12px 9px;
|
||||
border-bottom: 1px solid #dbe2ea;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress-label {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
color: #526172;
|
||||
font-size: 11px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress-label > span:first-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress-label > span:last-of-type,
|
||||
.simulation-console-dock-progress-label strong {
|
||||
color: #1767a8;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress-track {
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress-track > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: #2478b8;
|
||||
transition: width 160ms ease-out;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress.success .simulation-console-dock-progress-track > span {
|
||||
background: #2f7d55;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress.warning .simulation-console-dock-progress-track > span {
|
||||
background: #b7791f;
|
||||
}
|
||||
|
||||
.simulation-console-dock-progress.error .simulation-console-dock-progress-track > span {
|
||||
background: #c13d35;
|
||||
}
|
||||
|
||||
.simulation-console-dock-log {
|
||||
min-height: 0;
|
||||
padding: 8px 12px 14px;
|
||||
overflow: auto;
|
||||
background: #fbfcfd;
|
||||
font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.simulation-console-dock-empty {
|
||||
padding: 12px 4px;
|
||||
color: #8a97a6;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry {
|
||||
display: grid;
|
||||
grid-template-columns: 66px 44px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
padding: 3px 4px;
|
||||
border-bottom: 1px solid #edf1f5;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry time,
|
||||
.simulation-console-dock-entry-level {
|
||||
color: #7a8796;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry.success .simulation-console-dock-entry-level {
|
||||
color: #2f7d55;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry.warning .simulation-console-dock-entry-level {
|
||||
color: #9a6417;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry.error .simulation-console-dock-entry-level {
|
||||
color: #b42318;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry-message {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.simulation-console-dock-entry-xml {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
color: #334155;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.simulation-console.dragging {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user