feat: integrate AMESim media models and editor UI

This commit is contained in:
ljz committed 2026-07-31 23:36:58 +08:00
1 parent 3e4f466114
commit e7177ab03e
181 files changed
+7042 -25270

No files matched your search

+235
View File
@@ -0,0 +1,235 @@
import type { ReactNode } from "react";
export type ComponentSymbolSurface = "palette" | "canvas";
export type ComponentSymbolPresentation = "framed" | "bare";
type ComponentSymbolProps = {
symbol: string;
surface: ComponentSymbolSurface;
className?: string;
};
type SymbolDefinition = {
presentation: ComponentSymbolPresentation;
render: () => ReactNode;
};
type SymbolSvgProps = {
children: ReactNode;
className?: string;
};
function SymbolSvg({ children, className }: SymbolSvgProps) {
return (
<svg
aria-hidden="true"
className={["component-symbol-svg", className].filter(Boolean).join(" ")}
fill="none"
focusable="false"
viewBox="0 0 64 48"
xmlns="http://www.w3.org/2000/svg"
>
{children}
</svg>
);
}
function CylinderSymbol() {
return (
<SymbolSvg>
<path d="M3 24h7M54 24h7" stroke="currentColor" strokeWidth="2" />
<rect
fill="#dbeafe"
height="26"
rx="11"
stroke="currentColor"
strokeWidth="2"
width="44"
x="10"
y="11"
/>
<path d="M33 12v24M33 24h21" stroke="currentColor" strokeWidth="2" />
</SymbolSvg>
);
}
function TankSymbol() {
return (
<SymbolSvg>
<path d="M32 2v5M32 41v5" stroke="currentColor" strokeWidth="2" />
<path
d="M16 13c0-4 7.2-7 16-7s16 3 16 7v22c0 4-7.2 7-16 7s-16-3-16-7V13Z"
fill="#dcfce7"
stroke="currentColor"
strokeWidth="2"
/>
<path d="M16 13c0 4 7.2 7 16 7s16-3 16-7" stroke="currentColor" strokeWidth="2" />
</SymbolSvg>
);
}
function PipeSymbol() {
return (
<SymbolSvg>
<path d="M2 19h60M2 29h60" stroke="currentColor" strokeWidth="2" />
<path d="M9 17v14M55 17v14" stroke="currentColor" strokeWidth="2" />
</SymbolSvg>
);
}
function OrificeSymbol() {
return (
<SymbolSvg>
<path d="M2 24h18M44 24h18" stroke="currentColor" strokeWidth="2" />
<path
d="m19 9 13 15-13 15V9ZM45 9 32 24l13 15V9Z"
fill="#ffedd5"
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="2"
/>
</SymbolSvg>
);
}
function TeeSymbol() {
return (
<SymbolSvg>
<path d="M2 27h60M32 27V3" stroke="currentColor" strokeWidth="8" />
<path d="M2 27h60M32 27V3" stroke="#f8fafc" strokeWidth="4" />
</SymbolSvg>
);
}
function GenericSymbol() {
return (
<SymbolSvg>
<path d="M2 24h9M53 24h9" stroke="currentColor" strokeWidth="2" />
<rect
fill="#f8fafc"
height="30"
rx="3"
stroke="currentColor"
strokeWidth="2"
width="42"
x="11"
y="9"
/>
<path d="m23 30 18-12M23 18h18v12" stroke="currentColor" strokeWidth="1.5" />
</SymbolSvg>
);
}
function SignalSymbol() {
return (
<SymbolSvg>
<path d="M2 24h8M54 24h8" stroke="currentColor" strokeWidth="2" />
<circle cx="32" cy="24" fill="#f8fafc" r="22" stroke="currentColor" strokeWidth="2" />
<path
d="M16 25c4-13 8-13 12 0s8 13 12 0 6-10 8-4"
stroke="#2563eb"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</SymbolSvg>
);
}
function JunctionSymbol() {
return (
<SymbolSvg>
<path d="M2 24h60M32 2v44" stroke="currentColor" strokeWidth="2" />
<circle cx="32" cy="24" fill="currentColor" r="5" />
</SymbolSvg>
);
}
function MediumSymbol({ label }: { label: "Air" | "He" }) {
return (
<SymbolSvg className="component-symbol-medium-svg">
<circle cx="32" cy="24" fill="#ffffff" r="22" stroke="#c026d3" strokeWidth="2" />
<text
className="component-symbol-medium-label"
data-symbol-label={label}
dominantBaseline="central"
fill="#a21caf"
fontFamily="Arial, sans-serif"
fontSize={label === "Air" ? 12 : 16}
fontWeight="500"
textAnchor="middle"
x="32"
y="24"
>
{label}
</text>
</SymbolSvg>
);
}
const symbolRegistry = {
cylinder: { presentation: "framed", render: CylinderSymbol },
tank: { presentation: "framed", render: TankSymbol },
pipe: { presentation: "framed", render: PipeSymbol },
orifice: { presentation: "framed", render: OrificeSymbol },
tee: { presentation: "framed", render: TeeSymbol },
generic: { presentation: "framed", render: GenericSymbol },
signal: { presentation: "framed", render: SignalSymbol },
junction: { presentation: "framed", render: JunctionSymbol },
amesim_ideal_air_medium: {
presentation: "bare",
render: () => <MediumSymbol label="Air" />,
},
amesim_helium_medium: {
presentation: "bare",
render: () => <MediumSymbol label="He" />,
},
} satisfies Record<string, SymbolDefinition>;
type RegisteredComponentSymbol = keyof typeof symbolRegistry;
function isRegisteredComponentSymbol(symbol: string): symbol is RegisteredComponentSymbol {
return Object.prototype.hasOwnProperty.call(symbolRegistry, symbol);
}
export function hasComponentSymbol(symbol: string): boolean {
return isRegisteredComponentSymbol(symbol);
}
export function componentSymbolPresentation(
symbol: string,
): ComponentSymbolPresentation {
return isRegisteredComponentSymbol(symbol)
? symbolRegistry[symbol].presentation
: "framed";
}
export function ComponentSymbol({
symbol,
surface,
className,
}: ComponentSymbolProps) {
const resolvedSymbol: RegisteredComponentSymbol = isRegisteredComponentSymbol(symbol)
? symbol
: "generic";
const definition = symbolRegistry[resolvedSymbol];
const rootClassName = [
"component-symbol",
`component-symbol-${surface}`,
`component-symbol-${resolvedSymbol}`,
className,
]
.filter(Boolean)
.join(" ");
return (
<span
aria-hidden="true"
className={rootClassName}
data-component-symbol={resolvedSymbol}
data-symbol-presentation={definition.presentation}
>
{definition.render()}
</span>
);
}