Excalidraw scene now mirrors the v5 routing model: - Clamps export as 12×12 grey rounded squares (BackgroundColor=#888888, StrokeColor=#555555, Roundness type 3). Distinct from the red IO marker diamonds so wall outlets vs. routing anchors stay readable. Frame_id propagates into the element's FrameID per the existing pattern. - Cable arrows include clamp positions as mid-vertices in the `points` array. Pre-grouped + sort.Slice-sorted by ord; each mid-vertex is added as an (x-fromAnchor.x, y-fromAnchor.y) offset. startBinding / endBinding still point at the from / to endpoint excalidraw_ids; mid-vertices are unbound (Excalidraw doesn't have per-vertex binding). - IDAssignment grows a Clamps map; PersistExcalidrawIDs accepts it and updates clamps.excalidraw_id on first export so re-exports reuse the same element ids (collab cursors / undo history survive). - Bundle-stripe overlay is **viewer-only** — Excalidraw can't represent gradient strokes losslessly, so we export individual cable arrows and let the in-app viewer derive the bundle viz. Tests: - TestBuildScene_ClampsRenderAsRectangles — 2 clamps → 2 rectangle elements + 2 ids in IDAssignment.Clamps. - TestBuildScene_ArrowPointsIncludeClamps — cable with 1 clamp → arrow.Points has 3 entries; middle vertex equals the clamp's position relative to fromAnchor. This closes the v5 slice plan (§11.10). Six slices, one branch, one redeploy below.
64 lines
1.6 KiB
Go
64 lines
1.6 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
)
|
|
|
|
// PersistExcalidrawIDs writes the assignments returned by the exporter
|
|
// back onto the corresponding rows. Idempotent: only updates rows whose
|
|
// excalidraw_id is currently NULL (the first export "owns" the id; later
|
|
// exports reuse it so mxdrw's collab cursors / undo history survive).
|
|
//
|
|
// Caller passes one map per kind; keys are the in-project row ids,
|
|
// values are the 21-char Excalidraw element ids the exporter minted.
|
|
func (s *Store) PersistExcalidrawIDs(projectID int64,
|
|
frames, devices, ports, ios, cables, clamps map[int64]string,
|
|
) error {
|
|
tx, err := s.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := updateExIDs(tx, "frames", projectID, frames); err != nil {
|
|
return err
|
|
}
|
|
if err := updateExIDs(tx, "devices", projectID, devices); err != nil {
|
|
return err
|
|
}
|
|
if err := updateExIDs(tx, "ports", projectID, ports); err != nil {
|
|
return err
|
|
}
|
|
if err := updateExIDs(tx, "io_markers", projectID, ios); err != nil {
|
|
return err
|
|
}
|
|
if err := updateExIDs(tx, "cables", projectID, cables); err != nil {
|
|
return err
|
|
}
|
|
if err := updateExIDs(tx, "clamps", projectID, clamps); err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
func updateExIDs(tx *sql.Tx, table string, projectID int64, m map[int64]string) error {
|
|
if len(m) == 0 {
|
|
return nil
|
|
}
|
|
stmt, err := tx.Prepare(
|
|
`UPDATE ` + table + `
|
|
SET excalidraw_id = ?
|
|
WHERE id = ? AND project_id = ? AND excalidraw_id IS NULL`,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
for id, exID := range m {
|
|
if _, err := stmt.Exec(exID, id, projectID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|