Ilya Ashikhmin 372c2c25c4 feat(domain): add relations, properties, catalogue, symbols and flow model
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>
2026-07-02 23:13:45 +02:00

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'
}