Pure, framework-free core: port media + color schemes with compatibility rules, typed/grouped property model with coercion and validation, catalogue store with physics seeds, SVG symbol registry with port layouts, and a mass-conserving mock flow solver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
2.8 KiB
TypeScript
107 lines
2.8 KiB
TypeScript
/**
|
|
* Port "relations" describe the medium a port carries (water, power, gas ...).
|
|
* Two ports may be connected only when their media are compatible. Colors are
|
|
* provided by swappable color schemes so the whole diagram can be re-themed.
|
|
*/
|
|
|
|
export type MediumId =
|
|
| 'water'
|
|
| 'hotWater'
|
|
| 'sewage'
|
|
| 'gas'
|
|
| 'steam'
|
|
| 'air'
|
|
| 'power'
|
|
| 'signal'
|
|
|
|
export interface Medium {
|
|
id: MediumId
|
|
label: string
|
|
/** Media (besides itself) this medium may connect to. */
|
|
compatibleWith: MediumId[]
|
|
}
|
|
|
|
export const MEDIA: Record<MediumId, Medium> = {
|
|
water: { id: 'water', label: 'Cold water', compatibleWith: ['hotWater'] },
|
|
hotWater: { id: 'hotWater', label: 'Hot water', compatibleWith: ['water', 'steam'] },
|
|
sewage: { id: 'sewage', label: 'Sewage', compatibleWith: [] },
|
|
gas: { id: 'gas', label: 'Gas', compatibleWith: [] },
|
|
steam: { id: 'steam', label: 'Steam', compatibleWith: ['hotWater'] },
|
|
air: { id: 'air', label: 'Compressed air', compatibleWith: [] },
|
|
power: { id: 'power', label: 'Power line', compatibleWith: [] },
|
|
signal: { id: 'signal', label: 'Signal / control', compatibleWith: [] }
|
|
}
|
|
|
|
export const MEDIUM_IDS = Object.keys(MEDIA) as MediumId[]
|
|
|
|
/** True when a port of medium `a` may be connected to a port of medium `b`. */
|
|
export function canConnectMedia(a: MediumId, b: MediumId): boolean {
|
|
if (a === b) return true
|
|
return (
|
|
MEDIA[a]?.compatibleWith.includes(b) === true ||
|
|
MEDIA[b]?.compatibleWith.includes(a) === true
|
|
)
|
|
}
|
|
|
|
// ---- Color schemes --------------------------------------------------------
|
|
|
|
export interface ColorScheme {
|
|
id: string
|
|
label: string
|
|
colors: Record<MediumId, string>
|
|
}
|
|
|
|
export const COLOR_SCHEMES: ColorScheme[] = [
|
|
{
|
|
id: 'default',
|
|
label: 'Default',
|
|
colors: {
|
|
water: '#2f7fe0',
|
|
hotWater: '#e0562f',
|
|
sewage: '#7a5b34',
|
|
gas: '#e0b62f',
|
|
steam: '#d14fd1',
|
|
air: '#3fb6c0',
|
|
power: '#e0d030',
|
|
signal: '#9aa0a6'
|
|
}
|
|
},
|
|
{
|
|
id: 'colorblind',
|
|
label: 'Color-blind safe',
|
|
colors: {
|
|
water: '#0072B2',
|
|
hotWater: '#D55E00',
|
|
sewage: '#8c6d31',
|
|
gas: '#E69F00',
|
|
steam: '#CC79A7',
|
|
air: '#56B4E9',
|
|
power: '#F0E442',
|
|
signal: '#999999'
|
|
}
|
|
},
|
|
{
|
|
id: 'print',
|
|
label: 'Print (mono-friendly)',
|
|
colors: {
|
|
water: '#1f3a93',
|
|
hotWater: '#7b241c',
|
|
sewage: '#4d3b1f',
|
|
gas: '#7d6608',
|
|
steam: '#4a235a',
|
|
air: '#0e6251',
|
|
power: '#7d6608',
|
|
signal: '#424949'
|
|
}
|
|
}
|
|
]
|
|
|
|
export function getColorScheme(id: string): ColorScheme {
|
|
return COLOR_SCHEMES.find((s) => s.id === id) ?? COLOR_SCHEMES[0]
|
|
}
|
|
|
|
export function mediumColor(mediumId: MediumId | string, schemeId = 'default'): string {
|
|
const scheme = getColorScheme(schemeId)
|
|
return scheme.colors[mediumId as MediumId] ?? '#9aa0a6'
|
|
}
|