Adds first version of the app. Local storage only.

This commit is contained in:
2026-09-01 14:44:49 -05:00
commit 9710f1ee5a
44 changed files with 5865 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
import { acceptedFamilies, getPortType, hasBridgingCableType } from './project'
import type { Connection, Device, PortType, Project } from './types'
/**
* Two ports connect if the sets of families they each accept (their own
* family plus anything declared in `compatibleFamilyIds`) overlap at all.
* This has to be a full set intersection, not just "does A's family appear
* in B's list" — a pairing that only exists via a shared third "bridge"
* family (e.g. USB-A Host and USB-B Device both listing 'usb-a-to-b', while
* neither's own family is 'usb-a-to-b') would otherwise never match.
*/
export function portTypesCompatible(a: PortType, b: PortType): boolean {
if (a.id === b.id) return true
const aFamilies = acceptedFamilies(a)
for (const family of acceptedFamilies(b)) {
if (aFamilies.has(family)) return true
}
return false
}
export interface ConnectionCandidate {
sourceDeviceId: string
sourcePortId: string
targetDeviceId: string
targetPortId: string
}
export interface ConnectionValidation {
valid: boolean
reason?: string
}
/**
* Central rule set for "can these two ports be wired together". Used both by
* the canvas (to accept/reject a drag-to-connect gesture) and could be reused
* server-side later since it only depends on plain domain data.
*/
export function validateConnection(
project: Project,
candidate: ConnectionCandidate,
existingConnections: Connection[] = project.connections,
): ConnectionValidation {
const { sourceDeviceId, sourcePortId, targetDeviceId, targetPortId } = candidate
if (sourceDeviceId === targetDeviceId && sourcePortId === targetPortId) {
return { valid: false, reason: 'A port cannot connect to itself.' }
}
const sourceDevice = project.devices.find((d) => d.id === sourceDeviceId)
const targetDevice = project.devices.find((d) => d.id === targetDeviceId)
const sourcePort = sourceDevice?.ports.find((p) => p.id === sourcePortId)
const targetPort = targetDevice?.ports.find((p) => p.id === targetPortId)
if (!sourceDevice || !targetDevice || !sourcePort || !targetPort) {
return { valid: false, reason: 'Unknown device or port.' }
}
if (sourcePort.direction === 'input' && targetPort.direction === 'input') {
return { valid: false, reason: 'Cannot connect two input ports.' }
}
if (sourcePort.direction === 'output' && targetPort.direction === 'output') {
return { valid: false, reason: 'Cannot connect two output ports.' }
}
const sourcePortType = getPortType(project, sourcePort.portTypeId)
const targetPortType = getPortType(project, targetPort.portTypeId)
if (!sourcePortType || !targetPortType) {
return { valid: false, reason: 'Unknown port type.' }
}
// Directly compatible port types, or bridgeable by a specific adapter
// cable (different connectors on each end, e.g. XLR-to-TRS) even when the
// port types themselves declare no general compatibility.
if (!portTypesCompatible(sourcePortType, targetPortType) && !hasBridgingCableType(project, sourcePortType, targetPortType)) {
return {
valid: false,
reason: `${sourcePortType.name} is not compatible with ${targetPortType.name}.`,
}
}
const maxSource = sourcePortType.maxConnections ?? 1
const usedSource = existingConnections.filter(
(c) => c.sourcePortId === sourcePortId || c.targetPortId === sourcePortId,
).length
if (usedSource >= maxSource) {
return { valid: false, reason: `${sourcePort.name} already has the maximum number of cables.` }
}
const maxTarget = targetPortType.maxConnections ?? 1
const usedTarget = existingConnections.filter(
(c) => c.sourcePortId === targetPortId || c.targetPortId === targetPortId,
).length
if (usedTarget >= maxTarget) {
return { valid: false, reason: `${targetPort.name} already has the maximum number of cables.` }
}
return { valid: true }
}
export function findDevicePort(devices: Device[], deviceId: string, portId: string) {
const device = devices.find((d) => d.id === deviceId)
const port = device?.ports.find((p) => p.id === portId)
return device && port ? { device, port } : undefined
}