From d5c94ea2d2b0bce91423ece900820cdea769ccc4 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Thu, 20 Aug 2026 11:11:04 -0700 Subject: [PATCH 1/2] Make MDL SVG rendering deterministic Wait for mounted graphs and browser completion, keep labels clear of diagram elements, and honor each view's layout direction unless explicitly overridden. --- cmd/mdl/main.go | 130 ++++++++++++------ cmd/mdl/main_test.go | 67 +++++++++ cmd/mdl/svg_e2e_test.go | 122 +++++++++++++++- cmd/mdl/webapp/dist/948.js | 2 +- cmd/mdl/webapp/dist/948.js.map | 2 +- cmd/mdl/webapp/dist/main.js | 2 +- cmd/mdl/webapp/dist/main.js.map | 2 +- cmd/mdl/webapp/src/Root.tsx | 102 +++++++++++--- cmd/mdl/webapp/src/graph-view/edge-utils.ts | 9 +- cmd/mdl/webapp/src/graph-view/graph-react.tsx | 6 +- cmd/mdl/webapp/src/graph-view/graph.ts | 125 +++++++++++++++-- cmd/mdl/webapp/src/graph-view/layout.ts | 4 +- cmd/mdl/webapp/src/hooks.ts | 14 +- cmd/mdl/webapp/src/parseModel.ts | 16 ++- examples/label_collision/model/model.go | 82 +++++++++++ 15 files changed, 592 insertions(+), 93 deletions(-) create mode 100644 examples/label_collision/model/model.go diff --git a/cmd/mdl/main.go b/cmd/mdl/main.go index b0c0dc60..4287fa98 100644 --- a/cmd/mdl/main.go +++ b/cmd/mdl/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "flag" "fmt" @@ -17,30 +18,50 @@ import ( "goa.design/model/mdl" model "goa.design/model/pkg" - "context" - "github.com/chromedp/chromedp" ) -type config struct { - debug bool - help bool - out string - dir string - port int - devmode bool - devdist string - // svg command options - views SliceFlag - all bool - direction string - compact bool - timeout time.Duration - force bool -} +type ( + config struct { + debug bool + help bool + out string + dir string + port int + devmode bool + devdist string + // svg command options + views SliceFlag + all bool + direction string + compact bool + timeout time.Duration + force bool + } + + browserAutomationResult struct { + Status string `json:"status"` + Error string `json:"error"` + } + + // SliceFlag implements flag.Value for repeated string flags. + SliceFlag []string +) -// SliceFlag implements flag.Value for repeated string flags -type SliceFlag []string +const ( + browserAutomationReadyScript = `(() => { + const status = document.documentElement.dataset.mdlAutomationStatus; + return status === "complete" || status === "error"; +})()` + + browserAutomationResultScript = `(() => { + const root = document.documentElement; + return { + status: root.dataset.mdlAutomationStatus || "", + error: root.dataset.mdlAutomationError || "", + }; +})()` +) func (s *SliceFlag) String() string { return strings.Join(*s, ",") } func (s *SliceFlag) Set(v string) error { @@ -93,8 +114,7 @@ func parseArgs() config { devmode: os.Getenv("DEVMODE") == "1", devdist: os.Getenv("DEVDIST"), // defaults for svg command - direction: "DOWN", - timeout: 20 * time.Second, + timeout: 20 * time.Second, } flag.BoolVar(&cfg.debug, "debug", false, "print debug output") @@ -106,7 +126,12 @@ func parseArgs() config { // svg command flags (safe to always register) flag.Var(&cfg.views, "view", "view key to render (repeatable)") flag.BoolVar(&cfg.all, "all", false, "render all views") - flag.StringVar(&cfg.direction, "direction", cfg.direction, "auto-layout direction: DOWN|UP|LEFT|RIGHT") + flag.StringVar( + &cfg.direction, + "direction", + cfg.direction, + "override the view auto-layout direction: DOWN|UP|LEFT|RIGHT", + ) flag.BoolVar(&cfg.compact, "compact", false, "enable compact auto-layout") flag.DurationVar(&cfg.timeout, "timeout", cfg.timeout, "timeout per view (e.g. 15s)") flag.BoolVar(&cfg.force, "force", false, "replace a locally modified installed skill") @@ -373,9 +398,16 @@ func renderViewsHeadless(baseURL, outDir string, views []string, cfg config) err }) } + direction, err := normalizeLayoutDirection(cfg.direction) + if err != nil { + return err + } for _, key := range views { // Build URL with automation params - q := fmt.Sprintf("?id=%s&auto=1&save=1&direction=%s", key, strings.ToUpper(cfg.direction)) + q := fmt.Sprintf("?id=%s&auto=1&save=1", key) + if direction != "" { + q += "&direction=" + direction + } if cfg.compact { q += "&compact=1" } @@ -393,18 +425,20 @@ func renderViewsHeadless(baseURL, outDir string, views []string, cfg config) err return nil } -func waitForFile(path string, timeout time.Duration) error { - deadline := time.Now().Add(timeout) - for time.Now().Before(deadline) { - if st, err := os.Stat(path); err == nil && st.Size() > 0 { - return nil - } - time.Sleep(100 * time.Millisecond) +func normalizeLayoutDirection(direction string) (string, error) { + normalized := strings.ToUpper(direction) + switch normalized { + case "", "DOWN", "UP", "LEFT", "RIGHT": + return normalized, nil + default: + return "", fmt.Errorf( + "invalid auto-layout direction %q: use DOWN, UP, LEFT, or RIGHT", + direction, + ) } - return fmt.Errorf("timeout waiting for %s", path) } -// navigateExec abstracts chromedp.Navigate+Wait flow +// navigateExec abstracts browser navigation and automation result handling. type navigateExec func(url string, svgPath string, timeout time.Duration) error // withChromedp wraps the chromedp session lifecycle @@ -434,23 +468,37 @@ func chromedpExec(timeout time.Duration, debug bool, fn func(exec navigateExec) defer cancel() exec := func(url string, svgPath string, timeout time.Duration) error { - // Use a tab context so the page stays open while we wait for the file + // Use a tab context so the page stays open through layout and save. tabCtx, tabCancel := chromedp.NewContext(ctx) defer tabCancel() navCtx, navCancel := context.WithTimeout(tabCtx, timeout) defer navCancel() + var result browserAutomationResult if err := chromedp.Run(navCtx, chromedp.Navigate(url), - // Wait for the graph svg to exist to ensure the app is ready - chromedp.WaitVisible(`svg#graph`, chromedp.ByQuery), + chromedp.Poll( + browserAutomationReadyScript, + nil, + chromedp.WithPollingInterval(100*time.Millisecond), + chromedp.WithPollingTimeout(0), + ), + chromedp.Evaluate(browserAutomationResultScript, &result), ); err != nil { - return err + return fmt.Errorf("wait for browser automation status: %w", err) } - - // Keep tab alive while waiting for saved file - if err := waitForFile(svgPath, timeout); err != nil { - return err + if result.Status == "error" { + if result.Error == "" { + result.Error = "unknown browser error" + } + return fmt.Errorf("browser automation failed: %s", result.Error) + } + st, err := os.Stat(svgPath) + if err != nil { + return fmt.Errorf("browser reported completion but output %s is unavailable: %w", svgPath, err) + } + if st.Size() == 0 { + return fmt.Errorf("browser reported completion but output %s is empty", svgPath) } return nil } diff --git a/cmd/mdl/main_test.go b/cmd/mdl/main_test.go index 2360d93a..0da03acf 100644 --- a/cmd/mdl/main_test.go +++ b/cmd/mdl/main_test.go @@ -1,7 +1,13 @@ package main import ( + "fmt" + "net/http" + "net/http/httptest" + "path/filepath" + "strings" "testing" + "time" "goa.design/model/mdl" ) @@ -49,3 +55,64 @@ func TestCollectViewKeys(t *testing.T) { t.Fatalf("missing keys: %v", want) } } + +func TestNormalizeLayoutDirection(t *testing.T) { + tests := []struct { + name string + input string + expected string + shouldErr bool + }{ + {name: "view default", input: "", expected: ""}, + {name: "explicit direction", input: "RIGHT", expected: "RIGHT"}, + {name: "case normalization", input: "left", expected: "LEFT"}, + {name: "invalid direction", input: "diagonal", shouldErr: true}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual, err := normalizeLayoutDirection(test.input) + if test.shouldErr { + if err == nil { + t.Fatal("expected an error") + } + return + } + if err != nil { + t.Fatalf("normalize direction: %v", err) + } + if actual != test.expected { + t.Fatalf("expected %q, got %q", test.expected, actual) + } + }) + } +} + +func TestChromedpExecReportsBrowserAutomationError(t *testing.T) { + if !hasChrome() { + t.Skip("skipping: Chrome/Chromium not available in PATH") + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/html") + _, err := fmt.Fprint(w, ` + + +`) + if err != nil { + t.Errorf("write automation page: %v", err) + } + })) + defer server.Close() + + output := filepath.Join(t.TempDir(), "missing.svg") + timeout := 15 * time.Second + err := withChromedp(timeout, false, func(exec navigateExec) error { + return exec(server.URL, output, timeout) + }) + if err == nil { + t.Fatal("expected browser automation error") + } + if !strings.Contains(err.Error(), "browser automation failed: layout exploded") { + t.Fatalf("expected browser error detail, got %v", err) + } +} diff --git a/cmd/mdl/svg_e2e_test.go b/cmd/mdl/svg_e2e_test.go index 5961c09d..902f1207 100644 --- a/cmd/mdl/svg_e2e_test.go +++ b/cmd/mdl/svg_e2e_test.go @@ -52,11 +52,10 @@ func TestSVGEndToEnd(t *testing.T) { outDir := t.TempDir() cfg := config{ - dir: outDir, - port: 0, - direction: "DOWN", - timeout: 30_000_000_000, // 30s - all: true, + dir: outDir, + port: 0, + timeout: 30_000_000_000, // 30s + all: true, } if err := runSVG("goa.design/model/examples/basic/model", cfg); err != nil { t.Fatalf("runSVG failed: %v", err) @@ -74,9 +73,29 @@ func TestSVGEndToEnd(t *testing.T) { if len(links) != 1 || links[0] != "Container%20View.svg" { t.Fatalf("expected one container-view link, got %v", links) } + if orientation := inspectNodeOrientation(t, p); orientation != "horizontal" { + t.Fatalf("expected view-defined left-to-right layout, got %s", orientation) + } if overlaps := inspectVerticalEdgeLabelOverlaps(t, p); len(overlaps) > 0 { t.Fatalf("vertical relationship labels overlap their lines: %+v", overlaps) } + + overrideDir := t.TempDir() + overrideConfig := config{ + dir: overrideDir, + port: 0, + views: SliceFlag{"SystemContext"}, + direction: "DOWN", + timeout: 30 * time.Second, + } + if err := runSVG("goa.design/model/examples/basic/model", overrideConfig); err != nil { + t.Fatalf("runSVG with direction override failed: %v", err) + } + overridePath := filepath.Join(overrideDir, "SystemContext.svg") + if orientation := inspectNodeOrientation(t, overridePath); orientation != "vertical" { + t.Fatalf("expected explicit top-to-bottom override, got %s", orientation) + } + // Cleanup generated files explicitly (t.TempDir will be removed automatically). for _, path := range []string{p, target} { if err := os.Remove(path); err != nil { @@ -118,6 +137,29 @@ func TestSVGNodeTextFits(t *testing.T) { } } +func TestSVGEdgeLabelsAvoidElements(t *testing.T) { + if !hasChrome() { + t.Skip("skipping: Chrome/Chromium not available in PATH") + } + + outDir := t.TempDir() + cfg := config{ + dir: outDir, + port: 0, + direction: "DOWN", + timeout: 30 * time.Second, + all: true, + } + if err := runSVG("goa.design/model/examples/label_collision/model", cfg); err != nil { + t.Fatalf("runSVG failed: %v", err) + } + + path := filepath.Join(outDir, "Label Collision.svg") + if collisions := inspectEdgeLabelCollisions(t, path); len(collisions) > 0 { + t.Fatalf("relationship labels overlap diagram elements: %v", collisions) + } +} + func inspectNodeTextFit(t *testing.T, path string) ([]nodeOverflow, float64, []string, int) { t.Helper() @@ -190,6 +232,76 @@ func inspectNodeTextFit(t *testing.T, path string) ([]nodeOverflow, float64, []s return overflows, height, boundaryIntersections, groupCount } +func inspectEdgeLabelCollisions(t *testing.T, path string) []string { + t.Helper() + testContext, cleanup := newChromeContext(t) + defer cleanup() + + var collisions []string + fileURL := (&url.URL{Scheme: "file", Path: path}).String() + const collisionScript = `(() => { + const tolerance = 1; + const overlaps = (first, second) => + Math.min(first.right, second.right) - Math.max(first.left, second.left) > tolerance && + Math.min(first.bottom, second.bottom) - Math.max(first.top, second.top) > tolerance; + const nodes = [...document.querySelectorAll("g.node")].map((node) => ({ + name: node.querySelector("[data-field=name]")?.textContent.trim() || node.id, + rect: node.querySelector(".nodeBorder")?.getBoundingClientRect(), + })).filter((node) => node.rect); + const labels = [...document.querySelectorAll("g.edge")].map((edge) => ({ + name: edge.querySelector(":scope > text")?.textContent.trim() || edge.id, + rect: edge.querySelector(":scope > rect")?.getBoundingClientRect(), + })).filter((label) => label.rect); + const collisions = labels.flatMap((label) => + nodes.filter((node) => overlaps(label.rect, node.rect)) + .map((node) => label.name + " / node: " + node.name)); + for (let first = 0; first < labels.length; first++) { + for (let second = first + 1; second < labels.length; second++) { + if (overlaps(labels[first].rect, labels[second].rect)) { + collisions.push(labels[first].name + " / label: " + labels[second].name); + } + } + } + return collisions; + })()` + if err := chromedp.Run(testContext, + chromedp.Navigate(fileURL), + chromedp.WaitVisible("g.edge", chromedp.ByQuery), + chromedp.Evaluate(collisionScript, &collisions), + ); err != nil { + t.Fatalf("inspect relationship label collisions: %v", err) + } + return collisions +} + +func inspectNodeOrientation(t *testing.T, path string) string { + t.Helper() + testContext, cleanup := newChromeContext(t) + defer cleanup() + + var orientation string + fileURL := (&url.URL{Scheme: "file", Path: path}).String() + const orientationScript = `(() => { + const nodes = [...document.querySelectorAll("g.node")]; + if (nodes.length < 2) return "unknown"; + const first = nodes[0].getBoundingClientRect(); + const second = nodes[1].getBoundingClientRect(); + const deltaX = Math.abs( + (first.left + first.right) / 2 - (second.left + second.right) / 2); + const deltaY = Math.abs( + (first.top + first.bottom) / 2 - (second.top + second.bottom) / 2); + return deltaX > deltaY ? "horizontal" : "vertical"; + })()` + if err := chromedp.Run(testContext, + chromedp.Navigate(fileURL), + chromedp.WaitVisible("g.node", chromedp.ByQuery), + chromedp.Evaluate(orientationScript, &orientation), + ); err != nil { + t.Fatalf("inspect node orientation: %v", err) + } + return orientation +} + func inspectVerticalEdgeLabelOverlaps(t *testing.T, path string) []verticalEdgeLabelOverlap { t.Helper() diff --git a/cmd/mdl/webapp/dist/948.js b/cmd/mdl/webapp/dist/948.js index e0817e33..7bb5a09c 100644 --- a/cmd/mdl/webapp/dist/948.js +++ b/cmd/mdl/webapp/dist/948.js @@ -1,2 +1,2 @@ -"use strict";(self.webpackChunkapp=self.webpackChunkapp||[]).push([[948],{948(e,n,r){var t=r(763),u=r(408),s=r(987);r.d(n,["Graph",0,({data:e,onSelect:n,dragMode:r})=>{const[i,a]=(0,t.useState)(null),d=(0,t.useRef)(null);return(0,t.useEffect)(()=>{if(!d.current)return;d.current.innerHTML="";const t=(0,u.ZG)(e,e=>n(e?e.id:null),r);d.current.append(t.svg),a(t),(0,u.F_)(e.id)||e.shouldSkipAutoFit()||e.fitToView();const s=()=>{e?.id&&(0,u.Kp)(e.id)};return window.addEventListener("beforeunload",s),()=>{e?.id&&(0,u.Kp)(e.id),window.removeEventListener("beforeunload",s),d.current&&(d.current.innerHTML="")}},[e,n]),(0,t.useEffect)(()=>{if(i?.svg){const e=i.svg,n=e.__cursorInteractionCleanup;n&&n(),(0,u.Qy)(e,r)}},[r,i]),(0,s.jsx)("div",{className:"graph",ref:d})}])}}]); +"use strict";(self.webpackChunkapp=self.webpackChunkapp||[]).push([[948],{948(e,n,r){var t=r(763),u=r(408),s=r(987);r.d(n,["Graph",0,({data:e,onSelect:n,onReady:r,dragMode:i})=>{const[a,d]=(0,t.useState)(null),o=(0,t.useRef)(null);return(0,t.useEffect)(()=>{if(!o.current)return;o.current.innerHTML="";const t=(0,u.ZG)(e,e=>n(e?e.id:null),i);o.current.append(t.svg),d(t),(0,u.F_)(e.id)||e.shouldSkipAutoFit()||e.fitToView(),r();const s=()=>{e?.id&&(0,u.Kp)(e.id)};return window.addEventListener("beforeunload",s),()=>{e?.id&&(0,u.Kp)(e.id),window.removeEventListener("beforeunload",s),o.current&&(o.current.innerHTML="")}},[e,n,r]),(0,t.useEffect)(()=>{if(a?.svg){const e=a.svg,n=e.__cursorInteractionCleanup;n&&n(),(0,u.Qy)(e,i)}},[i,a]),(0,s.jsx)("div",{className:"graph",ref:o})}])}}]); //# sourceMappingURL=948.js.map \ No newline at end of file diff --git a/cmd/mdl/webapp/dist/948.js.map b/cmd/mdl/webapp/dist/948.js.map index e2cc34cd..ae0e5214 100644 --- a/cmd/mdl/webapp/dist/948.js.map +++ b/cmd/mdl/webapp/dist/948.js.map @@ -1 +1 @@ -{"version":3,"file":"948.js","mappings":"qIASgC,EAAEA,OAAMC,WAAUC,eACjD,MAAOC,EAAYC,IAAiB,EAAAC,EAAAC,UAAc,MAC5CC,GAAM,EAAAF,EAAAG,QAAuB,MA0DnC,OAvDA,EAAAH,EAAAI,WAAU,KACT,IAAKF,EAAIG,QAAS,OAGlBH,EAAIG,QAAQC,UAAY,GAGxB,MAAMC,GAAI,EAAAC,EAAAC,IAAWd,EAAOe,GAAmBd,EAASc,EAAIA,EAAEC,GAAK,MAAOd,GAC1EK,EAAIG,QAAQO,OAAOL,EAAEM,KACrBd,EAAcQ,IAIT,EAAAC,EAAAM,IAAiBnB,EAAKgB,KAAQhB,EAAKoB,qBACvCpB,EAAKqB,YAIN,MAAMC,EAAqB,KACtBtB,GAAMgB,KACT,EAAAH,EAAAU,IAAcvB,EAAKgB,KAMrB,OAFAQ,OAAOC,iBAAiB,eAAgBH,GAEjC,KAEFtB,GAAMgB,KACT,EAAAH,EAAAU,IAAcvB,EAAKgB,IAGpBQ,OAAOE,oBAAoB,eAAgBJ,GAEvCf,EAAIG,UACPH,EAAIG,QAAQC,UAAY,MAGxB,CAACX,EAAMC,KAGV,EAAAI,EAAAI,WAAU,KACT,GAAIN,GAAYe,IAAK,CAEpB,MAAMA,EAAMf,EAAWe,IACjBS,EAAmBT,EAAYU,2BACjCD,GACHA,KAID,EAAAd,EAAAgB,IAAqBX,EAAKhB,EAC3B,GACE,CAACA,EAAUC,KAEP,EAAA2B,EAAAC,KAAA,OAAKC,UAAU,QAAQzB,IAAKA","sources":["webpack://app/./src/graph-view/graph-react.tsx"],"sourcesContent":["import React, {FC, useEffect, useRef, useState} from \"react\";\nimport {buildGraph, GraphData, Node, addCursorInteraction, restoreViewState, saveViewState} from \"./graph\";\n\ninterface Props {\n\tdata: GraphData;\n\tonSelect: (nodeName: string | null) => void;\n\tdragMode: 'pan' | 'select';\n}\n\nexport const Graph: FC = ({data, onSelect, dragMode}) => {\n\tconst [graphState, setGraphState] = useState(null);\n\tconst ref = useRef(null);\n\n\t// Single effect for building the graph and handling all setup/cleanup\n\tuseEffect(() => {\n\t\tif (!ref.current) return;\n\n\t\t// Clear previous content\n\t\tref.current.innerHTML = '';\n\n\t\t// Build graph with current props\n\t\tconst g = buildGraph(data, (n: Node | null) => onSelect(n ? n.id : null), dragMode);\n\t\tref.current.append(g.svg);\n\t\tsetGraphState(g);\n\t\t\n\t\t// Try to restore previous view state, otherwise use auto fit\n\t\t// Skip auto fit if we're in the middle of a reset operation to prevent infinite loop\n\t\tif (!restoreViewState(data.id) && !data.shouldSkipAutoFit()) {\n\t\t\tdata.fitToView();\n\t\t}\n\n\t\t// Save view state before page unload\n\t\tconst handleBeforeUnload = () => {\n\t\t\tif (data?.id) {\n\t\t\t\tsaveViewState(data.id);\n\t\t\t}\n\t\t};\n\t\t\n\t\twindow.addEventListener('beforeunload', handleBeforeUnload);\n\n\t\treturn () => {\n\t\t\t// Save view state before cleanup\n\t\t\tif (data?.id) {\n\t\t\t\tsaveViewState(data.id);\n\t\t\t}\n\t\t\t\n\t\t\twindow.removeEventListener('beforeunload', handleBeforeUnload);\n\t\t\t\n\t\t\tif (ref.current) {\n\t\t\t\tref.current.innerHTML = '';\n\t\t\t}\n\t\t};\n\t}, [data, onSelect]);\n\n\t// Effect for updating drag mode on existing graph\n\tuseEffect(() => {\n\t\tif (graphState?.svg) {\n\t\t\t// Clean up existing cursor interaction\n\t\t\tconst svg = graphState.svg;\n\t\t\tconst existingCleanup = (svg as any).__cursorInteractionCleanup;\n\t\t\tif (existingCleanup) {\n\t\t\t\texistingCleanup();\n\t\t\t}\n\t\t\t\n\t\t\t// Set up cursor interaction with current drag mode\n\t\t\taddCursorInteraction(svg, dragMode);\n\t\t}\n\t}, [dragMode, graphState]);\n\n\treturn
;\n}"],"names":["data","onSelect","dragMode","graphState","setGraphState","react__WEBPACK_IMPORTED_MODULE_0__","useState","ref","useRef","useEffect","current","innerHTML","g","_graph__WEBPACK_IMPORTED_MODULE_1__","ZG","n","id","append","svg","F_","shouldSkipAutoFit","fitToView","handleBeforeUnload","Kp","window","addEventListener","removeEventListener","existingCleanup","__cursorInteractionCleanup","Qy","react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__","jsx","className"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"948.js","mappings":"qIAUgC,EAAEA,OAAMC,WAAUC,UAASC,eAC1D,MAAOC,EAAYC,IAAiB,EAAAC,EAAAC,UAAc,MAC5CC,GAAM,EAAAF,EAAAG,QAAuB,MA2DnC,OAxDA,EAAAH,EAAAI,WAAU,KACT,IAAKF,EAAIG,QAAS,OAGlBH,EAAIG,QAAQC,UAAY,GAGxB,MAAMC,GAAI,EAAAC,EAAAC,IAAWf,EAAOgB,GAAmBf,EAASe,EAAIA,EAAEC,GAAK,MAAOd,GAC1EK,EAAIG,QAAQO,OAAOL,EAAEM,KACrBd,EAAcQ,IAIT,EAAAC,EAAAM,IAAiBpB,EAAKiB,KAAQjB,EAAKqB,qBACvCrB,EAAKsB,YAENpB,IAGA,MAAMqB,EAAqB,KACtBvB,GAAMiB,KACT,EAAAH,EAAAU,IAAcxB,EAAKiB,KAMrB,OAFAQ,OAAOC,iBAAiB,eAAgBH,GAEjC,KAEFvB,GAAMiB,KACT,EAAAH,EAAAU,IAAcxB,EAAKiB,IAGpBQ,OAAOE,oBAAoB,eAAgBJ,GAEvCf,EAAIG,UACPH,EAAIG,QAAQC,UAAY,MAGxB,CAACZ,EAAMC,EAAUC,KAGpB,EAAAI,EAAAI,WAAU,KACT,GAAIN,GAAYe,IAAK,CAEpB,MAAMA,EAAMf,EAAWe,IACjBS,EAAmBT,EAAYU,2BACjCD,GACHA,KAID,EAAAd,EAAAgB,IAAqBX,EAAKhB,EAC3B,GACE,CAACA,EAAUC,KAEP,EAAA2B,EAAAC,KAAA,OAAKC,UAAU,QAAQzB,IAAKA","sources":["webpack://app/./src/graph-view/graph-react.tsx"],"sourcesContent":["import React, {FC, useEffect, useRef, useState} from \"react\";\nimport {buildGraph, GraphData, Node, addCursorInteraction, restoreViewState, saveViewState} from \"./graph\";\n\ninterface Props {\n\tdata: GraphData;\n\tonSelect: (nodeName: string | null) => void;\n\tonReady: () => void;\n\tdragMode: 'pan' | 'select';\n}\n\nexport const Graph: FC = ({data, onSelect, onReady, dragMode}) => {\n\tconst [graphState, setGraphState] = useState(null);\n\tconst ref = useRef(null);\n\n\t// Single effect for building the graph and handling all setup/cleanup\n\tuseEffect(() => {\n\t\tif (!ref.current) return;\n\n\t\t// Clear previous content\n\t\tref.current.innerHTML = '';\n\n\t\t// Build graph with current props\n\t\tconst g = buildGraph(data, (n: Node | null) => onSelect(n ? n.id : null), dragMode);\n\t\tref.current.append(g.svg);\n\t\tsetGraphState(g);\n\t\t\n\t\t// Try to restore previous view state, otherwise use auto fit\n\t\t// Skip auto fit if we're in the middle of a reset operation to prevent infinite loop\n\t\tif (!restoreViewState(data.id) && !data.shouldSkipAutoFit()) {\n\t\t\tdata.fitToView();\n\t\t}\n\t\tonReady();\n\n\t\t// Save view state before page unload\n\t\tconst handleBeforeUnload = () => {\n\t\t\tif (data?.id) {\n\t\t\t\tsaveViewState(data.id);\n\t\t\t}\n\t\t};\n\t\t\n\t\twindow.addEventListener('beforeunload', handleBeforeUnload);\n\n\t\treturn () => {\n\t\t\t// Save view state before cleanup\n\t\t\tif (data?.id) {\n\t\t\t\tsaveViewState(data.id);\n\t\t\t}\n\t\t\t\n\t\t\twindow.removeEventListener('beforeunload', handleBeforeUnload);\n\t\t\t\n\t\t\tif (ref.current) {\n\t\t\t\tref.current.innerHTML = '';\n\t\t\t}\n\t\t};\n\t}, [data, onSelect, onReady]);\n\n\t// Effect for updating drag mode on existing graph\n\tuseEffect(() => {\n\t\tif (graphState?.svg) {\n\t\t\t// Clean up existing cursor interaction\n\t\t\tconst svg = graphState.svg;\n\t\t\tconst existingCleanup = (svg as any).__cursorInteractionCleanup;\n\t\t\tif (existingCleanup) {\n\t\t\t\texistingCleanup();\n\t\t\t}\n\t\t\t\n\t\t\t// Set up cursor interaction with current drag mode\n\t\t\taddCursorInteraction(svg, dragMode);\n\t\t}\n\t}, [dragMode, graphState]);\n\n\treturn
;\n}"],"names":["data","onSelect","onReady","dragMode","graphState","setGraphState","react__WEBPACK_IMPORTED_MODULE_0__","useState","ref","useRef","useEffect","current","innerHTML","g","_graph__WEBPACK_IMPORTED_MODULE_1__","ZG","n","id","append","svg","F_","shouldSkipAutoFit","fitToView","handleBeforeUnload","Kp","window","addEventListener","removeEventListener","existingCleanup","__cursorInteractionCleanup","Qy","react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__","jsx","className"],"sourceRoot":""} \ No newline at end of file diff --git a/cmd/mdl/webapp/dist/main.js b/cmd/mdl/webapp/dist/main.js index 7c7f237c..d1053471 100644 --- a/cmd/mdl/webapp/dist/main.js +++ b/cmd/mdl/webapp/dist/main.js @@ -1,2 +1,2 @@ -"use strict";(self.webpackChunkapp=self.webpackChunkapp||[]).push([[792],{486(e,t,n){n.d(t,{Root:()=>B,S:()=>E});var o=n(763),i=n(408),r=n(32);const a=e=>{const t=[];return Object.keys(e.views).filter(e=>e.endsWith("Views")).forEach(n=>{e.views[n].forEach(e=>{t.push({key:e.key,title:e.title||e.key,section:n})})}),t};n(538);var s=n(264);const l={};function d(e){const t=e.replace(/([A-Z])/g," $1");return t.charAt(0).toUpperCase()+t.slice(1)}var c=n(686),h=n(987);const g=({model:e,currentID:t,onViewChange:n,graph:o,onAutoLayout:i,onSave:r,onToggleHelp:s,saving:l,layouting:d,dragMode:c,setDragMode:g})=>{const p=a(e);return(0,h.jsxs)("div",{className:"toolbar",children:[(0,h.jsx)(u,{views:p,currentID:t,onViewChange:n}),(0,h.jsx)(A,{graph:o,onAutoLayout:i,onSave:r,onToggleHelp:s,saving:l,layouting:d,dragMode:c,setDragMode:g})]})},u=({views:e,currentID:t,onViewChange:n})=>(0,h.jsxs)("div",{children:["View:",e.length>1?(0,h.jsxs)("select",{onChange:e=>n(e.target.value),value:t,children:[(0,h.jsx)("option",{disabled:!0,value:"",hidden:!0,children:"..."}),e.map(e=>(0,h.jsx)("option",{value:e.key,children:d(e.section)+": "+e.title},e.key))]}):(0,h.jsx)("span",{style:{marginLeft:"8px",fontWeight:"bold"},children:e[0]?d(e[0].section)+": "+e[0].title:"No views available"})]}),A=({graph:e,onAutoLayout:t,onSave:n,onToggleHelp:o,saving:i,layouting:r,dragMode:a,setDragMode:s})=>(0,h.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(p,{dragMode:a,setDragMode:s})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(f,{graph:e})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(m,{graph:e})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(b,{onAutoLayout:t,layouting:r})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(y,{graph:e})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(w,{graph:e})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(v,{onSave:n,saving:i,graph:e})}),(0,h.jsx)("div",{className:"toolbar-group",children:(0,h.jsx)(C,{onToggleHelp:o})})]}),p=({dragMode:e,setDragMode:t})=>(0,h.jsx)("button",{className:"mode-toggle "+("select"===e?"select-mode":"pan-mode"),onClick:()=>t("pan"===e?"select":"pan"),"data-tooltip":"pan"===e?"Pan Mode: Drag to pan the view (T)":"Select Mode: Drag to select elements, Shift+click to add/remove selection (T)",children:"pan"===e?(0,h.jsx)("i",{className:"fas fa-hand-paper"}):(0,h.jsx)("i",{className:"fas fa-mouse-pointer"})}),f=({graph:e})=>{const t=(0,c.sy)();return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)("button",{onClick:()=>e.undo(),"data-tooltip":`Undo the last change made to the diagram (${t}+Z)`,children:(0,h.jsx)("i",{className:"fas fa-undo"})}),(0,h.jsx)("button",{onClick:()=>e.redo(),"data-tooltip":`Redo the last undone action (${t}+Shift+Z / ${t}+Y)`,children:(0,h.jsx)("i",{className:"fas fa-redo"})})]})},m=({graph:e})=>{const t=(0,c.sy)();return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)("button",{onClick:()=>e.alignSelectionH(),"data-tooltip":`Align all selected elements horizontally (left edges) (${t}+Shift+H)`,children:(0,h.jsx)("i",{className:"fas fa-align-left"})}),(0,h.jsx)("button",{onClick:()=>e.alignSelectionV(),"data-tooltip":`Align all selected elements vertically (top edges) (${t}+Shift+A)`,children:(0,h.jsx)("i",{className:"fas fa-align-left",style:{transform:"rotate(90deg)"}})}),(0,h.jsx)("button",{onClick:()=>e.distributeSelectionH(),"data-tooltip":`Distribute selected elements evenly horizontally (equal spacing) (${t}+Alt+H)`,children:(0,h.jsx)("i",{className:"fas fa-ellipsis-h"})}),(0,h.jsx)("button",{onClick:()=>e.distributeSelectionV(),"data-tooltip":`Distribute selected elements evenly vertically (equal spacing) (${t}+Alt+V)`,children:(0,h.jsx)("i",{className:"fas fa-ellipsis-v"})})]})},b=({onAutoLayout:e,layouting:t})=>{const n=(0,c.sy)();return(0,h.jsx)("button",{className:"auto-arrange",onClick:e,disabled:t,"data-tooltip":`Automatically arrange all elements using the Layered algorithm (${n}+L)`,children:t?(0,h.jsx)("i",{className:"fas fa-spinner fa-spin"}):(0,h.jsx)("i",{className:"fas fa-magic"})})},y=({graph:e})=>{const[t,n]=(0,o.useState)(e.isGridVisible()),[i,r]=(0,o.useState)(e.isSnapToGrid()),a=(0,c.sy)();return o.useEffect(()=>{const t=()=>{n(e.isGridVisible()),r(e.isSnapToGrid())};return t(),window.addEventListener("gridStateChanged",t),()=>{window.removeEventListener("gridStateChanged",t)}},[e]),(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)("button",{className:t?"active-toggle":"inactive-toggle",onClick:()=>{e.toggleGrid(),n(e.isGridVisible())},"data-tooltip":`Toggle grid visibility (${a}+G)`,children:(0,h.jsx)("i",{className:"fas fa-th"})}),(0,h.jsx)("button",{className:i?"active-toggle":"inactive-toggle",onClick:()=>{e.toggleSnapToGrid(),r(e.isSnapToGrid())},"data-tooltip":`Toggle snap to grid (${a}+Shift+G)`,children:(0,h.jsx)("i",{className:"fas fa-magnet"})}),(0,h.jsx)("button",{onClick:()=>{e.snapAllToGrid()},disabled:!i,"data-tooltip":`Snap all elements to grid (${a}+Alt+G)`,children:(0,h.jsx)("i",{className:"fas fa-border-all"})})]})},x=()=>{const[e,t]=(0,o.useState)(100);return(0,o.useEffect)(()=>{const e=()=>{const e=Math.round(100*(0,i.IX)());t(e)};e();const n=setInterval(e,100);return()=>clearInterval(n)},[]),(0,h.jsxs)("button",{onClick:()=>(0,i.a_)(1),className:"zoom-display","data-tooltip":"Click to reset zoom to 100%",children:[e,"%"]})},w=({graph:e})=>{const t=(0,c.sy)();return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)("button",{onClick:()=>{(0,i.a_)(Math.max(.1,(0,i.IX)()/1.2))},"data-tooltip":`Zoom out to see more of the diagram (${t}+-)`,children:(0,h.jsx)("i",{className:"fas fa-search-minus"})}),(0,h.jsx)(x,{}),(0,h.jsx)("button",{onClick:()=>{(0,i.a_)(Math.min(5,1.2*(0,i.IX)()))},"data-tooltip":`Zoom in to see details more clearly (${t}+=)`,children:(0,h.jsx)("i",{className:"fas fa-search-plus"})}),(0,h.jsx)("button",{onClick:()=>{e.fitToView()},"data-tooltip":`Fit diagram to view (${t}+9)`,children:(0,h.jsx)("i",{className:"fas fa-expand"})})]})},v=({onSave:e,saving:t,graph:n})=>{const[i,r]=(0,o.useState)(!1),a=(0,c.sy)();return(0,o.useEffect)(()=>{const e=()=>{r(n.changed())};e();const t=setInterval(e,100);return()=>clearInterval(t)},[n]),(0,h.jsx)("button",{className:i?"grp":"action",disabled:t,onClick:e,"data-tooltip":`Save the current diagram layout (${a}+S)`,children:t?(0,h.jsx)("i",{className:"fas fa-spinner fa-spin"}):(0,h.jsx)("i",{className:"fas fa-save"})})},C=({onToggleHelp:e})=>(0,h.jsx)("button",{onClick:e,"data-tooltip":"Show keyboard shortcuts and help information (Shift+? / Shift+F1)",children:(0,h.jsx)("i",{className:"fas fa-question-circle"})}),k=(0,o.lazy)(()=>Promise.resolve().then(n.bind(n,264)).then(e=>({default:e.Help}))),I=(0,o.lazy)(()=>n.e(948).then(n.bind(n,948)).then(e=>({default:e.Graph}))),B=({model:e,layout:t})=>(0,h.jsx)(r.Kd,{children:(0,h.jsx)(r.BV,{children:(0,h.jsx)(r.qh,{path:"/",element:(0,h.jsx)(S,{model:e,layouts:t})})})}),E=()=>{var e;(e=new URLSearchParams(document.location.search).get("id")||"")?delete l[e]:Object.keys(l).forEach(e=>delete l[e])},S=({model:e,layouts:t})=>{const[n,a]=(0,r.ok)(),d=decodeURI(n.get("id")||""),[c,u]=(0,o.useState)(!1),[A,p]=(0,o.useState)("pan"),f=((e,t,n)=>{if(l[n])return l[n];const o=((e,t,n)=>{const o=new Map,r=new Map,a=e=>{Array.isArray(e.relationships)&&e.relationships.forEach(e=>{r.set(e.id,e)})};if(e.model.people&&e.model.people.forEach(e=>{o.set(e.id,e),Array.isArray(e.relationships)&&e.relationships.forEach(e=>{r.set(e.id,e)})}),e.model.softwareSystems&&e.model.softwareSystems.forEach(e=>{o.set(e.id,e),a(e),Array.isArray(e.containers)&&e.containers.forEach(t=>{t.parent=e,o.set(t.id,t),a(t),Array.isArray(t.components)&&t.components.forEach(e=>{e.parent=t,o.set(e.id,e),a(e)})})}),e.model.deploymentNodes){const t=e=>{e.containerInstances&&e.containerInstances.forEach(t=>{const n={...o.get(t.containerId),id:t.id};o.set(n.id,n),n.parent=e,a(t)})},n=(e,i)=>{e.parent=i,o.set(e.id,e),a(e),t(e),e.children&&e.children.forEach(t=>n(t,e)),e.infrastructureNodes&&e.infrastructureNodes.forEach(t=>n(t,e))};e.model.deploymentNodes.forEach(e=>n(e,null))}const{view:s,section:l}=function(e,t){let n=null,o="";return Object.keys(e.views).filter(e=>e.endsWith("Views")).some(i=>e.views[i].some(e=>{if(e.key==t)return n=e,o=i,!0})),{view:n,section:o}}(e,n);if(!s)return null;const d=new i.jg(s.key,s.title||s.key),c={name:d.name,description:s.description,version:e.version,elements:[]};if(d.metadata=c,!s.elements)return d;const h={};if("deploymentViews"==l||"containerViews"==l)s.elements.forEach(e=>{const t=o.get(e.id);t?.parent&&(h[t.parent.id]=!0)});else if(s.softwareSystemId)s.elements.find(e=>e.id==s.softwareSystemId)||(h[s.softwareSystemId]=!0);else if("systemLandscapeViews"==l){const t={id:"__enterprise__",...e.model.enterprise};o.set(t.id,t),e.model.people&&e.model.people.filter(e=>"External"!=e.location).forEach(e=>e.parent=t),e.model.softwareSystems&&e.model.softwareSystems.filter(e=>"External"!=e.location).forEach(e=>e.parent=t),h[t.id]=!0}const g=e.views.styles,u=e=>e.toLowerCase().replace(/[^a-z0-9-]/g,"-");g?.elements&&g.elements.forEach(e=>{if(e.tag){const t=`--mdl-${u(e.tag)}`;e.background&&d.colorToVarMap.set(e.background,`${t}-bg`),e.color&&d.colorToVarMap.set(e.color,`${t}-color`),e.stroke&&d.colorToVarMap.set(e.stroke,`${t}-stroke`)}}),g?.relationships&&g.relationships.forEach(e=>{if(e.tag){const t=`--mdl-rel-${u(e.tag)}`;e.color&&d.colorToVarMap.set(e.color,`${t}-color`)}}),s.elements.forEach(t=>{if(h[t.id])return;const n=o.get(t.id),i=n?function(e,t){const n=e.views.containerViews?.find(e=>e.softwareSystemId==t);return n?.key}(e,n.id):void 0;let r="",a={};if(n){const e=n.tags.split(",");r=e[e.length-1],n.technology&&(r+=": "+n.technology),e.forEach(e=>{const t=g&&g.elements&&g.elements.find(t=>t.tag==e);t&&(a={...a,...t})})}d.addNode(t.id,n&&n.name||t.id,r,n&&n.description?n.description:"",a,function(e,t){if(t){const e=encodeURIComponent(t);return{href:`?id=${e}`,exportHref:`${e}.svg`}}if(e?.url)return{href:e.url,exportHref:e.url}}(n,i)),n&&c.elements.push({id:n.id,tags:n.tags,location:n.location,properties:n.properties,elementViewKey:i,technology:n.technology,url:n.url})}),Array.isArray(s.relationships)&&s.relationships.forEach(e=>{const t=r.get(e.id);if(!t)return;if(!d.nodesMap.has(t.sourceId)){if(o.has(t.sourceId)){const e=o.get(t.sourceId);console.warn("Element not found in this view: ",e.id,e.name)}else console.warn("Element not found: ",t.sourceId);return}if(!d.nodesMap.has(t.destinationId)){if(o.has(t.destinationId)){const e=o.get(t.destinationId);console.warn("Element not found in this view: ",e.id,e.name)}else console.warn("Element not found: ",t.destinationId);return}let n={};t.tags.split(",").forEach(e=>{const t=g&&g.relationships&&g.relationships.find(t=>t.tag==e);t&&(n={...n,...t})}),e.routing&&(n.routing=e.routing),d.addEdge(t.id,t.sourceId,t.destinationId,t.description,e.vertices,n)});const A=e=>{let t=0;for(let n=e.parent;n;n=n.parent)t++;return t};return Object.keys(h).map(e=>o.get(e)).sort((e,t)=>A(e)>A(t)?-1:1).forEach(e=>{let t={};"deploymentViews"==l&&o.get(e.id).tags.split(",").forEach(e=>{const n=g&&g.elements&&g.elements.find(t=>t.tag==e);n&&(t={...t,...n})});const n=s.elements.map(e=>o.get(e.id)).filter(t=>!(!t||t.parent!==e||"systemLandscapeViews"===l&&"__enterprise__"===e.id&&"External"===t.location)).map(e=>e.id);n.length>0&&d.addGroup(e.id,e.name,n,t)}),d.init(t[d.id]),d})(e,t,n);return o&&(l[n]=o),o})(e,t,d),{layouting:m,handleAutoLayout:b}=(e=>{const[t,n]=(0,o.useState)(!1);return{layouting:t,handleAutoLayout:(0,o.useCallback)(async t=>{n(!0);try{const n={direction:"DOWN",...t||{}};await e.autoLayout(n)}catch(e){console.error("Layout failed:",e),alert("Layout failed. See console for details.")}finally{n(!1)}},[e])}})(f||{}),{saving:y,handleSave:x}=((e,t)=>{const[n,i]=(0,o.useState)(!1);return{saving:n,handleSave:(0,o.useCallback)(async()=>{i(!0);try{202!==(await fetch("data/save?id="+encodeURIComponent(t),{method:"post",body:e.exportSVG()})).status?alert("Error saving\nSee terminal output."):e.setSaved()}catch(e){console.error("Save failed:",e),alert("Save failed. See console for details.")}finally{i(!1)}},[e,t])}})(f||{},d);if(!f)return(0,h.jsx)(M,{model:e});const w=(0,o.useCallback)(()=>{u(!c)},[c]);(0,o.useEffect)(()=>{f&&f.name&&(document.title=`${f.name} - Model`)},[f]),(0,o.useEffect)(()=>{const e=Object.fromEntries(n.entries()),t="1"===e.auto||"true"===e.auto,o="1"===e.save||"true"===e.save,i=(e.direction||"").toUpperCase(),r="1"===e.compact||"true"===e.compact,a={};["UP","DOWN","LEFT","RIGHT"].includes(i)&&(a.direction=i),r&&(a.compactLayout=!0);let s=!1;return(async()=>{try{t&&await b(a),o&&await x()}catch(e){console.error("automation error",e)}})(),()=>{s=!0}},[f,b,x,n]),((e,t,n,i,r,a)=>{(0,o.useEffect)(()=>{const o=o=>{const l=(0,s.Yp)(o);l&&o.preventDefault(),"help"===l?e():"save"===l?t():l===s.aX&&r&&i?r("pan"===i?"select":"pan"):n&&(l===s.t9?n.alignSelectionH():l===s.Jk?n.alignSelectionV():l===s.DE?n.distributeSelectionH():l===s.Vy?n.distributeSelectionV():l===s.Hd&&a?a():l===s._t?n.resetView():l===s.Op?n.toggleGrid():l===s.hZ?n.toggleSnapToGrid():l===s.OE?n.snapAllToGrid():l===s.Gg?n.moveSelected(-n.getGridSize(),0):l===s.J8?n.moveSelected(-1,0,!0):l===s.b3?n.moveSelected(n.getGridSize(),0):l===s.iD?n.moveSelected(1,0,!0):l===s.uK?n.moveSelected(0,-n.getGridSize()):l===s.l8?n.moveSelected(0,-1,!0):l===s.rB?n.moveSelected(0,n.getGridSize()):l===s.mt&&n.moveSelected(0,1,!0))};return window.addEventListener("keydown",o),()=>window.removeEventListener("keydown",o)},[e,t,n,i,r,a])})(w,x,f,A,p,b);const v=(0,o.useCallback)(e=>{a({id:encodeURIComponent(e)})},[a]),C=(0,o.useCallback)(e=>{if(e){const n=f.metadata.elements.find(t=>t.id===e);console.log((t=n,JSON.parse(JSON.stringify(t))))}var t},[f]);return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(g,{model:e,currentID:d,onViewChange:v,graph:f,onAutoLayout:b,onSave:x,onToggleHelp:w,saving:y,layouting:m,dragMode:A,setDragMode:p}),(0,h.jsx)(o.Suspense,{fallback:(0,h.jsx)("div",{children:"Loading graph..."}),children:(0,h.jsx)(I,{data:f,onSelect:C,dragMode:A},d)}),c&&(0,h.jsx)(o.Suspense,{fallback:(0,h.jsx)("div",{children:"Loading help..."}),children:(0,h.jsx)(k,{})})]})},M=({model:e})=>{const t=a(e);return o.useEffect(()=>{document.title="Model - Architecture Diagrams as Code",t.length>0&&(document.location.href="?id="+t[0].key)},[t]),t.length>0?(0,h.jsxs)(h.Fragment,{children:["Redirecting to ",t[0].title]}):(0,h.jsx)(h.Fragment,{children:"No views available"})}},408(e,t,n){n.d(t,{jg:()=>S,Qy:()=>R,ZG:()=>L,IX:()=>F,F_:()=>X,Kp:()=>Z,a_:()=>W});const o=(e,t,n)=>{const o=(()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","svg");return document.body.appendChild(e),{measure:(t,n)=>{const o=document.createElementNS("http://www.w3.org/2000/svg","text");o.setAttribute("x","0"),o.setAttribute("y","0");for(let e in n)o.setAttribute(e,n[e]);o.appendChild(document.createTextNode(t)),e.appendChild(o);const{width:i,height:r}=o.getBBox();return e.removeChild(o),{width:i,height:r}},clean:()=>{document.body.removeChild(e)}}})();let i=0;const r=e.trim().split("\n").map(e=>{const r=e.trim().split(/\s+/);let a=[],s=[];return r.forEach(e=>{if(o.measure(e,n).width>t){s.length>0&&(a.push(s.join(" ")),s=[]);const r=((e,t,n,o)=>{const i=[];let r="";for(let a=0;at&&r.length>0?(i.push(r),r=e[a]):r=s}return r.length>0&&i.push(r),i})(e,t,n,o);for(let e=0;e0&&(s=[r[r.length-1]])}else{const r=[...s,e],l=o.measure(r.join(" "),n);l.width>t&&s.length>0?(a.push(s.join(" ")),s=[e]):(i=Math.max(i,l.width),s=r)}}),s.length>0&&a.push(s.join(" ")),a}).reduce((e,t)=>e.concat(t),[]);return o.clean(),{lines:r,maxW:i}},i={element(e,t={},n){const o=document.createElementNS("http://www.w3.org/2000/svg",e);return Object.entries(t).forEach(([e,t])=>o.setAttribute(e,String(t))),n&&o.classList.add(n),o},use(e,t={}){const n=this.element("use",t);return n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href","#"+e),n},path(e,t={},n){return this.element("path",{...t,d:e},n)},text(e,t={}){const n=this.element("text",t);return e&&(n.textContent=e),n},textArea(e,t,n,i,r=0,a=0,s=""){const l={"font-size":`${n}px`,"font-weight":i?"bold":"normal"},{lines:d,maxW:c}=o(e,t,l),h=this.text("",{x:0,y:a,"text-anchor":s||void 0});return d.forEach((e,t)=>{const o=this.element("tspan",{x:r,dy:`${n+2}px`,...l});o.textContent=e,h.append(o)}),{txt:h,dy:(d.length+1)*(n+2),maxW:c}},rect(e,t,n=0,o=0,i=0,r){return this.element("rect",{x:n,y:o,rx:i,ry:i,width:e,height:t},r)},icon(e,t=0,n=0){return this.use(e,{x:t,y:n})},expand(e,t,n){const o=this.element("g",{transform:`translate(${e},${t})`},"expand");return o.append(this.rect(19,19,0,0,1),this.text(n?"-":"+",{x:10,y:14,"text-anchor":"middle"})),o}};function r(e,t,n){e.setAttribute("transform",`translate(${t},${n})`)}function a(e,t,n=!0){return n?e.x>t.x-t.width/2&&e.xt.y-t.height/2&&e.yt.x&&e.xt.y&&e.yfunction(e,t,n,o){let i,r,a,s,l,d={x:null,y:null,onLine1:!1,onLine2:!1};return i=(o.y-n.y)*(t.x-e.x)-(o.x-n.x)*(t.y-e.y),0==i||(r=e.y-n.y,a=e.x-n.x,s=(o.x-n.x)*r-(o.y-n.y)*a,l=(t.x-e.x)*r-(t.y-e.y)*a,r=s/i,a=l/i,d.x=e.x+r*(t.x-e.x),d.y=e.y+r*(t.y-e.y),r>0&&r<1&&(d.onLine1=!0),a>=0&&a<=1&&(d.onLine2=!0)),d}(e,t,n.p,n.q)).filter(e=>e.onLine1&&e.onLine2)}function l(e,t){return a(t,e)?{x:e.x,y:e.y}:s(e,t,e)[0]||{x:e.x,y:e.y}}function d(e,t,n,o,i){const r={x:i.x-e.x,y:i.y-e.y},a=o.x-e.x,s=o.y-e.y;a==r.x&&(r.x+=1e-7);const l=(s-r.y)/(a-r.x),d=s-l*a,c=n*n+t*t*l*l,h=2*t*t*d*l,g=t*t*d*d-t*t*n*n,u=Math.sqrt(h*h-4*c*g),A=r.x>a?(-h+u)/(2*c):(-h-u)/(2*c),p={x:A,y:l*A+d};return p.x+=e.x,p.y+=e.y,p}function c(e,t,n){let o=n.x-t.x,i=n.y-t.y,r=o*o+i*i,a=(e.x-t.x)*o+(e.y-t.y)*i,s=Math.min(1,Math.max(0,a/r));return{x:t.x+o*s,y:t.y+i*s}}function h(e,t){return Math.abs(t.x-e.x)+Math.abs(t.y-e.y)}function g(e){return e/2/(5.5+e/70)}function u(e,t,n){switch(e.toLowerCase()){case"cylinder":return 2*g(t);case"person":return.4*n;case"folder":return t/10;case"robot":return.35*n;case"webbrowser":return n/8;default:return 0}}class A{_el;constructor(e){this._el=e}node(){return this._el}attr(e,t){return this._el.setAttribute(e,String(t)),this}insert(e,t){const n=document.createElementNS("http://www.w3.org/2000/svg",e),o=this._el.insertBefore(n,this._el.querySelector(t));return new A(o)}}function p(e,t,n,o=!1){const i=e.insert("rect",":first-child").attr("rx",o?n.width/8:3).attr("ry",o?n.width/8:3).attr("x",-t.width/2).attr("y",-t.height/2).attr("width",t.width).attr("height",t.height);return n.intersect=function(e){return l(n,e)},i}function f(e,t,n,o,i){const r=e.insert("ellipse",":first-child").attr("cx",0).attr("cy",0).attr("rx",o).attr("ry",i).attr("width",n.width).attr("height",n.height);return n.intersect=function(e){return d(n,o,i,n,e)},r}function m(e,t,n){const o=n.width/8,i=n.width/14,r=e.insert("g",":first-child");return r.insert("path",":first-child").attr("d",`M${-n.width/2},${-n.height/2} l${n.width},0 M${-n.width/2},${n.height/2} l${n.width},0`),r.insert("circle",":first-child").attr("cx",0).attr("cy",n.height/2+o/2).attr("r",.4*i),r.insert("rect",":first-child").attr("x",-i).attr("y",-n.height/2-o/2-.2*i).attr("width",2*i).attr("height",.4*i),r.insert("rect",":first-child").attr("rx",i).attr("ry",i).attr("x",-t.width/2).attr("y",-t.height/2-o).attr("width",t.width).attr("height",t.height+2*o),n.intersect=function(e){return l({x:n.x,y:n.y,width:n.width,height:n.height+2*o},e)},r}const b={box:(e,t)=>p(new A(e),t,t).node(),roundedbox:(e,t)=>p(new A(e),t,t,!0).node(),component:(e,t)=>function(e,t,n){const o=n.width/10,i=e.insert("g",":first-child");return i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2-o).attr("y",-n.height/2+o).attr("width",2*o).attr("height",o),i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2-o).attr("y",-n.height/2+2.5*o).attr("width",2*o).attr("height",o),i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2).attr("y",-n.height/2).attr("width",n.width).attr("height",n.height),n.intersect=function(e){return l({x:n.x-o/2,y:n.y,width:n.width+o,height:n.height},e)},i}(new A(e),0,t).node(),cylinder:(e,t)=>function(e,t,n){const o=t.width,i=o/2,r=g(o),a=t.height,s=`M 0,${r} a${i},${r} 0,0,0 ${o} 0 a ${i},${r} 0,0,0 ${-o} 0 l 0,${a-2*r} a ${i},${r} 0,0,0 ${o} 0 l 0,${2*r-a}`,c=e.attr("label-offset-y",u("cylinder",o,a)).insert("path",":first-child").attr("d",s).attr("transform","translate("+-o/2+","+-a/2+")");return n.intersect=function(e){const t=l(n,e);let o=n.y+n.height/2-r;return t.y>o?d({x:n.x,y:o},i,r,n,e):(o=n.y-n.height/2+r,t.yfunction(e,t,n){const o=t.width,i=t.height,r=`M ${.38*o},${i/3} A${o/2},${i/2} 0,0,0 0 ${i/2}\n\t\tL${o/11},${i} L${o-o/11},${i} L${o},${i/2}\n\t\tA${o/2},${i/2} 0,0,0 ${o-.38*o} ${i/3} \n\t\tA${o/6},${o/6} 0,1,0 ${.38*o} ${i/3}`,a=e.attr("label-offset-y",u("person",o,i)).insert("path",":first-child").attr("d",r).attr("transform","translate("+-o/2+","+-i/2+")");return n.intersect=function(e){return l(n,e)},a}(new A(e),t,t).node(),circle:(e,t)=>function(e,t,n){return f(e,0,n,n.width/2,n.width/2)}(new A(e),0,t).node(),ellipse:(e,t)=>function(e,t,n){return f(e,0,n,.55*n.width,.45*n.width)}(new A(e),0,t).node(),hexagon:(e,t)=>function(e,t,n){const o=n.width/2,i=e.insert("polygon",":first-child").attr("points",[.5,.866,1,0,.5,-.866,-.5,-.866,-1,-0,-.5,.866,.5,.866].map(e=>e*o).join(",")).attr("width",n.width).attr("height",n.height);return n.intersect=function(e){return d(n,n.width/2,n.width/2,n,e)},i}(new A(e),0,t).node(),folder:(e,t)=>function(e,t,n){const o=n.width/20,i=e.attr("label-offset-y",u("folder",n.width,n.height)).insert("g",":first-child");return i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2).attr("y",-n.height/2+2*o).attr("width",n.width).attr("height",n.height-2*o),i.insert("path",":first-child").attr("d",`M0,${-n.height/2+2*o} l${o},${-2*o} h${n.width/2-2*o} v${2*o}`),n.intersect=function(e){return l({x:n.x,y:n.y+o/2,width:n.width,height:n.height+o},e)},i}(new A(e),0,t).node(),mobiledevicelandscape:(e,t)=>function(e,t,n){const o=n.width/8,i=n.width/14,r=e.insert("g",":first-child");return r.insert("path",":first-child").attr("d",`M${-n.width/2},${-n.height/2} l0,${n.height} M${n.width/2},${-n.height/2} l0,${n.height}`),r.insert("circle",":first-child").attr("cx",-n.width/2-o/2).attr("cy",0).attr("r",.4*i),r.insert("rect",":first-child").attr("x",n.width/2+o/2-.2*i).attr("y",-i).attr("width",.4*i).attr("height",2*i),r.insert("rect",":first-child").attr("rx",i).attr("ry",i).attr("x",-t.width/2-o).attr("y",-t.height/2).attr("width",t.width+2*o).attr("height",t.height),n.intersect=function(e){return l({x:n.x,y:n.y,width:n.width+2*o,height:n.height},e)},r}(new A(e),t,t).node(),mobiledeviceportrait:(e,t)=>m(new A(e),t,t).node(),mobiledevice:(e,t)=>m(new A(e),t,t).node(),pipe:(e,t)=>function(e,t,n){const o=n.width,i=n.height,r=i/2,a=r/(2.5+o/70),s=`M${-a},0\n\t\ta${a},${r} 0,0,1 0,${i}\n\t\ta${a},${r} 0,0,1 0,${-i}\n\t\tl${o},0\n\t\ta${a},${r} 0,0,1 0,${i}\n\t\tl${-o},0`,d=e.insert("path",":first-child").attr("d",s).attr("transform","translate("+-o/2+","+-i/2+")");return n.intersect=function(e){return l({x:n.x-a,y:n.y,width:n.width+2*a,height:n.height},e)},d}(new A(e),0,t).node(),robot:(e,t)=>function(e,t,n){const o=n.width,i=n.height,r=Math.min(.28*o,.25*i),a=.2*r,s=.25*r,d=.08*r,c=.12*r,h=.22*r,g=.12*r,A=.3*r,p=o,f=-i/2+s+r,m=i-s-r,b=e.attr("label-offset-y",u("robot",o,i)).insert("g",":first-child");b.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-p/2).attr("y",f).attr("width",p).attr("height",m);const y=-i/2+s;b.insert("rect",":first-child").attr("rx",a).attr("ry",a).attr("x",-r/2).attr("y",y).attr("width",r).attr("height",r),b.insert("line",":first-child").attr("class","robot-antenna").attr("x1",0).attr("y1",y).attr("x2",0).attr("y2",-i/2+2*d).attr("stroke-width",.6*d).attr("stroke-linecap","round"),b.insert("circle",":first-child").attr("class","robot-antenna-ball").attr("cx",0).attr("cy",-i/2+2*d).attr("r",1.2*d);const x=y+.4*r;b.insert("circle",":first-child").attr("class","robot-eye").attr("cx",-h).attr("cy",x).attr("r",c),b.insert("circle",":first-child").attr("class","robot-eye").attr("cx",h).attr("cy",x).attr("r",c);const w=y+.7*r,v=.28*r;return b.insert("path",":first-child").attr("class","robot-mouth").attr("d",`M${-v/2},${w} Q0,${w+.3*v} ${v/2},${w}`).attr("fill","none").attr("stroke-width",.5*d).attr("stroke-linecap","round"),b.insert("rect",":first-child").attr("rx",.25*g).attr("ry",.25*g).attr("x",-r/2-g-1).attr("y",x-A/2).attr("width",g).attr("height",A),b.insert("rect",":first-child").attr("rx",.25*g).attr("ry",.25*g).attr("x",r/2+1).attr("y",x-A/2).attr("width",g).attr("height",A),n.intersect=function(e){return l(n,e)},b}(new A(e),0,t).node(),webbrowser:(e,t)=>function(e,t,n){const o=n.height/8,i=e.attr("label-offset-y",u("webbrowser",n.width,n.height)).insert("g",":first-child");return i.insert("path",":first-child").attr("d",`\n\t\t\tM${-n.width/2},${-n.height/2+o} h${n.width}\n\t\t\tM${-n.width/2+o/4},${-n.height/2+o/4} h${o/2} v${o/2} h${-o/2} z\n\t\t\tM${-n.width/2+o},${-n.height/2+o/4} h${n.width-o-o/4} v${o/2} h${-n.width+o+o/4} z\n\t\t`),i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2).attr("y",-n.height/2).attr("width",n.width).attr("height",n.height),n.intersect=function(e){return l(n,e)},i}(new A(e),0,t).node()};var y=n(538);class x{versions=[];pos=0;lastSavedPos=0;exportDoc;importDoc;change;tmpPreviousState=null;constructor(e,t,n){this.exportDoc=t,this.importDoc=n,this.change=function(e){let t;return function(){const n=this;clearTimeout(t),t=setTimeout(function(){t=null,e.apply(n)},300)}}(()=>this.saveNow())}beforeChange(){this.tmpPreviousState||(this.tmpPreviousState=this.deepClone(this.exportDoc()))}length(){return this.versions.length}currentState(){return this.deepClone(this.versions[this.pos-1])}saveNow(){if(!this.tmpPreviousState)throw Error("undo.change() was called without previously calling undo.beforeChange()!");this.versions[this.pos]=this.deepClone(this.exportDoc()),this.versions[this.pos-1]=this.tmpPreviousState,this.tmpPreviousState=null,this.pos+=1,this.versions.splice(this.pos)}deepClone(e){return"undefined"!=typeof structuredClone?structuredClone(e):JSON.parse(JSON.stringify(e))}undo(){if(this.pos<2)return;this.pos-=1;const e=this.versions[this.pos-1];this.importDoc(this.deepClone(e))}redo(){if(this.pos>this.versions.length-1)return;const e=this.versions[this.pos];this.importDoc(this.deepClone(e)),this.pos+=1}changed(){return this.pos!==this.lastSavedPos}setSaved(){this.lastSavedPos=this.pos}}var w=n(264);const v={"font-family":"Inter, -apple-system, BlinkMacSystemFont, sans-serif",stroke:"none"},C=(e,t)=>{Object.keys(t).forEach(n=>{const o=t[n];"number"==typeof o?e.style.setProperty(n,o.toString()):e.style.setProperty(n,o)})},k=(e,t)=>Math.sqrt((t.x-e.x)*(t.x-e.x)+(t.y-e.y)*(t.y-e.y));function I(e,t,n,i,r,a){const s={"font-family":String(v["font-family"]),"font-size":`${n}px`,"font-weight":i?"bold":"normal"},l=o(e,t,s);return{lines:l.lines.length>0?l.lines:[""],fontSize:n,lineHeight:n+2,bold:i,field:a,gapAfter:r}}const B={thickness:3,color:"#999",opacity:1,fontSize:22,dashed:!0},E={width:280,height:180,background:"rgba(255, 255, 255, .9)",color:"#666",opacity:.9,stroke:"#999",fontSize:22,shape:"Box"};class S{id;name;nodesMap;edges;edgeVertices;groupsMap;metadata;colorToVarMap=new Map;_undo;_gridVisible=!1;_snapToGrid=!0;_gridSize=25;_skipAutoFit=!1;constructor(e,t){this.id=e,this.name=t,this.edges=[],this.edgeVertices=new Map,this.nodesMap=new Map,this.groupsMap=new Map,this._undo=new x(this.id,()=>this.exportLayout(!0),e=>this.importLayout(e,!0)),window.graph=this}init(e){e&&this.importLayout(e),this._undo=new x(this.id,()=>this.exportLayout(!0),e=>this.importLayout(e,!0)),this._undo.length()&&this.importLayout(this._undo.currentState()),this._undo.beforeChange(),this._undo.change()}addNode(e,t,n,o,i,r){if(this.nodesMap.has(e))throw Error("duplicate node: "+e);const a={...E,...i},s=(a.shape||"Box").toLowerCase(),l="person"===s?240:180,d=Math.max(280,a.width||0),c=function(e,t,n,o,i){const r=Math.max(o-36,80),a=[I(e,r,i,!0,6,"name"),I(`[${t}]`,r,.75*i,!1,10),I(n,r,Math.min(.8*i,16),!1,0,"description")],s=a.reduce((e,t)=>e+t.lines.length*t.lineHeight+t.gapAfter,0);return{blocks:a,textHeight:s,minimumHeight:s+36}}(t,n,o,d,a.fontSize||22);let h=Math.max(l,a.height||0,c.minimumHeight);for(let e=0;e<20;e++){const e=c.minimumHeight+Math.abs(u(s,d,h));if(e<=h+.1)break;h=e}const g={id:e,title:t,sub:n,description:o,style:a,x:0,y:0,width:d,height:h,intersect:null,link:r,contentLayout:c};this.nodesMap.set(g.id,g)}nodes(){return Array.from(this.nodesMap.values())}addEdge(e,t,n,o,i,r){i&&i.forEach((t,n)=>{const o=t;o.id=`v-${e}-${n}`,this.edgeVertices.set(o.id,o)});const a={id:e,from:this.nodesMap.get(t),to:this.nodesMap.get(n),label:o,vertices:null,style:{...B,...r},initVertex:e=>{const t=e;return t.id||(t.id=((e,t)=>`v-${e}-a-${(e=>{let t=2166136261;for(let n=0;n>>0).toString(36)})(`${e}:${t.x}:${t.y}`)}`)(a.id,e),this.edgeVertices.set(t.id,t)),t.edge=a,e},userDeletedVertices:!1};this.edges.push(a),i&&(a.vertices=i.map(e=>a.initVertex(e)))}addGroup(e,t,n,o){if(this.groupsMap.has(e))return void console.error(`Group exists: ${e} ${t}`);const i={id:e,name:t,x:null,y:null,width:null,height:null,nodes:n.map(n=>{const o=this.nodesMap.get(n)||this.groupsMap.get(n);return o||console.error(`Node or group ${n} not found for group ${e} "${t}"`),o}).filter(Boolean),style:o};this.groupsMap.set(e,i)}setNodeSelected(e,t){e.selected=t,t?e.ref.classList.add("selected"):e.ref.classList.remove("selected"),this.updateEdgesSel()}updateEdgesSel(){this.edges.forEach(e=>{e.to.selected||e.from.selected?e.ref.classList.add("selected"):e.ref.classList.remove("selected")})}moveNode(e,t,n,o=!1,i=!1){if(e){if(this._snapToGrid&&!o){const e=this.snapToGrid(t,n);t=e.x,n=e.y}e.x==t&&e.y==n||(i||this._undo.beforeChange(),e.x=t,e.y=n,r(e.ref,t,n),this.redrawEdges(e),this.redrawGroups(e),i||this._undo.change())}}moveEdgeVertex(e,t,n,o=!1,i=!1){if(this._snapToGrid&&!o){const e=this.snapToGrid(t,n);t=e.x,n=e.y}e.x==t&&e.y==n||(i||this._undo.beforeChange(),e.x=t,e.y=n,this.redrawEdge(e.edge),i||this._undo.change())}moveSelected(e,t,n=!1){this.nodes().forEach(o=>o.selected&&this.moveNode(o,o.x+e,o.y+t,n,!1)),this.edgeVertices.forEach(o=>o.selected&&this.moveEdgeVertex(o,o.x+e,o.y+t,n,!1))}insertEdgeVertex(e,t,n,o){this._undo.beforeChange();const i=e.initVertex(t);i.selected=!0,o&&(e.vertices.forEach(e=>e.label=!1),i.label=!0),e.vertices.splice(n-1,0,i),this.redrawEdge(e),this._undo.change()}deleteEdgeVertex(e){this._undo.beforeChange();const t=e.edge.vertices.indexOf(e);t>=0&&(e.edge.vertices.splice(t,1),this.edgeVertices.delete(e.id),e.edge.userDeletedVertices=!0),this.redrawEdge(e.edge),this._undo.change()}changed(){return this._undo.changed()}undo(){this._undo.undo()}redo(){this._undo.redo()}alignTopLeft(){const e=this.calculateContentBounds(),t=100-e.x,n=100-e.y;this._skipAutoFit=!0,this._undo.beforeChange(),this.nodesMap.forEach(e=>{this.moveNode(e,e.x+t,e.y+n,!0,!0)}),this.edgeVertices.forEach(e=>{this.moveEdgeVertex(e,e.x+t,e.y+n,!0,!0)}),this._undo.change()}resetPanTransform(){const e=F(),t=$.querySelector("g.zoom");t&&(t.setAttribute("transform",`scale(${e}) translate(0, 0)`),function(e){const t=e.calculateContentBounds(),n=F(),o=Math.max($.parentElement.clientWidth/n,t.x+t.width+20),i=Math.max($.parentElement.clientHeight/n,t.y+t.height+20);$.setAttribute("width",String(o*n)),$.setAttribute("height",String(i*n))}(this)),K(this.id),this._skipAutoFit=!1}shouldSkipAutoFit(){return this._skipAutoFit}resetView(){const e=$.querySelector("g.zoom");e&&(e.setAttribute("transform","scale(1) translate(0, 0)"),H()),K(this.id)}redrawEdges(e){this.edges.forEach(t=>(e==t.from||e==t.to)&&this.redrawEdge(t)),this.updateEdgesSel()}redrawEdge(e){const t=e.ref.parentElement;t.removeChild(e.ref),e.ref=T(this,e),t.append(e.ref)}redrawGroups(e){this.groupsMap.forEach(e=>{const t=e.ref.parentElement;t.removeChild(e.ref),V(e),t.append(e.ref)})}exportSVG(){const e=document.querySelector("svg#graph"),t=(e.querySelector("rect.elastic"),e.cloneNode(!0));t.querySelectorAll("a.nodeLink[data-export-href]").forEach(e=>{e.setAttribute("href",e.getAttribute("data-export-href")||""),e.removeAttribute("data-export-href")});const n=t.querySelector("rect.elastic");n&&n.remove();const o=this.calculateContentBounds(),i=o.width+100,r=o.height+100,a=50-o.x,s=50-o.y,l=t.querySelector("g.zoom");l&&l.setAttribute("transform",`scale(1) translate(${a}, ${s})`),t.setAttribute("viewBox",`0 0 ${i} ${r}`),t.setAttribute("width",String(i)),t.setAttribute("height",String(r)),t.setAttribute("xmlns","http://www.w3.org/2000/svg"),this.convertStylesToCustomProperties(t);const d=document.createElement("script");return d.setAttribute("type","application/json"),this.metadata.layout=this.exportLayout(),d.append("/g,"]]]>]>"),t.insertBefore(d,t.firstChild),t.outerHTML}convertStylesToCustomProperties(e){0!==this.colorToVarMap.size&&(e.querySelectorAll("[fill]").forEach(e=>{const t=e.getAttribute("fill");t&&this.colorToVarMap.has(t)&&e.setAttribute("fill",`var(${this.colorToVarMap.get(t)}, ${t})`)}),e.querySelectorAll("[stroke]").forEach(e=>{const t=e.getAttribute("stroke");t&&this.colorToVarMap.has(t)&&e.setAttribute("stroke",`var(${this.colorToVarMap.get(t)}, ${t})`)}))}calculateContentBounds(){let e=1/0,t=1/0,n=-1/0,o=-1/0;return this.nodes().forEach(i=>{const r=i.x-i.width/2,a=i.x+i.width/2,s=i.y-i.height/2,l=i.y+i.height/2;e=Math.min(e,r),n=Math.max(n,a),t=Math.min(t,s),o=Math.max(o,l)}),this.edgeVertices.forEach(i=>{e=Math.min(e,i.x-5),n=Math.max(n,i.x+5),t=Math.min(t,i.y-5),o=Math.max(o,i.y+5)}),this.groupsMap.forEach(i=>{const r=i.x-i.width/2,a=i.x+i.width/2,s=i.y-i.height/2,l=i.y+i.height/2;e=Math.min(e,r),n=Math.max(n,a),t=Math.min(t,s),o=Math.max(o,l)}),this.edges.forEach(i=>{if(e=Math.min(e,i.from.x-10,i.to.x-10),n=Math.max(n,i.from.x+10,i.to.x+10),t=Math.min(t,i.from.y-10,i.to.y-10),o=Math.max(o,i.from.y+10,i.to.y+10),i.vertices&&i.vertices.forEach(i=>{e=Math.min(e,i.x-10),n=Math.max(n,i.x+10),t=Math.min(t,i.y-10),o=Math.max(o,i.y+10)}),i.label&&i.label.trim()){const r=(i.from.x+i.to.x)/2,a=(i.from.y+i.to.y)/2,s=10*i.label.length+50;e=Math.min(e,r-s),n=Math.max(n,r+s),t=Math.min(t,a-25),o=Math.max(o,a+25)}}),e===1/0?{x:0,y:0,width:100,height:100}:{x:e,y:t,width:n-e,height:o-t}}exportLayout(e=!1){const t={};return this.nodes().forEach(e=>t[e.id]={x:e.x,y:e.y}),this.edges.forEach(n=>{if(!n.vertices)return;const o=n.vertices.map(e=>({x:e.x,y:e.y,label:e.label,auto:e.auto}));(o.length||e)&&(t[`e-${n.id}`]=o),n.userDeletedVertices&&(t[`e-${n.id}-deleted`]=!0)}),t}setSaved(){this._undo.setSaved()}importLayout(e,t=!1){const n=[];Object.entries(e).forEach(([e,t])=>{e.startsWith("e-")||void 0===t.x||void 0===t.y?e.startsWith("e-")&&Array.isArray(t)&&t.forEach(e=>{void 0!==e.x&&void 0!==e.y&&n.push({x:e.x,y:e.y})}):n.push({x:t.x,y:t.y})});let o=0,i=0;if(n.length>0){const e=Math.min(...n.map(e=>e.x)),t=Math.min(...n.map(e=>e.y));if(e<-100||t<-100||Math.max(...n.map(e=>e.x))>3e3||Math.max(...n.map(e=>e.y))>2e3){const n=50;o=-e+n,i=-t+n}}Object.entries(e).forEach(([e,t])=>{const n=this.nodesMap.get(e);if(n)n.x=t.x+o,n.y=t.y+i;else if(e.startsWith("e-")&&!e.endsWith("-deleted")){const n=this.edges.find(t=>t.id==e.slice(2));if(!n)return;return n.vertices&&n.vertices.forEach(e=>this.edgeVertices.delete(e.id)),void(n.vertices=t.map(e=>{const t={x:e.x+o,y:e.y+i};Object.assign(t,e,{x:e.x+o,y:e.y+i});const r=n.initVertex(t);return e.auto&&(r.auto=!0),r}))}if(e.endsWith("-deleted")){const n=e.slice(2,-8),o=this.edges.find(e=>e.id==n);return void(o&&!0===t&&(o.userDeletedVertices=!0))}}),t&&(this.nodes().forEach(e=>r(e.ref,e.x,e.y)),this.edges.forEach(e=>this.redrawEdge(e)),this.updateEdgesSel(),this.redrawGroups(null))}async autoLayout(e){try{const t=await(0,y.k)(this,e);this._undo.beforeChange(),t.nodes.forEach(e=>{const t=this.nodesMap.get(e.id);t&&this.moveNode(t,e.x,e.y,!1,!0)}),t.edges.forEach(e=>{const t=this.edges.find(t=>t.id==e.id);if(t){if(t.vertices&&t.vertices.forEach(e=>{e.id&&this.edgeVertices.delete(e.id)}),t.vertices=[],t.userDeletedVertices=!1,e.vertices&&e.vertices.length>0&&(t.vertices=e.vertices.map(e=>{const n=t.initVertex(e);return n.auto=!0,n})),e.label){t.vertices&&(t.vertices.forEach(e=>{e.label&&this.edgeVertices.delete(e.id)}),t.vertices=t.vertices.filter(e=>!e.label));const n=t.initVertex(e.label);n.label=!0,n.auto=!0,t.vertices=t.vertices||[];const o=function(e,t,n,o){if(0===e.length)return 0;const i=[n,...e,o];let r=1/0,a=0;for(let e=0;ee.selected);e.push(...Array.from(this.edgeVertices.values()).filter(e=>e.selected));let t=Math.min(...e.map(e=>e.y));this.nodesMap.forEach(e=>e.selected&&this.moveNode(e,e.x,t,!1,!1)),this.edgeVertices.forEach(e=>e.selected&&this.moveEdgeVertex(e,e.x,t,!1,!1))}alignSelectionH(){const e=this.nodes().filter(e=>e.selected);e.push(...Array.from(this.edgeVertices.values()).filter(e=>e.selected));let t=Math.min(...e.map(e=>e.x));this.nodesMap.forEach(e=>e.selected&&this.moveNode(e,t,e.y,!1,!1)),this.edgeVertices.forEach(e=>e.selected&&this.moveEdgeVertex(e,t,e.y,!1,!1))}distributeSelectionH(){const e=this.nodes().filter(e=>e.selected),t=Array.from(this.edgeVertices.values()).filter(e=>e.selected);if(e.length+t.length<3)return;this._undo.beforeChange();const n=[...e,...t];n.sort((e,t)=>e.x-t.x);const o=n[0].x,i=(n[n.length-1].x-o)/(n.length-1);n.forEach((e,t)=>{const n=o+t*i;"title"in e?this.moveNode(e,n,e.y,!1,!0):this.moveEdgeVertex(e,n,e.y,!1,!0)}),this._undo.change()}distributeSelectionV(){const e=this.nodes().filter(e=>e.selected),t=Array.from(this.edgeVertices.values()).filter(e=>e.selected);if(e.length+t.length<3)return;this._undo.beforeChange();const n=[...e,...t];n.sort((e,t)=>e.y-t.y);const o=n[0].y,i=(n[n.length-1].y-o)/(n.length-1);n.forEach((e,t)=>{const n=o+t*i;"title"in e?this.moveNode(e,e.x,n,!1,!0):this.moveEdgeVertex(e,e.x,n,!1,!0)}),this._undo.change()}setEdgeSelected(e,t){t&&(this.setNodeSelected(e.from,!0),this.setNodeSelected(e.to,!0)),this.updateEdgesSel()}fitToView(){const e=this.calculateContentBounds();if(0===e.width||0===e.height)return;const t=$.parentElement?.clientWidth||800,n=$.parentElement?.clientHeight||600,o=(t-80)/e.width,i=(n-80)/e.height,r=Math.min(o,i),a=Math.max(Math.min(r,2),.1),s=t/2-(e.x+e.width/2)*a,l=n/2-(e.y+e.height/2)*a,d=$.querySelector("g.zoom");d&&d.setAttribute("transform",`translate(${s}, ${l}) scale(${a})`),H(),Z(this.id)}saveLayoutState(){return this.exportLayout(!0)}restoreLayoutState(e){this.importLayout(e,!0)}isGridVisible(){return this._gridVisible}isSnapToGrid(){return this._snapToGrid}getGridSize(){return this._gridSize}toggleGrid(){this._gridVisible=!this._gridVisible,this.updateGridDisplay(),window.dispatchEvent(new CustomEvent("gridStateChanged"))}toggleSnapToGrid(){this._snapToGrid=!this._snapToGrid,window.dispatchEvent(new CustomEvent("gridStateChanged"))}snapAllToGrid(){this._snapToGrid&&(this._undo.beforeChange(),this.nodes().forEach(e=>{const t=Math.round(e.x/this._gridSize)*this._gridSize,n=Math.round(e.y/this._gridSize)*this._gridSize;this.moveNode(e,t,n,!1,!0)}),this._undo.change())}snapToGrid(e,t){return{x:Math.round(e/this._gridSize)*this._gridSize,y:Math.round(t/this._gridSize)*this._gridSize}}updateGridDisplay(){if(!$)return;const e=$.querySelector("#grid-pattern");e&&e.remove();const t=$.querySelector("#grid-background");if(t&&t.remove(),!this._gridVisible)return;let n=$.querySelector("defs");n||(n=document.createElementNS("http://www.w3.org/2000/svg","defs"),$.insertBefore(n,$.firstChild));const o=document.createElementNS("http://www.w3.org/2000/svg","pattern");o.id="grid-pattern",o.setAttribute("width",this._gridSize.toString()),o.setAttribute("height",this._gridSize.toString()),o.setAttribute("patternUnits","userSpaceOnUse");const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.setAttribute("d",`M ${this._gridSize} 0 L 0 0 0 ${this._gridSize}`),i.setAttribute("fill","none"),i.setAttribute("stroke","#d0d0d0"),i.setAttribute("stroke-width","1"),i.setAttribute("opacity","0.8"),o.appendChild(i),n.appendChild(o);const r=document.createElementNS("http://www.w3.org/2000/svg","rect");r.id="grid-background",r.setAttribute("x","-10000"),r.setAttribute("y","-10000"),r.setAttribute("width","20000"),r.setAttribute("height","20000"),r.setAttribute("fill","url(#grid-pattern)"),r.setAttribute("pointer-events","none");const a=$.querySelector("g.zoom");a&&a.insertBefore(r,a.firstChild)}}let M,$=document.querySelector("svg#graph");$||($=document.createElementNS("http://www.w3.org/2000/svg","svg"),$.setAttribute("id","graph"),$.addEventListener("click",e=>M(e))),$.setAttribute("width","100%"),$.setAttribute("height","100%");let D,j=!1;const L=(e,t,n)=>{$.innerHTML='\n\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n',document.body.append($),$.__data=e,D=t,M=e=>{},_(e);const o=i.rect(300,300,50,50,0,"elastic");return $.append(o),e.updateGridDisplay(),R($,n),{svg:$,setZoom:O}},_=e=>{const t=i.element("g",{},"zoom"),n=i.element("g",{},"nodes"),o=i.element("g",{},"edges"),a=i.element("g",{},"groups");t.append(a,o,n),e.nodesMap.forEach(t=>{!function(e,t){window.gdata=t;const n=i.element("g",{},"node");n.setAttribute("id",e.id),e.selected&&n.classList.add("selected"),r(n,e.x,e.y);const o=e.link?i.element("a",{href:e.link.href,"data-export-href":e.link.exportHref,"aria-label":`Open ${e.title}`},"nodeLink"):null,a=o||n;o&&(n.classList.add("linked"),n.append(o));const s=e.style.shape||"Box",l=(b[s.toLowerCase()]||b.box)(a,e);l.classList.add("nodeBorder"),C(l,U.nodeBorder),l.setAttribute("fill",e.style.background),l.setAttribute("stroke",e.style.stroke),l.setAttribute("stroke-width","3"),l.setAttribute("opacity",String(e.style.opacity)),P(l,e.style.border);const d=function(e,t){const n=i.element("g");let o=-e.textHeight/2;return e.blocks.forEach(e=>{const r=i.text("",{"text-anchor":"middle"});C(r,v),t&&r.setAttribute("fill",t),e.field&&r.setAttribute("data-field",e.field),e.lines.forEach((t,n)=>{const a=i.element("tspan",{x:0,y:o+e.fontSize+n*e.lineHeight,"font-size":`${e.fontSize}px`,"font-weight":e.bold?"bold":"normal"});a.textContent=t,r.append(a)}),n.append(r),o+=e.lines.length*e.lineHeight+e.gapAfter}),n}(e.contentLayout,e.style.color);r(d,0,(Number(a.getAttribute("label-offset-y"))||0)/2),a.append(d),n.__data=e,e.ref=n}(t,e),n.append(t.ref)}),e.edges.forEach(t=>{T(e,t),o.append(t.ref)}),e.groupsMap.forEach(e=>{V(e),a.append(e.ref)}),$.append(t)};function T(e,t){const n=t.from,o=t.to,r=i.element("g",{},"edge");r.setAttribute("id",t.id),r.setAttribute("data-from",t.from.id),r.setAttribute("data-to",t.to.id);const l=(t.style.position||50)/100,d=function(e,t){const n=e.from,o=e.to;let i=e.vertices?e.vertices.concat():[];if(0==i.length&&!e.userDeletedVertices){const r=t.edges.filter(t=>t.from==e.from&&t.to==e.to);let a=0;if(r.length>1){a=r.indexOf(e)-(r.length-1)/2;let t=0,s=0;Math.abs(n.x-o.x)>Math.abs(n.y-o.y)?s=70*a:t=200*a;const l=e.initVertex({x:(n.x+o.x)/2+t,y:(n.y+o.y)/2+s});l.label=!0,l.auto=!0,i.push(l)}}i.unshift(n),i.push(o);let r=i[i.length-1];for(let e=1;e0;e--)if(!i[e].label){a=i[e];break}const s=(e,t)=>{const n=e.style?.shape?.toLowerCase()||"box",o=t.x-e.x,i=t.y-e.y;if(e.x,e.y,Math.abs(o)<.01&&Math.abs(i)<.01)return{x:e.x+e.width/2,y:e.y};if("cylinder"===n){const t=e.width,n=t/2,r=n/(5.5+t/70),a=e.height/2,s=Math.atan2(i,o),l=Math.cos(s),d=Math.sin(s);let c=1/0;Math.abs(l)>.01&&(c=Math.min(c,Math.abs(n/l))),Math.abs(d)>.01&&(c=Math.min(c,Math.abs(a/d)));const h=e.x+l*c,g=e.y+d*c,u=e.y-a+r,A=e.y+a-r;if(gA){const t=g=0){const e=Math.sqrt(l),n=(-s+e)/(2*i),r=(-s-e)/(2*i);return{x:o>0?Math.max(n,r):Math.min(n,r),y:t}}}return{x:h,y:g}}if("circle"===n){const t=e.width/2,n=Math.atan2(i,o);return{x:e.x+Math.cos(n)*t,y:e.y+Math.sin(n)*t}}if("ellipse"===n){const t=.55*e.width,n=.45*e.width,r=Math.atan2(i,o),a=Math.cos(r),s=Math.sin(r),l=Math.sqrt(t*t*s*s+n*n*a*a);return{x:e.x+t*a*n/l,y:e.y+n*s*t/l}}{const t=e.width/2,n=e.height/2,r=Math.atan2(i,o),a=Math.cos(r),s=Math.sin(r);let l=1/0;return Math.abs(a)>.01&&(l=Math.min(l,Math.abs(t/a))),Math.abs(s)>.01&&(l=Math.min(l,Math.abs(n/s))),{x:e.x+a*l,y:e.y+s*l}}};let l=s(n,r),d=s(o,a);return i[0]=l,i[i.length-1]=d,i}(t,e),c=function(e,t,n){let o,i={x:n.x,y:n.y};const r=e.findIndex(e=>e.label);if(r>=0){i=e[r];const t=[];r>0&&t.push({p:e[r-1],q:i}),re?k(t.p,t.q)>k(e.p,e.q)?t:e:t,void 0)}else{const n=e.slice(1).reduce((t,n,o)=>t+k(e[o],n),0)*t;let r=0;for(let t=1;t0&&r+s>=n){const e=(n-r)/s;i={x:a.p.x+(a.q.x-a.p.x)*e,y:a.p.y+(a.q.y-a.p.y)*e},o=a;break}r+=s}}const a=o?Math.abs(o.q.x-o.p.x):0,s=o?Math.abs(o.q.y-o.p.y):0;return{...i,orientation:s>a?"vertical":"horizontal"}}(d,l,n),{bg:h,txt:g,bbox:u}=function(e,t){const n=t.style.fontSize;let{txt:o,dy:r,maxW:a}=i.textArea(t.label,200,n,!1,e.x,e.y,"middle");r-=n/2,a+=n;const s="vertical"===e.orientation?e.x+a/2+12:e.x,l="vertical"===e.orientation?e.y:e.y-r/2-12;o.querySelectorAll("tspan").forEach(e=>{e.setAttribute("x",String(s))}),o.setAttribute("y",String(l-r/2)),C(o,U.edgeText),o.setAttribute("stroke","none"),o.setAttribute("font-size",String(t.style.fontSize)),o.setAttribute("fill",t.style.color);const d={x:s-a/2,y:l-r/2,width:a,height:r},c=i.rect(d.width,d.height,d.x,d.y);return C(c,U.edgeRect),o.setAttribute("data-field","label"),d.x+=d.width/2,d.y+=d.height/2,{bg:c,txt:o,bbox:d}}(c,t);r.append(h,g);const{segments:A,path:p}=function(e,t,n,o){const i=[];for(let t=1;t0&&i[i.length-1],function(e,t){for(let n=0;nMath.abs(i[1].x-o.p.x)+Math.abs(i[1].y-o.p.y)&&i.reverse();const t={p:i[1],q:o.q};o.q=i[0],e.splice(n+1,0,t),n+=1}}}}(i,t),i.length>0){r=`M${i[0].p.x},${i[0].p.y}`;for(let e=0;e{if("id"in e&&"edge"in e){const n=e;return n.edge=t,n}return t.initVertex(e)}),t.vertices.forEach((e,t)=>{const n=e;n.ref=i.element("circle",{id:n.id,cx:e.x,cy:e.y,r:7,fill:"none"},"v-dot"),n.selected&&n.ref.classList.add("selected"),n.auto&&n.ref.classList.add("auto"),r.append(n.ref)}),t.ref=r,r}function V(e){if(0==e.nodes.length)return;const t=i.element("g",{},"group");let n={x:1e100,y:1e100},o={x:0,y:0};e.nodes.forEach(e=>{const t=e.style?.shape?.toLowerCase()||"box";let i=e.height/2,r=e.height/2;if("robot"===t){const t=.12*e.height;i=e.height/2+t,r=e.height/2}else if("hexagon"===t){const t=e.width/2*.866;i=t,r=t}const a={x:e.x-e.width/2,y:e.y-i,width:e.width,height:i+r};n.x=Math.min(n.x,a.x),n.y=Math.min(n.y,a.y),o.x=Math.max(o.x,a.x+a.width),o.y=Math.max(o.y,a.y+a.height)});const r=Math.max(o.x-n.x,200),a=o.y-n.y,s={x:n.x-25,y:n.y-25,width:r+50,height:a+50+30},l=i.rect(s.width,s.height,s.x,s.y);e.x=s.x+s.width/2,e.y=s.y+s.height/2,e.width=s.width,e.height=s.height,C(l,U.groupRect),e.style.stroke&&l.setAttribute("stroke",e.style.stroke),e.style.background&&l.setAttribute("fill",e.style.background);const d=i.text(e.name,{x:n.x,y:s.y+s.height-U.groupText["font-size"]});C(d,U.groupText),e.style.color&&d.setAttribute("fill",e.style.color),t.append(l,d),e.ref=t}function N(e,t){let n={dst:Number.POSITIVE_INFINITY,pos:-1,edge:null,prj:null};return e.edges.forEach(e=>{const o=e.vertices||[],i=[e.from,...o,e.to];for(let o=1;o50||a3||Math.abs(s)>3)&&(h=!0,c=null,d)){const e=t.getSelection();e.length=0,e.push(d.node),t.setSelection(e),o=[{x:d.node.x,y:d.node.y,n:d.node}],d=null}if(r)!function(t,n){const o=e.querySelector("g.zoom");if(!o)return;const i=F();o.setAttribute("transform",`translate(${t}, ${n}) scale(${i})`)}(l.x+a,l.y+s);else if(o.length>0&&h){const e=t.getZoom(),n=a/e,i=s/e;o.forEach(e=>{t.moveNode(e.n,e.x+n,e.y+i)}),t.setDragging(!0)}else i&&(i.update(n),t.setDragging(!0))}(n=f(n),n.clientX-p.ex,n.clientY-p.ey)}function b(n){document.removeEventListener("touchmove",m),document.removeEventListener("mousemove",m),document.removeEventListener("mouseup",b),document.removeEventListener("touchend",b),function(n){t.setDragging(!1);const a=h?null:c;if(h&&(g=!0,window.setTimeout(()=>{g=!1},0)),d&&!h){const e=t.getSelection();e.length=0,e.push(d.node),t.setSelection(e)}if(i){const e=i.end();e?t.boxSelection(e,n.shiftKey):o.length||t.setSelection([]),i=null}if(r&&h){const t=e.__data;t&&t.id&&Z(t.id)}d=null,c=null,h=!1,r=!1,t.updatePanning(),a&&(window.location.href=a)}(f(n)),p=null}function y(g){g=f(g),p={ex:g.clientX,ey:g.clientY},function(g){g.preventDefault(),h=!1,d=null;const u=g.target,A=u instanceof Element?u.closest("a.nodeLink"):null;c=g.shiftKey?null:A?.getAttribute("href")||null;const p=t.nodeFromEvent(g),f=g.shiftKey?"pan"===n?"select":"pan":n;if(p)if("pan"===f){r=!1,i=null;const e=t.getSelection();t.isSelected(p)?o=e.map(e=>({x:e.x,y:e.y,n:e})):(t.setSelection([p]),o=[{x:p.x,y:p.y,n:p}])}else{r=!1,i=null;const e=t.getSelection();if(g.shiftKey&&"select"===n){if(t.isSelected(p)){const t=e.findIndex(e=>e.id===p.id);t>=0&&e.splice(t,1)}else e.push(p);t.setSelection(e),o=e.map(e=>({x:e.x,y:e.y,n:e}))}else t.isSelected(p)?(o=e.map(e=>({x:e.x,y:e.y,n:e})),d=null):(d={node:p,shiftKey:g.shiftKey},o=[{x:p.x,y:p.y,n:p}])}else"pan"===f?(r=!0,i=null,a=g.clientX,s=g.clientY,l=function(){const t=e.querySelector("g.zoom");if(!t)return{x:0,y:0};const n=(t.getAttribute("transform")||"").match(/translate\(([^,]+),([^)]+)\)/);return n?{x:parseFloat(n[1])||0,y:parseFloat(n[2])||0}:{x:0,y:0}}(),o=[],t.setSelection([])):(r=!1,i=function(){let t=0,n=0,o=null;return{ini(i){const r=z(i);t=r.x,n=r.y,o=document.createElementNS("http://www.w3.org/2000/svg","rect"),o.setAttribute("fill","rgba(0, 100, 255, 0.1)"),o.setAttribute("stroke","rgba(0, 100, 255, 0.5)"),o.setAttribute("stroke-width","1"),o.setAttribute("stroke-dasharray","3,3"),o.setAttribute("x",String(t)),o.setAttribute("y",String(n)),o.setAttribute("width","0"),o.setAttribute("height","0");const a=e.querySelector("g.zoom");a?a.appendChild(o):e.appendChild(o)},update(e){if(!o)return;const i=z(e),r=i.x,a=i.y,s=Math.min(t,r),l=Math.min(n,a),d=Math.abs(r-t),c=Math.abs(a-n);o.setAttribute("x",String(s)),o.setAttribute("y",String(l)),o.setAttribute("width",String(d)),o.setAttribute("height",String(c))},end(){if(!o)return null;const e=parseFloat(o.getAttribute("x")||"0"),t=parseFloat(o.getAttribute("y")||"0"),n=parseFloat(o.getAttribute("width")||"0"),i=parseFloat(o.getAttribute("height")||"0");return o.remove(),o=null,n>5&&i>5?{x:e,y:t,width:n,height:i,left:e,top:t,right:e+n,bottom:t+i}:null}}}(),i&&i.ini(g),o=[])}(g),document.addEventListener("touchmove",m),document.addEventListener("mousemove",m),document.addEventListener("mouseup",b),document.addEventListener("touchend",b)}A.addEventListener("mousedown",y),A.addEventListener("touchstart",y),u.push({element:A,event:"mousedown",handler:y},{element:A,event:"touchstart",handler:y})}(e),e.addEventListener("click",A),u.push({element:e,event:"click",handler:A}),()=>{u.forEach(({element:e,event:t,handler:n})=>{e.removeEventListener(t,n)})}}function R(e,t){const n=e.__cursorInteractionCleanup;function o(e){return e.__data}n&&n();const r=()=>o(e),s=[],l=e=>{r().changed()&&(e.preventDefault(),e.returnValue="")};function d(t,n){t.selected=n;const o=e.querySelector("#"+t.id);t.selected?o.classList.add("selected"):o.classList.remove("selected")}window.addEventListener("beforeunload",l),s.push({element:window,event:"beforeunload",handler:l});const c=t=>{if(!t.altKey)return;const n=N(r(),z(t));if(n){const{prj:t}=n,o=e.querySelector("g.edges");let r=o.querySelector("#prj");r||(r=i.element("circle",{id:"prj",cx:t.x,cy:t.y,r:7}),o.append(r)),r.setAttribute("cx",String(t.x)),r.setAttribute("cy",String(t.y))}else g()};e.addEventListener("mousemove",c),s.push({element:e,event:"mousemove",handler:c});const h=e=>{const t=(0,w.Yp)(e,!0);t!=w.Zj&&t!=w._s&&g()};function g(){const t=e.querySelector("g.edges #prj");t&&t.parentElement.removeChild(t)}window.addEventListener("keyup",h),s.push({element:window,event:"keyup",handler:h});const u=e=>{const t=(0,w.Yp)(e,!0);if(t!=w._s&&t!=w.Zj)return;const n=N(r(),z(e));if(n){const{edge:e,pos:o,prj:i}=n;r().insertEdgeVertex(e,i,o,t==w._s),g()}};e.addEventListener("click",u),s.push({element:e,event:"click",handler:u});const A=t=>{const n=.1*Math.sign(t.deltaY),o=F(),i=Math.max(.1,Math.min(5,o-n));if(i!==o){const n=e.getBoundingClientRect();W(i,t.clientX-n.left,t.clientY-n.top),t.preventDefault();const o=e.__data;o&&o.id&&Z(o.id)}};e.addEventListener("wheel",A),s.push({element:e,event:"wheel",handler:A});const p=e=>{const t=(0,w.Yp)(e);switch(t&&e.preventDefault(),t){case w.bl:Array.from(r().edgeVertices.values()).filter(e=>e.selected).forEach(e=>{r().deleteEdgeVertex(e)});break;case"undo":r().undo();break;case"redo":r().redo();break;case w.Ur:W(Math.min(5,1.2*F())),Z(r().id);break;case w.hU:W(Math.max(.1,F()/1.2)),Z(r().id);break;case w.i1:W(1),Z(r().id);break;case w.mD:r().fitToView();break;case w.F:r().nodes().forEach(e=>r().setNodeSelected(e,!0)),r().edgeVertices.forEach(e=>d(e,!0));break;case w.Gn:r().nodes().forEach(e=>r().setNodeSelected(e,!1)),r().edgeVertices.forEach(e=>d(e,!1))}};window.addEventListener("keydown",p),s.push({element:window,event:"keydown",handler:p});const f=q(e,{nodeFromEvent(e){e.preventDefault();let t=e.target.closest("g.nodes g.node");return t?o(t):(t=e.target.closest("g.edges g.edge .v-dot"),t?r().edgeVertices.get(t.id):null)},setSelection(e){r().nodes().forEach(t=>r().setNodeSelected(t,e.some(e=>e.id==t.id))),r().edgeVertices.forEach(t=>d(t,e.some(e=>e.id==t.id))),D(r().nodes().find(e=>e.selected))},setDragging(e){j=e},isSelected:e=>e.selected,getSelection(){const e=r().nodes().filter(e=>e.selected);return r().edgeVertices.forEach(t=>t.selected&&e.push(t)),e},getZoom:F,moveNode(e,t,n){r().nodesMap.has(e.id)?r().moveNode(e,t,n):(e.auto=!1,r().moveEdgeVertex(e,t,n))},boxSelection(e,t){r().nodesMap.forEach(n=>{var o,i,a;(o={x:(a=n).x-a.width/2,y:a.y-a.height/2,width:a.width,height:a.height}).x<(i=e).x+i.width&&o.yi.x&&o.y+o.height>i.y?r().setNodeSelected(n,!n.selected):t||r().setNodeSelected(n,!1)}),r().edgeVertices.forEach(n=>{a(n,e,!1)?d(n,!n.selected):t||d(n,!1)}),D(r().nodes().find(e=>e.selected))},updatePanning:H},t);e.__cursorInteractionCleanup=()=>{s.forEach(({element:e,event:t,handler:n})=>{e.removeEventListener(t,n)}),f&&f()}}function F(){if(!$)return 1;const e=$.querySelector("g.zoom");if(!e)return 1;const t=(e.getAttribute("transform")||"").match(/scale\(([^)]+)\)/);return t&&parseFloat(t[1])||1}function O(e){if(!$)return;const t=$.querySelector("g.zoom");if(!t)return;const n=G();t.setAttribute("transform",`translate(${n.x}, ${n.y}) scale(${e})`),H()}function W(e,t,n){const o=$.querySelector("g.zoom"),i=F();if(void 0===t||void 0===n){const e=$.parentElement;e?(t=e.clientWidth/2,n=e.clientHeight/2):(t=$.clientWidth/2,n=$.clientHeight/2)}const r=G(),a=t-(t-r.x)/i*e,s=n-(n-r.y)/i*e;o.setAttribute("transform",`translate(${a}, ${s}) scale(${e})`),H()}function G(){if(!$)return{x:0,y:0};const e=$.querySelector("g.zoom");if(!e)return{x:0,y:0};const t=(e.getAttribute("transform")||"").match(/translate\(([^,]+),([^)]+)\)/);return t?{x:parseFloat(t[1])||0,y:parseFloat(t[2])||0}:{x:0,y:0}}function H(){if(!$)return;const e=$.querySelector("g.zoom");if(!e)return;const t=e.getBBox(),n=F();if(!$.parentElement)return;const o=Math.max($.parentElement.clientWidth/n,t.x+t.width+20),i=Math.max($.parentElement.clientHeight/n,t.y+t.height+20);$.setAttribute("width",String(o*n)),$.setAttribute("height",String(i*n))}const P=(e,t)=>{"Dashed"==t?e.setAttribute("stroke-dasharray","4"):"Dotted"==t&&e.setAttribute("stroke-dasharray","2")},U={nodeBorder:{filter:"url(#shadow)"},nodeText:{"font-family":"Arial, sans-serif",stroke:"none"},edgeText:{"font-family":"Arial, sans-serif",stroke:"none"},edgeRect:{fill:"none",stroke:"none"},groupRect:{fill:"rgba(0, 0, 0, 0.02)",stroke:"#666","stroke-width":3,"stroke-dasharray":4},groupText:{"font-family":"Arial, sans-serif",fill:"#666","font-size":22,"font-weight":"bold",cursor:"default"}},Y=new Map;function Z(e){if(!$)return;const t=F(),n=G(),o={zoom:t,transform:{x:n.x,y:n.y}};Y.set(e,o)}function X(e){if(!$||!Y.has(e))return!1;const t=Y.get(e);if(!t)return!1;const n=$.querySelector("g.zoom");return n&&(n.setAttribute("transform",`scale(${t.zoom}) translate(${t.transform.x}, ${t.transform.y})`),H()),!0}function K(e){Y.delete(e)}function J(e,t,n){const o=e.x-t.x,i=e.y-t.y,r=n.x-t.x,a=n.y-t.y,s=o*r+i*a,l=r*r+a*a;if(0===l)return Math.sqrt(o*o+i*i);let d,c,h=s/l;h<0?(d=t.x,c=t.y):h>1?(d=n.x,c=n.y):(d=t.x+h*r,c=t.y+h*a);const g=e.x-d,u=e.y-c;return Math.sqrt(g*g+u*u)}},538(e,t,n){n.d(t,{k:()=>r}),n(408);function o(e={},t=!1){const n={nodeSpacing:e.nodeSpacing??80,layerSpacing:e.layerSpacing??60,componentSpacing:80,padding:40,groupMultiplier:.65};return t&&(n.nodeSpacing=Math.max(n.nodeSpacing*n.groupMultiplier,30),n.layerSpacing=Math.max(n.layerSpacing*n.groupMultiplier,35),n.componentSpacing=Math.max(n.componentSpacing*n.groupMultiplier,25),n.padding=Math.max(n.padding*n.groupMultiplier,15)),n}function i(e,t){const{direction:n="DOWN",compactLayout:o=!1}=t,i={"elk.algorithm":"layered","elk.direction":n,"elk.spacing.nodeNode":e.nodeSpacing.toString(),"elk.spacing.componentComponent":e.componentSpacing.toString(),"elk.padding":`[top=${e.padding},left=${e.padding},bottom=${e.padding},right=${e.padding}]`,"elk.layered.spacing.nodeNodeBetweenLayers":e.layerSpacing.toString(),"elk.layered.spacing.edgeNodeBetweenLayers":"10","elk.layered.spacing.edgeEdgeBetweenLayers":"10","elk.edgeRouting":"POLYLINE","elk.layered.unnecessaryBendpoints":"false","elk.layered.edgeRouting.orthogonal.mode":"DIRECTION_BASED","elk.layered.edgeRouting.orthogonal.spacing":"5","elk.layered.edgeRouting.orthogonal.nodeOverlapRatio":"0.1","elk.layered.compaction.connectedComponents":"true","elk.layered.compaction.postCompaction.strategy":"LEFT_RIGHT","elk.separateConnectedComponents":"true","elk.layered.nodePlacement.strategy":"NETWORK_SIMPLEX","elk.layered.nodePlacement.favorStraightEdges":"true","elk.layered.crossingMinimization.strategy":"LAYER_SWEEP","elk.layered.crossingMinimization.semiInteractive":"true","elk.hierarchyHandling":"SEPARATE_CHILDREN","elk.layered.considerModelOrder.strategy":"NONE","elk.edgeLabels.placement":"CENTER","elk.edgeLabels.inline":"true","elk.spacing.edgeLabel":"5","elk.edgeLabels.avoidOverlap":"false","elk.edgeLabels.considerModelOrder":"false","elk.layered.edgeLabels.sideSelection":"ALWAYS_UP"};return o&&(i["elk.spacing.nodeNode"]=Math.max(.7*e.nodeSpacing,30).toString(),i["elk.layered.spacing.nodeNodeBetweenLayers"]=Math.max(.7*e.layerSpacing,30).toString()),i}async function r(e,t={}){const r=new(await n.e(726).then(n.t.bind(n,862,23)).then(e=>e.default)),s={id:"root",layoutOptions:i(o(t,!1),t),children:[],edges:[]},l=new Map,d=new Map;e.nodesMap.forEach(e=>{if(!e.id)return;l.set(e.id,e);const t=Math.max(e.width||200,150),n=Math.max(e.height||100,250);d.set(e.id,{id:e.id,x:e.x,y:e.y,width:t+50,height:n+50,layoutOptions:{"elk.position":"","elk.nodeSize.constraints":"[FIXED_SIZE]"}})});const c=new Map,h=new Map,g=new Set;e.groupsMap.forEach(e=>{e.nodes.forEach(t=>{if(a(t))return h.has(t.id)||h.set(t.id,e.id),void g.add(t.id);c.has(t.id)||c.set(t.id,e.id)})});const u=new Map,A=e=>{const n=u.get(e.id);if(n)return n;const r=e.nodes.flatMap(t=>{if(a(t))return h.get(t.id)===e.id?[A(t)]:[];const n=d.get(t.id);return n&&c.get(t.id)===e.id?[n]:[]}),s={id:e.id,children:r,edges:[],layoutOptions:i(o(t,!0),t)};return u.set(e.id,s),s};e.groupsMap.forEach(e=>{if(!g.has(e.id)){const t=A(e);t.children.length>0&&s.children.push(t)}}),d.forEach((e,t)=>{c.has(t)||s.children.push(e)});const p=e=>{const t=[];let n=e;for(;n;)t.push(n),n=h.get(n);return t};if(e.edges.forEach(e=>{if(!e.id||!e.from?.id||!e.to?.id)return;if(!l.has(e.from.id)||!l.has(e.to.id))return void console.warn(`Skipping edge ${e.id}: source ${e.from.id} or target ${e.to.id} not found in nodes`);const t=e.label&&e.label.trim()?Math.min(7*e.label.length,200):0,n={id:e.id,sources:[e.from.id],targets:[e.to.id],labels:e.label&&e.label.trim()?[{id:`${e.id}-label`,text:e.label,width:t,height:20,layoutOptions:{"elk.edgeLabels.placement":"CENTER","elk.edgeLabels.inline":"true"}}]:[]},o=((e,t)=>{const n=p(c.get(e)),o=new Set(p(c.get(t)));return n.find(e=>o.has(e))})(e.from.id,e.to.id);(o?u.get(o):s).edges.push(n)}),!s.id||!s.children)throw new Error("Invalid ELK graph structure");try{const t=await r.layout(s),n=[],o=[],i=(e,t=0,o=0)=>{e.children?.forEach(e=>{e.children?i(e,t+(e.x||0),o+(e.y||0)):n.push({id:e.id,x:t+(e.x||0)+(e.width||0)/2,y:o+(e.y||0)+(e.height||0)/2})})},a=(t,n=0,i=0)=>{t.edges?.forEach(t=>{const r=[];let a;t.sections&&t.sections.length>0&&t.sections.forEach(e=>{e.startPoint&&r.push({x:n+e.startPoint.x,y:i+e.startPoint.y}),e.bendPoints&&e.bendPoints.length>0&&e.bendPoints.forEach(e=>{r.push({x:n+e.x,y:i+e.y})}),e.endPoint&&r.push({x:n+e.endPoint.x,y:i+e.endPoint.y})});const s=e.edges.find(e=>e.id===t.id);if(s?.label&&s.label.trim())if(t.labels&&t.labels.length>0){const e=t.labels[0];void 0!==e.x&&void 0!==e.y&&(a={x:n+e.x+(e.width||0)/2,y:i+e.y+(e.height||0)/2})}else if(r.length>=2){const e=Math.floor(r.length/2);if(r.length%2==0){const t=r[e-1],n=r[e];a={x:(t.x+n.x)/2,y:(t.y+n.y)/2}}else a=r[e]}o.push({id:t.id,vertices:r,label:a})}),t.children?.forEach(e=>{e.edges&&e.edges.length>0&&a(e,n+(e.x||0),i+(e.y||0))})};if(i(t),a(t),n.length>0){const e=Math.min(...n.map(e=>e.x)),t=Math.min(...n.map(e=>e.y)),i=50,r=-e+i,a=-t+i;n.forEach(e=>{e.x+=r,e.y+=a}),o.forEach(e=>{e.vertices.forEach(e=>{e.x+=r,e.y+=a}),e.label&&(e.label.x+=r,e.label.y+=a)})}return{nodes:n,edges:o}}catch(t){return console.warn("ELK layout failed, using fallback layout. Error:",t),function(e){const t=[],n=[];let o=0,i=0;const r=Math.ceil(Math.sqrt(e.nodesMap.size));let a=0;return e.nodesMap.forEach(e=>{t.push({id:e.id,x:o,y:i}),a++,a>=r?(a=0,o=0,i+=300):o+=300}),e.edges.forEach(e=>{n.push({id:e.id,vertices:[]})}),{nodes:t,edges:n}}(e)}}function a(e){return"nodes"in e}},245(e,t,n){var o=n(122),i=n(763),r=n(486),a=n(582),s=n.n(a),l=n(991),d=n.n(l),c=n(725),h=n.n(c),g=n(798),u=n.n(g),A=n(754),p=n.n(A),f=n(567),m=n.n(f),b=n(870),y={};y.styleTagTransform=m(),y.setAttributes=u(),y.insert=h().bind(null,"head"),y.domAPI=d(),y.insertStyleElement=p(),s()(b.A,y),b.A&&b.A.locals&&b.A.locals,n(769);class x{callback;handler;running=!1;timeoutId=null;constructor(e){this.callback=e,this.handler=()=>{this.running=!1,this.timeoutId=null,this.callback()}}start(e){this.running&&this.stop(),this.timeoutId=setTimeout(this.handler,e),this.running=!0}stop(){this.running&&null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.running=!1,this.timeoutId=null)}isRunning(){return this.running}}class w{static DEFAULT_OPTIONS={minDelay:1e3,maxDelay:6e4,handshakeTimeout:5e3};static LIVERELOAD_PROTOCOLS=["http://livereload.com/protocols/official-9","http://livereload.com/protocols/2.x-remote-control"];uri;options;fileChangeHandler;socket=null;nextDelay;connectionDesired=!1;disconnectionReason="";handshakeTimeout;reconnectTimer;constructor(e,t){this.fileChangeHandler=e,this.options={...w.DEFAULT_OPTIONS,...t},this.uri="ws://localhost:35729/livereload",this.nextDelay=this.options.minDelay,this.handshakeTimeout=new x(()=>this.handleHandshakeTimeout()),this.reconnectTimer=new x(()=>this.attemptReconnection())}connect(){this.connectionDesired=!0,this.isSocketConnected()||(this.prepareForConnection(),this.createWebSocket())}disconnect(){this.connectionDesired=!1,this.reconnectTimer.stop(),this.isSocketConnected()&&(this.disconnectionReason="manual",this.socket.close())}isSocketConnected(){return null!==this.socket&&this.socket.readyState===WebSocket.OPEN}prepareForConnection(){this.reconnectTimer.stop(),this.disconnectionReason="cannot-connect"}createWebSocket(){this.socket=new WebSocket(this.uri),this.socket.onopen=()=>this.handleOpen(),this.socket.onclose=()=>this.handleClose(),this.socket.onmessage=e=>this.handleMessage(e),this.socket.onerror=()=>this.handleError()}handleOpen(){this.disconnectionReason="handshake-failed",this.startHandshake()}handleClose(){console.log(`WebSocket disconnected: ${this.disconnectionReason}. Retry in ${this.nextDelay}ms`),this.scheduleReconnection()}handleMessage(e){try{const t=JSON.parse(e.data);this.processMessage(t)}catch(e){console.error("Failed to parse WebSocket message:",e)}}handleError(){}processMessage(e){switch(e.command){case"hello":this.handleHelloMessage();break;case"reload":this.handleReloadMessage(e);break;default:console.log("Unknown WebSocket message received:",e)}}handleHelloMessage(){this.handshakeTimeout.stop(),this.nextDelay=this.options.minDelay}handleReloadMessage(e){this.reconnectTimer.stop(),this.connect(),e.path&&this.fileChangeHandler(e.path)}startHandshake(){const e={command:"hello",protocols:w.LIVERELOAD_PROTOCOLS,ver:"3.3.1"};this.sendCommand(e),this.handshakeTimeout.start(this.options.handshakeTimeout)}handleHandshakeTimeout(){this.isSocketConnected()&&(this.disconnectionReason="handshake-timeout",this.socket.close())}attemptReconnection(){this.connectionDesired&&this.connect()}scheduleReconnection(){this.connectionDesired&&(this.reconnectTimer.isRunning()||(this.reconnectTimer.start(this.nextDelay),this.nextDelay=Math.min(this.options.maxDelay,2*this.nextDelay)))}sendCommand(e){this.isSocketConnected()&&this.socket.send(JSON.stringify(e))}}var v=n(987);const C=(0,i.lazy)(()=>Promise.resolve().then(n.bind(n,486)).then(e=>({default:e.Root}))),k=()=>{const[e,t]=(0,i.useState)({data:null,error:null,loading:!0}),n=async()=>{t(e=>({...e,loading:!0,error:null}));try{const[e,n]=await Promise.all([fetch("data/model.json"),fetch("data/layout.json")]);if(!e.ok)throw new Error(`Failed to fetch model: ${e.statusText}`);if(!n.ok)throw new Error(`Failed to fetch layout: ${n.statusText}`);const[o,i]=await Promise.all([e.json(),n.json()]);t({data:{model:o,layout:i},error:null,loading:!1})}catch(e){console.error("Failed to load data:",e),t({data:null,error:e instanceof Error?e.message:"Unknown error occurred",loading:!1})}},o=e=>{e.endsWith(".svg")||(console.log("File changed:",e),(0,r.S)(),n())};return(0,i.useEffect)(()=>(new w(o).connect(),n(),()=>{}),[]),e.loading?(0,v.jsx)(I,{}):e.error?(0,v.jsx)(B,{error:e.error,onRetry:n}):e.data?(0,v.jsx)(i.Suspense,{fallback:(0,v.jsx)(I,{}),children:(0,v.jsx)(C,{model:e.data.model,layout:e.data.layout})}):(0,v.jsx)(B,{error:"No data available",onRetry:n})},I=()=>(0,v.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh",fontFamily:"Arial, sans-serif"},children:(0,v.jsx)("div",{children:"Loading..."})}),B=({error:e,onRetry:t})=>(0,v.jsxs)("div",{style:{padding:"20px",color:"red",fontFamily:"monospace",whiteSpace:"pre-wrap",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100vh"},children:[(0,v.jsx)("h2",{children:"Error loading application"}),(0,v.jsx)("p",{children:e}),(0,v.jsx)("button",{onClick:t,style:{padding:"10px 20px",fontSize:"16px",cursor:"pointer",backgroundColor:"#007bff",color:"white",border:"none",borderRadius:"4px"},children:"Retry"})]}),E=document.getElementById("root");if(!E)throw new Error("Root container not found");(0,o.createRoot)(E).render((0,v.jsx)(k,{}))},264(e,t,n){n(763);var o=n(686),i=n(987);const r="add-vertex",a="add-label-vertex",s="del-vertex",l="zoom-in",d="zoom-out",c="zoom-fit",h="zoom-100",g="select-all",u="deselect",A="move-left",p="move-right",f="move-up",m="move-down",b="move-left-fine",y="move-right-fine",x="move-up-fine",w="move-down-fine",v="toggle_drag_mode",C="align_horizontal",k="align_vertical",I="distribute_horizontal",B="distribute_vertical",E="auto_layout",S="reset_position",M="toggle_grid",$="toggle_snap_to_grid",D="snap_all_to_grid",j=[{name:"Help",list:[{id:"help",help:"Show/hide this help",combinations:[{key:"?",shift:!0},{key:"F1",shift:!0}]}]},{name:"File",list:[{id:"save",help:"Save",combinations:[{key:"s",ctrl:!0}]}]},{name:"History",list:[{id:"undo",help:"Undo",combinations:[{ctrl:!0,key:"z"}]},{id:"redo",help:"Redo",combinations:[{ctrl:!0,shift:!0,key:"z"},{ctrl:!0,key:"y"}]}]},{name:"Relationship editing",list:[{id:r,help:"Add relationship vertex",combinations:[{alt:!0,click:!0}]},{id:a,help:"Add label anchor relationship vertex",combinations:[{alt:!0,shift:!0,click:!0}]},{id:s,help:"Remove relationship vertex",combinations:[{key:"DELETE"},{key:"BACKSPACE"}]}]},{name:"Zoom",list:[{id:l,help:"Zoom in",combinations:[{ctrl:!0,key:"="}]},{id:d,help:"Zoom out",combinations:[{ctrl:!0,key:"-"}]},{id:c,help:"Zoom - fit",combinations:[{ctrl:!0,key:"9"}]},{id:h,help:"Zoom 100%",combinations:[{ctrl:!0,key:"0"}]},{id:"wheel_zoom",help:"Zoom in/out with mouse wheel",combinations:[{wheel:!0}]}]},{name:"Mouse Interactions",list:[{id:"pan-view",help:"Pan view (drag empty space)",combinations:[{click:!0}]},{id:"select-element",help:"Select element",combinations:[{click:!0}]},{id:"multi-select",help:"Add/remove from selection",combinations:[{shift:!0,click:!0}]},{id:"box-select",help:"Box selection (drag empty space)",combinations:[{shift:!0,click:!0}]},{id:"move-elements",help:"Move selected elements",combinations:[{click:!0}]}]},{name:"Select",list:[{id:g,help:"Select All",combinations:[{ctrl:!0,key:"a"}]},{id:u,help:"Deselect",combinations:[{key:"ESC"}]}]},{name:"Move",list:[{id:f,help:"Move up (grid increment)",combinations:[{key:"UP"}]},{id:x,help:"Move up (1 pixel)",combinations:[{key:"UP",shift:!0}]},{id:p,help:"Move right (grid increment)",combinations:[{key:"RIGHT"}]},{id:y,help:"Move right (1 pixel)",combinations:[{key:"RIGHT",shift:!0}]},{id:m,help:"Move down (grid increment)",combinations:[{key:"DOWN"}]},{id:w,help:"Move down (1 pixel)",combinations:[{key:"DOWN",shift:!0}]},{id:A,help:"Move left (grid increment)",combinations:[{key:"LEFT"}]},{id:b,help:"Move left (1 pixel)",combinations:[{key:"LEFT",shift:!0}]}]},{name:"View",list:[{id:v,help:"Toggle between pan and select mode",combinations:[{key:"t"}]},{id:S,help:"Reset position and view",combinations:[{key:"Home",ctrl:!0}]}]},{name:"Alignment",list:[{id:C,help:"Align selected elements horizontally",combinations:[{key:"h",ctrl:!0,shift:!0}]},{id:k,help:"Align selected elements vertically",combinations:[{key:"a",ctrl:!0,shift:!0}]},{id:I,help:"Distribute selected elements horizontally",combinations:[{key:"h",ctrl:!0,alt:!0}]},{id:B,help:"Distribute selected elements vertically",combinations:[{key:"v",ctrl:!0,alt:!0}]}]},{name:"Layout",list:[{id:E,help:"Auto layout all elements",combinations:[{key:"l",ctrl:!0}]}]},{name:"Grid",list:[{id:M,help:"Toggle grid visibility",combinations:[{key:"g",ctrl:!0}]},{id:$,help:"Toggle snap to grid",combinations:[{key:"g",ctrl:!0,shift:!0}]},{id:D,help:"Snap all elements to grid",combinations:[{key:"g",ctrl:!0,alt:!0}]}]}],L=j.reduce((e,t)=>e.concat(t.list),[]).reduce((e,t)=>(e[t.id]=t,e),{}),_=e=>[e.ctrl&&(0,o.sy)().toUpperCase(),e.shift&&"SHIFT",e.alt&&"ALT",e.key&&(e.key.length>1?e.key:`"${e.key.toUpperCase()}"`),e.click&&"CLICK",e.wheel&&"WHEEL"].filter(Boolean).join(" + ");n.d(t,["DE",0,I,"F",0,g,"Gg",0,A,"Gn",0,u,"Hd",0,E,"Help",0,()=>(0,i.jsxs)("div",{className:"popover",children:[(0,i.jsx)("h1",{children:"Shortcuts"}),(0,i.jsx)("table",{children:(0,i.jsx)("tbody",{children:j.map(e=>(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("tr",{children:(0,i.jsx)("th",{colSpan:2,children:e.name})}),e.list.map(e=>(0,i.jsxs)("tr",{children:[(0,i.jsx)("td",{children:e.combinations.map(_).join(", ")}),(0,i.jsx)("td",{children:e.help})]}))]}))})})]}),"J8",0,b,"Jk",0,k,"OE",0,D,"Op",0,M,"Ur",0,l,"Vy",0,B,"Yp",0,(e,t=!1,n=!1)=>{const i=Object.keys(L).filter(i=>((e,t,n,i)=>t.combinations.some(t=>{if(Boolean(t.shift)!=e.shiftKey)return!1;if(t.ctrl&&!(0,o.SA)(e))return!1;if(Boolean(t.alt)!=e.altKey)return!1;if(n)return t.click;if(i)return t.wheel;if(t.key){const n=e;return"DELETE"==t.key?"Delete"==n.key:"BACKSPACE"==t.key?"Backspace"==n.key:"ESC"==t.key?"Escape"==n.key:"UP"==t.key?"ArrowUp"==n.key:"DOWN"==t.key?"ArrowDown"==n.key:"LEFT"==t.key?"ArrowLeft"==n.key:"RIGHT"==t.key?"ArrowRight"==n.key:t.key&&n.key&&t.key.toLowerCase()==n.key.toLowerCase()}return!1}))(e,L[i],t,n));if(0!==i.length)return 1===i.length?i[0]:i.sort((e,t)=>{const n=L[e],o=L[t],i=n.combinations[0],r=o.combinations[0],a=(i.shift?1:0)+(i.ctrl?1:0)+(i.alt?1:0);return(r.shift?1:0)+(r.ctrl?1:0)+(r.alt?1:0)-a})[0]},"Zj",0,r,"_s",0,a,"_t",0,S,"aX",0,v,"b3",0,p,"bl",0,s,"hU",0,d,"hZ",0,$,"i1",0,h,"iD",0,y,"l8",0,x,"mD",0,c,"mt",0,w,"rB",0,m,"t9",0,C,"uK",0,f])},686(e,t,n){const o=()=>{if("undefined"==typeof navigator)return!1;if("userAgentData"in navigator&&navigator.userAgentData){const e=navigator.userAgentData.platform;if(e&&e.toLowerCase().includes("mac"))return!0}const e=navigator.userAgent.toLowerCase();if(e.includes("mac os")||e.includes("macintosh"))return!0;if(navigator.platform){const e=navigator.platform.toLowerCase();if(e.includes("mac")||e.includes("darwin"))return!0}try{if(void 0!==new KeyboardEvent("keydown",{metaKey:!0}).metaKey)return/mac|darwin|os x/i.test(navigator.userAgent)}catch(e){}return!1};n.d(t,["SA",0,e=>o()?e.metaKey:e.ctrlKey,"sy",0,()=>o()?"Cmd":"Ctrl"])},870(e,t,n){var o=n(220),i=n.n(o),r=n(716),a=n.n(r)()(i());a.push([e.id,"html, body, #root {\n height: 100%;\n}\n\nbody {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #666;\n margin: 0;\n}\n\n#root {\n display: flex;\n flex-direction: column;\n}\n#root > div.graph {\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 10px;\n background-color: #f0f0f0;\n border-bottom: 1px solid #cccccc;\n}\n\n.toolbar > div {\n display: flex;\n align-items: center;\n}\n\n.toolbar button {\n padding: 5px 8px;\n margin: 0 2px;\n cursor: pointer;\n}\n\n.toolbar button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Drag mode toggle button styles */\n.toolbar button.mode-toggle {\n position: relative;\n border: 1px solid #8f9fc9;\n width: 40px;\n min-height: 28px;\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\n border-color: #8f9fc9;\n color: white;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n}\n\n.toolbar button.mode-toggle:hover {\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\n border-color: #abb8db;\n}\n\n.toolbar button.mode-toggle.select-mode:active {\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\n}\n\n/* Pan mode and active toggle - darker blue */\n.toolbar button.mode-toggle.pan-mode,\n.toolbar button.active-toggle {\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n.toolbar button.mode-toggle.pan-mode:hover,\n.toolbar button.active-toggle:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\n.toolbar button.mode-toggle.pan-mode:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n/* Toggle buttons when inactive - gray styling */\n.toolbar button.inactive-toggle {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n.toolbar button.inactive-toggle:hover {\n background: #b1b1b1;\n}\n\n.toolbar button.inactive-toggle:active {\n background: #a1a1a1;\n}\n\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\n.toolbar button .fas {\n font-size: 1em;\n vertical-align: middle;\n}\n\n/* Zoom percentage display */\n.toolbar button.zoom-display {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\n padding: 6px 8px;\n text-align: center;\n}\n\n.toolbar-group {\n display: flex;\n align-items: center;\n margin-right: 25px; /* Large space between groups */\n}\n\n.toolbar-group:last-child {\n margin-right: 0; /* Remove right margin from the last group (help button) */\n}\n\nbutton {\n border: none;\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n border-radius: 3px;\n padding: 6px 10px;\n color: #fff;\n outline: none;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n margin-right: 5px;\n min-width: 32px;\n min-height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n line-height: 1;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n position: relative;\n}\n\nbutton:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\nbutton:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n}\n\nbutton:last-child {\n margin-right: 0;\n}\n\n/* Save button when no changes - gray */\nbutton.action {\n background: #c1c1c1;\n color: #999;\n}\n\nbutton.action:active {\n background: #b1b1b1;\n}\n\n/* Save button when there are changes - orange */\nbutton.grp {\n background: linear-gradient(to bottom, #ee9564, #de7d48);\n margin-right: 0;\n}\n\nbutton.grp:active {\n background: #d67540;\n}\n\n/* Auto-arrange button with AI purple to blue gradient */\nbutton.auto-arrange {\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\n border-color: #4a4c93;\n}\n\nbutton.auto-arrange:hover {\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\n border-color: #5a5ca3;\n}\n\nbutton.auto-arrange:active {\n background: linear-gradient(135deg, #593B83, #357abd);\n border-color: #3a3c83;\n}\n\nselect {\n border: 1px solid #ccc;\n background: white;\n border-radius: 3px;\n padding: 3px 7px;\n color: #666;\n outline: none;\n margin-right: 5px;\n font-size: 12px;\n}\n\nselect:disabled {\n background: #f5f5f5;\n color: #999;\n}\n\nbutton:disabled {\n background: #c1c1c1;\n color: #999;\n}\n\n#root > div > svg {\n position: absolute;\n user-select: none;\n}\n\n\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\n stroke: #29c229;\n}\n.edge .v-dot {\n fill: transparent;\n stroke: transparent;\n stroke-width: 3px;\n cursor: pointer;\n transition: stroke 0.15s ease;\n}\n.edge .v-dot:hover {\n stroke: #999;\n fill: rgba(153, 153, 153, 0.1);\n}\n.edge .v-dot.selected {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.1);\n}\n.edge .v-dot.selected:hover {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.2);\n}\n.edge .v-dot.auto.selected {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.1);\n}\n.edge .v-dot.auto.selected:hover {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.2);\n}\ncircle#prj {\n fill: none;\n stroke: #777;\n}\n\n.nodeShadow {\n fill: none;\n stroke-width: 4px;\n stroke: rgba(0, 0, 0, 0.13);\n}\n\ng.node {\n user-select: none;\n cursor: default;\n}\n\ng.node.linked {\n cursor: pointer;\n}\n\ng.node text {\n pointer-events: none;\n}\n\n.icon {\n fill: #aaa;\n stroke: #fff;\n}\n#icon-cube {\n fill: #aaa;\n}\n\n/* Ensure all button icons are uncolored */\nbutton .icon,\nbutton svg,\nbutton path {\n fill: currentColor !important;\n stroke: none !important;\n}\n\n/* Font Awesome icon styling in buttons */\nbutton i {\n font-size: 12px;\n color: inherit;\n}\n\nrect.elastic {\n pointer-events: none;\n stroke: none;\n fill: #3bd8281f;\n display: none;\n}\nrect.elastic.on {\n display: block;\n}\n\n.popover {\n position: absolute;\n top: 50px;\n bottom: 10px;\n overflow: auto;\n right: 10px;\n background: ghostwhite;\n padding: 30px;\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\n border: solid 1px #eee;\n}\n\n.popover th {\n text-align: left;\n padding: 20px 0px 10px;\n}\n.popover td {\n padding-right: 20px;\n font-size: 14px;\n}\n\n/* Simple tooltip system with smart positioning */\n[data-tooltip] {\n position: relative;\n}\n\n[data-tooltip]:hover::after {\n content: attr(data-tooltip);\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.9);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n font-weight: normal !important;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n margin-top: 5px;\n animation: tooltip-appear 0.1s ease-out;\n min-width: 120px;\n max-width: calc(100vw - 20px);\n box-sizing: border-box;\n}\n\n[data-tooltip]:hover::before {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-bottom-color: rgba(0, 0, 0, 0.9);\n z-index: 1000;\n pointer-events: none;\n margin-top: 1px;\n animation: tooltip-appear 0.1s ease-out;\n}\n\n/* Special handling for rightmost elements that might overflow */\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 0;\n transform: none;\n}\n\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 16px;\n transform: none;\n}\n\n@keyframes tooltip-appear {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(-5px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\nselect {\n position: relative;\n}\n\n/* Robot shape internal elements - inherit stroke from parent node */\n.node .robot-eye-socket {\n fill: none;\n stroke: inherit;\n stroke-width: 2;\n}\n\n.node .robot-eye {\n fill: currentColor;\n stroke: none;\n}\n\n.node .robot-mouth {\n stroke: inherit;\n}\n\n.node .robot-antenna {\n stroke: inherit;\n}\n\n.node .robot-antenna-ball {\n fill: currentColor;\n stroke: inherit;\n stroke-width: 1.5;\n}\n\n.node .robot-panel {\n stroke: inherit;\n}\n\n.node .robot-indicator {\n fill: currentColor;\n stroke: none;\n}","",{version:3,sources:["webpack://./src/style.css"],names:[],mappings:"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",sourcesContent:["html, body, #root {\n height: 100%;\n}\n\nbody {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #666;\n margin: 0;\n}\n\n#root {\n display: flex;\n flex-direction: column;\n}\n#root > div.graph {\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 10px;\n background-color: #f0f0f0;\n border-bottom: 1px solid #cccccc;\n}\n\n.toolbar > div {\n display: flex;\n align-items: center;\n}\n\n.toolbar button {\n padding: 5px 8px;\n margin: 0 2px;\n cursor: pointer;\n}\n\n.toolbar button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Drag mode toggle button styles */\n.toolbar button.mode-toggle {\n position: relative;\n border: 1px solid #8f9fc9;\n width: 40px;\n min-height: 28px;\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\n border-color: #8f9fc9;\n color: white;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n}\n\n.toolbar button.mode-toggle:hover {\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\n border-color: #abb8db;\n}\n\n.toolbar button.mode-toggle.select-mode:active {\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\n}\n\n/* Pan mode and active toggle - darker blue */\n.toolbar button.mode-toggle.pan-mode,\n.toolbar button.active-toggle {\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n.toolbar button.mode-toggle.pan-mode:hover,\n.toolbar button.active-toggle:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\n.toolbar button.mode-toggle.pan-mode:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n/* Toggle buttons when inactive - gray styling */\n.toolbar button.inactive-toggle {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n.toolbar button.inactive-toggle:hover {\n background: #b1b1b1;\n}\n\n.toolbar button.inactive-toggle:active {\n background: #a1a1a1;\n}\n\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\n.toolbar button .fas {\n font-size: 1em;\n vertical-align: middle;\n}\n\n/* Zoom percentage display */\n.toolbar button.zoom-display {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\n padding: 6px 8px;\n text-align: center;\n}\n\n.toolbar-group {\n display: flex;\n align-items: center;\n margin-right: 25px; /* Large space between groups */\n}\n\n.toolbar-group:last-child {\n margin-right: 0; /* Remove right margin from the last group (help button) */\n}\n\nbutton {\n border: none;\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n border-radius: 3px;\n padding: 6px 10px;\n color: #fff;\n outline: none;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n margin-right: 5px;\n min-width: 32px;\n min-height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n line-height: 1;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n position: relative;\n}\n\nbutton:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\nbutton:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n}\n\nbutton:last-child {\n margin-right: 0;\n}\n\n/* Save button when no changes - gray */\nbutton.action {\n background: #c1c1c1;\n color: #999;\n}\n\nbutton.action:active {\n background: #b1b1b1;\n}\n\n/* Save button when there are changes - orange */\nbutton.grp {\n background: linear-gradient(to bottom, #ee9564, #de7d48);\n margin-right: 0;\n}\n\nbutton.grp:active {\n background: #d67540;\n}\n\n/* Auto-arrange button with AI purple to blue gradient */\nbutton.auto-arrange {\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\n border-color: #4a4c93;\n}\n\nbutton.auto-arrange:hover {\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\n border-color: #5a5ca3;\n}\n\nbutton.auto-arrange:active {\n background: linear-gradient(135deg, #593B83, #357abd);\n border-color: #3a3c83;\n}\n\nselect {\n border: 1px solid #ccc;\n background: white;\n border-radius: 3px;\n padding: 3px 7px;\n color: #666;\n outline: none;\n margin-right: 5px;\n font-size: 12px;\n}\n\nselect:disabled {\n background: #f5f5f5;\n color: #999;\n}\n\nbutton:disabled {\n background: #c1c1c1;\n color: #999;\n}\n\n#root > div > svg {\n position: absolute;\n user-select: none;\n}\n\n\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\n stroke: #29c229;\n}\n.edge .v-dot {\n fill: transparent;\n stroke: transparent;\n stroke-width: 3px;\n cursor: pointer;\n transition: stroke 0.15s ease;\n}\n.edge .v-dot:hover {\n stroke: #999;\n fill: rgba(153, 153, 153, 0.1);\n}\n.edge .v-dot.selected {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.1);\n}\n.edge .v-dot.selected:hover {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.2);\n}\n.edge .v-dot.auto.selected {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.1);\n}\n.edge .v-dot.auto.selected:hover {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.2);\n}\ncircle#prj {\n fill: none;\n stroke: #777;\n}\n\n.nodeShadow {\n fill: none;\n stroke-width: 4px;\n stroke: rgba(0, 0, 0, 0.13);\n}\n\ng.node {\n user-select: none;\n cursor: default;\n}\n\ng.node.linked {\n cursor: pointer;\n}\n\ng.node text {\n pointer-events: none;\n}\n\n.icon {\n fill: #aaa;\n stroke: #fff;\n}\n#icon-cube {\n fill: #aaa;\n}\n\n/* Ensure all button icons are uncolored */\nbutton .icon,\nbutton svg,\nbutton path {\n fill: currentColor !important;\n stroke: none !important;\n}\n\n/* Font Awesome icon styling in buttons */\nbutton i {\n font-size: 12px;\n color: inherit;\n}\n\nrect.elastic {\n pointer-events: none;\n stroke: none;\n fill: #3bd8281f;\n display: none;\n}\nrect.elastic.on {\n display: block;\n}\n\n.popover {\n position: absolute;\n top: 50px;\n bottom: 10px;\n overflow: auto;\n right: 10px;\n background: ghostwhite;\n padding: 30px;\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\n border: solid 1px #eee;\n}\n\n.popover th {\n text-align: left;\n padding: 20px 0px 10px;\n}\n.popover td {\n padding-right: 20px;\n font-size: 14px;\n}\n\n/* Simple tooltip system with smart positioning */\n[data-tooltip] {\n position: relative;\n}\n\n[data-tooltip]:hover::after {\n content: attr(data-tooltip);\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.9);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n font-weight: normal !important;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n margin-top: 5px;\n animation: tooltip-appear 0.1s ease-out;\n min-width: 120px;\n max-width: calc(100vw - 20px);\n box-sizing: border-box;\n}\n\n[data-tooltip]:hover::before {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-bottom-color: rgba(0, 0, 0, 0.9);\n z-index: 1000;\n pointer-events: none;\n margin-top: 1px;\n animation: tooltip-appear 0.1s ease-out;\n}\n\n/* Special handling for rightmost elements that might overflow */\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 0;\n transform: none;\n}\n\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 16px;\n transform: none;\n}\n\n@keyframes tooltip-appear {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(-5px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\nselect {\n position: relative;\n}\n\n/* Robot shape internal elements - inherit stroke from parent node */\n.node .robot-eye-socket {\n fill: none;\n stroke: inherit;\n stroke-width: 2;\n}\n\n.node .robot-eye {\n fill: currentColor;\n stroke: none;\n}\n\n.node .robot-mouth {\n stroke: inherit;\n}\n\n.node .robot-antenna {\n stroke: inherit;\n}\n\n.node .robot-antenna-ball {\n fill: currentColor;\n stroke: inherit;\n stroke-width: 1.5;\n}\n\n.node .robot-panel {\n stroke: inherit;\n}\n\n.node .robot-indicator {\n fill: currentColor;\n stroke: none;\n}"],sourceRoot:""}]);const s=a;n.d(t,["A",0,s])}},e=>{e.O(0,[453,96],()=>e(e.s=245)),e.O()}]); +"use strict";(self.webpackChunkapp=self.webpackChunkapp||[]).push([[792],{486(e,t,n){n.d(t,{Root:()=>M,S:()=>$});var o=n(763),i=n(408),r=(n(538),n(32));const a={TopBottom:"DOWN",BottomTop:"UP",LeftRight:"RIGHT",RightLeft:"LEFT"},s=e=>{const t=[];return Object.keys(e.views).filter(e=>e.endsWith("Views")).forEach(n=>{e.views[n].forEach(e=>{t.push({key:e.key,title:e.title||e.key,section:n})})}),t};var l=n(264);const d={};function c(e){const t=e.replace(/([A-Z])/g," $1");return t.charAt(0).toUpperCase()+t.slice(1)}var h=n(686),u=n(987);const g=({model:e,currentID:t,onViewChange:n,graph:o,onAutoLayout:i,onSave:r,onToggleHelp:a,saving:l,layouting:d,dragMode:c,setDragMode:h})=>{const g=s(e);return(0,u.jsxs)("div",{className:"toolbar",children:[(0,u.jsx)(A,{views:g,currentID:t,onViewChange:n}),(0,u.jsx)(p,{graph:o,onAutoLayout:i,onSave:r,onToggleHelp:a,saving:l,layouting:d,dragMode:c,setDragMode:h})]})},A=({views:e,currentID:t,onViewChange:n})=>(0,u.jsxs)("div",{children:["View:",e.length>1?(0,u.jsxs)("select",{onChange:e=>n(e.target.value),value:t,children:[(0,u.jsx)("option",{disabled:!0,value:"",hidden:!0,children:"..."}),e.map(e=>(0,u.jsx)("option",{value:e.key,children:c(e.section)+": "+e.title},e.key))]}):(0,u.jsx)("span",{style:{marginLeft:"8px",fontWeight:"bold"},children:e[0]?c(e[0].section)+": "+e[0].title:"No views available"})]}),p=({graph:e,onAutoLayout:t,onSave:n,onToggleHelp:o,saving:i,layouting:r,dragMode:a,setDragMode:s})=>(0,u.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(m,{dragMode:a,setDragMode:s})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(f,{graph:e})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(y,{graph:e})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(b,{onAutoLayout:t,layouting:r})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(x,{graph:e})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(v,{graph:e})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(C,{onSave:n,saving:i,graph:e})}),(0,u.jsx)("div",{className:"toolbar-group",children:(0,u.jsx)(k,{onToggleHelp:o})})]}),m=({dragMode:e,setDragMode:t})=>(0,u.jsx)("button",{className:"mode-toggle "+("select"===e?"select-mode":"pan-mode"),onClick:()=>t("pan"===e?"select":"pan"),"data-tooltip":"pan"===e?"Pan Mode: Drag to pan the view (T)":"Select Mode: Drag to select elements, Shift+click to add/remove selection (T)",children:"pan"===e?(0,u.jsx)("i",{className:"fas fa-hand-paper"}):(0,u.jsx)("i",{className:"fas fa-mouse-pointer"})}),f=({graph:e})=>{const t=(0,h.sy)();return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("button",{onClick:()=>e.undo(),"data-tooltip":`Undo the last change made to the diagram (${t}+Z)`,children:(0,u.jsx)("i",{className:"fas fa-undo"})}),(0,u.jsx)("button",{onClick:()=>e.redo(),"data-tooltip":`Redo the last undone action (${t}+Shift+Z / ${t}+Y)`,children:(0,u.jsx)("i",{className:"fas fa-redo"})})]})},y=({graph:e})=>{const t=(0,h.sy)();return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("button",{onClick:()=>e.alignSelectionH(),"data-tooltip":`Align all selected elements horizontally (left edges) (${t}+Shift+H)`,children:(0,u.jsx)("i",{className:"fas fa-align-left"})}),(0,u.jsx)("button",{onClick:()=>e.alignSelectionV(),"data-tooltip":`Align all selected elements vertically (top edges) (${t}+Shift+A)`,children:(0,u.jsx)("i",{className:"fas fa-align-left",style:{transform:"rotate(90deg)"}})}),(0,u.jsx)("button",{onClick:()=>e.distributeSelectionH(),"data-tooltip":`Distribute selected elements evenly horizontally (equal spacing) (${t}+Alt+H)`,children:(0,u.jsx)("i",{className:"fas fa-ellipsis-h"})}),(0,u.jsx)("button",{onClick:()=>e.distributeSelectionV(),"data-tooltip":`Distribute selected elements evenly vertically (equal spacing) (${t}+Alt+V)`,children:(0,u.jsx)("i",{className:"fas fa-ellipsis-v"})})]})},b=({onAutoLayout:e,layouting:t})=>{const n=(0,h.sy)();return(0,u.jsx)("button",{className:"auto-arrange",onClick:e,disabled:t,"data-tooltip":`Automatically arrange all elements using the Layered algorithm (${n}+L)`,children:t?(0,u.jsx)("i",{className:"fas fa-spinner fa-spin"}):(0,u.jsx)("i",{className:"fas fa-magic"})})},x=({graph:e})=>{const[t,n]=(0,o.useState)(e.isGridVisible()),[i,r]=(0,o.useState)(e.isSnapToGrid()),a=(0,h.sy)();return o.useEffect(()=>{const t=()=>{n(e.isGridVisible()),r(e.isSnapToGrid())};return t(),window.addEventListener("gridStateChanged",t),()=>{window.removeEventListener("gridStateChanged",t)}},[e]),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("button",{className:t?"active-toggle":"inactive-toggle",onClick:()=>{e.toggleGrid(),n(e.isGridVisible())},"data-tooltip":`Toggle grid visibility (${a}+G)`,children:(0,u.jsx)("i",{className:"fas fa-th"})}),(0,u.jsx)("button",{className:i?"active-toggle":"inactive-toggle",onClick:()=>{e.toggleSnapToGrid(),r(e.isSnapToGrid())},"data-tooltip":`Toggle snap to grid (${a}+Shift+G)`,children:(0,u.jsx)("i",{className:"fas fa-magnet"})}),(0,u.jsx)("button",{onClick:()=>{e.snapAllToGrid()},disabled:!i,"data-tooltip":`Snap all elements to grid (${a}+Alt+G)`,children:(0,u.jsx)("i",{className:"fas fa-border-all"})})]})},w=()=>{const[e,t]=(0,o.useState)(100);return(0,o.useEffect)(()=>{const e=()=>{const e=Math.round(100*(0,i.IX)());t(e)};e();const n=setInterval(e,100);return()=>clearInterval(n)},[]),(0,u.jsxs)("button",{onClick:()=>(0,i.a_)(1),className:"zoom-display","data-tooltip":"Click to reset zoom to 100%",children:[e,"%"]})},v=({graph:e})=>{const t=(0,h.sy)();return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("button",{onClick:()=>{(0,i.a_)(Math.max(.1,(0,i.IX)()/1.2))},"data-tooltip":`Zoom out to see more of the diagram (${t}+-)`,children:(0,u.jsx)("i",{className:"fas fa-search-minus"})}),(0,u.jsx)(w,{}),(0,u.jsx)("button",{onClick:()=>{(0,i.a_)(Math.min(5,1.2*(0,i.IX)()))},"data-tooltip":`Zoom in to see details more clearly (${t}+=)`,children:(0,u.jsx)("i",{className:"fas fa-search-plus"})}),(0,u.jsx)("button",{onClick:()=>{e.fitToView()},"data-tooltip":`Fit diagram to view (${t}+9)`,children:(0,u.jsx)("i",{className:"fas fa-expand"})})]})},C=({onSave:e,saving:t,graph:n})=>{const[i,r]=(0,o.useState)(!1),a=(0,h.sy)();return(0,o.useEffect)(()=>{const e=()=>{r(n.changed())};e();const t=setInterval(e,100);return()=>clearInterval(t)},[n]),(0,u.jsx)("button",{className:i?"grp":"action",disabled:t,onClick:e,"data-tooltip":`Save the current diagram layout (${a}+S)`,children:t?(0,u.jsx)("i",{className:"fas fa-spinner fa-spin"}):(0,u.jsx)("i",{className:"fas fa-save"})})},k=({onToggleHelp:e})=>(0,u.jsx)("button",{onClick:e,"data-tooltip":"Show keyboard shortcuts and help information (Shift+? / Shift+F1)",children:(0,u.jsx)("i",{className:"fas fa-question-circle"})}),I=(0,o.lazy)(()=>Promise.resolve().then(n.bind(n,264)).then(e=>({default:e.Help}))),B=(0,o.lazy)(()=>n.e(948).then(n.bind(n,948)).then(e=>({default:e.Graph}))),E=(e,t)=>{const n=document.documentElement;if(!e)return delete n.dataset.mdlAutomationStatus,void delete n.dataset.mdlAutomationError;n.dataset.mdlAutomationStatus=e,t?n.dataset.mdlAutomationError=t:delete n.dataset.mdlAutomationError},S=(e,t)=>{console.error(`${e} failed:`,t),alert(`${e} failed. See console for details.`)},M=({model:e,layout:t})=>(0,u.jsx)(r.Kd,{children:(0,u.jsx)(r.BV,{children:(0,u.jsx)(r.qh,{path:"/",element:(0,u.jsx)(D,{model:e,layouts:t})})})}),$=()=>{var e;(e=new URLSearchParams(document.location.search).get("id")||"")?delete d[e]:Object.keys(d).forEach(e=>delete d[e])},D=({model:e,layouts:t})=>{const[n,s]=(0,r.ok)(),c=decodeURI(n.get("id")||""),[h,A]=(0,o.useState)(!1),[p,m]=(0,o.useState)("pan"),[f,y]=(0,o.useState)(null),b=(0,o.useRef)(null),x=(0,o.useRef)(0),w=((e,t,n)=>{if(d[n])return d[n];const o=((e,t,n)=>{const o=new Map,r=new Map,s=e=>{Array.isArray(e.relationships)&&e.relationships.forEach(e=>{r.set(e.id,e)})};if(e.model.people&&e.model.people.forEach(e=>{o.set(e.id,e),Array.isArray(e.relationships)&&e.relationships.forEach(e=>{r.set(e.id,e)})}),e.model.softwareSystems&&e.model.softwareSystems.forEach(e=>{o.set(e.id,e),s(e),Array.isArray(e.containers)&&e.containers.forEach(t=>{t.parent=e,o.set(t.id,t),s(t),Array.isArray(t.components)&&t.components.forEach(e=>{e.parent=t,o.set(e.id,e),s(e)})})}),e.model.deploymentNodes){const t=e=>{e.containerInstances&&e.containerInstances.forEach(t=>{const n={...o.get(t.containerId),id:t.id};o.set(n.id,n),n.parent=e,s(t)})},n=(e,i)=>{e.parent=i,o.set(e.id,e),s(e),t(e),e.children&&e.children.forEach(t=>n(t,e)),e.infrastructureNodes&&e.infrastructureNodes.forEach(t=>n(t,e))};e.model.deploymentNodes.forEach(e=>n(e,null))}const{view:l,section:d}=function(e,t){let n=null,o="";return Object.keys(e.views).filter(e=>e.endsWith("Views")).some(i=>e.views[i].some(e=>{if(e.key==t)return n=e,o=i,!0})),{view:n,section:o}}(e,n);if(!l)return null;const c=new i.jg(l.key,l.title||l.key),h=l.automaticLayout?.rankDirection;c.layoutDirection=h?a[h]:void 0;const u={name:c.name,description:l.description,version:e.version,elements:[]};if(c.metadata=u,!l.elements)return c;const g={};if("deploymentViews"==d||"containerViews"==d)l.elements.forEach(e=>{const t=o.get(e.id);t?.parent&&(g[t.parent.id]=!0)});else if(l.softwareSystemId)l.elements.find(e=>e.id==l.softwareSystemId)||(g[l.softwareSystemId]=!0);else if("systemLandscapeViews"==d){const t={id:"__enterprise__",...e.model.enterprise};o.set(t.id,t),e.model.people&&e.model.people.filter(e=>"External"!=e.location).forEach(e=>e.parent=t),e.model.softwareSystems&&e.model.softwareSystems.filter(e=>"External"!=e.location).forEach(e=>e.parent=t),g[t.id]=!0}const A=e.views.styles,p=e=>e.toLowerCase().replace(/[^a-z0-9-]/g,"-");A?.elements&&A.elements.forEach(e=>{if(e.tag){const t=`--mdl-${p(e.tag)}`;e.background&&c.colorToVarMap.set(e.background,`${t}-bg`),e.color&&c.colorToVarMap.set(e.color,`${t}-color`),e.stroke&&c.colorToVarMap.set(e.stroke,`${t}-stroke`)}}),A?.relationships&&A.relationships.forEach(e=>{if(e.tag){const t=`--mdl-rel-${p(e.tag)}`;e.color&&c.colorToVarMap.set(e.color,`${t}-color`)}}),l.elements.forEach(t=>{if(g[t.id])return;const n=o.get(t.id),i=n?function(e,t){const n=e.views.containerViews?.find(e=>e.softwareSystemId==t);return n?.key}(e,n.id):void 0;let r="",a={};if(n){const e=n.tags.split(",");r=e[e.length-1],n.technology&&(r+=": "+n.technology),e.forEach(e=>{const t=A&&A.elements&&A.elements.find(t=>t.tag==e);t&&(a={...a,...t})})}c.addNode(t.id,n&&n.name||t.id,r,n&&n.description?n.description:"",a,function(e,t){if(t){const e=encodeURIComponent(t);return{href:`?id=${e}`,exportHref:`${e}.svg`}}if(e?.url)return{href:e.url,exportHref:e.url}}(n,i)),n&&u.elements.push({id:n.id,tags:n.tags,location:n.location,properties:n.properties,elementViewKey:i,technology:n.technology,url:n.url})}),Array.isArray(l.relationships)&&l.relationships.forEach(e=>{const t=r.get(e.id);if(!t)return;if(!c.nodesMap.has(t.sourceId)){if(o.has(t.sourceId)){const e=o.get(t.sourceId);console.warn("Element not found in this view: ",e.id,e.name)}else console.warn("Element not found: ",t.sourceId);return}if(!c.nodesMap.has(t.destinationId)){if(o.has(t.destinationId)){const e=o.get(t.destinationId);console.warn("Element not found in this view: ",e.id,e.name)}else console.warn("Element not found: ",t.destinationId);return}let n={};t.tags.split(",").forEach(e=>{const t=A&&A.relationships&&A.relationships.find(t=>t.tag==e);t&&(n={...n,...t})}),e.routing&&(n.routing=e.routing),c.addEdge(t.id,t.sourceId,t.destinationId,t.description,e.vertices,n)});const m=e=>{let t=0;for(let n=e.parent;n;n=n.parent)t++;return t};return Object.keys(g).map(e=>o.get(e)).sort((e,t)=>m(e)>m(t)?-1:1).forEach(e=>{let t={};"deploymentViews"==d&&o.get(e.id).tags.split(",").forEach(e=>{const n=A&&A.elements&&A.elements.find(t=>t.tag==e);n&&(t={...t,...n})});const n=l.elements.map(e=>o.get(e.id)).filter(t=>!(!t||t.parent!==e||"systemLandscapeViews"===d&&"__enterprise__"===e.id&&"External"===t.location)).map(e=>e.id);n.length>0&&c.addGroup(e.id,e.name,n,t)}),c.init(t[c.id]),c})(e,t,n);return o&&(d[n]=o),o})(e,t,c),{layouting:v,handleAutoLayout:C}=(e=>{const[t,n]=(0,o.useState)(!1);return{layouting:t,handleAutoLayout:(0,o.useCallback)(async t=>{n(!0);try{const n={direction:e.layoutDirection||"DOWN",...t||{}};await e.autoLayout(n)}finally{n(!1)}},[e])}})(w||{}),{saving:k,handleSave:M}=((e,t)=>{const[n,i]=(0,o.useState)(!1);return{saving:n,handleSave:(0,o.useCallback)(async()=>{i(!0);try{const n=await fetch("data/save?id="+encodeURIComponent(t),{method:"post",body:e.exportSVG()});if(202!==n.status){const e=(await n.text()).trim();throw new Error(e||`save failed with HTTP ${n.status}`)}e.setSaved()}finally{i(!1)}},[e,t])}})(w||{},c);if(!w)return(0,u.jsx)(j,{model:e});const $=(0,o.useCallback)(()=>{A(!h)},[h]),D=(0,o.useCallback)(()=>{y(c)},[c]),L=(0,o.useCallback)(()=>{C().catch(e=>S("Layout",e))},[C]),T=(0,o.useCallback)(()=>{M().catch(e=>S("Save",e))},[M]);(0,o.useEffect)(()=>{w&&w.name&&(document.title=`${w.name} - Model`)},[w]),(0,o.useEffect)(()=>{const e=Object.fromEntries(n.entries()),t="1"===e.auto||"true"===e.auto,o="1"===e.save||"true"===e.save;if(!t&&!o)return b.current=null,x.current++,void E(null);if(f!==c)return;const i=`${c}:${n.toString()}`;if(b.current===i)return;b.current=i;const r=++x.current;E("running");const a=(e.direction||"").toUpperCase(),s="1"===e.compact||"true"===e.compact,l={};["UP","DOWN","LEFT","RIGHT"].includes(a)&&(l.direction=a),s&&(l.compactLayout=!0),(async()=>{try{t&&await C(l),o&&await M(),x.current===r&&E("complete")}catch(e){const t=(e=>e instanceof Error?e.message:String(e))(e);console.error("Automation failed:",e),x.current===r&&E("error",t)}})()},[c,w,C,M,f,n]),((e,t,n,i,r,a)=>{(0,o.useEffect)(()=>{const o=o=>{const s=(0,l.Yp)(o);s&&o.preventDefault(),"help"===s?e():"save"===s?t():s===l.aX&&r&&i?r("pan"===i?"select":"pan"):n&&(s===l.t9?n.alignSelectionH():s===l.Jk?n.alignSelectionV():s===l.DE?n.distributeSelectionH():s===l.Vy?n.distributeSelectionV():s===l.Hd&&a?a():s===l._t?n.resetView():s===l.Op?n.toggleGrid():s===l.hZ?n.toggleSnapToGrid():s===l.OE?n.snapAllToGrid():s===l.Gg?n.moveSelected(-n.getGridSize(),0):s===l.J8?n.moveSelected(-1,0,!0):s===l.b3?n.moveSelected(n.getGridSize(),0):s===l.iD?n.moveSelected(1,0,!0):s===l.uK?n.moveSelected(0,-n.getGridSize()):s===l.l8?n.moveSelected(0,-1,!0):s===l.rB?n.moveSelected(0,n.getGridSize()):s===l.mt&&n.moveSelected(0,1,!0))};return window.addEventListener("keydown",o),()=>window.removeEventListener("keydown",o)},[e,t,n,i,r,a])})($,T,w,p,m,L);const _=(0,o.useCallback)(e=>{s({id:encodeURIComponent(e)})},[s]),V=(0,o.useCallback)(e=>{if(e){const n=w.metadata.elements.find(t=>t.id===e);console.log((t=n,JSON.parse(JSON.stringify(t))))}var t},[w]);return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(g,{model:e,currentID:c,onViewChange:_,graph:w,onAutoLayout:L,onSave:T,onToggleHelp:$,saving:k,layouting:v,dragMode:p,setDragMode:m}),(0,u.jsx)(o.Suspense,{fallback:(0,u.jsx)("div",{children:"Loading graph..."}),children:(0,u.jsx)(B,{data:w,onSelect:V,onReady:D,dragMode:p},c)}),h&&(0,u.jsx)(o.Suspense,{fallback:(0,u.jsx)("div",{children:"Loading help..."}),children:(0,u.jsx)(I,{})})]})},j=({model:e})=>{const t=s(e);return o.useEffect(()=>{document.title="Model - Architecture Diagrams as Code",t.length>0&&(document.location.href="?id="+t[0].key)},[t]),t.length>0?(0,u.jsxs)(u.Fragment,{children:["Redirecting to ",t[0].title]}):(0,u.jsx)(u.Fragment,{children:"No views available"})}},408(e,t,n){n.d(t,{jg:()=>M,Qy:()=>W,ZG:()=>T,IX:()=>G,F_:()=>Q,Kp:()=>J,a_:()=>P});const o=(e,t,n)=>{const o=(()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","svg");return document.body.appendChild(e),{measure:(t,n)=>{const o=document.createElementNS("http://www.w3.org/2000/svg","text");o.setAttribute("x","0"),o.setAttribute("y","0");for(let e in n)o.setAttribute(e,n[e]);o.appendChild(document.createTextNode(t)),e.appendChild(o);const{width:i,height:r}=o.getBBox();return e.removeChild(o),{width:i,height:r}},clean:()=>{document.body.removeChild(e)}}})();let i=0;const r=e.trim().split("\n").map(e=>{const r=e.trim().split(/\s+/);let a=[],s=[];return r.forEach(e=>{if(o.measure(e,n).width>t){s.length>0&&(a.push(s.join(" ")),s=[]);const r=((e,t,n,o)=>{const i=[];let r="";for(let a=0;at&&r.length>0?(i.push(r),r=e[a]):r=s}return r.length>0&&i.push(r),i})(e,t,n,o);for(let e=0;e0&&(s=[r[r.length-1]])}else{const r=[...s,e],l=o.measure(r.join(" "),n);l.width>t&&s.length>0?(a.push(s.join(" ")),s=[e]):(i=Math.max(i,l.width),s=r)}}),s.length>0&&a.push(s.join(" ")),a}).reduce((e,t)=>e.concat(t),[]);return o.clean(),{lines:r,maxW:i}},i={element(e,t={},n){const o=document.createElementNS("http://www.w3.org/2000/svg",e);return Object.entries(t).forEach(([e,t])=>o.setAttribute(e,String(t))),n&&o.classList.add(n),o},use(e,t={}){const n=this.element("use",t);return n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href","#"+e),n},path(e,t={},n){return this.element("path",{...t,d:e},n)},text(e,t={}){const n=this.element("text",t);return e&&(n.textContent=e),n},textArea(e,t,n,i,r=0,a=0,s=""){const l={"font-size":`${n}px`,"font-weight":i?"bold":"normal"},{lines:d,maxW:c}=o(e,t,l),h=this.text("",{x:0,y:a,"text-anchor":s||void 0});return d.forEach((e,t)=>{const o=this.element("tspan",{x:r,dy:`${n+2}px`,...l});o.textContent=e,h.append(o)}),{txt:h,dy:(d.length+1)*(n+2),maxW:c}},rect(e,t,n=0,o=0,i=0,r){return this.element("rect",{x:n,y:o,rx:i,ry:i,width:e,height:t},r)},icon(e,t=0,n=0){return this.use(e,{x:t,y:n})},expand(e,t,n){const o=this.element("g",{transform:`translate(${e},${t})`},"expand");return o.append(this.rect(19,19,0,0,1),this.text(n?"-":"+",{x:10,y:14,"text-anchor":"middle"})),o}};function r(e,t,n){e.setAttribute("transform",`translate(${t},${n})`)}function a(e,t,n=!0){return n?e.x>t.x-t.width/2&&e.xt.y-t.height/2&&e.yt.x&&e.xt.y&&e.yfunction(e,t,n,o){let i,r,a,s,l,d={x:null,y:null,onLine1:!1,onLine2:!1};return i=(o.y-n.y)*(t.x-e.x)-(o.x-n.x)*(t.y-e.y),0==i||(r=e.y-n.y,a=e.x-n.x,s=(o.x-n.x)*r-(o.y-n.y)*a,l=(t.x-e.x)*r-(t.y-e.y)*a,r=s/i,a=l/i,d.x=e.x+r*(t.x-e.x),d.y=e.y+r*(t.y-e.y),r>0&&r<1&&(d.onLine1=!0),a>=0&&a<=1&&(d.onLine2=!0)),d}(e,t,n.p,n.q)).filter(e=>e.onLine1&&e.onLine2)}function d(e,t){return a(t,e)?{x:e.x,y:e.y}:l(e,t,e)[0]||{x:e.x,y:e.y}}function c(e,t,n,o,i){const r={x:i.x-e.x,y:i.y-e.y},a=o.x-e.x,s=o.y-e.y;a==r.x&&(r.x+=1e-7);const l=(s-r.y)/(a-r.x),d=s-l*a,c=n*n+t*t*l*l,h=2*t*t*d*l,u=t*t*d*d-t*t*n*n,g=Math.sqrt(h*h-4*c*u),A=r.x>a?(-h+g)/(2*c):(-h-g)/(2*c),p={x:A,y:l*A+d};return p.x+=e.x,p.y+=e.y,p}function h(e,t,n){let o=n.x-t.x,i=n.y-t.y,r=o*o+i*i,a=(e.x-t.x)*o+(e.y-t.y)*i,s=Math.min(1,Math.max(0,a/r));return{x:t.x+o*s,y:t.y+i*s}}function u(e,t){return Math.abs(t.x-e.x)+Math.abs(t.y-e.y)}function g(e){return e/2/(5.5+e/70)}function A(e,t,n){switch(e.toLowerCase()){case"cylinder":return 2*g(t);case"person":return.4*n;case"folder":return t/10;case"robot":return.35*n;case"webbrowser":return n/8;default:return 0}}class p{_el;constructor(e){this._el=e}node(){return this._el}attr(e,t){return this._el.setAttribute(e,String(t)),this}insert(e,t){const n=document.createElementNS("http://www.w3.org/2000/svg",e),o=this._el.insertBefore(n,this._el.querySelector(t));return new p(o)}}function m(e,t,n,o=!1){const i=e.insert("rect",":first-child").attr("rx",o?n.width/8:3).attr("ry",o?n.width/8:3).attr("x",-t.width/2).attr("y",-t.height/2).attr("width",t.width).attr("height",t.height);return n.intersect=function(e){return d(n,e)},i}function f(e,t,n,o,i){const r=e.insert("ellipse",":first-child").attr("cx",0).attr("cy",0).attr("rx",o).attr("ry",i).attr("width",n.width).attr("height",n.height);return n.intersect=function(e){return c(n,o,i,n,e)},r}function y(e,t,n){const o=n.width/8,i=n.width/14,r=e.insert("g",":first-child");return r.insert("path",":first-child").attr("d",`M${-n.width/2},${-n.height/2} l${n.width},0 M${-n.width/2},${n.height/2} l${n.width},0`),r.insert("circle",":first-child").attr("cx",0).attr("cy",n.height/2+o/2).attr("r",.4*i),r.insert("rect",":first-child").attr("x",-i).attr("y",-n.height/2-o/2-.2*i).attr("width",2*i).attr("height",.4*i),r.insert("rect",":first-child").attr("rx",i).attr("ry",i).attr("x",-t.width/2).attr("y",-t.height/2-o).attr("width",t.width).attr("height",t.height+2*o),n.intersect=function(e){return d({x:n.x,y:n.y,width:n.width,height:n.height+2*o},e)},r}const b={box:(e,t)=>m(new p(e),t,t).node(),roundedbox:(e,t)=>m(new p(e),t,t,!0).node(),component:(e,t)=>function(e,t,n){const o=n.width/10,i=e.insert("g",":first-child");return i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2-o).attr("y",-n.height/2+o).attr("width",2*o).attr("height",o),i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2-o).attr("y",-n.height/2+2.5*o).attr("width",2*o).attr("height",o),i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2).attr("y",-n.height/2).attr("width",n.width).attr("height",n.height),n.intersect=function(e){return d({x:n.x-o/2,y:n.y,width:n.width+o,height:n.height},e)},i}(new p(e),0,t).node(),cylinder:(e,t)=>function(e,t,n){const o=t.width,i=o/2,r=g(o),a=t.height,s=`M 0,${r} a${i},${r} 0,0,0 ${o} 0 a ${i},${r} 0,0,0 ${-o} 0 l 0,${a-2*r} a ${i},${r} 0,0,0 ${o} 0 l 0,${2*r-a}`,l=e.attr("label-offset-y",A("cylinder",o,a)).insert("path",":first-child").attr("d",s).attr("transform","translate("+-o/2+","+-a/2+")");return n.intersect=function(e){const t=d(n,e);let o=n.y+n.height/2-r;return t.y>o?c({x:n.x,y:o},i,r,n,e):(o=n.y-n.height/2+r,t.yfunction(e,t,n){const o=t.width,i=t.height,r=`M ${.38*o},${i/3} A${o/2},${i/2} 0,0,0 0 ${i/2}\n\t\tL${o/11},${i} L${o-o/11},${i} L${o},${i/2}\n\t\tA${o/2},${i/2} 0,0,0 ${o-.38*o} ${i/3} \n\t\tA${o/6},${o/6} 0,1,0 ${.38*o} ${i/3}`,a=e.attr("label-offset-y",A("person",o,i)).insert("path",":first-child").attr("d",r).attr("transform","translate("+-o/2+","+-i/2+")");return n.intersect=function(e){return d(n,e)},a}(new p(e),t,t).node(),circle:(e,t)=>function(e,t,n){return f(e,0,n,n.width/2,n.width/2)}(new p(e),0,t).node(),ellipse:(e,t)=>function(e,t,n){return f(e,0,n,.55*n.width,.45*n.width)}(new p(e),0,t).node(),hexagon:(e,t)=>function(e,t,n){const o=n.width/2,i=e.insert("polygon",":first-child").attr("points",[.5,.866,1,0,.5,-.866,-.5,-.866,-1,-0,-.5,.866,.5,.866].map(e=>e*o).join(",")).attr("width",n.width).attr("height",n.height);return n.intersect=function(e){return c(n,n.width/2,n.width/2,n,e)},i}(new p(e),0,t).node(),folder:(e,t)=>function(e,t,n){const o=n.width/20,i=e.attr("label-offset-y",A("folder",n.width,n.height)).insert("g",":first-child");return i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2).attr("y",-n.height/2+2*o).attr("width",n.width).attr("height",n.height-2*o),i.insert("path",":first-child").attr("d",`M0,${-n.height/2+2*o} l${o},${-2*o} h${n.width/2-2*o} v${2*o}`),n.intersect=function(e){return d({x:n.x,y:n.y+o/2,width:n.width,height:n.height+o},e)},i}(new p(e),0,t).node(),mobiledevicelandscape:(e,t)=>function(e,t,n){const o=n.width/8,i=n.width/14,r=e.insert("g",":first-child");return r.insert("path",":first-child").attr("d",`M${-n.width/2},${-n.height/2} l0,${n.height} M${n.width/2},${-n.height/2} l0,${n.height}`),r.insert("circle",":first-child").attr("cx",-n.width/2-o/2).attr("cy",0).attr("r",.4*i),r.insert("rect",":first-child").attr("x",n.width/2+o/2-.2*i).attr("y",-i).attr("width",.4*i).attr("height",2*i),r.insert("rect",":first-child").attr("rx",i).attr("ry",i).attr("x",-t.width/2-o).attr("y",-t.height/2).attr("width",t.width+2*o).attr("height",t.height),n.intersect=function(e){return d({x:n.x,y:n.y,width:n.width+2*o,height:n.height},e)},r}(new p(e),t,t).node(),mobiledeviceportrait:(e,t)=>y(new p(e),t,t).node(),mobiledevice:(e,t)=>y(new p(e),t,t).node(),pipe:(e,t)=>function(e,t,n){const o=n.width,i=n.height,r=i/2,a=r/(2.5+o/70),s=`M${-a},0\n\t\ta${a},${r} 0,0,1 0,${i}\n\t\ta${a},${r} 0,0,1 0,${-i}\n\t\tl${o},0\n\t\ta${a},${r} 0,0,1 0,${i}\n\t\tl${-o},0`,l=e.insert("path",":first-child").attr("d",s).attr("transform","translate("+-o/2+","+-i/2+")");return n.intersect=function(e){return d({x:n.x-a,y:n.y,width:n.width+2*a,height:n.height},e)},l}(new p(e),0,t).node(),robot:(e,t)=>function(e,t,n){const o=n.width,i=n.height,r=Math.min(.28*o,.25*i),a=.2*r,s=.25*r,l=.08*r,c=.12*r,h=.22*r,u=.12*r,g=.3*r,p=o,m=-i/2+s+r,f=i-s-r,y=e.attr("label-offset-y",A("robot",o,i)).insert("g",":first-child");y.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-p/2).attr("y",m).attr("width",p).attr("height",f);const b=-i/2+s;y.insert("rect",":first-child").attr("rx",a).attr("ry",a).attr("x",-r/2).attr("y",b).attr("width",r).attr("height",r),y.insert("line",":first-child").attr("class","robot-antenna").attr("x1",0).attr("y1",b).attr("x2",0).attr("y2",-i/2+2*l).attr("stroke-width",.6*l).attr("stroke-linecap","round"),y.insert("circle",":first-child").attr("class","robot-antenna-ball").attr("cx",0).attr("cy",-i/2+2*l).attr("r",1.2*l);const x=b+.4*r;y.insert("circle",":first-child").attr("class","robot-eye").attr("cx",-h).attr("cy",x).attr("r",c),y.insert("circle",":first-child").attr("class","robot-eye").attr("cx",h).attr("cy",x).attr("r",c);const w=b+.7*r,v=.28*r;return y.insert("path",":first-child").attr("class","robot-mouth").attr("d",`M${-v/2},${w} Q0,${w+.3*v} ${v/2},${w}`).attr("fill","none").attr("stroke-width",.5*l).attr("stroke-linecap","round"),y.insert("rect",":first-child").attr("rx",.25*u).attr("ry",.25*u).attr("x",-r/2-u-1).attr("y",x-g/2).attr("width",u).attr("height",g),y.insert("rect",":first-child").attr("rx",.25*u).attr("ry",.25*u).attr("x",r/2+1).attr("y",x-g/2).attr("width",u).attr("height",g),n.intersect=function(e){return d(n,e)},y}(new p(e),0,t).node(),webbrowser:(e,t)=>function(e,t,n){const o=n.height/8,i=e.attr("label-offset-y",A("webbrowser",n.width,n.height)).insert("g",":first-child");return i.insert("path",":first-child").attr("d",`\n\t\t\tM${-n.width/2},${-n.height/2+o} h${n.width}\n\t\t\tM${-n.width/2+o/4},${-n.height/2+o/4} h${o/2} v${o/2} h${-o/2} z\n\t\t\tM${-n.width/2+o},${-n.height/2+o/4} h${n.width-o-o/4} v${o/2} h${-n.width+o+o/4} z\n\t\t`),i.insert("rect",":first-child").attr("rx",3).attr("ry",3).attr("x",-n.width/2).attr("y",-n.height/2).attr("width",n.width).attr("height",n.height),n.intersect=function(e){return d(n,e)},i}(new p(e),0,t).node()};var x=n(538);class w{versions=[];pos=0;lastSavedPos=0;exportDoc;importDoc;change;tmpPreviousState=null;constructor(e,t,n){this.exportDoc=t,this.importDoc=n,this.change=function(e){let t;return function(){const n=this;clearTimeout(t),t=setTimeout(function(){t=null,e.apply(n)},300)}}(()=>this.saveNow())}beforeChange(){this.tmpPreviousState||(this.tmpPreviousState=this.deepClone(this.exportDoc()))}length(){return this.versions.length}currentState(){return this.deepClone(this.versions[this.pos-1])}saveNow(){if(!this.tmpPreviousState)throw Error("undo.change() was called without previously calling undo.beforeChange()!");this.versions[this.pos]=this.deepClone(this.exportDoc()),this.versions[this.pos-1]=this.tmpPreviousState,this.tmpPreviousState=null,this.pos+=1,this.versions.splice(this.pos)}deepClone(e){return"undefined"!=typeof structuredClone?structuredClone(e):JSON.parse(JSON.stringify(e))}undo(){if(this.pos<2)return;this.pos-=1;const e=this.versions[this.pos-1];this.importDoc(this.deepClone(e))}redo(){if(this.pos>this.versions.length-1)return;const e=this.versions[this.pos];this.importDoc(this.deepClone(e)),this.pos+=1}changed(){return this.pos!==this.lastSavedPos}setSaved(){this.lastSavedPos=this.pos}}var v=n(264);const C={"font-family":"Inter, -apple-system, BlinkMacSystemFont, sans-serif",stroke:"none"},k=(e,t)=>{Object.keys(t).forEach(n=>{const o=t[n];"number"==typeof o?e.style.setProperty(n,o.toString()):e.style.setProperty(n,o)})},I=(e,t)=>Math.sqrt((t.x-e.x)*(t.x-e.x)+(t.y-e.y)*(t.y-e.y));function B(e,t,n,i,r,a){const s={"font-family":String(C["font-family"]),"font-size":`${n}px`,"font-weight":i?"bold":"normal"},l=o(e,t,s);return{lines:l.lines.length>0?l.lines:[""],fontSize:n,lineHeight:n+2,bold:i,field:a,gapAfter:r}}const E={thickness:3,color:"#999",opacity:1,fontSize:22,dashed:!0},S={width:280,height:180,background:"rgba(255, 255, 255, .9)",color:"#666",opacity:.9,stroke:"#999",fontSize:22,shape:"Box"};class M{id;name;nodesMap;edges;edgeVertices;groupsMap;metadata;layoutDirection;colorToVarMap=new Map;_undo;_gridVisible=!1;_snapToGrid=!0;_gridSize=25;_skipAutoFit=!1;constructor(e,t){this.id=e,this.name=t,this.edges=[],this.edgeVertices=new Map,this.nodesMap=new Map,this.groupsMap=new Map,this._undo=new w(this.id,()=>this.exportLayout(!0),e=>this.importLayout(e,!0)),window.graph=this}init(e){e&&this.importLayout(e),this._undo=new w(this.id,()=>this.exportLayout(!0),e=>this.importLayout(e,!0)),this._undo.length()&&this.importLayout(this._undo.currentState()),this._undo.beforeChange(),this._undo.change()}addNode(e,t,n,o,i,r){if(this.nodesMap.has(e))throw Error("duplicate node: "+e);const a={...S,...i},s=(a.shape||"Box").toLowerCase(),l="person"===s?240:180,d=Math.max(280,a.width||0),c=function(e,t,n,o,i){const r=Math.max(o-36,80),a=[B(e,r,i,!0,6,"name"),B(`[${t}]`,r,.75*i,!1,10),B(n,r,Math.min(.8*i,16),!1,0,"description")],s=a.reduce((e,t)=>e+t.lines.length*t.lineHeight+t.gapAfter,0);return{blocks:a,textHeight:s,minimumHeight:s+36}}(t,n,o,d,a.fontSize||22);let h=Math.max(l,a.height||0,c.minimumHeight);for(let e=0;e<20;e++){const e=c.minimumHeight+Math.abs(A(s,d,h));if(e<=h+.1)break;h=e}const u={id:e,title:t,sub:n,description:o,style:a,x:0,y:0,width:d,height:h,intersect:null,link:r,contentLayout:c};this.nodesMap.set(u.id,u)}nodes(){return Array.from(this.nodesMap.values())}addEdge(e,t,n,o,i,r){i&&i.forEach((t,n)=>{const o=t;o.id=`v-${e}-${n}`,this.edgeVertices.set(o.id,o)});const a={id:e,from:this.nodesMap.get(t),to:this.nodesMap.get(n),label:o,vertices:null,style:{...E,...r},initVertex:e=>{const t=e;return t.id||(t.id=((e,t)=>`v-${e}-a-${(e=>{let t=2166136261;for(let n=0;n>>0).toString(36)})(`${e}:${t.x}:${t.y}`)}`)(a.id,e),this.edgeVertices.set(t.id,t)),t.edge=a,e},userDeletedVertices:!1};this.edges.push(a),i&&(a.vertices=i.map(e=>a.initVertex(e)))}addGroup(e,t,n,o){if(this.groupsMap.has(e))return void console.error(`Group exists: ${e} ${t}`);const i={id:e,name:t,x:null,y:null,width:null,height:null,nodes:n.map(n=>{const o=this.nodesMap.get(n)||this.groupsMap.get(n);return o||console.error(`Node or group ${n} not found for group ${e} "${t}"`),o}).filter(Boolean),style:o};this.groupsMap.set(e,i)}setNodeSelected(e,t){e.selected=t,t?e.ref.classList.add("selected"):e.ref.classList.remove("selected"),this.updateEdgesSel()}updateEdgesSel(){this.edges.forEach(e=>{e.to.selected||e.from.selected?e.ref.classList.add("selected"):e.ref.classList.remove("selected")})}moveNode(e,t,n,o=!1,i=!1){if(e){if(this._snapToGrid&&!o){const e=this.snapToGrid(t,n);t=e.x,n=e.y}e.x==t&&e.y==n||(i||this._undo.beforeChange(),e.x=t,e.y=n,r(e.ref,t,n),this.redrawEdges(e),this.redrawGroups(e),i||this._undo.change())}}moveEdgeVertex(e,t,n,o=!1,i=!1){if(this._snapToGrid&&!o){const e=this.snapToGrid(t,n);t=e.x,n=e.y}e.x==t&&e.y==n||(i||this._undo.beforeChange(),e.x=t,e.y=n,this.redrawEdge(e.edge),i||this._undo.change())}moveSelected(e,t,n=!1){this.nodes().forEach(o=>o.selected&&this.moveNode(o,o.x+e,o.y+t,n,!1)),this.edgeVertices.forEach(o=>o.selected&&this.moveEdgeVertex(o,o.x+e,o.y+t,n,!1))}insertEdgeVertex(e,t,n,o){this._undo.beforeChange();const i=e.initVertex(t);i.selected=!0,o&&(e.vertices.forEach(e=>e.label=!1),i.label=!0),e.vertices.splice(n-1,0,i),this.redrawEdge(e),this._undo.change()}deleteEdgeVertex(e){this._undo.beforeChange();const t=e.edge.vertices.indexOf(e);t>=0&&(e.edge.vertices.splice(t,1),this.edgeVertices.delete(e.id),e.edge.userDeletedVertices=!0),this.redrawEdge(e.edge),this._undo.change()}changed(){return this._undo.changed()}undo(){this._undo.undo()}redo(){this._undo.redo()}alignTopLeft(){const e=this.calculateContentBounds(),t=100-e.x,n=100-e.y;this._skipAutoFit=!0,this._undo.beforeChange(),this.nodesMap.forEach(e=>{this.moveNode(e,e.x+t,e.y+n,!0,!0)}),this.edgeVertices.forEach(e=>{this.moveEdgeVertex(e,e.x+t,e.y+n,!0,!0)}),this._undo.change()}resetPanTransform(){const e=G(),t=D.querySelector("g.zoom");t&&(t.setAttribute("transform",`scale(${e}) translate(0, 0)`),function(e){const t=e.calculateContentBounds(),n=G(),o=Math.max(D.parentElement.clientWidth/n,t.x+t.width+20),i=Math.max(D.parentElement.clientHeight/n,t.y+t.height+20);D.setAttribute("width",String(o*n)),D.setAttribute("height",String(i*n))}(this)),ee(this.id),this._skipAutoFit=!1}shouldSkipAutoFit(){return this._skipAutoFit}resetView(){const e=D.querySelector("g.zoom");e&&(e.setAttribute("transform","scale(1) translate(0, 0)"),Y()),ee(this.id)}redrawEdges(e){this.edges.forEach(t=>(e==t.from||e==t.to)&&this.redrawEdge(t)),this.updateEdgesSel()}redrawEdge(e){const t=e.ref.parentElement;t.removeChild(e.ref),e.ref=V(this,e),t.append(e.ref)}redrawGroups(e){this.groupsMap.forEach(e=>{const t=e.ref.parentElement;t.removeChild(e.ref),q(e),t.append(e.ref)})}exportSVG(){const e=document.querySelector("svg#graph"),t=(e.querySelector("rect.elastic"),e.cloneNode(!0));t.querySelectorAll("a.nodeLink[data-export-href]").forEach(e=>{e.setAttribute("href",e.getAttribute("data-export-href")||""),e.removeAttribute("data-export-href")});const n=t.querySelector("rect.elastic");n&&n.remove();const o=this.calculateContentBounds(),i=o.width+100,r=o.height+100,a=50-o.x,s=50-o.y,l=t.querySelector("g.zoom");l&&l.setAttribute("transform",`scale(1) translate(${a}, ${s})`),t.setAttribute("viewBox",`0 0 ${i} ${r}`),t.setAttribute("width",String(i)),t.setAttribute("height",String(r)),t.setAttribute("xmlns","http://www.w3.org/2000/svg"),this.convertStylesToCustomProperties(t);const d=document.createElement("script");return d.setAttribute("type","application/json"),this.metadata.layout=this.exportLayout(),d.append("/g,"]]]>]>"),t.insertBefore(d,t.firstChild),t.outerHTML}convertStylesToCustomProperties(e){0!==this.colorToVarMap.size&&(e.querySelectorAll("[fill]").forEach(e=>{const t=e.getAttribute("fill");t&&this.colorToVarMap.has(t)&&e.setAttribute("fill",`var(${this.colorToVarMap.get(t)}, ${t})`)}),e.querySelectorAll("[stroke]").forEach(e=>{const t=e.getAttribute("stroke");t&&this.colorToVarMap.has(t)&&e.setAttribute("stroke",`var(${this.colorToVarMap.get(t)}, ${t})`)}))}calculateContentBounds(){let e=1/0,t=1/0,n=-1/0,o=-1/0;return this.nodes().forEach(i=>{const r=i.x-i.width/2,a=i.x+i.width/2,s=i.y-i.height/2,l=i.y+i.height/2;e=Math.min(e,r),n=Math.max(n,a),t=Math.min(t,s),o=Math.max(o,l)}),this.edgeVertices.forEach(i=>{e=Math.min(e,i.x-5),n=Math.max(n,i.x+5),t=Math.min(t,i.y-5),o=Math.max(o,i.y+5)}),this.groupsMap.forEach(i=>{const r=i.x-i.width/2,a=i.x+i.width/2,s=i.y-i.height/2,l=i.y+i.height/2;e=Math.min(e,r),n=Math.max(n,a),t=Math.min(t,s),o=Math.max(o,l)}),this.edges.forEach(i=>{if(e=Math.min(e,i.from.x-10,i.to.x-10),n=Math.max(n,i.from.x+10,i.to.x+10),t=Math.min(t,i.from.y-10,i.to.y-10),o=Math.max(o,i.from.y+10,i.to.y+10),i.vertices&&i.vertices.forEach(i=>{e=Math.min(e,i.x-10),n=Math.max(n,i.x+10),t=Math.min(t,i.y-10),o=Math.max(o,i.y+10)}),i.label&&i.label.trim()){const r=(i.from.x+i.to.x)/2,a=(i.from.y+i.to.y)/2,s=10*i.label.length+50;e=Math.min(e,r-s),n=Math.max(n,r+s),t=Math.min(t,a-25),o=Math.max(o,a+25)}}),e===1/0?{x:0,y:0,width:100,height:100}:{x:e,y:t,width:n-e,height:o-t}}exportLayout(e=!1){const t={};return this.nodes().forEach(e=>t[e.id]={x:e.x,y:e.y}),this.edges.forEach(n=>{if(!n.vertices)return;const o=n.vertices.map(e=>({x:e.x,y:e.y,label:e.label,auto:e.auto}));(o.length||e)&&(t[`e-${n.id}`]=o),n.userDeletedVertices&&(t[`e-${n.id}-deleted`]=!0)}),t}setSaved(){this._undo.setSaved()}importLayout(e,t=!1){const n=[];Object.entries(e).forEach(([e,t])=>{e.startsWith("e-")||void 0===t.x||void 0===t.y?e.startsWith("e-")&&Array.isArray(t)&&t.forEach(e=>{void 0!==e.x&&void 0!==e.y&&n.push({x:e.x,y:e.y})}):n.push({x:t.x,y:t.y})});let o=0,i=0;if(n.length>0){const e=Math.min(...n.map(e=>e.x)),t=Math.min(...n.map(e=>e.y));if(e<-100||t<-100||Math.max(...n.map(e=>e.x))>3e3||Math.max(...n.map(e=>e.y))>2e3){const n=50;o=-e+n,i=-t+n}}Object.entries(e).forEach(([e,t])=>{const n=this.nodesMap.get(e);if(n)n.x=t.x+o,n.y=t.y+i;else if(e.startsWith("e-")&&!e.endsWith("-deleted")){const n=this.edges.find(t=>t.id==e.slice(2));if(!n)return;return n.vertices&&n.vertices.forEach(e=>this.edgeVertices.delete(e.id)),void(n.vertices=t.map(e=>{const t={x:e.x+o,y:e.y+i};Object.assign(t,e,{x:e.x+o,y:e.y+i});const r=n.initVertex(t);return e.auto&&(r.auto=!0),r}))}if(e.endsWith("-deleted")){const n=e.slice(2,-8),o=this.edges.find(e=>e.id==n);return void(o&&!0===t&&(o.userDeletedVertices=!0))}}),t&&(this.nodes().forEach(e=>r(e.ref,e.x,e.y)),this.edges.forEach(e=>this.redrawEdge(e)),this.updateEdgesSel(),this.redrawGroups(null))}async autoLayout(e){try{const t=await(0,x.k)(this,e);this._undo.beforeChange(),t.nodes.forEach(e=>{const t=this.nodesMap.get(e.id);t&&this.moveNode(t,e.x,e.y,!1,!0)}),t.edges.forEach(e=>{const t=this.edges.find(t=>t.id==e.id);if(t){if(t.vertices&&t.vertices.forEach(e=>{e.id&&this.edgeVertices.delete(e.id)}),t.vertices=[],t.userDeletedVertices=!1,e.vertices&&e.vertices.length>0&&(t.vertices=e.vertices.map(e=>{const n=t.initVertex(e);return n.auto=!0,n})),e.label){t.vertices&&(t.vertices.forEach(e=>{e.label&&this.edgeVertices.delete(e.id)}),t.vertices=t.vertices.filter(e=>!e.label));const n=t.initVertex(e.label);n.label=!0,n.auto=!0,t.vertices=t.vertices||[];const o=function(e,t,n,o){if(0===e.length)return 0;const i=[n,...e,o];let r=1/0,a=0;for(let e=0;ee.selected);e.push(...Array.from(this.edgeVertices.values()).filter(e=>e.selected));let t=Math.min(...e.map(e=>e.y));this.nodesMap.forEach(e=>e.selected&&this.moveNode(e,e.x,t,!1,!1)),this.edgeVertices.forEach(e=>e.selected&&this.moveEdgeVertex(e,e.x,t,!1,!1))}alignSelectionH(){const e=this.nodes().filter(e=>e.selected);e.push(...Array.from(this.edgeVertices.values()).filter(e=>e.selected));let t=Math.min(...e.map(e=>e.x));this.nodesMap.forEach(e=>e.selected&&this.moveNode(e,t,e.y,!1,!1)),this.edgeVertices.forEach(e=>e.selected&&this.moveEdgeVertex(e,t,e.y,!1,!1))}distributeSelectionH(){const e=this.nodes().filter(e=>e.selected),t=Array.from(this.edgeVertices.values()).filter(e=>e.selected);if(e.length+t.length<3)return;this._undo.beforeChange();const n=[...e,...t];n.sort((e,t)=>e.x-t.x);const o=n[0].x,i=(n[n.length-1].x-o)/(n.length-1);n.forEach((e,t)=>{const n=o+t*i;"title"in e?this.moveNode(e,n,e.y,!1,!0):this.moveEdgeVertex(e,n,e.y,!1,!0)}),this._undo.change()}distributeSelectionV(){const e=this.nodes().filter(e=>e.selected),t=Array.from(this.edgeVertices.values()).filter(e=>e.selected);if(e.length+t.length<3)return;this._undo.beforeChange();const n=[...e,...t];n.sort((e,t)=>e.y-t.y);const o=n[0].y,i=(n[n.length-1].y-o)/(n.length-1);n.forEach((e,t)=>{const n=o+t*i;"title"in e?this.moveNode(e,e.x,n,!1,!0):this.moveEdgeVertex(e,e.x,n,!1,!0)}),this._undo.change()}setEdgeSelected(e,t){t&&(this.setNodeSelected(e.from,!0),this.setNodeSelected(e.to,!0)),this.updateEdgesSel()}fitToView(){const e=this.calculateContentBounds();if(0===e.width||0===e.height)return;const t=D.parentElement?.clientWidth||800,n=D.parentElement?.clientHeight||600,o=(t-80)/e.width,i=(n-80)/e.height,r=Math.min(o,i),a=Math.max(Math.min(r,2),.1),s=t/2-(e.x+e.width/2)*a,l=n/2-(e.y+e.height/2)*a,d=D.querySelector("g.zoom");d&&d.setAttribute("transform",`translate(${s}, ${l}) scale(${a})`),Y(),J(this.id)}saveLayoutState(){return this.exportLayout(!0)}restoreLayoutState(e){this.importLayout(e,!0)}isGridVisible(){return this._gridVisible}isSnapToGrid(){return this._snapToGrid}getGridSize(){return this._gridSize}toggleGrid(){this._gridVisible=!this._gridVisible,this.updateGridDisplay(),window.dispatchEvent(new CustomEvent("gridStateChanged"))}toggleSnapToGrid(){this._snapToGrid=!this._snapToGrid,window.dispatchEvent(new CustomEvent("gridStateChanged"))}snapAllToGrid(){this._snapToGrid&&(this._undo.beforeChange(),this.nodes().forEach(e=>{const t=Math.round(e.x/this._gridSize)*this._gridSize,n=Math.round(e.y/this._gridSize)*this._gridSize;this.moveNode(e,t,n,!1,!0)}),this._undo.change())}snapToGrid(e,t){return{x:Math.round(e/this._gridSize)*this._gridSize,y:Math.round(t/this._gridSize)*this._gridSize}}updateGridDisplay(){if(!D)return;const e=D.querySelector("#grid-pattern");e&&e.remove();const t=D.querySelector("#grid-background");if(t&&t.remove(),!this._gridVisible)return;let n=D.querySelector("defs");n||(n=document.createElementNS("http://www.w3.org/2000/svg","defs"),D.insertBefore(n,D.firstChild));const o=document.createElementNS("http://www.w3.org/2000/svg","pattern");o.id="grid-pattern",o.setAttribute("width",this._gridSize.toString()),o.setAttribute("height",this._gridSize.toString()),o.setAttribute("patternUnits","userSpaceOnUse");const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.setAttribute("d",`M ${this._gridSize} 0 L 0 0 0 ${this._gridSize}`),i.setAttribute("fill","none"),i.setAttribute("stroke","#d0d0d0"),i.setAttribute("stroke-width","1"),i.setAttribute("opacity","0.8"),o.appendChild(i),n.appendChild(o);const r=document.createElementNS("http://www.w3.org/2000/svg","rect");r.id="grid-background",r.setAttribute("x","-10000"),r.setAttribute("y","-10000"),r.setAttribute("width","20000"),r.setAttribute("height","20000"),r.setAttribute("fill","url(#grid-pattern)"),r.setAttribute("pointer-events","none");const a=D.querySelector("g.zoom");a&&a.insertBefore(r,a.firstChild)}}let $,D=document.querySelector("svg#graph");D||(D=document.createElementNS("http://www.w3.org/2000/svg","svg"),D.setAttribute("id","graph"),D.addEventListener("click",e=>$(e))),D.setAttribute("width","100%"),D.setAttribute("height","100%");let j,L=!1;const T=(e,t,n)=>{D.innerHTML='\n\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n',document.body.append(D),D.__data=e,j=t,$=e=>{},_(e);const o=i.rect(300,300,50,50,0,"elastic");return D.append(o),e.updateGridDisplay(),W(D,n),{svg:D,setZoom:H}},_=e=>{const t=i.element("g",{},"zoom"),n=i.element("g",{},"nodes"),o=i.element("g",{},"edges"),a=i.element("g",{},"groups");t.append(a,o,n),e.nodesMap.forEach(t=>{!function(e,t){window.gdata=t;const n=i.element("g",{},"node");n.setAttribute("id",e.id),e.selected&&n.classList.add("selected"),r(n,e.x,e.y);const o=e.link?i.element("a",{href:e.link.href,"data-export-href":e.link.exportHref,"aria-label":`Open ${e.title}`},"nodeLink"):null,a=o||n;o&&(n.classList.add("linked"),n.append(o));const s=e.style.shape||"Box",l=(b[s.toLowerCase()]||b.box)(a,e);l.classList.add("nodeBorder"),k(l,Z.nodeBorder),l.setAttribute("fill",e.style.background),l.setAttribute("stroke",e.style.stroke),l.setAttribute("stroke-width","3"),l.setAttribute("opacity",String(e.style.opacity)),X(l,e.style.border);const d=function(e,t){const n=i.element("g");let o=-e.textHeight/2;return e.blocks.forEach(e=>{const r=i.text("",{"text-anchor":"middle"});k(r,C),t&&r.setAttribute("fill",t),e.field&&r.setAttribute("data-field",e.field),e.lines.forEach((t,n)=>{const a=i.element("tspan",{x:0,y:o+e.fontSize+n*e.lineHeight,"font-size":`${e.fontSize}px`,"font-weight":e.bold?"bold":"normal"});a.textContent=t,r.append(a)}),n.append(r),o+=e.lines.length*e.lineHeight+e.gapAfter}),n}(e.contentLayout,e.style.color);r(d,0,(Number(a.getAttribute("label-offset-y"))||0)/2),a.append(d),n.__data=e,e.ref=n}(t,e),n.append(t.ref)}),e.edges.forEach(e=>{e.labelBounds=void 0}),e.edges.forEach(t=>{V(e,t),o.append(t.ref)}),e.groupsMap.forEach(e=>{q(e),a.append(e.ref)}),D.append(t)};function V(e,t){const n=t.from,o=t.to,r=i.element("g",{},"edge");r.setAttribute("id",t.id),r.setAttribute("data-from",t.from.id),r.setAttribute("data-to",t.to.id);const d=(t.style.position||50)/100,c=function(e,t){const n=e.from,o=e.to;let i=e.vertices?e.vertices.concat():[];if(0==i.length&&!e.userDeletedVertices){const r=t.edges.filter(t=>t.from==e.from&&t.to==e.to);let a=0;if(r.length>1){a=r.indexOf(e)-(r.length-1)/2;let t=0,s=0;Math.abs(n.x-o.x)>Math.abs(n.y-o.y)?s=70*a:t=200*a;const l=e.initVertex({x:(n.x+o.x)/2+t,y:(n.y+o.y)/2+s});l.label=!0,l.auto=!0,i.push(l)}}i.unshift(n),i.push(o);let r=i[i.length-1];for(let e=1;e0;e--)if(!i[e].label){a=i[e];break}const s=(e,t)=>{const n=e.style?.shape?.toLowerCase()||"box",o=t.x-e.x,i=t.y-e.y;if(e.x,e.y,Math.abs(o)<.01&&Math.abs(i)<.01)return{x:e.x+e.width/2,y:e.y};if("cylinder"===n){const t=e.width,n=t/2,r=n/(5.5+t/70),a=e.height/2,s=Math.atan2(i,o),l=Math.cos(s),d=Math.sin(s);let c=1/0;Math.abs(l)>.01&&(c=Math.min(c,Math.abs(n/l))),Math.abs(d)>.01&&(c=Math.min(c,Math.abs(a/d)));const h=e.x+l*c,u=e.y+d*c,g=e.y-a+r,A=e.y+a-r;if(uA){const t=u=0){const e=Math.sqrt(l),n=(-s+e)/(2*i),r=(-s-e)/(2*i);return{x:o>0?Math.max(n,r):Math.min(n,r),y:t}}}return{x:h,y:u}}if("circle"===n){const t=e.width/2,n=Math.atan2(i,o);return{x:e.x+Math.cos(n)*t,y:e.y+Math.sin(n)*t}}if("ellipse"===n){const t=.55*e.width,n=.45*e.width,r=Math.atan2(i,o),a=Math.cos(r),s=Math.sin(r),l=Math.sqrt(t*t*s*s+n*n*a*a);return{x:e.x+t*a*n/l,y:e.y+n*s*t/l}}{const t=e.width/2,n=e.height/2,r=Math.atan2(i,o),a=Math.cos(r),s=Math.sin(r);let l=1/0;return Math.abs(a)>.01&&(l=Math.min(l,Math.abs(t/a))),Math.abs(s)>.01&&(l=Math.min(l,Math.abs(n/s))),{x:e.x+a*l,y:e.y+s*l}}};let l=s(n,r),d=s(o,a);return i[0]=l,i[i.length-1]=d,i}(t,e),h=function(e,t,n){let o,i={x:n.x,y:n.y};const r=e.findIndex(e=>e.label);let a=!0;if(r>=0){const t=e[r];i=t,a=!0===t.auto;const n=[];r>0&&n.push({p:e[r-1],q:i}),re?I(t.p,t.q)>I(e.p,e.q)?t:e:t,void 0)}else{const n=e.slice(1).reduce((t,n,o)=>t+I(e[o],n),0)*t;let r=0;for(let t=1;t0&&r+s>=n){const e=(n-r)/s;i={x:a.p.x+(a.q.x-a.p.x)*e,y:a.p.y+(a.q.y-a.p.y)*e},o=a;break}r+=s}}const s=o?Math.abs(o.q.x-o.p.x):0,l=o?Math.abs(o.q.y-o.p.y):0;return{...i,orientation:l>s?"vertical":"horizontal",segment:o,movable:a}}(c,d,n),{bg:u,txt:g,bbox:A}=function(e,t,n){const o=t.style.fontSize;let{txt:r,dy:a,maxW:l}=i.textArea(t.label,200,o,!1,e.x,e.y,"middle");a-=o/2,l+=o;const d=[{x:e.x,y:e.y}];if(e.movable&&e.segment)for(const t of[.5,.35,.65,.2,.8]){const n={x:e.segment.p.x+(e.segment.q.x-e.segment.p.x)*t,y:e.segment.p.y+(e.segment.q.y-e.segment.p.y)*t};d.some(e=>Math.abs(e.x-n.x)<.1&&Math.abs(e.y-n.y)<.1)||d.push(n)}const c=[...n.nodes().map(e=>z(s(e),8)),...n.edges.filter(e=>e!==t&&e.labelBounds).map(e=>z(e.labelBounds,8))],h=d.flatMap(t=>"vertical"===e.orientation?[1,-1].map(n=>N(t.x+n*(l/2+12),t.y,l,a,t,e,c)):[-1,1].map(n=>N(t.x,t.y+n*(a/2+12),l,a,t,e,c))).reduce((e,t)=>t.score{e.setAttribute("x",String(u))}),r.setAttribute("y",String(g-a/2)),k(r,Z.edgeText),r.setAttribute("stroke","none"),r.setAttribute("font-size",String(t.style.fontSize)),r.setAttribute("fill",t.style.color);const p={...A},m=i.rect(p.width,p.height,p.x,p.y);return k(m,Z.edgeRect),r.setAttribute("data-field","label"),t.labelBounds=A,p.x+=p.width/2,p.y+=p.height/2,{bg:m,txt:r,bbox:p}}(h,t,e);r.append(u,g);const{segments:p,path:m}=function(e,t,n,o){const i=[];for(let t=1;t0&&i[i.length-1],function(e,t){for(let n=0;nMath.abs(i[1].x-o.p.x)+Math.abs(i[1].y-o.p.y)&&i.reverse();const t={p:i[1],q:o.q};o.q=i[0],e.splice(n+1,0,t),n+=1}}}}(i,t),i.length>0){r=`M${i[0].p.x},${i[0].p.y}`;for(let e=0;e{if("id"in e&&"edge"in e){const n=e;return n.edge=t,n}return t.initVertex(e)}),t.vertices.forEach((e,t)=>{const n=e;n.ref=i.element("circle",{id:n.id,cx:e.x,cy:e.y,r:7,fill:"none"},"v-dot"),n.selected&&n.ref.classList.add("selected"),n.auto&&n.ref.classList.add("auto"),r.append(n.ref)}),t.ref=r,r}function N(e,t,n,o,i,r,a){const s={x:e-n/2,y:t-o/2,width:n,height:o},l=a.reduce((e,t)=>{return e+(n=s,o=t,Math.max(0,Math.min(n.x+n.width,o.x+o.width)-Math.max(n.x,o.x))*Math.max(0,Math.min(n.y+n.height,o.y+o.height)-Math.max(n.y,o.y)));var n,o},0);return{centerX:e,centerY:t,bounds:s,score:1e3*l+I(i,r)}}function z(e,t){return{x:e.x-t,y:e.y-t,width:e.width+2*t,height:e.height+2*t}}function q(e){if(0==e.nodes.length)return;const t=i.element("g",{},"group");let n={x:1e100,y:1e100},o={x:0,y:0};e.nodes.forEach(e=>{const t=e.style?.shape?.toLowerCase()||"box";let i=e.height/2,r=e.height/2;if("robot"===t){const t=.12*e.height;i=e.height/2+t,r=e.height/2}else if("hexagon"===t){const t=e.width/2*.866;i=t,r=t}const a={x:e.x-e.width/2,y:e.y-i,width:e.width,height:i+r};n.x=Math.min(n.x,a.x),n.y=Math.min(n.y,a.y),o.x=Math.max(o.x,a.x+a.width),o.y=Math.max(o.y,a.y+a.height)});const r=Math.max(o.x-n.x,200),a=o.y-n.y,s={x:n.x-25,y:n.y-25,width:r+50,height:a+50+30},l=i.rect(s.width,s.height,s.x,s.y);e.x=s.x+s.width/2,e.y=s.y+s.height/2,e.width=s.width,e.height=s.height,k(l,Z.groupRect),e.style.stroke&&l.setAttribute("stroke",e.style.stroke),e.style.background&&l.setAttribute("fill",e.style.background);const d=i.text(e.name,{x:n.x,y:s.y+s.height-Z.groupText["font-size"]});k(d,Z.groupText),e.style.color&&d.setAttribute("fill",e.style.color),t.append(l,d),e.ref=t}function R(e,t){let n={dst:Number.POSITIVE_INFINITY,pos:-1,edge:null,prj:null};return e.edges.forEach(e=>{const o=e.vertices||[],i=[e.from,...o,e.to];for(let o=1;o50||a3||Math.abs(s)>3)&&(h=!0,c=null,d)){const e=t.getSelection();e.length=0,e.push(d.node),t.setSelection(e),o=[{x:d.node.x,y:d.node.y,n:d.node}],d=null}if(r)!function(t,n){const o=e.querySelector("g.zoom");if(!o)return;const i=G();o.setAttribute("transform",`translate(${t}, ${n}) scale(${i})`)}(l.x+a,l.y+s);else if(o.length>0&&h){const e=t.getZoom(),n=a/e,i=s/e;o.forEach(e=>{t.moveNode(e.n,e.x+n,e.y+i)}),t.setDragging(!0)}else i&&(i.update(n),t.setDragging(!0))}(n=m(n),n.clientX-p.ex,n.clientY-p.ey)}function y(n){document.removeEventListener("touchmove",f),document.removeEventListener("mousemove",f),document.removeEventListener("mouseup",y),document.removeEventListener("touchend",y),function(n){t.setDragging(!1);const a=h?null:c;if(h&&(u=!0,window.setTimeout(()=>{u=!1},0)),d&&!h){const e=t.getSelection();e.length=0,e.push(d.node),t.setSelection(e)}if(i){const e=i.end();e?t.boxSelection(e,n.shiftKey):o.length||t.setSelection([]),i=null}if(r&&h){const t=e.__data;t&&t.id&&J(t.id)}d=null,c=null,h=!1,r=!1,t.updatePanning(),a&&(window.location.href=a)}(m(n)),p=null}function b(u){u=m(u),p={ex:u.clientX,ey:u.clientY},function(u){u.preventDefault(),h=!1,d=null;const g=u.target,A=g instanceof Element?g.closest("a.nodeLink"):null;c=u.shiftKey?null:A?.getAttribute("href")||null;const p=t.nodeFromEvent(u),m=u.shiftKey?"pan"===n?"select":"pan":n;if(p)if("pan"===m){r=!1,i=null;const e=t.getSelection();t.isSelected(p)?o=e.map(e=>({x:e.x,y:e.y,n:e})):(t.setSelection([p]),o=[{x:p.x,y:p.y,n:p}])}else{r=!1,i=null;const e=t.getSelection();if(u.shiftKey&&"select"===n){if(t.isSelected(p)){const t=e.findIndex(e=>e.id===p.id);t>=0&&e.splice(t,1)}else e.push(p);t.setSelection(e),o=e.map(e=>({x:e.x,y:e.y,n:e}))}else t.isSelected(p)?(o=e.map(e=>({x:e.x,y:e.y,n:e})),d=null):(d={node:p,shiftKey:u.shiftKey},o=[{x:p.x,y:p.y,n:p}])}else"pan"===m?(r=!0,i=null,a=u.clientX,s=u.clientY,l=function(){const t=e.querySelector("g.zoom");if(!t)return{x:0,y:0};const n=(t.getAttribute("transform")||"").match(/translate\(([^,]+),([^)]+)\)/);return n?{x:parseFloat(n[1])||0,y:parseFloat(n[2])||0}:{x:0,y:0}}(),o=[],t.setSelection([])):(r=!1,i=function(){let t=0,n=0,o=null;return{ini(i){const r=F(i);t=r.x,n=r.y,o=document.createElementNS("http://www.w3.org/2000/svg","rect"),o.setAttribute("fill","rgba(0, 100, 255, 0.1)"),o.setAttribute("stroke","rgba(0, 100, 255, 0.5)"),o.setAttribute("stroke-width","1"),o.setAttribute("stroke-dasharray","3,3"),o.setAttribute("x",String(t)),o.setAttribute("y",String(n)),o.setAttribute("width","0"),o.setAttribute("height","0");const a=e.querySelector("g.zoom");a?a.appendChild(o):e.appendChild(o)},update(e){if(!o)return;const i=F(e),r=i.x,a=i.y,s=Math.min(t,r),l=Math.min(n,a),d=Math.abs(r-t),c=Math.abs(a-n);o.setAttribute("x",String(s)),o.setAttribute("y",String(l)),o.setAttribute("width",String(d)),o.setAttribute("height",String(c))},end(){if(!o)return null;const e=parseFloat(o.getAttribute("x")||"0"),t=parseFloat(o.getAttribute("y")||"0"),n=parseFloat(o.getAttribute("width")||"0"),i=parseFloat(o.getAttribute("height")||"0");return o.remove(),o=null,n>5&&i>5?{x:e,y:t,width:n,height:i,left:e,top:t,right:e+n,bottom:t+i}:null}}}(),i&&i.ini(u),o=[])}(u),document.addEventListener("touchmove",f),document.addEventListener("mousemove",f),document.addEventListener("mouseup",y),document.addEventListener("touchend",y)}A.addEventListener("mousedown",b),A.addEventListener("touchstart",b),g.push({element:A,event:"mousedown",handler:b},{element:A,event:"touchstart",handler:b})}(e),e.addEventListener("click",A),g.push({element:e,event:"click",handler:A}),()=>{g.forEach(({element:e,event:t,handler:n})=>{e.removeEventListener(t,n)})}}function W(e,t){const n=e.__cursorInteractionCleanup;function o(e){return e.__data}n&&n();const r=()=>o(e),l=[],d=e=>{r().changed()&&(e.preventDefault(),e.returnValue="")};function c(t,n){t.selected=n;const o=e.querySelector("#"+t.id);t.selected?o.classList.add("selected"):o.classList.remove("selected")}window.addEventListener("beforeunload",d),l.push({element:window,event:"beforeunload",handler:d});const h=t=>{if(!t.altKey)return;const n=R(r(),F(t));if(n){const{prj:t}=n,o=e.querySelector("g.edges");let r=o.querySelector("#prj");r||(r=i.element("circle",{id:"prj",cx:t.x,cy:t.y,r:7}),o.append(r)),r.setAttribute("cx",String(t.x)),r.setAttribute("cy",String(t.y))}else g()};e.addEventListener("mousemove",h),l.push({element:e,event:"mousemove",handler:h});const u=e=>{const t=(0,v.Yp)(e,!0);t!=v.Zj&&t!=v._s&&g()};function g(){const t=e.querySelector("g.edges #prj");t&&t.parentElement.removeChild(t)}window.addEventListener("keyup",u),l.push({element:window,event:"keyup",handler:u});const A=e=>{const t=(0,v.Yp)(e,!0);if(t!=v._s&&t!=v.Zj)return;const n=R(r(),F(e));if(n){const{edge:e,pos:o,prj:i}=n;r().insertEdgeVertex(e,i,o,t==v._s),g()}};e.addEventListener("click",A),l.push({element:e,event:"click",handler:A});const p=t=>{const n=.1*Math.sign(t.deltaY),o=G(),i=Math.max(.1,Math.min(5,o-n));if(i!==o){const n=e.getBoundingClientRect();P(i,t.clientX-n.left,t.clientY-n.top),t.preventDefault();const o=e.__data;o&&o.id&&J(o.id)}};e.addEventListener("wheel",p),l.push({element:e,event:"wheel",handler:p});const m=e=>{const t=(0,v.Yp)(e);switch(t&&e.preventDefault(),t){case v.bl:Array.from(r().edgeVertices.values()).filter(e=>e.selected).forEach(e=>{r().deleteEdgeVertex(e)});break;case"undo":r().undo();break;case"redo":r().redo();break;case v.Ur:P(Math.min(5,1.2*G())),J(r().id);break;case v.hU:P(Math.max(.1,G()/1.2)),J(r().id);break;case v.i1:P(1),J(r().id);break;case v.mD:r().fitToView();break;case v.F:r().nodes().forEach(e=>r().setNodeSelected(e,!0)),r().edgeVertices.forEach(e=>c(e,!0));break;case v.Gn:r().nodes().forEach(e=>r().setNodeSelected(e,!1)),r().edgeVertices.forEach(e=>c(e,!1))}};window.addEventListener("keydown",m),l.push({element:window,event:"keydown",handler:m});const f=O(e,{nodeFromEvent(e){e.preventDefault();let t=e.target.closest("g.nodes g.node");return t?o(t):(t=e.target.closest("g.edges g.edge .v-dot"),t?r().edgeVertices.get(t.id):null)},setSelection(e){r().nodes().forEach(t=>r().setNodeSelected(t,e.some(e=>e.id==t.id))),r().edgeVertices.forEach(t=>c(t,e.some(e=>e.id==t.id))),j(r().nodes().find(e=>e.selected))},setDragging(e){L=e},isSelected:e=>e.selected,getSelection(){const e=r().nodes().filter(e=>e.selected);return r().edgeVertices.forEach(t=>t.selected&&e.push(t)),e},getZoom:G,moveNode(e,t,n){r().nodesMap.has(e.id)?r().moveNode(e,t,n):(e.auto=!1,r().moveEdgeVertex(e,t,n))},boxSelection(e,t){r().nodesMap.forEach(n=>{var o,i;o=s(n),i=e,o.xi.x&&o.y+o.height>i.y?r().setNodeSelected(n,!n.selected):t||r().setNodeSelected(n,!1)}),r().edgeVertices.forEach(n=>{a(n,e,!1)?c(n,!n.selected):t||c(n,!1)}),j(r().nodes().find(e=>e.selected))},updatePanning:Y},t);e.__cursorInteractionCleanup=()=>{l.forEach(({element:e,event:t,handler:n})=>{e.removeEventListener(t,n)}),f&&f()}}function G(){if(!D)return 1;const e=D.querySelector("g.zoom");if(!e)return 1;const t=(e.getAttribute("transform")||"").match(/scale\(([^)]+)\)/);return t&&parseFloat(t[1])||1}function H(e){if(!D)return;const t=D.querySelector("g.zoom");if(!t)return;const n=U();t.setAttribute("transform",`translate(${n.x}, ${n.y}) scale(${e})`),Y()}function P(e,t,n){const o=D.querySelector("g.zoom"),i=G();if(void 0===t||void 0===n){const e=D.parentElement;e?(t=e.clientWidth/2,n=e.clientHeight/2):(t=D.clientWidth/2,n=D.clientHeight/2)}const r=U(),a=t-(t-r.x)/i*e,s=n-(n-r.y)/i*e;o.setAttribute("transform",`translate(${a}, ${s}) scale(${e})`),Y()}function U(){if(!D)return{x:0,y:0};const e=D.querySelector("g.zoom");if(!e)return{x:0,y:0};const t=(e.getAttribute("transform")||"").match(/translate\(([^,]+),([^)]+)\)/);return t?{x:parseFloat(t[1])||0,y:parseFloat(t[2])||0}:{x:0,y:0}}function Y(){if(!D)return;const e=D.querySelector("g.zoom");if(!e)return;const t=e.getBBox(),n=G();if(!D.parentElement)return;const o=Math.max(D.parentElement.clientWidth/n,t.x+t.width+20),i=Math.max(D.parentElement.clientHeight/n,t.y+t.height+20);D.setAttribute("width",String(o*n)),D.setAttribute("height",String(i*n))}const X=(e,t)=>{"Dashed"==t?e.setAttribute("stroke-dasharray","4"):"Dotted"==t&&e.setAttribute("stroke-dasharray","2")},Z={nodeBorder:{filter:"url(#shadow)"},nodeText:{"font-family":"Arial, sans-serif",stroke:"none"},edgeText:{"font-family":"Arial, sans-serif",stroke:"none"},edgeRect:{fill:"none",stroke:"none"},groupRect:{fill:"rgba(0, 0, 0, 0.02)",stroke:"#666","stroke-width":3,"stroke-dasharray":4},groupText:{"font-family":"Arial, sans-serif",fill:"#666","font-size":22,"font-weight":"bold",cursor:"default"}},K=new Map;function J(e){if(!D)return;const t=G(),n=U(),o={zoom:t,transform:{x:n.x,y:n.y}};K.set(e,o)}function Q(e){if(!D||!K.has(e))return!1;const t=K.get(e);if(!t)return!1;const n=D.querySelector("g.zoom");return n&&(n.setAttribute("transform",`scale(${t.zoom}) translate(${t.transform.x}, ${t.transform.y})`),Y()),!0}function ee(e){K.delete(e)}function te(e,t,n){const o=e.x-t.x,i=e.y-t.y,r=n.x-t.x,a=n.y-t.y,s=o*r+i*a,l=r*r+a*a;if(0===l)return Math.sqrt(o*o+i*i);let d,c,h=s/l;h<0?(d=t.x,c=t.y):h>1?(d=n.x,c=n.y):(d=t.x+h*r,c=t.y+h*a);const u=e.x-d,g=e.y-c;return Math.sqrt(u*u+g*g)}},538(e,t,n){n.d(t,{k:()=>r}),n(408);function o(e={},t=!1){const n={nodeSpacing:e.nodeSpacing??80,layerSpacing:e.layerSpacing??60,componentSpacing:80,padding:40,groupMultiplier:.65};return t&&(n.nodeSpacing=Math.max(n.nodeSpacing*n.groupMultiplier,30),n.layerSpacing=Math.max(n.layerSpacing*n.groupMultiplier,35),n.componentSpacing=Math.max(n.componentSpacing*n.groupMultiplier,25),n.padding=Math.max(n.padding*n.groupMultiplier,15)),n}function i(e,t){const{direction:n="DOWN",compactLayout:o=!1}=t,i={"elk.algorithm":"layered","elk.direction":n,"elk.spacing.nodeNode":e.nodeSpacing.toString(),"elk.spacing.componentComponent":e.componentSpacing.toString(),"elk.padding":`[top=${e.padding},left=${e.padding},bottom=${e.padding},right=${e.padding}]`,"elk.layered.spacing.nodeNodeBetweenLayers":e.layerSpacing.toString(),"elk.layered.spacing.edgeNodeBetweenLayers":"10","elk.layered.spacing.edgeEdgeBetweenLayers":"10","elk.edgeRouting":"POLYLINE","elk.layered.unnecessaryBendpoints":"false","elk.layered.edgeRouting.orthogonal.mode":"DIRECTION_BASED","elk.layered.edgeRouting.orthogonal.spacing":"5","elk.layered.edgeRouting.orthogonal.nodeOverlapRatio":"0.1","elk.layered.compaction.connectedComponents":"true","elk.layered.compaction.postCompaction.strategy":"LEFT_RIGHT","elk.separateConnectedComponents":"true","elk.layered.nodePlacement.strategy":"NETWORK_SIMPLEX","elk.layered.nodePlacement.favorStraightEdges":"true","elk.layered.crossingMinimization.strategy":"LAYER_SWEEP","elk.layered.crossingMinimization.semiInteractive":"true","elk.hierarchyHandling":"SEPARATE_CHILDREN","elk.layered.considerModelOrder.strategy":"NONE","elk.edgeLabels.placement":"CENTER","elk.edgeLabels.inline":"true","elk.spacing.edgeLabel":"5","elk.edgeLabels.avoidOverlap":"false","elk.edgeLabels.considerModelOrder":"false","elk.layered.edgeLabels.sideSelection":"ALWAYS_UP"};return o&&(i["elk.spacing.nodeNode"]=Math.max(.7*e.nodeSpacing,30).toString(),i["elk.layered.spacing.nodeNodeBetweenLayers"]=Math.max(.7*e.layerSpacing,30).toString()),i}async function r(e,t={}){const r=new(await n.e(726).then(n.t.bind(n,862,23)).then(e=>e.default)),s={id:"root",layoutOptions:i(o(t,!1),t),children:[],edges:[]},l=new Map,d=new Map;e.nodesMap.forEach(e=>{if(!e.id)return;l.set(e.id,e);const t=Math.max(e.width||200,150),n=Math.max(e.height||100,250);d.set(e.id,{id:e.id,x:e.x,y:e.y,width:t+50,height:n+50,layoutOptions:{"elk.position":"","elk.nodeSize.constraints":"[FIXED_SIZE]"}})});const c=new Map,h=new Map,u=new Set;e.groupsMap.forEach(e=>{e.nodes.forEach(t=>{if(a(t))return h.has(t.id)||h.set(t.id,e.id),void u.add(t.id);c.has(t.id)||c.set(t.id,e.id)})});const g=new Map,A=e=>{const n=g.get(e.id);if(n)return n;const r=e.nodes.flatMap(t=>{if(a(t))return h.get(t.id)===e.id?[A(t)]:[];const n=d.get(t.id);return n&&c.get(t.id)===e.id?[n]:[]}),s={id:e.id,children:r,edges:[],layoutOptions:i(o(t,!0),t)};return g.set(e.id,s),s};e.groupsMap.forEach(e=>{if(!u.has(e.id)){const t=A(e);t.children.length>0&&s.children.push(t)}}),d.forEach((e,t)=>{c.has(t)||s.children.push(e)});const p=e=>{const t=[];let n=e;for(;n;)t.push(n),n=h.get(n);return t};if(e.edges.forEach(e=>{if(!e.id||!e.from?.id||!e.to?.id)return;if(!l.has(e.from.id)||!l.has(e.to.id))return void console.warn(`Skipping edge ${e.id}: source ${e.from.id} or target ${e.to.id} not found in nodes`);const t=e.label&&e.label.trim()?Math.min(7*e.label.length,200):0,n={id:e.id,sources:[e.from.id],targets:[e.to.id],labels:e.label&&e.label.trim()?[{id:`${e.id}-label`,text:e.label,width:t,height:20,layoutOptions:{"elk.edgeLabels.placement":"CENTER","elk.edgeLabels.inline":"true"}}]:[]},o=((e,t)=>{const n=p(c.get(e)),o=new Set(p(c.get(t)));return n.find(e=>o.has(e))})(e.from.id,e.to.id);(o?g.get(o):s).edges.push(n)}),!s.id||!s.children)throw new Error("Invalid ELK graph structure");try{const t=await r.layout(s),n=[],o=[],i=(e,t=0,o=0)=>{e.children?.forEach(e=>{e.children?i(e,t+(e.x||0),o+(e.y||0)):n.push({id:e.id,x:t+(e.x||0)+(e.width||0)/2,y:o+(e.y||0)+(e.height||0)/2})})},a=(t,n=0,i=0)=>{t.edges?.forEach(t=>{const r=[];let a;t.sections&&t.sections.length>0&&t.sections.forEach(e=>{e.startPoint&&r.push({x:n+e.startPoint.x,y:i+e.startPoint.y}),e.bendPoints&&e.bendPoints.length>0&&e.bendPoints.forEach(e=>{r.push({x:n+e.x,y:i+e.y})}),e.endPoint&&r.push({x:n+e.endPoint.x,y:i+e.endPoint.y})});const s=e.edges.find(e=>e.id===t.id);if(s?.label&&s.label.trim())if(t.labels&&t.labels.length>0){const e=t.labels[0];void 0!==e.x&&void 0!==e.y&&(a={x:n+e.x+(e.width||0)/2,y:i+e.y+(e.height||0)/2})}else if(r.length>=2){const e=Math.floor(r.length/2);if(r.length%2==0){const t=r[e-1],n=r[e];a={x:(t.x+n.x)/2,y:(t.y+n.y)/2}}else a=r[e]}o.push({id:t.id,vertices:r,label:a})}),t.children?.forEach(e=>{e.edges&&e.edges.length>0&&a(e,n+(e.x||0),i+(e.y||0))})};if(i(t),a(t),n.length>0){const e=Math.min(...n.map(e=>e.x)),t=Math.min(...n.map(e=>e.y)),i=50,r=-e+i,a=-t+i;n.forEach(e=>{e.x+=r,e.y+=a}),o.forEach(e=>{e.vertices.forEach(e=>{e.x+=r,e.y+=a}),e.label&&(e.label.x+=r,e.label.y+=a)})}return{nodes:n,edges:o}}catch(t){return console.warn("ELK layout failed, using fallback layout. Error:",t),function(e){const t=[],n=[];let o=0,i=0;const r=Math.ceil(Math.sqrt(e.nodesMap.size));let a=0;return e.nodesMap.forEach(e=>{t.push({id:e.id,x:o,y:i}),a++,a>=r?(a=0,o=0,i+=300):o+=300}),e.edges.forEach(e=>{n.push({id:e.id,vertices:[]})}),{nodes:t,edges:n}}(e)}}function a(e){return"nodes"in e}},245(e,t,n){var o=n(122),i=n(763),r=n(486),a=n(582),s=n.n(a),l=n(991),d=n.n(l),c=n(725),h=n.n(c),u=n(798),g=n.n(u),A=n(754),p=n.n(A),m=n(567),f=n.n(m),y=n(870),b={};b.styleTagTransform=f(),b.setAttributes=g(),b.insert=h().bind(null,"head"),b.domAPI=d(),b.insertStyleElement=p(),s()(y.A,b),y.A&&y.A.locals&&y.A.locals,n(769);class x{callback;handler;running=!1;timeoutId=null;constructor(e){this.callback=e,this.handler=()=>{this.running=!1,this.timeoutId=null,this.callback()}}start(e){this.running&&this.stop(),this.timeoutId=setTimeout(this.handler,e),this.running=!0}stop(){this.running&&null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.running=!1,this.timeoutId=null)}isRunning(){return this.running}}class w{static DEFAULT_OPTIONS={minDelay:1e3,maxDelay:6e4,handshakeTimeout:5e3};static LIVERELOAD_PROTOCOLS=["http://livereload.com/protocols/official-9","http://livereload.com/protocols/2.x-remote-control"];uri;options;fileChangeHandler;socket=null;nextDelay;connectionDesired=!1;disconnectionReason="";handshakeTimeout;reconnectTimer;constructor(e,t){this.fileChangeHandler=e,this.options={...w.DEFAULT_OPTIONS,...t},this.uri="ws://localhost:35729/livereload",this.nextDelay=this.options.minDelay,this.handshakeTimeout=new x(()=>this.handleHandshakeTimeout()),this.reconnectTimer=new x(()=>this.attemptReconnection())}connect(){this.connectionDesired=!0,this.isSocketConnected()||(this.prepareForConnection(),this.createWebSocket())}disconnect(){this.connectionDesired=!1,this.reconnectTimer.stop(),this.isSocketConnected()&&(this.disconnectionReason="manual",this.socket.close())}isSocketConnected(){return null!==this.socket&&this.socket.readyState===WebSocket.OPEN}prepareForConnection(){this.reconnectTimer.stop(),this.disconnectionReason="cannot-connect"}createWebSocket(){this.socket=new WebSocket(this.uri),this.socket.onopen=()=>this.handleOpen(),this.socket.onclose=()=>this.handleClose(),this.socket.onmessage=e=>this.handleMessage(e),this.socket.onerror=()=>this.handleError()}handleOpen(){this.disconnectionReason="handshake-failed",this.startHandshake()}handleClose(){console.log(`WebSocket disconnected: ${this.disconnectionReason}. Retry in ${this.nextDelay}ms`),this.scheduleReconnection()}handleMessage(e){try{const t=JSON.parse(e.data);this.processMessage(t)}catch(e){console.error("Failed to parse WebSocket message:",e)}}handleError(){}processMessage(e){switch(e.command){case"hello":this.handleHelloMessage();break;case"reload":this.handleReloadMessage(e);break;default:console.log("Unknown WebSocket message received:",e)}}handleHelloMessage(){this.handshakeTimeout.stop(),this.nextDelay=this.options.minDelay}handleReloadMessage(e){this.reconnectTimer.stop(),this.connect(),e.path&&this.fileChangeHandler(e.path)}startHandshake(){const e={command:"hello",protocols:w.LIVERELOAD_PROTOCOLS,ver:"3.3.1"};this.sendCommand(e),this.handshakeTimeout.start(this.options.handshakeTimeout)}handleHandshakeTimeout(){this.isSocketConnected()&&(this.disconnectionReason="handshake-timeout",this.socket.close())}attemptReconnection(){this.connectionDesired&&this.connect()}scheduleReconnection(){this.connectionDesired&&(this.reconnectTimer.isRunning()||(this.reconnectTimer.start(this.nextDelay),this.nextDelay=Math.min(this.options.maxDelay,2*this.nextDelay)))}sendCommand(e){this.isSocketConnected()&&this.socket.send(JSON.stringify(e))}}var v=n(987);const C=(0,i.lazy)(()=>Promise.resolve().then(n.bind(n,486)).then(e=>({default:e.Root}))),k=()=>{const[e,t]=(0,i.useState)({data:null,error:null,loading:!0}),n=async()=>{t(e=>({...e,loading:!0,error:null}));try{const[e,n]=await Promise.all([fetch("data/model.json"),fetch("data/layout.json")]);if(!e.ok)throw new Error(`Failed to fetch model: ${e.statusText}`);if(!n.ok)throw new Error(`Failed to fetch layout: ${n.statusText}`);const[o,i]=await Promise.all([e.json(),n.json()]);t({data:{model:o,layout:i},error:null,loading:!1})}catch(e){console.error("Failed to load data:",e),t({data:null,error:e instanceof Error?e.message:"Unknown error occurred",loading:!1})}},o=e=>{e.endsWith(".svg")||(console.log("File changed:",e),(0,r.S)(),n())};return(0,i.useEffect)(()=>(new w(o).connect(),n(),()=>{}),[]),e.loading?(0,v.jsx)(I,{}):e.error?(0,v.jsx)(B,{error:e.error,onRetry:n}):e.data?(0,v.jsx)(i.Suspense,{fallback:(0,v.jsx)(I,{}),children:(0,v.jsx)(C,{model:e.data.model,layout:e.data.layout})}):(0,v.jsx)(B,{error:"No data available",onRetry:n})},I=()=>(0,v.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh",fontFamily:"Arial, sans-serif"},children:(0,v.jsx)("div",{children:"Loading..."})}),B=({error:e,onRetry:t})=>(0,v.jsxs)("div",{style:{padding:"20px",color:"red",fontFamily:"monospace",whiteSpace:"pre-wrap",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",height:"100vh"},children:[(0,v.jsx)("h2",{children:"Error loading application"}),(0,v.jsx)("p",{children:e}),(0,v.jsx)("button",{onClick:t,style:{padding:"10px 20px",fontSize:"16px",cursor:"pointer",backgroundColor:"#007bff",color:"white",border:"none",borderRadius:"4px"},children:"Retry"})]}),E=document.getElementById("root");if(!E)throw new Error("Root container not found");(0,o.createRoot)(E).render((0,v.jsx)(k,{}))},264(e,t,n){n(763);var o=n(686),i=n(987);const r="add-vertex",a="add-label-vertex",s="del-vertex",l="zoom-in",d="zoom-out",c="zoom-fit",h="zoom-100",u="select-all",g="deselect",A="move-left",p="move-right",m="move-up",f="move-down",y="move-left-fine",b="move-right-fine",x="move-up-fine",w="move-down-fine",v="toggle_drag_mode",C="align_horizontal",k="align_vertical",I="distribute_horizontal",B="distribute_vertical",E="auto_layout",S="reset_position",M="toggle_grid",$="toggle_snap_to_grid",D="snap_all_to_grid",j=[{name:"Help",list:[{id:"help",help:"Show/hide this help",combinations:[{key:"?",shift:!0},{key:"F1",shift:!0}]}]},{name:"File",list:[{id:"save",help:"Save",combinations:[{key:"s",ctrl:!0}]}]},{name:"History",list:[{id:"undo",help:"Undo",combinations:[{ctrl:!0,key:"z"}]},{id:"redo",help:"Redo",combinations:[{ctrl:!0,shift:!0,key:"z"},{ctrl:!0,key:"y"}]}]},{name:"Relationship editing",list:[{id:r,help:"Add relationship vertex",combinations:[{alt:!0,click:!0}]},{id:a,help:"Add label anchor relationship vertex",combinations:[{alt:!0,shift:!0,click:!0}]},{id:s,help:"Remove relationship vertex",combinations:[{key:"DELETE"},{key:"BACKSPACE"}]}]},{name:"Zoom",list:[{id:l,help:"Zoom in",combinations:[{ctrl:!0,key:"="}]},{id:d,help:"Zoom out",combinations:[{ctrl:!0,key:"-"}]},{id:c,help:"Zoom - fit",combinations:[{ctrl:!0,key:"9"}]},{id:h,help:"Zoom 100%",combinations:[{ctrl:!0,key:"0"}]},{id:"wheel_zoom",help:"Zoom in/out with mouse wheel",combinations:[{wheel:!0}]}]},{name:"Mouse Interactions",list:[{id:"pan-view",help:"Pan view (drag empty space)",combinations:[{click:!0}]},{id:"select-element",help:"Select element",combinations:[{click:!0}]},{id:"multi-select",help:"Add/remove from selection",combinations:[{shift:!0,click:!0}]},{id:"box-select",help:"Box selection (drag empty space)",combinations:[{shift:!0,click:!0}]},{id:"move-elements",help:"Move selected elements",combinations:[{click:!0}]}]},{name:"Select",list:[{id:u,help:"Select All",combinations:[{ctrl:!0,key:"a"}]},{id:g,help:"Deselect",combinations:[{key:"ESC"}]}]},{name:"Move",list:[{id:m,help:"Move up (grid increment)",combinations:[{key:"UP"}]},{id:x,help:"Move up (1 pixel)",combinations:[{key:"UP",shift:!0}]},{id:p,help:"Move right (grid increment)",combinations:[{key:"RIGHT"}]},{id:b,help:"Move right (1 pixel)",combinations:[{key:"RIGHT",shift:!0}]},{id:f,help:"Move down (grid increment)",combinations:[{key:"DOWN"}]},{id:w,help:"Move down (1 pixel)",combinations:[{key:"DOWN",shift:!0}]},{id:A,help:"Move left (grid increment)",combinations:[{key:"LEFT"}]},{id:y,help:"Move left (1 pixel)",combinations:[{key:"LEFT",shift:!0}]}]},{name:"View",list:[{id:v,help:"Toggle between pan and select mode",combinations:[{key:"t"}]},{id:S,help:"Reset position and view",combinations:[{key:"Home",ctrl:!0}]}]},{name:"Alignment",list:[{id:C,help:"Align selected elements horizontally",combinations:[{key:"h",ctrl:!0,shift:!0}]},{id:k,help:"Align selected elements vertically",combinations:[{key:"a",ctrl:!0,shift:!0}]},{id:I,help:"Distribute selected elements horizontally",combinations:[{key:"h",ctrl:!0,alt:!0}]},{id:B,help:"Distribute selected elements vertically",combinations:[{key:"v",ctrl:!0,alt:!0}]}]},{name:"Layout",list:[{id:E,help:"Auto layout all elements",combinations:[{key:"l",ctrl:!0}]}]},{name:"Grid",list:[{id:M,help:"Toggle grid visibility",combinations:[{key:"g",ctrl:!0}]},{id:$,help:"Toggle snap to grid",combinations:[{key:"g",ctrl:!0,shift:!0}]},{id:D,help:"Snap all elements to grid",combinations:[{key:"g",ctrl:!0,alt:!0}]}]}],L=j.reduce((e,t)=>e.concat(t.list),[]).reduce((e,t)=>(e[t.id]=t,e),{}),T=e=>[e.ctrl&&(0,o.sy)().toUpperCase(),e.shift&&"SHIFT",e.alt&&"ALT",e.key&&(e.key.length>1?e.key:`"${e.key.toUpperCase()}"`),e.click&&"CLICK",e.wheel&&"WHEEL"].filter(Boolean).join(" + ");n.d(t,["DE",0,I,"F",0,u,"Gg",0,A,"Gn",0,g,"Hd",0,E,"Help",0,()=>(0,i.jsxs)("div",{className:"popover",children:[(0,i.jsx)("h1",{children:"Shortcuts"}),(0,i.jsx)("table",{children:(0,i.jsx)("tbody",{children:j.map(e=>(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("tr",{children:(0,i.jsx)("th",{colSpan:2,children:e.name})}),e.list.map(e=>(0,i.jsxs)("tr",{children:[(0,i.jsx)("td",{children:e.combinations.map(T).join(", ")}),(0,i.jsx)("td",{children:e.help})]}))]}))})})]}),"J8",0,y,"Jk",0,k,"OE",0,D,"Op",0,M,"Ur",0,l,"Vy",0,B,"Yp",0,(e,t=!1,n=!1)=>{const i=Object.keys(L).filter(i=>((e,t,n,i)=>t.combinations.some(t=>{if(Boolean(t.shift)!=e.shiftKey)return!1;if(t.ctrl&&!(0,o.SA)(e))return!1;if(Boolean(t.alt)!=e.altKey)return!1;if(n)return t.click;if(i)return t.wheel;if(t.key){const n=e;return"DELETE"==t.key?"Delete"==n.key:"BACKSPACE"==t.key?"Backspace"==n.key:"ESC"==t.key?"Escape"==n.key:"UP"==t.key?"ArrowUp"==n.key:"DOWN"==t.key?"ArrowDown"==n.key:"LEFT"==t.key?"ArrowLeft"==n.key:"RIGHT"==t.key?"ArrowRight"==n.key:t.key&&n.key&&t.key.toLowerCase()==n.key.toLowerCase()}return!1}))(e,L[i],t,n));if(0!==i.length)return 1===i.length?i[0]:i.sort((e,t)=>{const n=L[e],o=L[t],i=n.combinations[0],r=o.combinations[0],a=(i.shift?1:0)+(i.ctrl?1:0)+(i.alt?1:0);return(r.shift?1:0)+(r.ctrl?1:0)+(r.alt?1:0)-a})[0]},"Zj",0,r,"_s",0,a,"_t",0,S,"aX",0,v,"b3",0,p,"bl",0,s,"hU",0,d,"hZ",0,$,"i1",0,h,"iD",0,b,"l8",0,x,"mD",0,c,"mt",0,w,"rB",0,f,"t9",0,C,"uK",0,m])},686(e,t,n){const o=()=>{if("undefined"==typeof navigator)return!1;if("userAgentData"in navigator&&navigator.userAgentData){const e=navigator.userAgentData.platform;if(e&&e.toLowerCase().includes("mac"))return!0}const e=navigator.userAgent.toLowerCase();if(e.includes("mac os")||e.includes("macintosh"))return!0;if(navigator.platform){const e=navigator.platform.toLowerCase();if(e.includes("mac")||e.includes("darwin"))return!0}try{if(void 0!==new KeyboardEvent("keydown",{metaKey:!0}).metaKey)return/mac|darwin|os x/i.test(navigator.userAgent)}catch(e){}return!1};n.d(t,["SA",0,e=>o()?e.metaKey:e.ctrlKey,"sy",0,()=>o()?"Cmd":"Ctrl"])},870(e,t,n){var o=n(220),i=n.n(o),r=n(716),a=n.n(r)()(i());a.push([e.id,"html, body, #root {\n height: 100%;\n}\n\nbody {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #666;\n margin: 0;\n}\n\n#root {\n display: flex;\n flex-direction: column;\n}\n#root > div.graph {\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 10px;\n background-color: #f0f0f0;\n border-bottom: 1px solid #cccccc;\n}\n\n.toolbar > div {\n display: flex;\n align-items: center;\n}\n\n.toolbar button {\n padding: 5px 8px;\n margin: 0 2px;\n cursor: pointer;\n}\n\n.toolbar button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Drag mode toggle button styles */\n.toolbar button.mode-toggle {\n position: relative;\n border: 1px solid #8f9fc9;\n width: 40px;\n min-height: 28px;\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\n border-color: #8f9fc9;\n color: white;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n}\n\n.toolbar button.mode-toggle:hover {\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\n border-color: #abb8db;\n}\n\n.toolbar button.mode-toggle.select-mode:active {\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\n}\n\n/* Pan mode and active toggle - darker blue */\n.toolbar button.mode-toggle.pan-mode,\n.toolbar button.active-toggle {\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n.toolbar button.mode-toggle.pan-mode:hover,\n.toolbar button.active-toggle:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\n.toolbar button.mode-toggle.pan-mode:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n/* Toggle buttons when inactive - gray styling */\n.toolbar button.inactive-toggle {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n.toolbar button.inactive-toggle:hover {\n background: #b1b1b1;\n}\n\n.toolbar button.inactive-toggle:active {\n background: #a1a1a1;\n}\n\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\n.toolbar button .fas {\n font-size: 1em;\n vertical-align: middle;\n}\n\n/* Zoom percentage display */\n.toolbar button.zoom-display {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\n padding: 6px 8px;\n text-align: center;\n}\n\n.toolbar-group {\n display: flex;\n align-items: center;\n margin-right: 25px; /* Large space between groups */\n}\n\n.toolbar-group:last-child {\n margin-right: 0; /* Remove right margin from the last group (help button) */\n}\n\nbutton {\n border: none;\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n border-radius: 3px;\n padding: 6px 10px;\n color: #fff;\n outline: none;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n margin-right: 5px;\n min-width: 32px;\n min-height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n line-height: 1;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n position: relative;\n}\n\nbutton:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\nbutton:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n}\n\nbutton:last-child {\n margin-right: 0;\n}\n\n/* Save button when no changes - gray */\nbutton.action {\n background: #c1c1c1;\n color: #999;\n}\n\nbutton.action:active {\n background: #b1b1b1;\n}\n\n/* Save button when there are changes - orange */\nbutton.grp {\n background: linear-gradient(to bottom, #ee9564, #de7d48);\n margin-right: 0;\n}\n\nbutton.grp:active {\n background: #d67540;\n}\n\n/* Auto-arrange button with AI purple to blue gradient */\nbutton.auto-arrange {\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\n border-color: #4a4c93;\n}\n\nbutton.auto-arrange:hover {\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\n border-color: #5a5ca3;\n}\n\nbutton.auto-arrange:active {\n background: linear-gradient(135deg, #593B83, #357abd);\n border-color: #3a3c83;\n}\n\nselect {\n border: 1px solid #ccc;\n background: white;\n border-radius: 3px;\n padding: 3px 7px;\n color: #666;\n outline: none;\n margin-right: 5px;\n font-size: 12px;\n}\n\nselect:disabled {\n background: #f5f5f5;\n color: #999;\n}\n\nbutton:disabled {\n background: #c1c1c1;\n color: #999;\n}\n\n#root > div > svg {\n position: absolute;\n user-select: none;\n}\n\n\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\n stroke: #29c229;\n}\n.edge .v-dot {\n fill: transparent;\n stroke: transparent;\n stroke-width: 3px;\n cursor: pointer;\n transition: stroke 0.15s ease;\n}\n.edge .v-dot:hover {\n stroke: #999;\n fill: rgba(153, 153, 153, 0.1);\n}\n.edge .v-dot.selected {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.1);\n}\n.edge .v-dot.selected:hover {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.2);\n}\n.edge .v-dot.auto.selected {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.1);\n}\n.edge .v-dot.auto.selected:hover {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.2);\n}\ncircle#prj {\n fill: none;\n stroke: #777;\n}\n\n.nodeShadow {\n fill: none;\n stroke-width: 4px;\n stroke: rgba(0, 0, 0, 0.13);\n}\n\ng.node {\n user-select: none;\n cursor: default;\n}\n\ng.node.linked {\n cursor: pointer;\n}\n\ng.node text {\n pointer-events: none;\n}\n\n.icon {\n fill: #aaa;\n stroke: #fff;\n}\n#icon-cube {\n fill: #aaa;\n}\n\n/* Ensure all button icons are uncolored */\nbutton .icon,\nbutton svg,\nbutton path {\n fill: currentColor !important;\n stroke: none !important;\n}\n\n/* Font Awesome icon styling in buttons */\nbutton i {\n font-size: 12px;\n color: inherit;\n}\n\nrect.elastic {\n pointer-events: none;\n stroke: none;\n fill: #3bd8281f;\n display: none;\n}\nrect.elastic.on {\n display: block;\n}\n\n.popover {\n position: absolute;\n top: 50px;\n bottom: 10px;\n overflow: auto;\n right: 10px;\n background: ghostwhite;\n padding: 30px;\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\n border: solid 1px #eee;\n}\n\n.popover th {\n text-align: left;\n padding: 20px 0px 10px;\n}\n.popover td {\n padding-right: 20px;\n font-size: 14px;\n}\n\n/* Simple tooltip system with smart positioning */\n[data-tooltip] {\n position: relative;\n}\n\n[data-tooltip]:hover::after {\n content: attr(data-tooltip);\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.9);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n font-weight: normal !important;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n margin-top: 5px;\n animation: tooltip-appear 0.1s ease-out;\n min-width: 120px;\n max-width: calc(100vw - 20px);\n box-sizing: border-box;\n}\n\n[data-tooltip]:hover::before {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-bottom-color: rgba(0, 0, 0, 0.9);\n z-index: 1000;\n pointer-events: none;\n margin-top: 1px;\n animation: tooltip-appear 0.1s ease-out;\n}\n\n/* Special handling for rightmost elements that might overflow */\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 0;\n transform: none;\n}\n\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 16px;\n transform: none;\n}\n\n@keyframes tooltip-appear {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(-5px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\nselect {\n position: relative;\n}\n\n/* Robot shape internal elements - inherit stroke from parent node */\n.node .robot-eye-socket {\n fill: none;\n stroke: inherit;\n stroke-width: 2;\n}\n\n.node .robot-eye {\n fill: currentColor;\n stroke: none;\n}\n\n.node .robot-mouth {\n stroke: inherit;\n}\n\n.node .robot-antenna {\n stroke: inherit;\n}\n\n.node .robot-antenna-ball {\n fill: currentColor;\n stroke: inherit;\n stroke-width: 1.5;\n}\n\n.node .robot-panel {\n stroke: inherit;\n}\n\n.node .robot-indicator {\n fill: currentColor;\n stroke: none;\n}","",{version:3,sources:["webpack://./src/style.css"],names:[],mappings:"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",sourcesContent:["html, body, #root {\n height: 100%;\n}\n\nbody {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #666;\n margin: 0;\n}\n\n#root {\n display: flex;\n flex-direction: column;\n}\n#root > div.graph {\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 10px;\n background-color: #f0f0f0;\n border-bottom: 1px solid #cccccc;\n}\n\n.toolbar > div {\n display: flex;\n align-items: center;\n}\n\n.toolbar button {\n padding: 5px 8px;\n margin: 0 2px;\n cursor: pointer;\n}\n\n.toolbar button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Drag mode toggle button styles */\n.toolbar button.mode-toggle {\n position: relative;\n border: 1px solid #8f9fc9;\n width: 40px;\n min-height: 28px;\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\n border-color: #8f9fc9;\n color: white;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n}\n\n.toolbar button.mode-toggle:hover {\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\n border-color: #abb8db;\n}\n\n.toolbar button.mode-toggle.select-mode:active {\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\n}\n\n/* Pan mode and active toggle - darker blue */\n.toolbar button.mode-toggle.pan-mode,\n.toolbar button.active-toggle {\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n.toolbar button.mode-toggle.pan-mode:hover,\n.toolbar button.active-toggle:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\n.toolbar button.mode-toggle.pan-mode:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n/* Toggle buttons when inactive - gray styling */\n.toolbar button.inactive-toggle {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n.toolbar button.inactive-toggle:hover {\n background: #b1b1b1;\n}\n\n.toolbar button.inactive-toggle:active {\n background: #a1a1a1;\n}\n\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\n.toolbar button .fas {\n font-size: 1em;\n vertical-align: middle;\n}\n\n/* Zoom percentage display */\n.toolbar button.zoom-display {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\n padding: 6px 8px;\n text-align: center;\n}\n\n.toolbar-group {\n display: flex;\n align-items: center;\n margin-right: 25px; /* Large space between groups */\n}\n\n.toolbar-group:last-child {\n margin-right: 0; /* Remove right margin from the last group (help button) */\n}\n\nbutton {\n border: none;\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n border-radius: 3px;\n padding: 6px 10px;\n color: #fff;\n outline: none;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n margin-right: 5px;\n min-width: 32px;\n min-height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n line-height: 1;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n position: relative;\n}\n\nbutton:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\nbutton:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n}\n\nbutton:last-child {\n margin-right: 0;\n}\n\n/* Save button when no changes - gray */\nbutton.action {\n background: #c1c1c1;\n color: #999;\n}\n\nbutton.action:active {\n background: #b1b1b1;\n}\n\n/* Save button when there are changes - orange */\nbutton.grp {\n background: linear-gradient(to bottom, #ee9564, #de7d48);\n margin-right: 0;\n}\n\nbutton.grp:active {\n background: #d67540;\n}\n\n/* Auto-arrange button with AI purple to blue gradient */\nbutton.auto-arrange {\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\n border-color: #4a4c93;\n}\n\nbutton.auto-arrange:hover {\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\n border-color: #5a5ca3;\n}\n\nbutton.auto-arrange:active {\n background: linear-gradient(135deg, #593B83, #357abd);\n border-color: #3a3c83;\n}\n\nselect {\n border: 1px solid #ccc;\n background: white;\n border-radius: 3px;\n padding: 3px 7px;\n color: #666;\n outline: none;\n margin-right: 5px;\n font-size: 12px;\n}\n\nselect:disabled {\n background: #f5f5f5;\n color: #999;\n}\n\nbutton:disabled {\n background: #c1c1c1;\n color: #999;\n}\n\n#root > div > svg {\n position: absolute;\n user-select: none;\n}\n\n\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\n stroke: #29c229;\n}\n.edge .v-dot {\n fill: transparent;\n stroke: transparent;\n stroke-width: 3px;\n cursor: pointer;\n transition: stroke 0.15s ease;\n}\n.edge .v-dot:hover {\n stroke: #999;\n fill: rgba(153, 153, 153, 0.1);\n}\n.edge .v-dot.selected {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.1);\n}\n.edge .v-dot.selected:hover {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.2);\n}\n.edge .v-dot.auto.selected {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.1);\n}\n.edge .v-dot.auto.selected:hover {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.2);\n}\ncircle#prj {\n fill: none;\n stroke: #777;\n}\n\n.nodeShadow {\n fill: none;\n stroke-width: 4px;\n stroke: rgba(0, 0, 0, 0.13);\n}\n\ng.node {\n user-select: none;\n cursor: default;\n}\n\ng.node.linked {\n cursor: pointer;\n}\n\ng.node text {\n pointer-events: none;\n}\n\n.icon {\n fill: #aaa;\n stroke: #fff;\n}\n#icon-cube {\n fill: #aaa;\n}\n\n/* Ensure all button icons are uncolored */\nbutton .icon,\nbutton svg,\nbutton path {\n fill: currentColor !important;\n stroke: none !important;\n}\n\n/* Font Awesome icon styling in buttons */\nbutton i {\n font-size: 12px;\n color: inherit;\n}\n\nrect.elastic {\n pointer-events: none;\n stroke: none;\n fill: #3bd8281f;\n display: none;\n}\nrect.elastic.on {\n display: block;\n}\n\n.popover {\n position: absolute;\n top: 50px;\n bottom: 10px;\n overflow: auto;\n right: 10px;\n background: ghostwhite;\n padding: 30px;\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\n border: solid 1px #eee;\n}\n\n.popover th {\n text-align: left;\n padding: 20px 0px 10px;\n}\n.popover td {\n padding-right: 20px;\n font-size: 14px;\n}\n\n/* Simple tooltip system with smart positioning */\n[data-tooltip] {\n position: relative;\n}\n\n[data-tooltip]:hover::after {\n content: attr(data-tooltip);\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.9);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n font-weight: normal !important;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n margin-top: 5px;\n animation: tooltip-appear 0.1s ease-out;\n min-width: 120px;\n max-width: calc(100vw - 20px);\n box-sizing: border-box;\n}\n\n[data-tooltip]:hover::before {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-bottom-color: rgba(0, 0, 0, 0.9);\n z-index: 1000;\n pointer-events: none;\n margin-top: 1px;\n animation: tooltip-appear 0.1s ease-out;\n}\n\n/* Special handling for rightmost elements that might overflow */\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 0;\n transform: none;\n}\n\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 16px;\n transform: none;\n}\n\n@keyframes tooltip-appear {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(-5px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\nselect {\n position: relative;\n}\n\n/* Robot shape internal elements - inherit stroke from parent node */\n.node .robot-eye-socket {\n fill: none;\n stroke: inherit;\n stroke-width: 2;\n}\n\n.node .robot-eye {\n fill: currentColor;\n stroke: none;\n}\n\n.node .robot-mouth {\n stroke: inherit;\n}\n\n.node .robot-antenna {\n stroke: inherit;\n}\n\n.node .robot-antenna-ball {\n fill: currentColor;\n stroke: inherit;\n stroke-width: 1.5;\n}\n\n.node .robot-panel {\n stroke: inherit;\n}\n\n.node .robot-indicator {\n fill: currentColor;\n stroke: none;\n}"],sourceRoot:""}]);const s=a;n.d(t,["A",0,s])}},e=>{e.O(0,[453,96],()=>e(e.s=245)),e.O()}]); //# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/cmd/mdl/webapp/dist/main.js.map b/cmd/mdl/webapp/dist/main.js.map index 341df7a7..383b7222 100644 --- a/cmd/mdl/webapp/dist/main.js.map +++ b/cmd/mdl/webapp/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","mappings":"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","sources":["webpack://app/./src/parseModel.ts","webpack://app/./src/hooks.ts","webpack://app/./src/utils.ts","webpack://app/./src/components/Toolbar.tsx","webpack://app/./src/Root.tsx","webpack://app/./src/graph-view/defs.ts","webpack://app/./src/graph-view/svg-text.ts","webpack://app/./src/graph-view/svg-create.ts","webpack://app/./src/graph-view/intersect.ts","webpack://app/./src/graph-view/shapes.ts","webpack://app/./src/graph-view/undo.ts","webpack://app/./src/graph-view/constants.ts","webpack://app/./src/graph-view/node-content.ts","webpack://app/./src/graph-view/graph.ts","webpack://app/./src/graph-view/edge-utils.ts","webpack://app/./src/graph-view/layout.ts","webpack://app/./src/style.css?dd02","webpack://app/./src/websocket.ts","webpack://app/./src/index.tsx","webpack://app/./src/shortcuts.tsx","webpack://app/./src/utils/platform.ts","webpack://app/./src/style.css"],"sourcesContent":["import {GraphData, NodeLink} from \"./graph-view/graph\";\n\n\ninterface Model {\n\tname: string\n\tdescription: string\n\tversion: string\n\tmodel: {\n\t\tenterprise: {\n\t\t\tname: string\n\t\t}\n\t\tpeople: Element[]\n\t\tsoftwareSystems: Element[]\n\t\tdeploymentNodes: Element[]\n\t}\n\tviews: {\n\t\tsystemLandscapeViews: View[]\n\t\tcontainerViews: View[]\n\t\tcomponentViews: View[]\n\t\tdynamicViews: View[]\n\t\tdeploymentViews: View[]\n\t\tstyles: {\n\t\t\telements: {\n\t\t\t\t[key: string]: string\n\t\t\t}[];\n\t\t\trelationships: {\n\t\t\t\t[key: string]: string\n\t\t\t}[]\n\t\t}\n\t}\n}\n\ninterface Layouts {\n\t[key: string]: { // keyed by view key\n\t\t[key: string]: { x: number; y: number } // keyed by element id\n\t}\n}\n\ninterface Element {\n\tid: string;\n\tname: string;\n\ttechnology?: string;\n\tdescription?: string;\n\turl?: string;\n\tparent?: Element;\n\ttags?: string;\n\tlocation?: string;\n\tcontainers?: Element[];\n\tcomponents?: Element[];\n\trelationships?: Relation[];\n\tproperties?: { [key: string]: string }\n\tchildren?: Element[];\n\tinfrastructureNodes?: Element[];\n}\n\ninterface Relation {\n\tid: string;\n\tdescription: string;\n\ttags: string;\n\tsourceId: string;\n\tdestinationId: string;\n\ttechnology: string;\n\tinteractionStyle: string;\n}\n\ninterface View {\n\tkey: string;\n\ttitle: string;\n\tdescription: string\n\telements: {\n\t\tid: string\n\t}[];\n\trelationships: {\n\t\tid: string;\n\t\tvertices: { x: number; y: number }[];\n\t\trouting: string; // takes priority over style\n\t}[];\n\tsoftwareSystemId: string;\n}\n\ninterface Metadata {\n\tname: string\n\tdescription: string\n\tversion: string\n\telements: {\n\t\tid: string\n\t\ttags?: string;\n\t\tlocation?: string;\n\t\tproperties?: { [key: string]: string };\n\t\telementViewKey?: string;\n\t\ttechnology?: string;\n\t\turl?: string;\n\t}[]\n}\n\nexport type ViewsList = {\n\tkey: string;\n\ttitle: string;\n\tsection: string;\n}[]\n\nexport const parseView = (model: Model, layouts: Layouts, viewKey: string) => {\n\n\tconst elements = new Map();\n\tconst relations = new Map();\n\n\tconst collectRels = (el: Element) => {\n\t\tif (Array.isArray(el.relationships)) {\n\t\t\tel.relationships.forEach(rel => {\n\t\t\t\trelations.set(rel.id, rel)\n\t\t\t})\n\t\t}\n\t}\n\n\t// People\n\tmodel.model.people && model.model.people.forEach((el: Element) => {\n\t\telements.set(el.id, el)\n\t\tif (Array.isArray(el.relationships)) {\n\t\t\tel.relationships.forEach(rel => {\n\t\t\t\trelations.set(rel.id, rel)\n\t\t\t})\n\t\t}\n\t})\n\t// Software Systems\n\tmodel.model.softwareSystems && model.model.softwareSystems.forEach((el: Element) => {\n\t\telements.set(el.id, el)\n\t\tcollectRels(el)\n\n\t\tif (Array.isArray(el.containers)) {\n\t\t\tel.containers.forEach((el1: Element) => {\n\t\t\t\tel1.parent = el;\n\t\t\t\telements.set(el1.id, el1)\n\t\t\t\tcollectRels(el1)\n\t\t\t\tif (Array.isArray(el1.components)) {\n\t\t\t\t\tel1.components.forEach((el2: Element) => {\n\t\t\t\t\t\tel2.parent = el1;\n\t\t\t\t\t\telements.set(el2.id, el2)\n\t\t\t\t\t\tcollectRels(el2)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\t// Deployment Nodes\n\tif (model.model.deploymentNodes) {\n\t\tconst containerInstances = (el: any) => {\n\t\t\tel.containerInstances && el.containerInstances.forEach((item: any) => {\n\t\t\t\tconst el1 = {...elements.get(item.containerId), id: item.id}\n\t\t\t\telements.set(el1.id, el1)\n\t\t\t\tel1.parent = el\n\t\t\t\tcollectRels(item)\n\t\t\t})\n\t\t}\n\n\t\tconst recAddNodes = (el: Element, parent: Element) => {\n\t\t\tel.parent = parent;\n\t\t\telements.set(el.id, el)\n\t\t\tcollectRels(el)\n\t\t\tcontainerInstances(el)\n\t\t\tel.children && el.children.forEach((el1: Element) => recAddNodes(el1, el))\n\t\t\tel.infrastructureNodes && el.infrastructureNodes.forEach((el1: Element) => recAddNodes(el1, el))\n\t\t}\n\n\t\tmodel.model.deploymentNodes.forEach((el: Element) => recAddNodes(el, null))\n\t}\n\n\t// Create graph from selected view\n\tconst {view, section} = getView(model, viewKey)\n\n\tif (!view) return null\n\n\tconst graph = new GraphData(view.key, view.title || view.key)\n\tconst metadata: Metadata = {name: graph.name, description: view.description, version: model.version, elements: []}\n\tgraph.metadata = metadata\n\n\tif (!view.elements) return graph\n\n\t//grouping rules - elements that are groups will not be nodes\n\tconst groupingIDs: { [key: string]: boolean } = {}\n\tif (section == 'deploymentViews' || section == 'containerViews') {\n\t\tview.elements.forEach(ref => {\n\t\t\tconst el = elements.get(ref.id)\n\t\t\tif (el?.parent) {\n\t\t\t\tgroupingIDs[el.parent.id] = true\n\t\t\t}\n\t\t})\n\t} else if (view.softwareSystemId) {\n\t\t//don't show grouping if the element is listed in the view\n\t\tif (!view.elements.find(ref => ref.id == view.softwareSystemId))\n\t\t\tgroupingIDs[view.softwareSystemId] = true\n\t} else if (section == 'systemLandscapeViews') {\n\t\t// create a virtual parent element from enterprise\n\t\tconst p: Element = {id: '__enterprise__', ...model.model.enterprise}\n\t\telements.set(p.id, p)\n\t\tif (model.model.people) model.model.people.filter(el => el.location != 'External').forEach(el => el.parent = p)\n\t\tif (model.model.softwareSystems) model.model.softwareSystems.filter(el => el.location != 'External').forEach(el => el.parent = p)\n\t\tgroupingIDs[p.id] = true\n\t}\n\n\tconst styles = model.views.styles\n\n\t// Build color-to-variable mapping for CSS custom properties theming\n\tconst cssClassName = (tag: string) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')\n\t\n\tif (styles?.elements) {\n\t\tstyles.elements.forEach(s => {\n\t\t\tif (s.tag) {\n\t\t\t\tconst varPrefix = `--mdl-${cssClassName(s.tag)}`\n\t\t\t\tif (s.background) graph.colorToVarMap.set(s.background as string, `${varPrefix}-bg`)\n\t\t\t\tif (s.color) graph.colorToVarMap.set(s.color as string, `${varPrefix}-color`)\n\t\t\t\tif (s.stroke) graph.colorToVarMap.set(s.stroke as string, `${varPrefix}-stroke`)\n\t\t\t}\n\t\t})\n\t}\n\n\tif (styles?.relationships) {\n\t\tstyles.relationships.forEach(s => {\n\t\t\tif (s.tag) {\n\t\t\t\tconst varPrefix = `--mdl-rel-${cssClassName(s.tag)}`\n\t\t\t\tif (s.color) graph.colorToVarMap.set(s.color as string, `${varPrefix}-color`)\n\t\t\t}\n\t\t})\n\t}\n\n\t//nodes\n\tview.elements.forEach((ref) => {\n\t\t// except grouping elements\n\t\tif (groupingIDs[ref.id]) return\n\n\t\tconst el = elements.get(ref.id)\n\t\tconst elementViewKey = el ? lookupContainerViewKey(model, el.id) : undefined\n\n\t\tlet sub = ''\n\t\tlet style = {}\n\t\tif (el) {\n\t\t\tconst tags = el.tags.split(',')\n\t\t\tsub = tags[tags.length - 1] // subtitle is []\n\t\t\tif (el.technology)\n\t\t\t\tsub += ': ' + el.technology // or [: ]\n\n\t\t\ttags.forEach(tag => {\n\t\t\t\tconst s = styles && styles.elements && styles.elements.find(s => s.tag == tag)\n\t\t\t\ts && (style = {...style, ...s})\n\t\t\t})\n\t\t}\n\n\t\tgraph.addNode(\n\t\t\tref.id,\n\t\t\tel ? (el.name || ref.id) : ref.id,\n\t\t\tsub,\n\t\t\t(el && el.description) ? el.description : '',\n\t\t\tstyle,\n\t\t\tnodeLink(el, elementViewKey)\n\t\t)\n\t\tel && metadata.elements.push({\n\t\t\tid: el.id,\n\t\t\ttags: el.tags,\n\t\t\tlocation: el.location,\n\t\t\tproperties: el.properties,\n\t\t\telementViewKey,\n\t\t\ttechnology: el.technology,\n\t\t\turl: el.url\n\t\t})\n\t})\n\t//edges\n\tif (Array.isArray(view.relationships)) {\n\t\tview.relationships.forEach(ref => {\n\t\t\tconst rel = relations.get(ref.id)\n\t\t\tif (!rel) return;\n\n\t\t\tif (!graph.nodesMap.has(rel.sourceId)) {\n\t\t\t\tif (elements.has(rel.sourceId)) {\n\t\t\t\t\tconst el = elements.get(rel.sourceId)\n\t\t\t\t\tconsole.warn('Element not found in this view: ', el.id, el.name)\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn('Element not found: ', rel.sourceId)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!graph.nodesMap.has(rel.destinationId)) {\n\t\t\t\tif (elements.has(rel.destinationId)) {\n\t\t\t\t\tconst el = elements.get(rel.destinationId)\n\t\t\t\t\tconsole.warn('Element not found in this view: ', el.id, el.name)\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn('Element not found: ', rel.destinationId)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet style: any = {}\n\t\t\trel.tags.split(',').forEach(tag => {\n\t\t\t\tconst s = styles && styles.relationships && styles.relationships.find(s => s.tag == tag)\n\t\t\t\ts && (style = {...style, ...s})\n\t\t\t})\n\t\t\tif (ref.routing) style.routing = ref.routing\n\n\t\t\tgraph.addEdge(rel.id, rel.sourceId, rel.destinationId, rel.description, ref.vertices, style)\n\t\t})\n\t}\n\n\t//groups\n\t//sort by depth to solve dependency\n\tconst level = (el: Element) => {\n\t\tlet i = 0\n\t\tfor (let p = el.parent; p; p = p.parent) i++;\n\t\treturn i\n\t}\n\tconst gElements = Object.keys(groupingIDs)\n\t\t.map(id => elements.get(id))\n\t\t.sort((a, b) => level(a) > level(b) ? -1 : 1)\n\n\tgElements.forEach(parent => {\n\t\tlet style = {}\n\t\tif (section == 'deploymentViews') {\n\t\t\tconst el = elements.get(parent.id)\n\t\t\tconst tags = el.tags.split(',')\n\t\t\ttags.forEach(tag => {\n\t\t\t\tconst s = styles && styles.elements && styles.elements.find(s => s.tag == tag)\n\t\t\t\ts && (style = {...style, ...s})\n\t\t\t})\n\t\t}\n\t\t\n\t\t// Filter group members more carefully to respect boundaries\n\t\tconst groupMembers = view.elements\n\t\t\t.map(ref => elements.get(ref.id))\n\t\t\t.filter(el => {\n\t\t\t\tif (!el || el.parent !== parent) return false;\n\t\t\t\t\n\t\t\t\t// For system landscape views, respect the location-based grouping\n\t\t\t\tif (section === 'systemLandscapeViews' && parent.id === '__enterprise__') {\n\t\t\t\t\t// Only include elements that are explicitly non-external\n\t\t\t\t\treturn el.location !== 'External';\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// For other view types, include if parent matches\n\t\t\t\treturn true;\n\t\t\t})\n\t\t\t.map(el => el.id);\n\t\t\n\t\t// Only create group if it has members\n\t\tif (groupMembers.length > 0) {\n\t\t\tgraph.addGroup(\n\t\t\t\tparent.id,\n\t\t\t\tparent.name,\n\t\t\t\tgroupMembers,\n\t\t\t\tstyle\n\t\t\t)\n\t\t}\n\t})\n\n\t//layout if any and init graph\n\tgraph.init(layouts[graph.id])\n\treturn graph\n}\n\nfunction nodeLink(element: Element | undefined, elementViewKey: string | undefined): NodeLink | undefined {\n\tif (elementViewKey) {\n\t\tconst encodedViewKey = encodeURIComponent(elementViewKey)\n\t\treturn {\n\t\t\thref: `?id=${encodedViewKey}`,\n\t\t\texportHref: `${encodedViewKey}.svg`,\n\t\t}\n\t}\n\tif (element?.url) {\n\t\treturn {\n\t\t\thref: element.url,\n\t\t\texportHref: element.url,\n\t\t}\n\t}\n\treturn undefined\n}\n\n// lookup the view in all Views sections in the model. return the view and the section\nfunction getView(model: Model, viewKey: string) {\n\tlet view: View = null, section: string = ''\n\tObject.keys(model.views).filter(s => s.endsWith('Views')).some((s: string) => {\n\t\treturn ((model.views as any)[s]).some((v: View) => {\n\t\t\tif (v.key == viewKey) {\n\t\t\t\tview = v\n\t\t\t\tsection = s\n\t\t\t\treturn true\n\t\t\t}\n\t\t})\n\t})\n\treturn {view, section}\n}\n\n\nfunction lookupContainerViewKey(model: Model, softwareSystemId: string) {\n\tconst view = model.views.containerViews?.find(candidate => candidate.softwareSystemId == softwareSystemId)\n\treturn view?.key\n}\n\nexport const listViews = (model: any) => {\n\tconst viewsList: ViewsList = []\n\tconst sections = Object.keys(model.views).filter(section => section.endsWith('Views'))\n\tsections.forEach(s => {\n\t\tmodel.views[s].forEach((v: View) => {\n\t\t\tviewsList.push({key: v.key, title: v.title || v.key, section: s})\n\t\t})\n\t})\n\treturn viewsList;\n}\n","import { useState, useCallback, useEffect } from 'react';\nimport { GraphData } from './graph-view/graph';\nimport { parseView } from './parseModel';\nimport { LayoutOptions } from './graph-view/layout';\nimport { \n findShortcut, \n HELP, \n SAVE, \n TOGGLE_DRAG_MODE,\n ALIGN_HORIZONTAL,\n ALIGN_VERTICAL,\n DISTRIBUTE_HORIZONTAL,\n DISTRIBUTE_VERTICAL,\n AUTO_LAYOUT,\n RESET_POSITION,\n TOGGLE_GRID,\n TOGGLE_SNAP_TO_GRID,\n SNAP_ALL_TO_GRID,\n MOVE_LEFT,\n MOVE_RIGHT,\n MOVE_UP,\n MOVE_DOWN,\n MOVE_LEFT_FINE,\n MOVE_RIGHT_FINE,\n MOVE_UP_FINE,\n MOVE_DOWN_FINE\n} from './shortcuts';\n\n// Global state for graphs to preserve edits\nconst graphs: { [key: string]: GraphData } = {};\n\n// Custom hook for graph management\nexport const useGraph = (model: any, layouts: any, currentID: string): GraphData | null => {\n if (graphs[currentID]) {\n return graphs[currentID];\n }\n \n const graph = parseView(model, layouts, currentID);\n if (graph) {\n graphs[currentID] = graph;\n }\n \n return graph;\n};\n\n// Custom hook for auto layout functionality\nexport const useAutoLayout = (graph: GraphData) => {\n const [layouting, setLayouting] = useState(false);\n\n const handleAutoLayout = useCallback(async (opts?: LayoutOptions) => {\n setLayouting(true);\n try {\n const options: LayoutOptions = {\n direction: 'DOWN',\n ...(opts || {})\n };\n await graph.autoLayout(options);\n } catch (error) {\n console.error('Layout failed:', error);\n alert('Layout failed. See console for details.');\n } finally {\n setLayouting(false);\n }\n }, [graph]);\n\n return { layouting, handleAutoLayout };\n};\n\n// Custom hook for save functionality\nexport const useSave = (graph: GraphData, currentID: string) => {\n const [saving, setSaving] = useState(false);\n\n const handleSave = useCallback(async () => {\n setSaving(true);\n \n try {\n const response = await fetch('data/save?id=' + encodeURIComponent(currentID), {\n method: 'post',\n body: graph.exportSVG()\n });\n \n if (response.status !== 202) {\n alert('Error saving\\nSee terminal output.');\n } else {\n graph.setSaved();\n }\n } catch (error) {\n console.error('Save failed:', error);\n alert('Save failed. See console for details.');\n } finally {\n setSaving(false);\n }\n }, [graph, currentID]);\n\n return { saving, handleSave };\n};\n\n// Custom hook for keyboard shortcuts\nexport const useKeyboardShortcuts = (\n toggleHelp: () => void,\n saveLayout: () => void,\n graph?: GraphData,\n dragMode?: 'pan' | 'select',\n setDragMode?: (mode: 'pan' | 'select') => void,\n onAutoLayout?: () => void\n) => {\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const shortcut = findShortcut(e);\n \n // Prevent browser default for all recognized shortcuts\n if (shortcut) {\n e.preventDefault();\n }\n \n if (shortcut === HELP) {\n toggleHelp();\n } else if (shortcut === SAVE) {\n saveLayout();\n } else if (shortcut === TOGGLE_DRAG_MODE && setDragMode && dragMode) {\n setDragMode(dragMode === 'pan' ? 'select' : 'pan');\n } else if (graph) {\n // Graph-dependent shortcuts\n if (shortcut === ALIGN_HORIZONTAL) {\n graph.alignSelectionH();\n } else if (shortcut === ALIGN_VERTICAL) {\n graph.alignSelectionV();\n } else if (shortcut === DISTRIBUTE_HORIZONTAL) {\n graph.distributeSelectionH();\n } else if (shortcut === DISTRIBUTE_VERTICAL) {\n graph.distributeSelectionV();\n } else if (shortcut === AUTO_LAYOUT && onAutoLayout) {\n onAutoLayout();\n } else if (shortcut === RESET_POSITION) {\n graph.resetView();\n } else if (shortcut === TOGGLE_GRID) {\n graph.toggleGrid();\n } else if (shortcut === TOGGLE_SNAP_TO_GRID) {\n graph.toggleSnapToGrid();\n } else if (shortcut === SNAP_ALL_TO_GRID) {\n graph.snapAllToGrid();\n } else if (shortcut === MOVE_LEFT) {\n graph.moveSelected(-graph.getGridSize(), 0);\n } else if (shortcut === MOVE_LEFT_FINE) {\n graph.moveSelected(-1, 0, true); // Disable snap for fine movement\n } else if (shortcut === MOVE_RIGHT) {\n graph.moveSelected(graph.getGridSize(), 0);\n } else if (shortcut === MOVE_RIGHT_FINE) {\n graph.moveSelected(1, 0, true); // Disable snap for fine movement\n } else if (shortcut === MOVE_UP) {\n graph.moveSelected(0, -graph.getGridSize());\n } else if (shortcut === MOVE_UP_FINE) {\n graph.moveSelected(0, -1, true); // Disable snap for fine movement\n } else if (shortcut === MOVE_DOWN) {\n graph.moveSelected(0, graph.getGridSize());\n } else if (shortcut === MOVE_DOWN_FINE) {\n graph.moveSelected(0, 1, true); // Disable snap for fine movement\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [toggleHelp, saveLayout, graph, dragMode, setDragMode, onAutoLayout]);\n};\n\n// Utility function to clear graph cache\nexport const clearGraphCache = (currentID?: string) => {\n if (currentID) {\n delete graphs[currentID];\n } else {\n Object.keys(graphs).forEach(key => delete graphs[key]);\n }\n};","// Helper functions for the application\n\nexport function removeEmptyProps(obj: any) {\n return JSON.parse(JSON.stringify(obj));\n}\n\nexport function camelToWords(camel: string) {\n const split = camel.replace(/([A-Z])/g, \" $1\");\n return split.charAt(0).toUpperCase() + split.slice(1);\n}\n\nexport function getCurrentViewID() {\n const params = new URLSearchParams(document.location.search);\n return params.get('id') || '';\n} ","import React, { FC, useState, useEffect } from 'react';\nimport { getZoomAuto, GraphData, setZoom, getZoom, setZoomCentered } from '../graph-view/graph';\nimport { listViews } from '../parseModel';\nimport { camelToWords } from '../utils';\nimport { getModifierKeyName } from '../utils/platform';\n\n// Types\ninterface ToolbarProps {\n model: any;\n currentID: string;\n onViewChange: (id: string) => void;\n graph: GraphData;\n onAutoLayout: () => void;\n onSave: () => void;\n onToggleHelp: () => void;\n saving: boolean;\n layouting: boolean;\n dragMode: 'pan' | 'select';\n setDragMode: (mode: 'pan' | 'select') => void;\n}\n\nexport const Toolbar: FC = ({\n model, currentID, onViewChange, graph, \n onAutoLayout, onSave, onToggleHelp, saving, layouting,\n dragMode, setDragMode\n}) => {\n const views = listViews(model);\n \n return (\n
\n \n \n
\n );\n};\n\nconst ViewSelector: FC<{\n views: any[];\n currentID: string;\n onViewChange: (id: string) => void;\n}> = ({ views, currentID, onViewChange }) => (\n
\n View:\n {views.length > 1 ? (\n \n ) : (\n \n {views[0] ? camelToWords(views[0].section) + ': ' + views[0].title : 'No views available'}\n \n )}\n
\n);\n\nconst ToolbarActions: FC<{\n graph: GraphData;\n onAutoLayout: () => void;\n onSave: () => void;\n onToggleHelp: () => void;\n saving: boolean;\n layouting: boolean;\n dragMode: 'pan' | 'select';\n setDragMode: (mode: 'pan' | 'select') => void;\n}> = ({\n graph, onAutoLayout, onSave, onToggleHelp, saving, layouting,\n dragMode, setDragMode\n}) => (\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n);\n\nconst DragModeButton: FC<{\n dragMode: 'pan' | 'select';\n setDragMode: (mode: 'pan' | 'select') => void;\n}> = ({ dragMode, setDragMode }) => (\n \n);\n\nconst UndoRedoButtons: FC<{ graph: GraphData }> = ({ graph }) => {\n const modKey = getModifierKeyName();\n return (\n <>\n \n \n \n );\n};\n\nconst AlignmentButtons: FC<{ graph: GraphData }> = ({ graph }) => {\n const modKey = getModifierKeyName();\n return (\n <>\n \n \n \n \n \n );\n};\n\nconst LayoutControls: FC<{\n onAutoLayout: () => void;\n layouting: boolean;\n}> = ({ onAutoLayout, layouting }) => {\n const modKey = getModifierKeyName();\n return (\n \n );\n};\n\nconst GridControls: FC<{ graph: GraphData }> = ({ graph }) => {\n const [gridVisible, setGridVisible] = useState(graph.isGridVisible());\n const [snapToGrid, setSnapToGrid] = useState(graph.isSnapToGrid());\n const modKey = getModifierKeyName();\n \n // Update state when graph changes or when grid state changes via shortcuts\n React.useEffect(() => {\n const updateGridState = () => {\n setGridVisible(graph.isGridVisible());\n setSnapToGrid(graph.isSnapToGrid());\n };\n \n // Initial update\n updateGridState();\n \n // Listen for grid state changes from keyboard shortcuts\n window.addEventListener('gridStateChanged', updateGridState);\n \n return () => {\n window.removeEventListener('gridStateChanged', updateGridState);\n };\n }, [graph]);\n \n const handleToggleGrid = () => {\n graph.toggleGrid();\n setGridVisible(graph.isGridVisible());\n };\n \n const handleToggleSnap = () => {\n graph.toggleSnapToGrid();\n setSnapToGrid(graph.isSnapToGrid());\n };\n \n const handleSnapAll = () => {\n graph.snapAllToGrid();\n };\n \n return (\n <>\n \n \n \n \n );\n};\n\nconst ZoomDisplay: FC = () => {\n const [zoom, setZoomState] = useState(100);\n\n useEffect(() => {\n const updateZoom = () => {\n const currentZoom = Math.round(getZoom() * 100);\n setZoomState(currentZoom);\n };\n\n // Update zoom initially\n updateZoom();\n\n // Update zoom every 100ms to catch changes from wheel/keyboard/etc\n const interval = setInterval(updateZoom, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n return (\n \n );\n};\n\nconst ZoomControls: FC<{ graph: GraphData }> = ({ graph }) => {\n const modKey = getModifierKeyName();\n return (\n <>\n \n \n \n \n \n );\n};\n\nconst SaveButton: FC<{\n onSave: () => void;\n saving: boolean;\n graph: GraphData;\n}> = ({ onSave, saving, graph }) => {\n const [hasChanges, setHasChanges] = useState(false);\n const modKey = getModifierKeyName();\n \n // Check for changes periodically\n useEffect(() => {\n const checkChanges = () => {\n setHasChanges(graph.changed());\n };\n \n // Initial check\n checkChanges();\n \n // Check every 100ms for changes\n const interval = setInterval(checkChanges, 100);\n \n return () => clearInterval(interval);\n }, [graph]);\n \n return (\n \n );\n};\n\nconst HelpButton: FC<{\n onToggleHelp: () => void;\n}> = ({ onToggleHelp }) => {\n return (\n \n );\n};","import React, { FC, useState, useCallback, useEffect, Suspense, lazy } from \"react\";\nimport { GraphData } from \"./graph-view/graph\";\nimport { BrowserRouter as Router, Routes, Route, useSearchParams } from 'react-router-dom';\nimport { listViews } from \"./parseModel\";\nimport { useGraph, useAutoLayout, useSave, useKeyboardShortcuts, clearGraphCache } from \"./hooks\";\nimport { Toolbar } from \"./components/Toolbar\";\nimport { removeEmptyProps, getCurrentViewID } from \"./utils\";\n\nconst Help = lazy(() => import(\"./shortcuts\").then(module => ({ default: module.Help })));\nconst Graph = lazy(() => import(\"./graph-view/graph-react\").then(module => ({ default: module.Graph })));\n\n// Types\ninterface ModelData {\n model: any;\n layout: any;\n}\n\nexport const Root: FC = ({ model, layout }) => (\n \n \n } />\n \n \n);\n\nexport const refreshGraph = () => {\n const currentID = getCurrentViewID();\n clearGraphCache(currentID);\n};\n\nconst ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => {\n const [searchParams, setSearchParams] = useSearchParams();\n const currentID = decodeURI(searchParams.get('id') || '');\n \n // UI State\n const [helpVisible, setHelpVisible] = useState(false);\n const [dragMode, setDragMode] = useState<'pan' | 'select'>('pan');\n \n // Get or create graph for current view\n const graph = useGraph(model, layouts, currentID);\n \n // Custom hooks for functionality\n const { layouting, handleAutoLayout } = useAutoLayout(graph || ({} as GraphData));\n const { saving, handleSave } = useSave(graph || ({} as GraphData), currentID);\n \n if (!graph) {\n return ;\n }\n\n const handleToggleHelp = useCallback(() => {\n setHelpVisible(!helpVisible);\n }, [helpVisible]);\n\n // Update document title when view changes\n useEffect(() => {\n if (graph && graph.name) {\n document.title = `${graph.name} - Model`;\n }\n }, [graph]);\n\n // Headless automation: support query params to auto-layout and save\n useEffect(() => {\n // Only run when graph changes to avoid duplicate actions\n const params = Object.fromEntries(searchParams.entries());\n const auto = params['auto'] === '1' || params['auto'] === 'true';\n const save = params['save'] === '1' || params['save'] === 'true';\n const direction = (params['direction'] || '').toUpperCase();\n const compact = params['compact'] === '1' || params['compact'] === 'true';\n\n const validDirections = ['UP', 'DOWN', 'LEFT', 'RIGHT'];\n const layoutOpts: any = {};\n if (validDirections.includes(direction)) {\n layoutOpts.direction = direction as any;\n }\n if (compact) {\n layoutOpts.compactLayout = true;\n }\n\n let cancelled = false;\n (async () => {\n try {\n if (auto) {\n await handleAutoLayout(layoutOpts);\n }\n if (save) {\n await handleSave();\n }\n } catch (e) {\n console.error('automation error', e);\n }\n })();\n\n return () => { cancelled = true; };\n }, [graph, handleAutoLayout, handleSave, searchParams]);\n\n // Setup keyboard shortcuts\n useKeyboardShortcuts(handleToggleHelp, handleSave, graph, dragMode, setDragMode, handleAutoLayout);\n\n const handleViewChange = useCallback((id: string) => {\n setSearchParams({ id: encodeURIComponent(id) });\n }, [setSearchParams]);\n\n const handleSelect = useCallback((id: string | null) => {\n if (id) {\n const element = graph.metadata.elements.find((m: any) => m.id === id);\n console.log(removeEmptyProps(element));\n }\n }, [graph]);\n\n\treturn (\n\t\t<>\n\t\t\t\n\t\t\tLoading graph...
}>\n\t\t\t\t\n\t\t\t\n\t\t\t{helpVisible && (\n\t\t\t\tLoading help...
}>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t\n\t);\n};\n\nconst ViewRedirect: FC<{ model: any }> = ({ model }) => {\n const views = listViews(model);\n \n React.useEffect(() => {\n // Set default title when no view is selected\n document.title = 'Model - Architecture Diagrams as Code';\n \n if (views.length > 0) {\n document.location.href = '?id=' + views[0].key;\n }\n }, [views]);\n\n if (views.length > 0) {\n return <>Redirecting to {views[0].title};\n }\n return <>No views available;\n};\n\n","export const defs = `\n\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n`\n","const textMeasure = () => {\n\tconst svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n\tdocument.body.appendChild(svg);\n\n\treturn {\n\t\tmeasure: (text: string, attrs: { [key: string]: string }) => {\n\t\t\tconst node = document.createElementNS('http://www.w3.org/2000/svg', 'text')\n\t\t\tnode.setAttribute('x', '0');\n\t\t\tnode.setAttribute('y', '0');\n\t\t\tfor (let attr in attrs) {\n\t\t\t\tnode.setAttribute(attr, attrs[attr]);\n\t\t\t}\n\t\t\tnode.appendChild(document.createTextNode(text));\n\n\t\t\tsvg.appendChild(node);\n\t\t\tconst {width, height} = node.getBBox();\n\t\t\tsvg.removeChild(node);\n\t\t\treturn {width, height};\n\t\t},\n\t\tclean: () => {\n\t\t\tdocument.body.removeChild(svg);\n\t\t}\n\t}\n}\n\n// Helper function to break long words that exceed width\nconst breakLongWord = (word: string, maxWidth: number, attrs: { [key: string]: string }, mt: any): string[] => {\n\tconst parts: string[] = [];\n\tlet currentPart = '';\n\t\n\tfor (let i = 0; i < word.length; i++) {\n\t\tconst testPart = currentPart + word[i];\n\t\tconst size = mt.measure(testPart, attrs);\n\t\t\n\t\tif (size.width > maxWidth && currentPart.length > 0) {\n\t\t\tparts.push(currentPart);\n\t\t\tcurrentPart = word[i];\n\t\t} else {\n\t\t\tcurrentPart = testPart;\n\t\t}\n\t}\n\t\n\tif (currentPart.length > 0) {\n\t\tparts.push(currentPart);\n\t}\n\t\n\treturn parts;\n}\n\n// split a text in lines wrapped at a certain width\nexport const svgTextWrap = (text: string, width: number, attrs: { [key: string]: string }) => {\n\tconst mt = textMeasure()\n\tlet maxW = 0;\n\t\n\tconst ret = text.trim().split('\\n').map(text => { //split paragraphs\n\t\t//do one paragraph\n\t\tconst words = text.trim().split(/\\s+/);\n\t\tlet lines: string[] = [];\n\t\tlet currentLine: string[] = [];\n\t\t\n\t\twords.forEach(word => {\n\t\t\t// First check if the single word exceeds the width\n\t\t\tconst wordSize = mt.measure(word, attrs);\n\t\t\tif (wordSize.width > width) {\n\t\t\t\t// If we have content in current line, finish it first\n\t\t\t\tif (currentLine.length > 0) {\n\t\t\t\t\tlines.push(currentLine.join(' '));\n\t\t\t\t\tcurrentLine = [];\n\t\t\t\t}\n\t\t\t\t// Break the long word into smaller parts\n\t\t\t\tconst brokenParts = breakLongWord(word, width, attrs, mt);\n\t\t\t\t// Add all but the last part as complete lines\n\t\t\t\tfor (let i = 0; i < brokenParts.length - 1; i++) {\n\t\t\t\t\tlines.push(brokenParts[i]);\n\t\t\t\t\tmaxW = Math.max(maxW, mt.measure(brokenParts[i], attrs).width);\n\t\t\t\t}\n\t\t\t\t// Start new line with the last part\n\t\t\t\tif (brokenParts.length > 0) {\n\t\t\t\t\tcurrentLine = [brokenParts[brokenParts.length - 1]];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Normal word processing\n\t\t\t\tconst newLine = [...currentLine, word];\n\t\t\t\tconst size = mt.measure(newLine.join(' '), attrs);\n\t\t\t\tif (size.width > width && currentLine.length > 0) {\n\t\t\t\t\tlines.push(currentLine.join(' '));\n\t\t\t\t\tcurrentLine = [word];\n\t\t\t\t} else {\n\t\t\t\t\tmaxW = Math.max(maxW, size.width)\n\t\t\t\t\tcurrentLine = newLine;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tif (currentLine.length > 0) {\n\t\t\tlines.push(currentLine.join(' '));\n\t\t}\n\t\treturn lines;\n\t}).reduce((a, v) => a.concat(v), []) //flatten\n\n\tmt.clean()\n\treturn {lines: ret, maxW};\n};\n\n\n","import {svgTextWrap} from \"./svg-text\";\n\nexport const create = {\n\telement(type: string, attrs: Record = {}, className?: string) {\n\t\tconst el = document.createElementNS('http://www.w3.org/2000/svg', type);\n\t\tObject.entries(attrs).forEach(([k, v]) => el.setAttribute(k, String(v)));\n\t\tif (className) el.classList.add(className);\n\t\treturn el;\n\t},\n\n\tuse(id: string, attrs: Record = {}) {\n\t\tconst el = this.element('use', attrs);\n\t\tel.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#' + id);\n\t\treturn el;\n\t},\n\n\tpath(path: string, attrs: Record = {}, className?: string) {\n\t\tconst p = this.element(\"path\", {...attrs, d: path}, className);\n\t\treturn p;\n\t},\n\n\ttext(text: string, attrs: Record = {}) {\n\t\tconst t = this.element('text', attrs) as SVGTextElement;\n\t\tif (text) t.textContent = text;\n\t\treturn t;\n\t},\n\n\ttextArea(text: string, width: number, fontSize: number, bold: boolean, x = 0, y = 0, anchor = '') {\n\t\tconst attrs: Record = {\n\t\t\t'font-size': `${fontSize}px`,\n\t\t\t'font-weight': bold ? 'bold' : 'normal'\n\t\t};\n\t\tconst {lines, maxW} = svgTextWrap(text, width, attrs);\n\t\tconst txt = this.text('', {x: 0, y, 'text-anchor': anchor || undefined});\n\t\t\n\t\tlines.forEach((line, i) => {\n\t\t\tconst span = this.element('tspan', {x, dy: `${fontSize + 2}px`, ...attrs});\n\t\t\tspan.textContent = line;\n\t\t\ttxt.append(span);\n\t\t});\n\t\t\n\t\treturn {txt, dy: (lines.length + 1) * (fontSize + 2), maxW};\n\t},\n\n\trect(width: number, height: number, x = 0, y = 0, r = 0, className?: string) {\n\t\treturn this.element('rect', {x, y, rx: r, ry: r, width, height}, className) as SVGRectElement;\n\t},\n\n\ticon(icon: string, x = 0, y = 0) {\n\t\treturn this.use(icon, {x, y});\n\t},\n\n\texpand(x: number, y: number, expanded: boolean) {\n\t\tconst g = this.element('g', {transform: `translate(${x},${y})`}, 'expand') as SVGGElement;\n\t\tg.append(\n\t\t\tthis.rect(19, 19, 0, 0, 1),\n\t\t\tthis.text(expanded ? '-' : '+', {x: 10, y: 14, 'text-anchor': 'middle'})\n\t\t);\n\t\treturn g;\n\t}\n};\n\nexport function setPosition(g: SVGGElement, x: number, y: number) {\n\tg.setAttribute('transform', `translate(${x},${y})`);\n}","interface Point {\n\tx: number;\n\ty: number;\n}\n\ninterface BBox extends Point {\n\twidth: number;\n\theight: number;\n}\n\n\nexport function insideBox(p: Point, b: BBox, centeredBox = true): boolean {\n\treturn centeredBox ?\n\t\t(p.x > b.x - b.width / 2 && p.x < b.x + b.width / 2 && p.y > b.y - b.height / 2 && p.y < b.y + b.height / 2) :\n\t\t(p.x > b.x && p.x < b.x + b.width && p.y > b.y && p.y < b.y + b.height)\n}\n\nexport function boxesOverlap(b1: BBox, b2: BBox): boolean {\n\treturn b1.x < b2.x + b2.width && b1.y < b2.y + b2.height && b1.x + b1.width > b2.x && b1.y + b1.height > b2.y\n}\n\nexport function uncenterBox(b: BBox): BBox {\n\treturn {x: b.x - b.width / 2, y: b.y - b.height / 2, width: b.width, height: b.height}\n}\n\nexport function scaleBox(b: BBox, sc: number): BBox {\n\treturn {x: b.x * sc, y: b.y * sc, width: b.width * sc, height: b.height * sc}\n}\n\n// intersect 2 segments (p1->q1) with (p2, q2)\n// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite)\n// and booleans for whether line segment 1 or line segment 2 contain the point\nfunction segmentIntersection(p1: Point, q1: Point, p2: Point, q2: Point) {\n\tlet denominator, a, b, numerator1, numerator2,\n\t\tresult: { x: number, y: number, onLine1: boolean, onLine2: boolean } = {\n\t\t\tx: null,\n\t\t\ty: null,\n\t\t\tonLine1: false,\n\t\t\tonLine2: false\n\t\t};\n\tdenominator = (q2.y - p2.y) * (q1.x - p1.x) - (q2.x - p2.x) * (q1.y - p1.y);\n\tif (denominator == 0) {\n\t\treturn result;\n\t}\n\ta = p1.y - p2.y;\n\tb = p1.x - p2.x;\n\tnumerator1 = ((q2.x - p2.x) * a) - ((q2.y - p2.y) * b);\n\tnumerator2 = ((q1.x - p1.x) * a) - ((q1.y - p1.y) * b);\n\ta = numerator1 / denominator;\n\tb = numerator2 / denominator;\n\n\t// if we cast these lines infinitely in both directions, they intersect here:\n\tresult.x = p1.x + (a * (q1.x - p1.x));\n\tresult.y = p1.y + (a * (q1.y - p1.y));\n\n\t// if line1 is a segment and line2 is infinite, they intersect if:\n\tif (a > 0 && a < 1) {\n\t\tresult.onLine1 = true;\n\t}\n\t// if line2 is a segment and line1 is infinite, they intersect if:\n\tif (b >= 0 && b <= 1) {\n\t\tresult.onLine2 = true;\n\t}\n\t// if line1 and line2 are segments, they intersect if both of the above are true\n\treturn result;\n}\n\n// intersects a segment (p1->p2) with a box\nexport function intersectRectFull(p1: Point, p2: Point, box: BBox): Point[] {\n\tconst w = box.width / 2\n\tconst h = box.height / 2\n\tconst segs: { p: Point; q: Point }[] = [\n\t\t{p: {x: box.x - w, y: box.y - h}, q: {x: box.x - w, y: box.y + h}},\n\t\t{p: {x: box.x - w, y: box.y - h}, q: {x: box.x + w, y: box.y - h}},\n\t\t{p: {x: box.x + w, y: box.y - h}, q: {x: box.x + w, y: box.y + h}},\n\t\t{p: {x: box.x - w, y: box.y + h}, q: {x: box.x + w, y: box.y + h}},\n\t]\n\treturn segs.map(s => segmentIntersection(p1, p2, s.p, s.q)).filter(ret => ret.onLine1 && ret.onLine2)\n}\n\n// intersects a line that goes from p to the center of the box\nexport function intersectRect(box: BBox, p: Point): Point {\n\tif (insideBox(p, box)) return {x: box.x, y: box.y}\n\treturn intersectRectFull(box, p, box)[0] || {x: box.x, y: box.y}\n}\n\nexport function intersectEllipse(ellCenter: Point, rx: number, ry: number, nodeCenter: Point, point: Point) {\n\n\t//translate all to center ellipse\n\tconst p1 = {x: point.x - ellCenter.x, y: point.y - ellCenter.y}\n\tconst p2 = {x: nodeCenter.x - ellCenter.x, y: nodeCenter.y - ellCenter.y}\n\n\tif (p2.x == p1.x) { //hack to avoid singularity\n\t\tp1.x += .0000001\n\t}\n\n\tconst s = (p2.y - p1.y) / (p2.x - p1.x);\n\tconst si = p2.y - (s * p2.x);\n\tconst a = (ry * ry) + (rx * rx * s * s);\n\tconst b = 2 * rx * rx * si * s;\n\tconst c = rx * rx * si * si - rx * rx * ry * ry;\n\n\tconst radicand_sqrt = Math.sqrt((b * b) - (4 * a * c));\n\tconst x = p1.x > p2.x ?\n\t\t(-b + radicand_sqrt) / (2 * a) :\n\t\t(-b - radicand_sqrt) / (2 * a)\n\tconst pos = {\n\t\tx: x,\n\t\ty: s * x + si\n\t}\n\t//translate back\n\tpos.x += ellCenter.x;\n\tpos.y += ellCenter.y\n\n\treturn pos;\n}\n\nexport interface Segment {\n\tp: Point;\n\tq: Point;\n}\n\n// given a polyline as a list of segments, interrupt it over the box so no line is inside the box\nexport function intersectPolylineBox(segments: Segment[], box: BBox) {\n\tfor (let i = 0; i < segments.length; i++) {\n\t\tconst s = segments[i]\n\t\tif (insideBox(s.p, box)) {\n\t\t\tif (insideBox(s.q, box)) { // segment both ends inside box\n\t\t\t\tsegments.splice(i, 1)\n\t\t\t\ti -= 1\n\t\t\t} else { // segment start inside box\n\t\t\t\ts.p = intersectRectFull(s.p, s.q, box)[0]\n\t\t\t}\n\t\t} else {\n\t\t\tif (insideBox(s.q, box)) { // segment end inside box\n\t\t\t\ts.q = intersectRectFull(s.p, s.q, box)[0]\n\t\t\t} else { // both ends outside\n\t\t\t\tconst ret = intersectRectFull(s.p, s.q, box)\n\t\t\t\tif (ret.length == 2) { // intersects the box, splice segment\n\t\t\t\t\t// order the intersection points, closest first\n\t\t\t\t\tconst dst1 = Math.abs(ret[0].x - s.p.x) + Math.abs(ret[0].y - s.p.y)\n\t\t\t\t\tconst dst2 = Math.abs(ret[1].x - s.p.x) + Math.abs(ret[1].y - s.p.y)\n\t\t\t\t\tif (dst1 > dst2) ret.reverse()\n\t\t\t\t\t// split the segment in 2\n\t\t\t\t\tconst s2 = {p: ret[1], q: s.q}\n\t\t\t\t\ts.q = ret[0]\n\t\t\t\t\tsegments.splice(i + 1, 0, s2)\n\t\t\t\t\ti += 1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport function project(p: Point, a: Point, b: Point): Point {\n\tlet atob = {x: b.x - a.x, y: b.y - a.y};\n\tlet atop = {x: p.x - a.x, y: p.y - a.y};\n\tlet len = atob.x * atob.x + atob.y * atob.y;\n\tlet dot = atop.x * atob.x + atop.y * atob.y;\n\tlet t = Math.min(1, Math.max(0, dot / len));\n\treturn {\n\t\tx: a.x + atob.x * t,\n\t\ty: a.y + atob.y * t\n\t};\n}\n\nexport function cabDistance(p1: Point, p2: Point): number {\n\treturn Math.abs(p2.x - p1.x) + Math.abs(p2.y - p1.y)\n}","import {intersectEllipse, intersectRect} from \"./intersect\";\n\ninterface Point {\n\tx: number;\n\ty: number;\n}\n\ninterface BBox extends Point {\n\twidth: number;\n\theight: number;\n}\n\ninterface D3Node extends BBox {\n\tintersect: (p: Point) => Point\n}\n\nfunction cylinderRadiusY(width: number) {\n\treturn width / 2 / (5.5 + width / 70);\n}\n\nexport function shapeLabelOffsetY(shape: string, width: number, height: number) {\n\tswitch (shape.toLowerCase()) {\n\t\tcase \"cylinder\":\n\t\t\treturn 2 * cylinderRadiusY(width);\n\t\tcase \"person\":\n\t\t\treturn height * 0.4;\n\t\tcase \"folder\":\n\t\t\treturn width / 10;\n\t\tcase \"robot\":\n\t\t\treturn height * 0.35;\n\t\tcase \"webbrowser\":\n\t\t\treturn height / 8;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nclass D3Element {\n\tprivate readonly _el: SVGElement\n\n\tconstructor(el: SVGElement) {\n\t\tthis._el = el;\n\t}\n\n\tnode() {\n\t\treturn this._el;\n\t}\n\n\tattr(name: string, value: string | number) {\n\t\tthis._el.setAttribute(name, String(value))\n\t\treturn this;\n\t}\n\n\tinsert(type: string, pos: string) {\n\t\tconst el = document.createElementNS('http://www.w3.org/2000/svg', type)\n\t\tconst el2 = this._el.insertBefore(el, this._el.querySelector(pos))\n\t\treturn new D3Element(el2)\n\t}\n}\n\n\nfunction rect(parent: D3Element, bbox: BBox, node: D3Node, rounded = false) {\n\tconst shapeSvg = parent.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", rounded ? node.width / 8 : 3)\n\t\t.attr(\"ry\", rounded ? node.width / 8 : 3)\n\t\t.attr(\"x\", -bbox.width / 2)\n\t\t.attr(\"y\", -bbox.height / 2)\n\t\t.attr(\"width\", bbox.width)\n\t\t.attr(\"height\", bbox.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect(node, point);\n\t};\n\n\treturn shapeSvg;\n}\n\n\nfunction cylinder(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = bbox.width;\n\tconst rx = w / 2;\n\tconst ry = cylinderRadiusY(w);\n\tconst h = bbox.height;\n\n\tconst shape =\n\t\t`M 0,${ry} a${rx},${ry} 0,0,0 ${w} 0 a ${rx},${ry} 0,0,0 ${-w} 0 l 0,${h - 2 * ry} a ${rx},${ry} 0,0,0 ${w} 0 l 0,${-h + 2 * ry}`;\n\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"cylinder\", w, h))\n\t\t.insert('path', ':first-child')\n\t\t.attr('d', shape)\n\t\t.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2) + ')');\n\n\tnode.intersect = function (point: Point) {\n\t\tconst pos = intersectRect(node, point)\n\t\tlet cy = node.y + node.height / 2 - ry\n\t\tif (pos.y > cy)\n\t\t\treturn intersectEllipse({x: node.x, y: cy}, rx, ry, node, point)\n\n\t\tcy = node.y - node.height / 2 + ry\n\t\tif (pos.y < cy)\n\t\t\treturn intersectEllipse({x: node.x, y: cy}, rx, ry, node, point)\n\n\t\treturn pos;\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction person(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = bbox.width;\n\tconst h = bbox.height;\n\n\tconst shape =\n\t\t`M ${.38 * w},${h / 3} A${w / 2},${h / 2} 0,0,0 0 ${h / 2}\n\t\tL${w / 11},${h} L${w - w / 11},${h} L${w},${h / 2}\n\t\tA${w / 2},${h / 2} 0,0,0 ${w - .38 * w} ${h / 3} \n\t\tA${w / 6},${w / 6} 0,1,0 ${.38 * w} ${h / 3}`;\n\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"person\", w, h))\n\t\t.insert('path', ':first-child')\n\t\t.attr('d', shape)\n\t\t.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2) + ')');\n\n\tnode.intersect = function (point: Point) {\n\t\tconst pos = intersectRect(node, point)\n\t\treturn pos;\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction _ellipse(parent: D3Element, bbox: BBox, node: D3Node, rx: number, ry: number) {\n\tconst shapeSvg = parent.insert(\"ellipse\", \":first-child\")\n\t\t.attr(\"cx\", 0)\n\t\t.attr(\"cy\", 0)\n\t\t.attr('rx', rx)\n\t\t.attr('ry', ry)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectEllipse(node, rx, ry, node, point)\n\t};\n\treturn shapeSvg;\n}\n\nfunction circle(parent: D3Element, bbox: BBox, node: D3Node) {\n\treturn _ellipse(parent, bbox, node, node.width / 2, node.width / 2)\n}\n\nfunction ellipse(parent: D3Element, bbox: BBox, node: D3Node) {\n\treturn _ellipse(parent, bbox, node, node.width * .55, node.width * .45)\n}\n\nfunction hexagon(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst sz = node.width / 2\n\t// drawing a hexagon from polar coords\n\t// [0,1,2,3,4,5,6].map(i=>`${Math.sin(Math.PI/3*i+Math.PI/6).toFixed(4)},${Math.cos(Math.PI/3*i+Math.PI/6).toFixed(4)}`).join(',')\n\tconst shapeSvg = parent.insert(\"polygon\", \":first-child\")\n\t\t.attr(\"points\",\n\t\t\t[0.5000, 0.8660, 1.0000, 0.0000, 0.5000, -0.8660, -0.5000, -0.8660, -1.0000, -0.0000, -0.5000, 0.8660, 0.5000, 0.8660].map(n => n * sz).join(','))\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectEllipse(node, node.width / 2, node.width / 2, node, point)\n\t};\n\treturn shapeSvg;\n}\n\nfunction component(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dx = node.width / 10\n\tconst shapeSvg = parent.insert('g', ':first-child')\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2 - dx)\n\t\t.attr(\"y\", -node.height / 2 + dx)\n\t\t.attr(\"width\", dx * 2)\n\t\t.attr(\"height\", dx);\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2 - dx)\n\t\t.attr(\"y\", -node.height / 2 + dx * 2.5)\n\t\t.attr(\"width\", dx * 2)\n\t\t.attr(\"height\", dx);\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2)\n\t\t.attr(\"y\", -node.height / 2)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x - dx / 2, y: node.y, width: node.width + dx, height: node.height}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction folder(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dy = node.width / 20\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"folder\", node.width, node.height))\n\t\t.insert('g', ':first-child')\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2)\n\t\t.attr(\"y\", -node.height / 2 + dy * 2)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height - dy * 2);\n\tshapeSvg.insert(\"path\", \":first-child\")\n\t\t.attr('d', `M0,${-node.height / 2 + 2 * dy} l${dy},${-2 * dy} h${node.width / 2 - dy * 2} v${dy * 2}`)\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x, y: node.y + dy / 2, width: node.width, height: node.height + dy}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction mobiledevicelandscape(parent: D3Element, bbox: BBox, node: D3Node, rounded = false) {\n\tconst dx = node.width / 8\n\tconst r = node.width / 14\n\tconst shapeSvg = parent.insert('g', ':first-child')\n\tshapeSvg.insert('path', ':first-child')\n\t\t.attr('d', `M${-node.width / 2},${-node.height / 2} l0,${node.height} M${node.width / 2},${-node.height / 2} l0,${node.height}`)\n\tshapeSvg.insert('circle', ':first-child')\n\t\t.attr('cx', -node.width / 2 - dx / 2)\n\t\t.attr('cy', 0)\n\t\t.attr('r', r * .4)\n\tshapeSvg.insert('rect', ':first-child')\n\t\t.attr('x', node.width / 2 + dx / 2 - r * .2)\n\t\t.attr('y', -r)\n\t\t.attr('width', r * .4)\n\t\t.attr('height', r * 2)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", r)\n\t\t.attr(\"ry\", r)\n\t\t.attr(\"x\", -bbox.width / 2 - dx)\n\t\t.attr(\"y\", -bbox.height / 2)\n\t\t.attr(\"width\", bbox.width + 2 * dx)\n\t\t.attr(\"height\", bbox.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x, y: node.y, width: node.width + 2 * dx, height: node.height}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction mobiledeviceportrait(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dy = node.width / 8\n\tconst r = node.width / 14\n\tconst shapeSvg = parent.insert('g', ':first-child')\n\tshapeSvg.insert('path', ':first-child')\n\t\t.attr('d', `M${-node.width / 2},${-node.height / 2} l${node.width},0 M${-node.width / 2},${node.height / 2} l${node.width},0`)\n\tshapeSvg.insert('circle', ':first-child')\n\t\t.attr('cx', 0)\n\t\t.attr('cy', node.height / 2 + dy / 2)\n\t\t.attr('r', r * .4)\n\tshapeSvg.insert('rect', ':first-child')\n\t\t.attr('x', -r)\n\t\t.attr('y', -node.height / 2 - dy / 2 - r * .2)\n\t\t.attr('width', r * 2)\n\t\t.attr('height', r * .4)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", r)\n\t\t.attr(\"ry\", r)\n\t\t.attr(\"x\", -bbox.width / 2)\n\t\t.attr(\"y\", -bbox.height / 2 - dy)\n\t\t.attr(\"width\", bbox.width)\n\t\t.attr(\"height\", bbox.height + 2 * dy);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x, y: node.y, width: node.width, height: node.height + 2 * dy}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction pipe(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = node.width;\n\tconst h = node.height;\n\tconst ry = h / 2;\n\tconst rx = ry / (2.5 + w / 70);\n\n\tconst shape =\n\t\t`M${-rx},0\n\t\ta${rx},${ry} 0,0,1 0,${h}\n\t\ta${rx},${ry} 0,0,1 0,${-h}\n\t\tl${w},0\n\t\ta${rx},${ry} 0,0,1 0,${h}\n\t\tl${-w},0`;\n\n\tconst shapeSvg = parent\n\t\t.insert('path', ':first-child')\n\t\t.attr('d', shape)\n\t\t.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2) + ')');\n\n\tnode.intersect = function (point: Point) {\n\t\treturn intersectRect({x: node.x - rx, y: node.y, width: node.width + 2 * rx, height: node.height}, point)\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction robot(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = node.width\n\tconst h = node.height\n\t\n\t// Small head at top (like person shape but robot-styled)\n\tconst headSize = Math.min(w * 0.28, h * 0.25)\n\tconst headR = headSize * 0.2\n\tconst antennaH = headSize * 0.25\n\tconst antennaR = headSize * 0.08\n\t\n\t// Eye dimensions\n\tconst eyeR = headSize * 0.12\n\tconst eyeSpacing = headSize * 0.22\n\t\n\t// Ear dimensions \n\tconst earW = headSize * 0.12\n\tconst earH = headSize * 0.3\n\t\n\t// Body fills most of the space for text\n\tconst bodyW = w\n\tconst bodyTop = -h / 2 + antennaH + headSize\n\tconst bodyH = h - antennaH - headSize\n\tconst bodyR = 3\n\t\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"robot\", w, h))\n\t\t.insert('g', ':first-child')\n\t\n\t// Body - main rectangle for text (draw first so it's behind)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", bodyR)\n\t\t.attr(\"ry\", bodyR)\n\t\t.attr('x', -bodyW / 2)\n\t\t.attr('y', bodyTop)\n\t\t.attr('width', bodyW)\n\t\t.attr('height', bodyH)\n\t\n\t// Head\n\tconst headTop = -h / 2 + antennaH\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", headR)\n\t\t.attr(\"ry\", headR)\n\t\t.attr('x', -headSize / 2)\n\t\t.attr('y', headTop)\n\t\t.attr('width', headSize)\n\t\t.attr('height', headSize)\n\t\n\t// Antenna\n\tshapeSvg.insert(\"line\", \":first-child\")\n\t\t.attr('class', 'robot-antenna')\n\t\t.attr('x1', 0)\n\t\t.attr('y1', headTop)\n\t\t.attr('x2', 0)\n\t\t.attr('y2', -h / 2 + antennaR * 2)\n\t\t.attr('stroke-width', antennaR * 0.6)\n\t\t.attr('stroke-linecap', 'round')\n\t\n\t// Antenna ball\n\tshapeSvg.insert(\"circle\", \":first-child\")\n\t\t.attr('class', 'robot-antenna-ball')\n\t\t.attr('cx', 0)\n\t\t.attr('cy', -h / 2 + antennaR * 2)\n\t\t.attr('r', antennaR * 1.2)\n\t\n\t// Eyes\n\tconst eyeY = headTop + headSize * 0.4\n\tshapeSvg.insert(\"circle\", \":first-child\")\n\t\t.attr('class', 'robot-eye')\n\t\t.attr('cx', -eyeSpacing)\n\t\t.attr('cy', eyeY)\n\t\t.attr('r', eyeR)\n\tshapeSvg.insert(\"circle\", \":first-child\")\n\t\t.attr('class', 'robot-eye')\n\t\t.attr('cx', eyeSpacing)\n\t\t.attr('cy', eyeY)\n\t\t.attr('r', eyeR)\n\t\n\t// Mouth - simple smile\n\tconst mouthY = headTop + headSize * 0.7\n\tconst mouthW = headSize * 0.28\n\tshapeSvg.insert(\"path\", \":first-child\")\n\t\t.attr('class', 'robot-mouth')\n\t\t.attr('d', `M${-mouthW / 2},${mouthY} Q0,${mouthY + mouthW * 0.3} ${mouthW / 2},${mouthY}`)\n\t\t.attr('fill', 'none')\n\t\t.attr('stroke-width', antennaR * 0.5)\n\t\t.attr('stroke-linecap', 'round')\n\t\n\t// Ears\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", earW * 0.25)\n\t\t.attr(\"ry\", earW * 0.25)\n\t\t.attr('x', -headSize / 2 - earW - 1)\n\t\t.attr('y', eyeY - earH / 2)\n\t\t.attr('width', earW)\n\t\t.attr('height', earH)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", earW * 0.25)\n\t\t.attr(\"ry\", earW * 0.25)\n\t\t.attr('x', headSize / 2 + 1)\n\t\t.attr('y', eyeY - earH / 2)\n\t\t.attr('width', earW)\n\t\t.attr('height', earH)\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect(node, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction webbrowser(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dy = node.height / 8\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"webbrowser\", node.width, node.height))\n\t\t.insert('g', ':first-child')\n\tshapeSvg.insert(\"path\", \":first-child\")\n\t\t.attr('d', `\n\t\t\tM${-node.width / 2},${-node.height / 2 + dy} h${node.width}\n\t\t\tM${-node.width / 2 + dy / 4},${-node.height / 2 + dy / 4} h${dy / 2} v${dy / 2} h${-dy / 2} z\n\t\t\tM${-node.width / 2 + dy},${-node.height / 2 + dy / 4} h${node.width - dy - dy / 4} v${dy / 2} h${-node.width + dy + dy / 4} z\n\t\t`)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2)\n\t\t.attr(\"y\", -node.height / 2)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect(node, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nexport const shapes: { [key: string]: (parent: SVGElement, node: D3Node) => SVGElement } = {\n\tbox: (parent: SVGElement, node: D3Node) => rect(new D3Element(parent), node, node).node(),\n\troundedbox: (parent: SVGElement, node: D3Node) => rect(new D3Element(parent), node, node, true).node(),\n\tcomponent: (parent: SVGElement, node: D3Node) => component(new D3Element(parent), node, node).node(),\n\tcylinder: (parent: SVGElement, node: D3Node) => cylinder(new D3Element(parent), node, node).node(),\n\tperson: (parent: SVGElement, node: D3Node) => person(new D3Element(parent), node, node).node(),\n\tcircle: (parent: SVGElement, node: D3Node) => circle(new D3Element(parent), node, node).node(),\n\tellipse: (parent: SVGElement, node: D3Node) => ellipse(new D3Element(parent), node, node).node(),\n\thexagon: (parent: SVGElement, node: D3Node) => hexagon(new D3Element(parent), node, node).node(),\n\tfolder: (parent: SVGElement, node: D3Node) => folder(new D3Element(parent), node, node).node(),\n\tmobiledevicelandscape: (parent: SVGElement, node: D3Node) => mobiledevicelandscape(new D3Element(parent), node, node).node(),\n\tmobiledeviceportrait: (parent: SVGElement, node: D3Node) => mobiledeviceportrait(new D3Element(parent), node, node).node(),\n\tmobiledevice: (parent: SVGElement, node: D3Node) => mobiledeviceportrait(new D3Element(parent), node, node).node(),\n\tpipe: (parent: SVGElement, node: D3Node) => pipe(new D3Element(parent), node, node).node(),\n\trobot: (parent: SVGElement, node: D3Node) => robot(new D3Element(parent), node, node).node(),\n\twebbrowser: (parent: SVGElement, node: D3Node) => webbrowser(new D3Element(parent), node, node).node(),\n}\n","/**\n * Undo functionality\n * at every change in the document, Undo can save a new version\n * so the user can \"undo\" and \"redo\" changes by reverting to an\n * older version of the document\n */\n\nexport class Undo {\n\tprivate readonly versions: Doc[] = [];\n\tprivate pos: number = 0;\n\tprivate lastSavedPos: number = 0;\n\tprivate readonly exportDoc: () => Doc;\n\tprivate readonly importDoc: (d: Doc) => void;\n\tchange: () => void;\n\tprivate tmpPreviousState: Doc | null = null;\n\n\tconstructor(id: string, exportDoc: () => Doc, importDoc: (d: Doc) => void) {\n\t\tthis.exportDoc = exportDoc;\n\t\tthis.importDoc = importDoc;\n\t\tthis.change = debounce(() => this.saveNow(), 300);\n\t}\n\n\t// Store the state previous to the changes collected in the debounce period\n\tbeforeChange() {\n\t\tif (!this.tmpPreviousState) {\n\t\t\tthis.tmpPreviousState = this.deepClone(this.exportDoc());\n\t\t}\n\t}\n\n\tlength() {\n\t\treturn this.versions.length;\n\t}\n\n\tcurrentState() {\n\t\treturn this.deepClone(this.versions[this.pos - 1]);\n\t}\n\n\tprivate saveNow() {\n\t\tif (!this.tmpPreviousState) {\n\t\t\tthrow Error(\"undo.change() was called without previously calling undo.beforeChange()!\");\n\t\t}\n\t\t\n\t\tthis.versions[this.pos] = this.deepClone(this.exportDoc());\n\t\tthis.versions[this.pos - 1] = this.tmpPreviousState;\n\t\tthis.tmpPreviousState = null;\n\t\tthis.pos += 1;\n\t\t\n\t\t// Remove anything that might be on top of this version\n\t\tthis.versions.splice(this.pos);\n\t}\n\n\tprivate deepClone(doc: Doc): Doc {\n\t\t// Use modern structuredClone if available, fallback to JSON\n\t\tif (typeof structuredClone !== 'undefined') {\n\t\t\treturn structuredClone(doc);\n\t\t}\n\t\treturn JSON.parse(JSON.stringify(doc));\n\t}\n\n\tundo() {\n\t\tif (this.pos < 2) return;\n\t\tthis.pos -= 1;\n\t\tconst doc = this.versions[this.pos - 1];\n\t\tthis.importDoc(this.deepClone(doc));\n\t}\n\n\tredo() {\n\t\tif (this.pos > this.versions.length - 1) return;\n\t\tconst doc = this.versions[this.pos];\n\t\tthis.importDoc(this.deepClone(doc));\n\t\tthis.pos += 1;\n\t}\n\n\tchanged() {\n\t\treturn this.pos !== this.lastSavedPos;\n\t}\n\n\tsetSaved() {\n\t\tthis.lastSavedPos = this.pos;\n\t}\n}\n\nfunction debounce(func: () => void, wait: number) {\n\tlet timeout: ReturnType;\n\treturn function () {\n\t\tconst context = this;\n\t\tconst later = function () {\n\t\t\ttimeout = null;\n\t\t\tfunc.apply(context);\n\t\t};\n\t\tclearTimeout(timeout);\n\t\ttimeout = setTimeout(later, wait);\n\t};\n}","// Constants and default configurations for the graph view\n\nexport interface Point {\n\tx: number;\n\ty: number;\n}\n\nexport interface BBox extends Point {\n\twidth: number;\n\theight: number;\n}\n\nexport interface NodeStyle {\n\t// Width of element, in pixels.\n\twidth?: number\n\t// Height of element, in pixels.\n\theight?: number\n\t// Background color of element as HTML RGB hex string (e.g. \"#ffffff\")\n\tbackground?: string\n\t// Stroke color of element as HTML RGB hex string (e.g. \"#000000\")\n\tstroke?: string\n\t// Foreground (text) color of element as HTML RGB hex string (e.g. \"#ffffff\")\n\tcolor?: string\n\t// Standard font size used to render text, in pixels.\n\tfontSize?: number\n\t// Shape used to render element.\n\tshape?: string\n\t// URL of PNG/JPG/GIF file or Base64 data URI representation.\n\ticon?: string\n\t// Type of border used to render element.\n\tborder?: string\n\t// Opacity used to render element; 0-100.\n\topacity?: number\n\t// Whether element metadata should be shown.\n\tmetadata?: boolean\n\t// Whether element description should be shown.\n\tdescription?: boolean\n}\n\nexport interface EdgeStyle {\n\t// Thickness of line, in pixels.\n\tthickness?: number\n\t// Color of line as HTML RGB hex string (e.g. \"#ffffff\").\n\tcolor?: string\n\t// Standard font size used to render relationship annotation, in pixels.\n\tfontSize?: number\n\t// Width of relationship annotation, in pixels.\n\twidth?: number\n\t// Whether line is dashed.\n\tdashed?: boolean\n\t// Position of label along edge (0-100).\n\tposition?: number\n\t// Opacity used to render relationship; 0-100.\n\topacity?: number\n\t// Arrow style for the edge.\n\tarrowStyle?: 'normal' | 'large' | 'small' | 'none'\n}\n\n// Default styles\nexport const DEFAULT_EDGE_STYLE: EdgeStyle = {\n\tthickness: 3,\n\tcolor: '#999',\n\topacity: 1,\n\tfontSize: 22,\n\tdashed: true,\n};\n\nexport const DEFAULT_NODE_STYLE: NodeStyle = {\n\twidth: 280,\n\theight: 180,\n\tbackground: 'rgba(255, 255, 255, .9)',\n\tcolor: '#666',\n\topacity: .9,\n\tstroke: '#999',\n\tfontSize: 22,\n\tshape: 'Box'\n};\n\n// SVG styles\nexport const SVG_STYLES = {\n\tnodeBorder: {\n\t\tfill: \"rgba(255, 255, 255, 0.86)\",\n\t\tstroke: \"#aaa\",\n\t\tfilter: 'url(#shadow)',\n\t},\n\tnodeText: {\n\t\t'font-family': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\tedgeText: {\n\t\t'font-family': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\tedgeRect: {\n\t\tfill: \"none\",\n\t\tstroke: \"none\",\n\t},\n\tgroupRect: {\n\t\tfill: \"rgba(0, 0, 0, 0.02)\",\n\t\tstroke: \"#666\",\n\t\t'stroke-width': 3,\n\t\t\"stroke-dasharray\": 4,\n\t},\n\tgroupText: {\n\t\tfill: \"#666\",\n\t\t\"font-size\": 22,\n\t\t\"font-weight\": \"500\",\n\t\t'font-family': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',\n\t\tcursor: \"default\"\n\t}\n};\n\n// Configuration constants\nexport const SVG_PADDING = 20;\nexport const DEFAULT_GRID_SIZE = 25;\nexport const EDGE_SPREAD_DISTANCE = 70;\nexport const EDGE_SPREAD_DISTANCE_X = 200;\n\n// Utility function to apply styles to SVG elements\nexport const applyStyle = (el: SVGElement, style: { [key: string]: string | number }) => {\n\tObject.keys(style).forEach(key => {\n\t\tconst value = style[key];\n\t\tif (typeof value === 'number') {\n\t\t\tel.style.setProperty(key, value.toString());\n\t\t} else {\n\t\t\tel.style.setProperty(key, value);\n\t\t}\n\t});\n};\n\n// Utility function to calculate distance between two points\nexport const calculateDistance = (p1: Point, p2: Point): number => {\n\treturn Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));\n};","import {applyStyle, SVG_STYLES} from \"./constants\";\nimport {create} from \"./svg-create\";\nimport {svgTextWrap} from \"./svg-text\";\n\ninterface TextBlockLayout {\n\tlines: string[];\n\tfontSize: number;\n\tlineHeight: number;\n\tbold: boolean;\n\tfield?: string;\n\tgapAfter: number;\n}\n\nexport interface NodeContentLayout {\n\tblocks: TextBlockLayout[];\n\ttextHeight: number;\n\tminimumHeight: number;\n}\n\nconst HORIZONTAL_PADDING = 18;\nconst VERTICAL_PADDING = 18;\nconst TITLE_GAP = 6;\nconst METADATA_GAP = 10;\n\nfunction textBlock(\n\ttext: string,\n\twidth: number,\n\tfontSize: number,\n\tbold: boolean,\n\tgapAfter: number,\n\tfield?: string,\n): TextBlockLayout {\n\tconst attrs = {\n\t\t\"font-family\": String(SVG_STYLES.nodeText[\"font-family\"]),\n\t\t\"font-size\": `${fontSize}px`,\n\t\t\"font-weight\": bold ? \"bold\" : \"normal\",\n\t};\n\tconst wrapped = svgTextWrap(text, width, attrs);\n\tconst lines = wrapped.lines.length > 0 ? wrapped.lines : [\"\"];\n\n\treturn {\n\t\tlines,\n\t\tfontSize,\n\t\tlineHeight: fontSize + 2,\n\t\tbold,\n\t\tfield,\n\t\tgapAfter,\n\t};\n}\n\nexport function layoutNodeContent(\n\ttitle: string,\n\tsubtitle: string,\n\tdescription: string,\n\tnodeWidth: number,\n\tfontSize: number,\n): NodeContentLayout {\n\tconst textWidth = Math.max(nodeWidth - HORIZONTAL_PADDING * 2, 80);\n\tconst blocks = [\n\t\ttextBlock(title, textWidth, fontSize, true, TITLE_GAP, \"name\"),\n\t\ttextBlock(`[${subtitle}]`, textWidth, fontSize * 0.75, false, METADATA_GAP),\n\t\ttextBlock(description, textWidth, Math.min(fontSize * 0.8, 16), false, 0, \"description\"),\n\t];\n\tconst textHeight = blocks.reduce(\n\t\t(height, block) => height + block.lines.length * block.lineHeight + block.gapAfter,\n\t\t0,\n\t);\n\n\treturn {\n\t\tblocks,\n\t\ttextHeight,\n\t\tminimumHeight: textHeight + VERTICAL_PADDING * 2,\n\t};\n}\n\nexport function buildNodeContent(layout: NodeContentLayout, color?: string): SVGGElement {\n\tconst group = create.element(\"g\") as SVGGElement;\n\tlet top = -layout.textHeight / 2;\n\n\tlayout.blocks.forEach((block) => {\n\t\tconst text = create.text(\"\", {\"text-anchor\": \"middle\"});\n\t\tapplyStyle(text, SVG_STYLES.nodeText);\n\t\tif (color) {\n\t\t\ttext.setAttribute(\"fill\", color);\n\t\t}\n\t\tif (block.field) {\n\t\t\ttext.setAttribute(\"data-field\", block.field);\n\t\t}\n\n\t\tblock.lines.forEach((line, index) => {\n\t\t\tconst span = create.element(\"tspan\", {\n\t\t\t\tx: 0,\n\t\t\t\ty: top + block.fontSize + index * block.lineHeight,\n\t\t\t\t\"font-size\": `${block.fontSize}px`,\n\t\t\t\t\"font-weight\": block.bold ? \"bold\" : \"normal\",\n\t\t\t});\n\t\t\tspan.textContent = line;\n\t\t\ttext.append(span);\n\t\t});\n\n\t\tgroup.append(text);\n\t\ttop += block.lines.length * block.lineHeight + block.gapAfter;\n\t});\n\n\treturn group;\n}\n","import {defs} from \"./defs\";\nimport {create, setPosition} from \"./svg-create\";\nimport {cursorInteraction} from \"svg-editor-tools/lib/cursor-interaction\";\nimport {shapeLabelOffsetY, shapes} from \"./shapes\";\nimport {\n\tboxesOverlap,\n\tcabDistance,\n\tinsideBox,\n\tintersectPolylineBox,\n\tproject,\n\tscaleBox,\n\tSegment,\n\tuncenterBox\n} from \"./intersect\";\nimport {autoLayout} from \"./layout\";\nimport {Undo} from \"./undo\";\nimport {\n\tADD_LABEL_VERTEX,\n\tADD_VERTEX,\n\tDEL_VERTEX,\n\tDESELECT,\n\tfindShortcut,\n\tREDO,\n\tSELECT_ALL,\n\tUNDO,\n\tZOOM_100,\n\tZOOM_FIT,\n\tZOOM_IN,\n\tZOOM_OUT\n} from \"../shortcuts\";\nimport {\n\tPoint,\n\tBBox,\n\tNodeStyle,\n\tEdgeStyle,\n\tDEFAULT_EDGE_STYLE,\n\tDEFAULT_NODE_STYLE,\n\tSVG_STYLES,\n\tSVG_PADDING,\n\tDEFAULT_GRID_SIZE,\n\tapplyStyle,\n\tcalculateDistance\n} from \"./constants\";\nimport {\n\tcalculateEdgeVertices,\n\tcalculateLabelPlacement,\n\tcreateEdgeSegments,\n\tEdgeLabelPlacement\n} from \"./edge-utils\";\nimport {\n\tbuildNodeContent,\n\tlayoutNodeContent,\n\tNodeContentLayout\n} from \"./node-content\";\n\n\n// Point and BBox interfaces are now imported from constants\n\nexport interface Group extends BBox {\n\tid: string;\n\tname: string;\n\tnodes: (Node | Group)[];\n\tref?: SVGGElement;\n\tstyle: NodeStyle;\n}\n\nexport interface NodeLink {\n\thref: string;\n\texportHref: string;\n}\n\nexport interface Node extends BBox {\n\tid: string;\n\ttitle: string;\n\tsub: string;\n\tdescription: string;\n\n\tref?: SVGGElement;\n\tselected?: boolean;\n\n\tintersect: (p: Point) => Point\n\n\tstyle: NodeStyle\n\tlink?: NodeLink\n\tcontentLayout: NodeContentLayout\n}\n\n// NodeStyle interface is now imported from constants\n\n// EdgeStyle interface is now imported from constants\n\n// Default styles are now imported from constants\nconst defaultEdgeStyle = DEFAULT_EDGE_STYLE;\nconst defaultNodeStyle = DEFAULT_NODE_STYLE;\n\n// Edge and EdgeVertex interfaces are now defined in edge-utils.ts\n// Using local interfaces for compatibility with existing code\ninterface Edge {\n\tid: string;\n\tlabel: string;\n\tfrom: Node;\n\tto: Node;\n\tvertices?: EdgeVertex[];\n\tref?: SVGGElement;\n\tstyle: EdgeStyle;\n\tinitVertex: (p: Point) => EdgeVertex;\n\tuserDeletedVertices?: boolean; // Track if user explicitly deleted vertices\n\tlabelVertex?: EdgeVertex; // ELK-calculated label position (separate from routing vertices)\n}\n\ninterface EdgeVertex extends Point {\n\tid: string\n\tselected?: boolean\n\tedge: Edge\n\tref?: SVGElement\n\tlabel?: boolean\n\tauto?: boolean\n}\n\ninterface Layout {\n\t[k: string]: Point | (Point & { label: boolean })[] | boolean\n}\n\nexport class GraphData {\n\tid: string;\n\tname: string;\n\tnodesMap: Map;\n\tedges: Edge[];\n\tedgeVertices: Map\n\tgroupsMap: Map;\n\tmetadata: any;\n\tcolorToVarMap: Map = new Map(); // For CSS custom properties theming\n\tprivate _undo: Undo;\n\tprivate _gridVisible: boolean = false;\n\tprivate _snapToGrid: boolean = true;\n\tprivate _gridSize: number = 25;\n\tprivate _skipAutoFit: boolean = false;\n\n\tconstructor(id?: string, name?: string) {\n\t\tthis.id = id;\n\t\tthis.name = name;\n\n\t\tthis.edges = [];\n\t\tthis.edgeVertices = new Map;\n\t\tthis.nodesMap = new Map;\n\t\tthis.groupsMap = new Map;\n\n\t\tthis._undo = new Undo(\n\t\t\tthis.id,\n\t\t\t() => this.exportLayout(true),\n\t\t\t(lo) => this.importLayout(lo, true)\n\t\t)\n\n\t\t// @ts-ignore\n\t\twindow.graph = this\n\t}\n\n\t// after the graph model is build using addNode, addEdge etc, initialize\n\tinit(layout?: Layout) {\n\t\tlayout && this.importLayout(layout)\n\t\tthis._undo = new Undo(\n\t\t\tthis.id,\n\t\t\t() => this.exportLayout(true),\n\t\t\t(lo) => this.importLayout(lo, true)\n\t\t)\n\t\tif (this._undo.length()) {\n\t\t\tthis.importLayout(this._undo.currentState())\n\t\t}\n\t\t\n\t\t// Save initial state so first action is undoable\n\t\tthis._undo.beforeChange()\n\t\tthis._undo.change()\n\t}\n\n\taddNode(id: string, label: string, sub: string, description: string, style: NodeStyle, link?: NodeLink) {\n\t\tif (this.nodesMap.has(id)) throw Error('duplicate node: ' + id)\n\t\tconst nodeStyle = {...defaultNodeStyle, ...style};\n\t\tconst shape = (nodeStyle.shape || 'Box').toLowerCase();\n\t\tconst minimumWidth = 280;\n\t\tconst minimumHeight = shape === 'person' ? 240 : 180;\n\t\tconst width = Math.max(minimumWidth, nodeStyle.width || 0);\n\t\tconst fontSize = nodeStyle.fontSize || 22;\n\t\tconst contentLayout = layoutNodeContent(label, sub, description, width, fontSize);\n\t\tlet height = Math.max(minimumHeight, nodeStyle.height || 0, contentLayout.minimumHeight);\n\n\t\t// Some shapes shift labels down to reserve visual space for an icon,\n\t\t// header, or curved edge. Grow until the shifted content also fits.\n\t\tfor (let i = 0; i < 20; i++) {\n\t\t\tconst requiredHeight = contentLayout.minimumHeight +\n\t\t\t\tMath.abs(shapeLabelOffsetY(shape, width, height));\n\t\t\tif (requiredHeight <= height + 0.1) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\theight = requiredHeight;\n\t\t}\n\n\t\tconst n: Node = {\n\t\t\tid, title: label, sub, description, style: nodeStyle,\n\t\t\tx: 0, y: 0, width, height, intersect: null, link, contentLayout\n\t\t}\n\t\tthis.nodesMap.set(n.id, n)\n\t}\n\n\tnodes() {\n\t\treturn Array.from(this.nodesMap.values())\n\t}\n\n\taddEdge(id: string, fromNode: string, toNode: string, label: string, vertices: Point[], style: EdgeStyle) {\n\t\tvertices && vertices.forEach((p, i) => {\n\t\t\tconst v = p as EdgeVertex\n\t\t\tv.id = `v-${id}-${i}`\n\t\t\tthis.edgeVertices.set(v.id, v)\n\t\t})\n\t\t// Deterministic vertex IDs.\n\t\t//\n\t\t// `mdl svg` renders diagrams headlessly and saves the resulting SVG. If we\n\t\t// generate random vertex IDs, the SVG changes on every run even when the\n\t\t// underlying model and layout are unchanged. Use a stable hash instead.\n\t\tconst fnv1a36 = (input: string) => {\n\t\t\tlet h = 0x811c9dc5\n\t\t\tfor (let i = 0; i < input.length; i++) {\n\t\t\t\th ^= input.charCodeAt(i)\n\t\t\t\th = Math.imul(h, 0x01000193)\n\t\t\t}\n\t\t\treturn (h >>> 0).toString(36)\n\t\t}\n\t\tconst stableVertexID = (edgeID: string, p: Point) => {\n\t\t\tconst x = (p as any).x\n\t\t\tconst y = (p as any).y\n\t\t\treturn `v-${edgeID}-a-${fnv1a36(`${edgeID}:${x}:${y}`)}`\n\t\t}\n\t\tconst initVertex = (p: Point) => {\n\t\t\tconst v = p as EdgeVertex\n\t\t\tif (!v.id) {\n\t\t\t\tv.id = stableVertexID(edge.id, p)\n\t\t\t\tthis.edgeVertices.set(v.id, v)\n\t\t\t}\n\t\t\tv.edge = edge\n\t\t\treturn p as EdgeVertex\n\t\t}\n\t\tconst edge = {\n\t\t\tid,\n\t\t\tfrom: this.nodesMap.get(fromNode),\n\t\t\tto: this.nodesMap.get(toNode),\n\t\t\tlabel,\n\t\t\tvertices: null as EdgeVertex[],\n\t\t\tstyle: {...defaultEdgeStyle, ...style},\n\t\t\tinitVertex,\n\t\t\tuserDeletedVertices: false\n\t\t}\n\t\tthis.edges.push(edge)\n\t\tif (vertices) {\n\t\t\tedge.vertices = vertices.map(p => edge.initVertex(p))\n\t\t}\n\t}\n\n\taddGroup(id: string, name: string, nodesOrGroups: string[], style: NodeStyle) {\n\t\tif (this.groupsMap.has(id)) {\n\t\t\tconsole.error(`Group exists: ${id} ${name}`)\n\t\t\treturn\n\t\t}\n\t\tconst group: Group = {\n\t\t\tid, name, x: null, y: null, width: null, height: null,\n\t\t\tnodes: nodesOrGroups.map(k => {\n\t\t\t\tconst n = this.nodesMap.get(k) || this.groupsMap.get(k)\n\t\t\t\tif (!n) console.error(`Node or group ${k} not found for group ${id} \"${name}\"`)\n\t\t\t\treturn n\n\t\t\t}).filter(Boolean),\n\t\t\tstyle\n\t\t}\n\t\tthis.groupsMap.set(id, group)\n\t}\n\n\t// private rebuildNode(node: Node) {\n\t// \tconst p = node.ref.parentElement;\n\t// \tp.removeChild(node.ref)\n\t// \tnode.ref = buildNode(node, this)\n\t// \tp.appendChild(node.ref)\n\t// \tthis.redrawEdges(node)\n\t// \tthis.redrawGroups(node)\n\t// }\n\n\tsetNodeSelected(node: Node, selected: boolean) {\n\t\tnode.selected = selected\n\t\tselected ?\n\t\t\tnode.ref.classList.add('selected') :\n\t\t\tnode.ref.classList.remove('selected')\n\t\tthis.updateEdgesSel()\n\t}\n\n\tprivate updateEdgesSel() {\n\t\tthis.edges.forEach(e => {\n\t\t\tif (e.to.selected || e.from.selected) {\n\t\t\t\te.ref.classList.add('selected')\n\t\t\t} else {\n\t\t\t\te.ref.classList.remove('selected')\n\t\t\t}\n\t\t})\n\t}\n\n\tmoveNode(n: Node, x: number, y: number, disableSnap: boolean = false, skipUndo: boolean = false) {\n\t\tif (!n) return\n\t\t\n\t\t// Apply snap-to-grid if enabled and not explicitly disabled\n\t\tif (this._snapToGrid && !disableSnap) {\n\t\t\tconst snapped = this.snapToGrid(x, y);\n\t\t\tx = snapped.x;\n\t\t\ty = snapped.y;\n\t\t}\n\t\t\n\t\tif (n.x == x && n.y == y) return\n\t\t\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.beforeChange()\n\t\t}\n\t\tn.x = x;\n\t\tn.y = y;\n\t\tsetPosition(n.ref, x, y)\n\t\tthis.redrawEdges(n);\n\t\tthis.redrawGroups(n)\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.change()\n\t\t}\n\t}\n\n\tmoveEdgeVertex(v: EdgeVertex, x: number, y: number, disableSnap: boolean = false, skipUndo: boolean = false) {\n\t\t\n\t\tif (this._snapToGrid && !disableSnap) {\n\t\t\tconst snapped = this.snapToGrid(x, y);\n\t\t\tx = snapped.x;\n\t\t\ty = snapped.y;\n\t\t}\n\t\t// Use exact coordinates (no rounding needed with modern grid system)\n\t\t\n\t\tif (v.x == x && v.y == y) return\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.beforeChange()\n\t\t}\n\t\tv.x = x;\n\t\tv.y = y;\n\t\tthis.redrawEdge(v.edge)\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.change()\n\t\t}\n\t}\n\n\tmoveSelected(dx: number, dy: number, disableSnap: boolean = false) {\n\t\tthis.nodes().forEach(n => n.selected && this.moveNode(n, n.x + dx, n.y + dy, disableSnap, false))\n\t\tthis.edgeVertices.forEach(v => v.selected && this.moveEdgeVertex(v, v.x + dx, v.y + dy, disableSnap, false))\n\t}\n\n\tinsertEdgeVertex(edge: Edge, p: Point, pos: number, isLabel: boolean) {\n\t\tthis._undo.beforeChange()\n\t\tconst v = edge.initVertex(p)\n\t\tv.selected = true\n\t\tif (isLabel) { // when shift down, make it label position\n\t\t\tedge.vertices.forEach(v => v.label = false)\n\t\t\tv.label = true\n\t\t}\n\t\tedge.vertices.splice(pos - 1, 0, v)\n\t\tthis.redrawEdge(edge)\n\t\tthis._undo.change()\n\t}\n\n\tdeleteEdgeVertex(v: EdgeVertex) {\n\t\tthis._undo.beforeChange()\n\t\t\n\t\tconst index = v.edge.vertices.indexOf(v)\n\t\tif (index >= 0) {\n\t\t\tv.edge.vertices.splice(index, 1)\n\t\t\tthis.edgeVertices.delete(v.id)\n\t\t\t\n\t\t\t// Mark that user explicitly deleted vertices from this edge\n\t\t\tv.edge.userDeletedVertices = true\n\t\t}\n\t\t\n\t\tthis.redrawEdge(v.edge)\n\t\tthis._undo.change()\n\t}\n\n\tchanged() {\n\t\treturn this._undo.changed()\n\t}\n\n\tundo() {\n\t\tthis._undo.undo()\n\t}\n\n\tredo() {\n\t\tthis._undo.redo()\n\t}\n\n\t// moves the entire graph to be aligned top-left of the drawing area\n\t// used to bring back to visible the nodes that end up at negative coordinates\n\talignTopLeft() {\n\t\tconst contentBounds = this.calculateContentBounds()\n\t\tconst padding = 100 // Reasonable padding for viewport\n\t\t\n\t\tconst offsetX = -contentBounds.x + padding\n\t\tconst offsetY = -contentBounds.y + padding\n\t\t\n\t\t// Set flag to prevent React useEffect from calling fitToView during this operation\n\t\tthis._skipAutoFit = true\n\t\t\n\t\tthis._undo.beforeChange()\n\t\t\n\t\tthis.nodesMap.forEach(node => {\n\t\t\tthis.moveNode(node, node.x + offsetX, node.y + offsetY, true, true) // Disable snap and undo during reset\n\t\t})\n\t\t\n\t\tthis.edgeVertices.forEach(vertex => {\n\t\t\tthis.moveEdgeVertex(vertex, vertex.x + offsetX, vertex.y + offsetY, true, true) // Disable snap and undo during reset\n\t\t})\n\t\t\n\t\tthis._undo.change()\n\t\t\n\t\t// DON'T clear view state here - let resetPanTransform handle it to avoid React useEffect recursion\n\t}\n\t\n\t// Reset pan transform to (0,0) while preserving zoom\n\tresetPanTransform() {\n\t\tconst currentZoom = getZoom()\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (zoomGroup) {\n\t\t\tzoomGroup.setAttribute('transform', `scale(${currentZoom}) translate(0, 0)`)\n\t\t\tupdatePanningOptimized(this)\n\t\t}\n\t\t\n\t\t// Clear view state so this reset is not overridden\n\t\tclearViewState(this.id)\n\t\t\n\t\t// Reset the skip auto fit flag after reset is complete\n\t\tthis._skipAutoFit = false\n\t}\n\t\n\t// Check if auto-fit should be skipped (used by React useEffect)\n\tshouldSkipAutoFit(): boolean {\n\t\treturn this._skipAutoFit\n\t}\n\n\t// Reset view to default state: 100% zoom, centered at origin\n\tresetView() {\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (zoomGroup) {\n\t\t\t// Reset to 100% zoom, centered at origin\n\t\t\tzoomGroup.setAttribute('transform', 'scale(1) translate(0, 0)')\n\t\t\tupdatePanning()\n\t\t}\n\t\t\n\t\t// Clear any saved view state so this reset position is not overridden\n\t\tclearViewState(this.id)\n\t}\n\n\t//redraw connected edges\n\tprivate redrawEdges(n: Node) {\n\t\tthis.edges.forEach(e => (n == e.from || n == e.to) && this.redrawEdge(e))\n\t\tthis.updateEdgesSel()\n\t}\n\n\tredrawEdge(e: Edge) {\n\t\tconst p = e.ref.parentElement;\n\t\tp.removeChild(e.ref)\n\t\te.ref = buildEdge(this, e)\n\t\tp.append(e.ref)\n\t}\n\n\tprivate redrawGroups(node: Node) {\n\t\tthis.groupsMap.forEach(group => {\n\t\t\t//if (group.nodes.indexOf(node) == -1) return\n\t\t\tconst p = group.ref.parentElement\n\t\t\tp.removeChild(group.ref)\n\t\t\tbuildGroup(group)\n\t\t\tp.append(group.ref)\n\t\t})\n\t}\n\n\texportSVG() {\n\t\t// Get the original SVG\n\t\tconst originalSvg: SVGSVGElement = document.querySelector('svg#graph')\n\t\tconst elastic = originalSvg.querySelector('rect.elastic')\n\t\t\n\t\t// Clone the SVG for export (completely separate from the live one)\n\t\tconst exportSvg = originalSvg.cloneNode(true) as SVGSVGElement\n\n\t\t// Standalone SVGs navigate to sibling view files instead of editor routes.\n\t\texportSvg.querySelectorAll('a.nodeLink[data-export-href]').forEach(link => {\n\t\t\tlink.setAttribute('href', link.getAttribute('data-export-href') || '')\n\t\t\tlink.removeAttribute('data-export-href')\n\t\t})\n\t\t\n\t\t// Remove elastic element from export\n\t\tconst exportElastic = exportSvg.querySelector('rect.elastic')\n\t\tif (exportElastic) {\n\t\t\texportElastic.remove()\n\t\t}\n\t\t\n\t\t// Calculate actual content bounds including all elements\n\t\tconst contentBounds = this.calculateContentBounds()\n\t\t\n\t\t// Add padding around content\n\t\tconst padding = 50\n\t\t\n\t\t// Calculate final export dimensions (always positive)\n\t\tconst exportWidth = contentBounds.width + (padding * 2)\n\t\tconst exportHeight = contentBounds.height + (padding * 2)\n\t\t\n\t\t// Calculate offset to move content to start at (padding, padding) within the export area\n\t\tconst offsetX = -contentBounds.x + padding\n\t\tconst offsetY = -contentBounds.y + padding\n\t\t\n\t\t// Apply export positioning to the cloned SVG elements\n\t\tconst exportZoomGroup = exportSvg.querySelector('g.zoom') as SVGGElement\n\t\tif (exportZoomGroup) {\n\t\t\t// Reset zoom to 1 and apply offset transform to position content properly\n\t\t\texportZoomGroup.setAttribute('transform', `scale(1) translate(${offsetX}, ${offsetY})`)\n\t\t}\n\t\t\n\t\t// Set proper viewBox and dimensions for export - viewBox always starts at (0,0)\n\t\texportSvg.setAttribute('viewBox', `0 0 ${exportWidth} ${exportHeight}`)\n\t\texportSvg.setAttribute('width', String(exportWidth))\n\t\texportSvg.setAttribute('height', String(exportHeight))\n\t\t\n\t\t// Add required SVG namespace for browser compatibility\n\t\texportSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')\n\t\t\n\t\t// Convert inline styles to CSS custom properties for theming support\n\t\tthis.convertStylesToCustomProperties(exportSvg)\n\t\t\n\t\t// Inject metadata with current layout\n\t\tconst script = document.createElement('script')\n\t\tscript.setAttribute('type', 'application/json')\n\t\tthis.metadata.layout = this.exportLayout()\n\t\tscript.append('')\n\t\texportSvg.insertBefore(script, exportSvg.firstChild)\n\t\t\n\t\t// Get the export SVG as string\n\t\tconst src = exportSvg.outerHTML\n\t\t\n\t\t// No restoration needed since we never touched the original SVG!\n\t\treturn src\n\t}\n\n\t// Convert inline fill/stroke attributes to CSS custom properties with fallbacks.\n\t// This enables theming: container pages can override colors via CSS variables.\n\tprivate convertStylesToCustomProperties(svg: SVGSVGElement) {\n\t\tif (this.colorToVarMap.size === 0) return\n\n\t\t// Process all elements with fill attribute\n\t\tsvg.querySelectorAll('[fill]').forEach(el => {\n\t\t\tconst fill = el.getAttribute('fill')\n\t\t\tif (fill && this.colorToVarMap.has(fill)) {\n\t\t\t\tel.setAttribute('fill', `var(${this.colorToVarMap.get(fill)}, ${fill})`)\n\t\t\t}\n\t\t})\n\n\t\t// Process all elements with stroke attribute\n\t\tsvg.querySelectorAll('[stroke]').forEach(el => {\n\t\t\tconst stroke = el.getAttribute('stroke')\n\t\t\tif (stroke && this.colorToVarMap.has(stroke)) {\n\t\t\t\tel.setAttribute('stroke', `var(${this.colorToVarMap.get(stroke)}, ${stroke})`)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Calculate the actual bounds of all content including nodes, edges, and groups\n\tcalculateContentBounds(): BBox {\n\t\tlet minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n\t\t\n\t\t// Process nodes (including their actual dimensions)\n\t\tthis.nodes().forEach(node => {\n\t\t\tconst left = node.x - node.width / 2\n\t\t\tconst right = node.x + node.width / 2\n\t\t\tconst top = node.y - node.height / 2\n\t\t\tconst bottom = node.y + node.height / 2\n\t\t\t\n\t\t\tminX = Math.min(minX, left)\n\t\t\tmaxX = Math.max(maxX, right)\n\t\t\tminY = Math.min(minY, top)\n\t\t\tmaxY = Math.max(maxY, bottom)\n\t\t})\n\t\t\n\t\t// Process edge vertices (much faster than complex label calculations)\n\t\tthis.edgeVertices.forEach(vertex => {\n\t\t\tminX = Math.min(minX, vertex.x - 5)\n\t\t\tmaxX = Math.max(maxX, vertex.x + 5)\n\t\t\tminY = Math.min(minY, vertex.y - 5)\n\t\t\tmaxY = Math.max(maxY, vertex.y + 5)\n\t\t})\n\t\t\n\t\t// Process groups\n\t\tthis.groupsMap.forEach(group => {\n\t\t\tconst left = group.x - group.width / 2\n\t\t\tconst right = group.x + group.width / 2\n\t\t\tconst top = group.y - group.height / 2\n\t\t\tconst bottom = group.y + group.height / 2\n\t\t\t\n\t\t\tminX = Math.min(minX, left)\n\t\t\tmaxX = Math.max(maxX, right)\n\t\t\tminY = Math.min(minY, top)\n\t\t\tmaxY = Math.max(maxY, bottom)\n\t\t})\n\t\t\n\t\t// Process edges (simplified - just endpoints and vertices, skip complex label calculations)\n\t\tthis.edges.forEach(edge => {\n\t\t\t// Edge endpoints\n\t\t\tminX = Math.min(minX, edge.from.x - 10, edge.to.x - 10)\n\t\t\tmaxX = Math.max(maxX, edge.from.x + 10, edge.to.x + 10)\n\t\t\tminY = Math.min(minY, edge.from.y - 10, edge.to.y - 10)\n\t\t\tmaxY = Math.max(maxY, edge.from.y + 10, edge.to.y + 10)\n\t\t\t\n\t\t\t// Edge vertices (if any)\n\t\t\tif (edge.vertices) {\n\t\t\t\tedge.vertices.forEach(vertex => {\n\t\t\t\t\tminX = Math.min(minX, vertex.x - 10)\n\t\t\t\t\tmaxX = Math.max(maxX, vertex.x + 10)\n\t\t\t\t\tminY = Math.min(minY, vertex.y - 10)\n\t\t\t\t\tmaxY = Math.max(maxY, vertex.y + 10)\n\t\t\t\t})\n\t\t\t}\n\t\t\t\n\t\t\t// Simplified label bounds (avoid expensive path calculations)\n\t\t\tif (edge.label && edge.label.trim()) {\n\t\t\t\t// Just use approximate center between from and to nodes\n\t\t\t\tconst centerX = (edge.from.x + edge.to.x) / 2\n\t\t\t\tconst centerY = (edge.from.y + edge.to.y) / 2\n\t\t\t\tconst approxLabelSize = edge.label.length * 10 + 50 // Rough estimate\n\t\t\t\t\n\t\t\t\tminX = Math.min(minX, centerX - approxLabelSize)\n\t\t\t\tmaxX = Math.max(maxX, centerX + approxLabelSize)\n\t\t\t\tminY = Math.min(minY, centerY - 25)\n\t\t\t\tmaxY = Math.max(maxY, centerY + 25)\n\t\t\t}\n\t\t})\n\t\t\n\t\t// Handle empty graph\n\t\tif (minX === Infinity) {\n\t\t\treturn { x: 0, y: 0, width: 100, height: 100 }\n\t\t}\n\t\t\n\t\treturn {\n\t\t\tx: minX,\n\t\t\ty: minY,\n\t\t\twidth: maxX - minX,\n\t\t\theight: maxY - minY\n\t\t}\n\t}\n\n\n\n\t/**\n\t * @param full when true, the edges without vertices are saved too, used for undo buffer\n\t * for saving, full is false\n\t */\n\texportLayout(full = false) {\n\t\tconst ret: Layout = {}\n\t\tthis.nodes().forEach(n => ret[n.id] = {x: n.x, y: n.y})\n\t\tthis.edges.forEach(e => {\n\t\t\tif (!e.vertices) return\n\t\t\t// Save all vertices (both user and auto-generated), preserving their properties\n\t\t\tconst lst = e.vertices.map(v => ({\n\t\t\t\tx: v.x, \n\t\t\t\ty: v.y, \n\t\t\t\tlabel: v.label,\n\t\t\t\tauto: v.auto // Preserve auto flag so we know which are ELK-generated\n\t\t\t}));\n\t\t\tif (lst.length || full) {\n\t\t\t\tret[`e-${e.id}`] = lst\n\t\t\t}\n\t\t\t// Also save the userDeletedVertices flag as metadata\n\t\t\tif (e.userDeletedVertices) {\n\t\t\t\tret[`e-${e.id}-deleted`] = true\n\t\t\t}\n\t\t})\n\t\treturn ret\n\t}\n\n\tsetSaved() {\n\t\tthis._undo.setSaved()\n\t}\n\n\timportLayout(layout: { [key: string]: any }, rerender = false) {\n\t\t// First pass: collect all coordinate values to find bounds\n\t\tconst coordinates: Array<{x: number, y: number}> = [];\n\t\t\n\t\tObject.entries(layout).forEach(([k, v]) => {\n\t\t\tif (!k.startsWith('e-') && v.x !== undefined && v.y !== undefined) {\n\t\t\t\t// Node coordinates\n\t\t\t\tcoordinates.push({x: v.x, y: v.y});\n\t\t\t} else if (k.startsWith('e-') && Array.isArray(v)) {\n\t\t\t\t// Edge vertex coordinates\n\t\t\t\tv.forEach((vertex: any) => {\n\t\t\t\t\tif (vertex.x !== undefined && vertex.y !== undefined) {\n\t\t\t\t\t\tcoordinates.push({x: vertex.x, y: vertex.y});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\t\n\t\t// Calculate normalization offset if we have coordinates\n\t\tlet offsetX = 0;\n\t\tlet offsetY = 0;\n\t\t\n\t\tif (coordinates.length > 0) {\n\t\t\tconst minX = Math.min(...coordinates.map(c => c.x));\n\t\t\tconst minY = Math.min(...coordinates.map(c => c.y));\n\t\t\t\n\t\t\t// Only normalize if coordinates are problematic (negative or very large)\n\t\t\tif (minX < -100 || minY < -100 || Math.max(...coordinates.map(c => c.x)) > 3000 || Math.max(...coordinates.map(c => c.y)) > 2000) {\n\t\t\t\tconst padding = 50;\n\t\t\t\toffsetX = -minX + padding;\n\t\t\t\toffsetY = -minY + padding;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Second pass: apply coordinates with normalization\n\t\tObject.entries(layout).forEach(([k, v]) => {\n\t\t\t// nodes\n\t\t\tconst n = this.nodesMap.get(k)\n\t\t\tif (n) {\n\t\t\t\tn.x = v.x + offsetX\n\t\t\t\tn.y = v.y + offsetY\n\t\t\t} else\n\t\t\t\t// edge vertices\n\t\t\tif (k.startsWith('e-') && !k.endsWith('-deleted')) {\n\t\t\t\tconst edge = this.edges.find(e => e.id == k.slice(2))\n\t\t\t\tif (!edge) return;\n\t\t\t\tedge.vertices && edge.vertices.forEach(v => this.edgeVertices.delete(v.id))\n\t\t\t\tedge.vertices = v.map((p: Point) => {\n\t\t\t\t\tconst normalizedPoint = { \n\t\t\t\t\t\tx: p.x + offsetX, \n\t\t\t\t\t\ty: p.y + offsetY \n\t\t\t\t\t} as Point;\n\t\t\t\t\t// Preserve any additional properties like 'label' and 'auto'\n\t\t\t\t\tObject.assign(normalizedPoint, p, { x: p.x + offsetX, y: p.y + offsetY });\n\t\t\t\t\tconst vertex = edge.initVertex(normalizedPoint);\n\t\t\t\t\t// Ensure auto flag is preserved after initVertex\n\t\t\t\t\tif ((p as any).auto) {\n\t\t\t\t\t\tvertex.auto = true;\n\t\t\t\t\t}\n\t\t\t\t\treturn vertex;\n\t\t\t\t})\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (k.endsWith('-deleted')) {\n\t\t\t\tconst edgeId = k.slice(2, -8); // Remove 'e-' prefix and '-deleted' suffix\n\t\t\t\tconst edge = this.edges.find(e => e.id == edgeId)\n\t\t\t\tif (edge && v === true) {\n\t\t\t\t\tedge.userDeletedVertices = true\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t})\n\t\tif (rerender) {\n\t\t\tthis.nodes().forEach(n => setPosition(n.ref, n.x, n.y))\n\t\t\tthis.edges.forEach(e => this.redrawEdge(e))\n\t\t\tthis.updateEdgesSel()\n\t\t\tthis.redrawGroups(null)\n\t\t}\n\t}\n\n\tasync autoLayout(options?: import('./layout').LayoutOptions) {\n\t\ttry {\n\t\t\tconst auto = await autoLayout(this, options)\n\t\t\t\n\t\t\tthis._undo.beforeChange()\n\t\t\t\n\t\t\t// Apply node positions\n\t\t\tauto.nodes.forEach(an => {\n\t\t\t\tconst n = this.nodesMap.get(an.id)\n\t\t\t\tif (n) {\n\t\t\t\t\tthis.moveNode(n, an.x, an.y, false, true) // Skip undo for individual moves\n\t\t\t\t}\n\t\t\t})\n\t\t\t\n\t\t\t// Apply edge routing from ELK layout\n\t\t\tauto.edges.forEach(ae => {\n\t\t\t\tconst edge = this.edges.find(e => e.id == ae.id)\n\t\t\t\tif (edge) {\n\t\t\t\t\t// Clear existing vertices for this edge only\n\t\t\t\t\tif (edge.vertices) {\n\t\t\t\t\t\tedge.vertices.forEach(v => {\n\t\t\t\t\t\t\tif (v.id) {\n\t\t\t\t\t\t\t\tthis.edgeVertices.delete(v.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tedge.vertices = []\n\t\t\t\t\tedge.userDeletedVertices = false\n\t\t\t\t\t\n\t\t\t\t\t// Add routing vertices from ELK (these are proper bend points, not nodes)\n\t\t\t\t\tif (ae.vertices && ae.vertices.length > 0) {\n\t\t\t\t\t\tedge.vertices = ae.vertices.map(p => {\n\t\t\t\t\t\t\tconst vertex = edge.initVertex(p)\n\t\t\t\t\t\t\tvertex.auto = true // Mark as auto-generated\n\t\t\t\t\t\t\treturn vertex\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Handle edge label positioning - create proper interactive label vertices\n\t\t\t\t\tif (ae.label) {\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Remove any existing label vertices (ELK or user-created)\n\t\t\t\t\t\tif (edge.vertices) {\n\t\t\t\t\t\t\tedge.vertices.forEach(v => {\n\t\t\t\t\t\t\t\tif (v.label) {\n\t\t\t\t\t\t\t\t\tthis.edgeVertices.delete(v.id)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tedge.vertices = edge.vertices.filter(v => !v.label)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Create a proper label vertex that behaves like a user-created vertex\n\t\t\t\t\t\tconst labelVertex = edge.initVertex(ae.label)\n\t\t\t\t\t\tlabelVertex.label = true\n\t\t\t\t\t\tlabelVertex.auto = true // Mark as auto-generated so it can be cleaned up\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Insert label vertex at the optimal position in the routing path\n\t\t\t\t\t\t// Find the best position to insert it and project it onto that line segment\n\t\t\t\t\t\tedge.vertices = edge.vertices || []\n\t\t\t\t\t\tconst insertPos = findOptimalLabelPosition(edge.vertices, ae.label, edge.from, edge.to)\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Project the label position onto the line segment where it will be inserted\n\t\t\t\t\t\tconst projectedPos = projectLabelOntoSegment(edge.vertices, ae.label, insertPos, edge.from, edge.to)\n\t\t\t\t\t\tlabelVertex.x = projectedPos.x\n\t\t\t\t\t\tlabelVertex.y = projectedPos.y\n\t\t\t\t\t\t\n\t\t\t\t\t\tedge.vertices.splice(insertPos, 0, labelVertex)\n\t\t\t\t\t\tthis.edgeVertices.set(labelVertex.id, labelVertex)\n\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Redraw the edge with new routing\n\t\t\t\t\tthis.redrawEdge(edge)\n\t\t\t\t}\n\t\t\t})\n\t\t\t\n\t\t\t// Fit the layout to the viewport with optimal positioning\n\t\t\tthis.fitToView()\n\t\t\t\n\t\t\tthis._undo.change()\n\t\t\t\n\t\t} catch (error) {\n\t\t\tconsole.error('Auto layout failed:', error)\n\t\t\t// Could show user notification here\n\t\t}\n\t}\n\n\talignSelectionV() {\n\t\tconst lst: Point[] = this.nodes().filter(n => n.selected)\n\t\tlst.push(...Array.from(this.edgeVertices.values()).filter(v => v.selected))\n\t\tlet minY = Math.min(...lst.map(p => p.y))\n\t\tthis.nodesMap.forEach(n => n.selected && this.moveNode(n, n.x, minY, false, false))\n\t\tthis.edgeVertices.forEach(v => v.selected && this.moveEdgeVertex(v, v.x, minY, false, false))\n\t}\n\n\talignSelectionH() {\n\t\tconst lst: Point[] = this.nodes().filter(n => n.selected)\n\t\tlst.push(...Array.from(this.edgeVertices.values()).filter(v => v.selected))\n\t\tlet minX = Math.min(...lst.map(p => p.x))\n\t\tthis.nodesMap.forEach(n => n.selected && this.moveNode(n, minX, n.y, false, false))\n\t\tthis.edgeVertices.forEach(v => v.selected && this.moveEdgeVertex(v, minX, v.y, false, false))\n\t}\n\n\tdistributeSelectionH() {\n\t\tconst selectedNodes = this.nodes().filter(n => n.selected)\n\t\tconst selectedVertices = Array.from(this.edgeVertices.values()).filter(v => v.selected)\n\t\t\n\t\tif (selectedNodes.length + selectedVertices.length < 3) return // Need at least 3 elements to distribute\n\t\t\n\t\tthis._undo.beforeChange()\n\t\t\n\t\t// Combine and sort by X coordinate\n\t\tconst allElements = [...selectedNodes, ...selectedVertices]\n\t\tallElements.sort((a, b) => a.x - b.x)\n\t\t\n\t\tconst minX = allElements[0].x\n\t\tconst maxX = allElements[allElements.length - 1].x\n\t\tconst spacing = (maxX - minX) / (allElements.length - 1)\n\t\t\n\t\t// Distribute elements evenly between leftmost and rightmost\n\t\tallElements.forEach((element, index) => {\n\t\t\tconst newX = minX + (index * spacing)\n\t\t\tif ('title' in element) {\n\t\t\t\t// It's a Node\n\t\t\t\tthis.moveNode(element as Node, newX, element.y, false, true)\n\t\t\t} else {\n\t\t\t\t// It's an EdgeVertex\n\t\t\t\tthis.moveEdgeVertex(element as EdgeVertex, newX, element.y, false, true)\n\t\t\t}\n\t\t})\n\t\t\n\t\tthis._undo.change()\n\t}\n\n\tdistributeSelectionV() {\n\t\tconst selectedNodes = this.nodes().filter(n => n.selected)\n\t\tconst selectedVertices = Array.from(this.edgeVertices.values()).filter(v => v.selected)\n\t\t\n\t\tif (selectedNodes.length + selectedVertices.length < 3) return // Need at least 3 elements to distribute\n\t\t\n\t\tthis._undo.beforeChange()\n\t\t\n\t\t// Combine and sort by Y coordinate\n\t\tconst allElements = [...selectedNodes, ...selectedVertices]\n\t\tallElements.sort((a, b) => a.y - b.y)\n\t\t\n\t\tconst minY = allElements[0].y\n\t\tconst maxY = allElements[allElements.length - 1].y\n\t\tconst spacing = (maxY - minY) / (allElements.length - 1)\n\t\t\n\t\t// Distribute elements evenly between topmost and bottommost\n\t\tallElements.forEach((element, index) => {\n\t\t\tconst newY = minY + (index * spacing)\n\t\t\tif ('title' in element) {\n\t\t\t\t// It's a Node\n\t\t\t\tthis.moveNode(element as Node, element.x, newY, false, true)\n\t\t\t} else {\n\t\t\t\t// It's an EdgeVertex\n\t\t\t\tthis.moveEdgeVertex(element as EdgeVertex, element.x, newY, false, true)\n\t\t\t}\n\t\t})\n\t\t\n\t\tthis._undo.change()\n\t}\n\n\t// Set edge selection state\n\tsetEdgeSelected(edge: Edge, selected: boolean) {\n\t\t// Mark the edge as selected by selecting its connected nodes\n\t\tif (selected) {\n\t\t\tthis.setNodeSelected(edge.from, true)\n\t\t\tthis.setNodeSelected(edge.to, true)\n\t\t}\n\t\t// Update visual selection\n\t\tthis.updateEdgesSel()\n\t}\n\n\t// Fit the entire graph to the current viewport\n\tfitToView() {\n\t\tconst contentBounds = this.calculateContentBounds()\n\t\t\n\t\t// Handle edge case where there's no content\n\t\tif (contentBounds.width === 0 || contentBounds.height === 0) {\n\t\t\treturn\n\t\t}\n\t\t\n\t\t// Get viewport dimensions\n\t\tconst viewportWidth = svg.parentElement?.clientWidth || 800\n\t\tconst viewportHeight = svg.parentElement?.clientHeight || 600\n\t\t\n\t\t// Add padding around content\n\t\tconst padding = 40\n\t\t\n\t\t// Calculate zoom to fit content with padding\n\t\tconst zoomX = (viewportWidth - padding * 2) / contentBounds.width\n\t\tconst zoomY = (viewportHeight - padding * 2) / contentBounds.height\n\t\tconst optimalZoom = Math.min(zoomX, zoomY)\n\t\t\n\t\t// Clamp zoom between reasonable bounds\n\t\tconst finalZoom = Math.max(Math.min(optimalZoom, 2), 0.1)\n\t\t\n\t\t// Calculate content center in drawing coordinates\n\t\tconst contentCenterX = contentBounds.x + contentBounds.width / 2\n\t\tconst contentCenterY = contentBounds.y + contentBounds.height / 2\n\t\t\n\t\t// Calculate viewport center in screen coordinates\n\t\tconst viewportCenterX = viewportWidth / 2\n\t\tconst viewportCenterY = viewportHeight / 2\n\t\t\n\t\t// Calculate translation needed to center content in viewport\n\t\t// With translate(x,y) scale(zoom), translation is in screen coordinates\n\t\tconst translateX = viewportCenterX - (contentCenterX * finalZoom)\n\t\tconst translateY = viewportCenterY - (contentCenterY * finalZoom)\n\t\t\n\t\t// Apply zoom and translation transform\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (zoomGroup) {\n\t\t\tzoomGroup.setAttribute('transform', `translate(${translateX}, ${translateY}) scale(${finalZoom})`)\n\t\t}\n\t\t\n\t\t// Update panning\n\t\tupdatePanning()\n\t\t\n\t\t// Save view state so this fit position is preserved after reload\n\t\tsaveViewState(this.id)\n\t}\n\n\t// Save current layout state for restoration\n\tprivate saveLayoutState(): Layout {\n\t\treturn this.exportLayout(true) // Include all vertices for complete state\n\t}\n\n\t// Restore layout state\n\tprivate restoreLayoutState(state: Layout) {\n\t\tthis.importLayout(state, true) // Rerender after restoring\n\t}\n\n\t// Grid functionality\n\tisGridVisible(): boolean {\n\t\treturn this._gridVisible;\n\t}\n\n\tisSnapToGrid(): boolean {\n\t\treturn this._snapToGrid;\n\t}\n\n\tgetGridSize(): number {\n\t\treturn this._gridSize;\n\t}\n\n\ttoggleGrid() {\n\t\tthis._gridVisible = !this._gridVisible;\n\t\tthis.updateGridDisplay();\n\t\t// Force toolbar update by dispatching a custom event\n\t\twindow.dispatchEvent(new CustomEvent('gridStateChanged'));\n\t}\n\n\ttoggleSnapToGrid() {\n\t\tthis._snapToGrid = !this._snapToGrid;\n\t\t// Force toolbar update by dispatching a custom event\n\t\twindow.dispatchEvent(new CustomEvent('gridStateChanged'));\n\t}\n\n\tsnapAllToGrid() {\n\t\tif (!this._snapToGrid) return;\n\t\t\n\t\tthis._undo.beforeChange();\n\t\tthis.nodes().forEach(node => {\n\t\t\tconst snappedX = Math.round(node.x / this._gridSize) * this._gridSize;\n\t\t\tconst snappedY = Math.round(node.y / this._gridSize) * this._gridSize;\n\t\t\tthis.moveNode(node, snappedX, snappedY, false, true);\n\t\t});\n\t\tthis._undo.change();\n\t}\n\n\t// Helper method to snap a point to grid\n\tprivate snapToGrid(x: number, y: number): { x: number, y: number } {\n\t\treturn {\n\t\t\tx: Math.round(x / this._gridSize) * this._gridSize,\n\t\t\ty: Math.round(y / this._gridSize) * this._gridSize\n\t\t};\n\t}\n\n\tupdateGridDisplay() {\n\t\tif (!svg) return;\n\n\t\t// Remove existing grid pattern and background\n\t\tconst existingGrid = svg.querySelector('#grid-pattern');\n\t\tif (existingGrid) {\n\t\t\texistingGrid.remove();\n\t\t}\n\n\t\tconst existingGridRect = svg.querySelector('#grid-background');\n\t\tif (existingGridRect) {\n\t\t\texistingGridRect.remove();\n\t\t}\n\n\t\tif (!this._gridVisible) return;\n\n\t\t// Create grid pattern in defs\n\t\tlet defs = svg.querySelector('defs');\n\t\tif (!defs) {\n\t\t\tdefs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');\n\t\t\tsvg.insertBefore(defs, svg.firstChild);\n\t\t}\n\n\t\tconst pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');\n\t\tpattern.id = 'grid-pattern';\n\t\tpattern.setAttribute('width', this._gridSize.toString());\n\t\tpattern.setAttribute('height', this._gridSize.toString());\n\t\tpattern.setAttribute('patternUnits', 'userSpaceOnUse');\n\n\t\tconst path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n\t\tpath.setAttribute('d', `M ${this._gridSize} 0 L 0 0 0 ${this._gridSize}`);\n\t\tpath.setAttribute('fill', 'none');\n\t\tpath.setAttribute('stroke', '#d0d0d0');\n\t\tpath.setAttribute('stroke-width', '1');\n\t\tpath.setAttribute('opacity', '0.8');\n\n\t\tpattern.appendChild(path);\n\t\tdefs.appendChild(pattern);\n\n\t\t// Create grid background rectangle\n\t\tconst rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n\t\trect.id = 'grid-background';\n\t\trect.setAttribute('x', '-10000');\n\t\trect.setAttribute('y', '-10000');\n\t\trect.setAttribute('width', '20000');\n\t\trect.setAttribute('height', '20000');\n\t\trect.setAttribute('fill', 'url(#grid-pattern)');\n\t\trect.setAttribute('pointer-events', 'none');\n\n\t\t// Insert grid as first child of zoom group so it transforms with content\n\t\tconst zoomGroup = svg.querySelector('g.zoom');\n\t\tif (zoomGroup) {\n\t\t\tzoomGroup.insertBefore(rect, zoomGroup.firstChild);\n\t\t}\n\t}\n}\n\nfunction escapeCdata(code: string) {\n\treturn code.replace(/]]>/g, ']]]>]> clickListener(e))\n\t// addCursorInteraction(svg) // Call will be updated in buildGraph\n}\nsvg.setAttribute('width', '100%')\nsvg.setAttribute('height', '100%')\n\nlet clickListener: (e: MouseEvent) => void\nlet dragging = false;\nlet selectListener: (n: Node) => void\n\n\nexport const buildGraph = (data: GraphData, onNodeSelect: (n: Node) => void, dragMode: 'pan' | 'select') => {\n\t// empty svg\n\tsvg.innerHTML = defs\n\tdocument.body.append(svg) // make sure svg element is connected, we will measure texts sizes\n\t// @ts-ignore\n\tsvg.__data = data\n\n\tselectListener = onNodeSelect\n\n\t//use event delegation\n\tclickListener = e => {\n\t\tif (dragging) {\n\t\t\treturn;\n\t\t}\n\t\t// const el = (e.target as any).closest('.node > .expand');\n\t}\n\n\t_buildGraph(data)\n\tconst elasticEl = create.rect(300, 300, 50, 50, 0, 'elastic')\n\tsvg.append(elasticEl)\n\n\t// Initialize grid display now that the zoom group exists\n\tdata.updateGridDisplay()\n\n\t// Call addCursorInteraction with dragMode\n\taddCursorInteraction(svg, dragMode)\n\n\treturn {\n\t\tsvg,\n\t\tsetZoom,\n\t}\n}\n\nexport const buildGraphView = (data: GraphData) => {\n\tsvg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n\tsvg.setAttribute('id', 'graph')\n\t_buildGraph(data)\n\treturn svg\n}\n\nconst _buildGraph = (data: GraphData) => {\n\t//toplevel groups\n\tconst zoomG = create.element('g', {}, 'zoom') as SVGGElement\n\tconst nodesG = create.element('g', {}, 'nodes') as SVGGElement\n\tconst edgesG = create.element('g', {}, 'edges') as SVGGElement\n\tconst groupsG = create.element('g', {}, 'groups') as SVGGElement\n\tzoomG.append(groupsG, edgesG, nodesG)\n\n\n\tdata.nodesMap.forEach((n) => {\n\t\tbuildNode(n, data)\n\t\tnodesG.append(n.ref)\n\t})\n\n\tdata.edges.forEach(e => {\n\t\tbuildEdge(data, e)\n\t\tedgesG.append(e.ref)\n\t})\n\n\tdata.groupsMap.forEach((group) => {\n\t\tbuildGroup(group)\n\t\tgroupsG.append(group.ref)\n\t})\n\n\tsvg.append(zoomG)\n}\n\nfunction buildEdge(data: GraphData, edge: Edge) {\n\tconst n1 = edge.from, n2 = edge.to;\n\n\tconst g = create.element('g', {}, 'edge') as SVGGElement\n\tg.setAttribute('id', edge.id)\n\tg.setAttribute('data-from', edge.from.id)\n\tg.setAttribute('data-to', edge.to.id)\n\n\tconst position = (edge.style.position || 50) / 100\n\n\t// Calculate edge vertices using utility function\n\tconst vertices = calculateEdgeVertices(edge, data)\n\n\tconst labelPlacement = calculateLabelPlacement(vertices, position, n1)\n\n\tconst {bg, txt, bbox} = buildEdgeLabel(labelPlacement, edge)\n\tg.append(bg, txt)\n\n\t// Create edge segments and path using utility function\n\tconst {segments, path} = createEdgeSegments(vertices, bbox, n1, n2)\n\n\tconst p = create.path(path, {'marker-end': 'url(#arrow)'}, 'edge')\n\tp.setAttribute('fill', 'none')\n\tp.setAttribute('stroke', edge.style.color)\n\tp.setAttribute('stroke-width', String(edge.style.thickness))\n\tp.setAttribute('stroke-linecap', 'round')\n\tedge.style.dashed && p.setAttribute('stroke-dasharray', '8')\n\tg.append(p)\n\t\n\t// Debug visualization removed - arrow issue fixed\n\n\t// drag handlers\n\tedge.vertices = vertices.slice(1, -1).map(p => {\n\t\t// Preserve existing EdgeVertex objects to maintain IDs and selection state\n\t\tif ('id' in p && 'edge' in p) {\n\t\t\t// This is already an EdgeVertex, preserve it\n\t\t\tconst v = p as EdgeVertex;\n\t\t\tv.edge = edge; // Ensure edge reference is correct\n\t\t\treturn v;\n\t\t} else {\n\t\t\t// This is a new Point, convert to EdgeVertex\n\t\t\treturn edge.initVertex(p);\n\t\t}\n\t})\n\tedge.vertices.forEach((p, i) => {\n\t\tconst v = p as EdgeVertex\n\t\tv.ref = create.element('circle', {id: v.id, cx: p.x, cy: p.y, r: 7, fill: 'none'}, 'v-dot')\n\t\tv.selected && v.ref.classList.add('selected')\n\t\tv.auto && v.ref.classList.add('auto')\n\t\tg.append(v.ref)\n\t})\n\n\tedge.ref = g\n\treturn g\n}\n\nfunction buildEdgeLabel(placement: EdgeLabelPlacement, edge: Edge) {\n\tconst labelGap = 12;\n\tconst fontSize = edge.style.fontSize\n\tlet {txt, dy, maxW} = create.textArea(edge.label, 200, fontSize, false, placement.x, placement.y, 'middle')\n\tdy -= fontSize / 2\n\tmaxW += fontSize\n\n\tconst centerX = placement.orientation === 'vertical'\n\t\t? placement.x + maxW / 2 + labelGap\n\t\t: placement.x\n\tconst centerY = placement.orientation === 'vertical'\n\t\t? placement.y\n\t\t: placement.y - dy / 2 - labelGap\n\ttxt.querySelectorAll('tspan').forEach((span: SVGTSpanElement) => {\n\t\tspan.setAttribute('x', String(centerX))\n\t})\n\ttxt.setAttribute('y', String(centerY - dy / 2))\n\n\tapplyStyle(txt, styles.edgeText)\n\ttxt.setAttribute('stroke', 'none')\n\ttxt.setAttribute('font-size', String(edge.style.fontSize))\n\ttxt.setAttribute('fill', edge.style.color)\n\n\tconst bbox = {x: centerX - maxW / 2, y: centerY - dy / 2, width: maxW, height: dy}\n\tconst bg = create.rect(bbox.width, bbox.height, bbox.x, bbox.y)\n\tapplyStyle(bg, styles.edgeRect)\n\ttxt.setAttribute('data-field', 'label')\n\n\tbbox.x += bbox.width / 2\n\tbbox.y += bbox.height / 2\n\treturn {bg, txt, bbox}\n}\n\n\nfunction buildNode(n: Node, data: GraphData) {\n\t// @ts-ignore\n\twindow.gdata = data\n\n\tconst g = create.element('g', {}, 'node') as SVGGElement\n\tg.setAttribute('id', n.id)\n\tn.selected && g.classList.add('selected')\n\tsetPosition(g, n.x, n.y)\n\tconst link = n.link\n\t\t? create.element('a', {\n\t\t\thref: n.link.href,\n\t\t\t'data-export-href': n.link.exportHref,\n\t\t\t'aria-label': `Open ${n.title}`,\n\t\t}, 'nodeLink') as SVGAElement\n\t\t: null\n\tconst content = link || g\n\tif (link) {\n\t\tg.classList.add('linked')\n\t\tg.append(link)\n\t}\n\n\t// Ensure we use the correct shape from style, defaulting to Box\n\tconst shapeType = n.style.shape || 'Box';\n\tconst shapeFn = shapes[shapeType.toLowerCase()] || shapes.box\n\tconst shape: SVGElement = shapeFn(content, n);\n\n\tshape.classList.add('nodeBorder')\n\n\t// Apply generic styles first\n\tapplyStyle(shape, styles.nodeBorder)\n\t// Then apply tag-specific styles to override generic ones\n\tshape.setAttribute('fill', n.style.background)\n\tshape.setAttribute('stroke', n.style.stroke)\n\t// Consistent border width for all elements\n\tshape.setAttribute('stroke-width', '3')\n\tshape.setAttribute('opacity', String(n.style.opacity))\n\tsetBorderStyle(shape, n.style.border)\n\n\tconst tg = buildNodeContent(n.contentLayout, n.style.color)\n\tconst labelOffsetY = Number(content.getAttribute('label-offset-y')) || 0\n\tsetPosition(tg, 0, labelOffsetY / 2)\n\tcontent.append(tg)\n\n\t// @ts-ignore\n\tg.__data = n;\n\tn.ref = g;\n\n\treturn g\n}\n\n\nfunction buildGroup(group: Group) {\n\tif (group.nodes.length == 0) {\n\t\treturn\n\t}\n\tconst g = create.element('g', {}, 'group') as SVGGElement\n\n\tlet p0: Point = {x: 1e100, y: 1e100}, p1: Point = {x: 0, y: 0}\n\tgroup.nodes.forEach(n => {\n\t\t// Calculate visual bounds accounting for shapes that extend beyond center\n\t\tconst shape = (n as Node).style?.shape?.toLowerCase() || 'box'\n\t\t\n\t\tlet topExtension = n.height / 2\n\t\tlet bottomExtension = n.height / 2\n\t\t\n\t\tif (shape === 'robot') {\n\t\t\t// Robot shape extends above with antenna\n\t\t\t// Account for antenna height (h * 0.08) plus some margin\n\t\t\tconst antennaH = n.height * 0.12\n\t\t\ttopExtension = n.height / 2 + antennaH\n\t\t\tbottomExtension = n.height / 2\n\t\t} else if (shape === 'hexagon') {\n\t\t\t// Hexagon extends to ±0.866 * (width/2) vertically\n\t\t\t// For width=280, that's ±121.24px from center\n\t\t\tconst hexHeight = n.width / 2 * 0.866\n\t\t\ttopExtension = hexHeight\n\t\t\tbottomExtension = hexHeight\n\t\t}\n\t\t\n\t\tconst b = {\n\t\t\tx: n.x - n.width / 2,\n\t\t\ty: n.y - topExtension,\n\t\t\twidth: n.width,\n\t\t\theight: topExtension + bottomExtension\n\t\t}\n\t\tp0.x = Math.min(p0.x, b.x)\n\t\tp0.y = Math.min(p0.y, b.y)\n\t\tp1.x = Math.max(p1.x, b.x + b.width)\n\t\tp1.y = Math.max(p1.y, b.y + b.height)\n\t})\n\tconst pad = 25 // Padding around content\n\tconst labelHeight = 30 // Space for the group label at bottom\n\tconst w = Math.max(p1.x - p0.x, 200)\n\tconst h = p1.y - p0.y\n\tconst bb = {\n\t\tx: p0.x - pad,\n\t\ty: p0.y - pad,\n\t\twidth: w + pad * 2,\n\t\theight: h + pad * 2 + labelHeight,\n\t}\n\tconst r = create.rect(bb.width, bb.height, bb.x, bb.y)\n\tgroup.x = bb.x + bb.width / 2\n\tgroup.y = bb.y + bb.height / 2\n\tgroup.width = bb.width\n\tgroup.height = bb.height\n\tapplyStyle(r, styles.groupRect)\n\tgroup.style.stroke && r.setAttribute('stroke', group.style.stroke)\n\tgroup.style.background && r.setAttribute('fill', group.style.background)\n\n\tconst txt = create.text(group.name, {x: p0.x, y: bb.y + bb.height - styles.groupText[\"font-size\"]})\n\tapplyStyle(txt, styles.groupText)\n\tgroup.style.color && txt.setAttribute('fill', group.style.color)\n\n\tg.append(r, txt)\n\tgroup.ref = g\n}\n\nfunction findClosestSegment(graph: GraphData, p: Point) {\n\t// find the closest point on a segment\n\tlet fnd = {dst: Number.POSITIVE_INFINITY, pos: -1, edge: null as Edge, prj: null as Point}\n\tgraph.edges.forEach(edge => {\n\t\tconst vertices = edge.vertices || []\n\t\tconst pts = [edge.from, ...vertices, edge.to]\n\t\tfor (let i = 1; i < pts.length; i++) {\n\t\t\tconst prj = project(p, pts[i - 1], pts[i])\n\t\t\tconst dst = cabDistance(p, prj)\n\t\t\tif (dst > 50) continue\n\t\t\tif (dst < fnd.dst) {\n\t\t\t\tfnd = {dst, pos: i, prj, edge}\n\t\t\t}\n\t\t}\n\t})\n\treturn fnd.edge ? fnd : null\n}\n\nfunction mouseToDrawing(e: MouseEvent): Point {\n\t// transform event coords to drawing coords\n\tconst b = svg.getBoundingClientRect()\n\tconst z = getZoom()\n\t\n\t// Get current pan transform\n\tconst currentTransform = getCurrentTransform()\n\t\n\t// Convert screen coordinates to drawing coordinates accounting for zoom and pan\n\t// The transform values are in screen coordinates, so we need to divide by zoom and subtract\n\treturn {\n\t\tx: (e.clientX - b.x - currentTransform.x) / z,\n\t\ty: (e.clientY - b.y - currentTransform.y) / z\n\t}\n}\n\ninterface Handle extends Point {\n\tid: string\n\tselected?: boolean\n\tref?: SVGElement\n}\n\n// Custom cursor interaction that prioritizes panning over selection\nfunction addCustomCursorInteraction(svg: SVGSVGElement, conn: {\n\tnodeFromEvent(e: MouseEvent): Handle | null;\n\tsetSelection(handles: Handle[]): void;\n\tsetDragging(dragging: boolean): void;\n\tisSelected(handle: Handle): boolean;\n\tgetSelection(): Handle[];\n\tgetZoom(): number;\n\tmoveNode(h: Handle, x: number, y: number): void;\n\tboxSelection(box: DOMRect, add: boolean): void;\n\tupdatePanning(): void;\n}, dragMode: 'pan' | 'select') {\n\tlet ini: { x: number; y: number; n: Handle }[] = []\n\tlet elastic: any = null\n\tlet isPanning = false\n\tlet panStartX = 0\n\tlet panStartY = 0\n\tlet initialTransform = { x: 0, y: 0 }\n\tlet pendingSelectionChange: { node: Handle; shiftKey: boolean } | null = null\n\tlet pendingNavigation: string | null = null\n\tlet hasDragged = false\n\tlet suppressLinkClick = false\n\t\n\t// Store event listeners for cleanup\n\tconst eventListeners: Array<{ element: Element | Window, event: string, handler: EventListener }> = []\n\n\t// Simple elastic selection box implementation - use mouseToDrawing for proper coordinate conversion\n\tfunction createElastic() {\n\t\tlet startDrawingX = 0, startDrawingY = 0, rect: SVGRectElement | null = null\n\t\t\n\t\treturn {\n\t\t\tini(e: MouseEvent) {\n\t\t\t\t// Use mouseToDrawing to get proper drawing coordinates (accounts for zoom and pan)\n\t\t\t\tconst pt = mouseToDrawing(e)\n\t\t\t\tstartDrawingX = pt.x\n\t\t\t\tstartDrawingY = pt.y\n\t\t\t\t\n\t\t\t\trect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\")\n\t\t\t\trect.setAttribute('fill', 'rgba(0, 100, 255, 0.1)')\n\t\t\t\trect.setAttribute('stroke', 'rgba(0, 100, 255, 0.5)')\n\t\t\t\trect.setAttribute('stroke-width', '1')\n\t\t\t\trect.setAttribute('stroke-dasharray', '3,3')\n\t\t\t\trect.setAttribute('x', String(startDrawingX))\n\t\t\t\trect.setAttribute('y', String(startDrawingY))\n\t\t\t\trect.setAttribute('width', '0')\n\t\t\t\trect.setAttribute('height', '0')\n\t\t\t\t\n\t\t\t\t// Add to the zoom group so it transforms with the content\n\t\t\t\tconst zoomGroup = svg.querySelector('g.zoom')\n\t\t\t\tif (zoomGroup) {\n\t\t\t\t\tzoomGroup.appendChild(rect)\n\t\t\t\t} else {\n\t\t\t\t\tsvg.appendChild(rect)\n\t\t\t\t}\n\t\t\t},\n\t\t\tupdate(e: MouseEvent) {\n\t\t\t\tif (!rect) return\n\t\t\t\t\n\t\t\t\t// Convert current mouse position to drawing coordinates (accounts for zoom and pan)\n\t\t\t\tconst currentPt = mouseToDrawing(e)\n\t\t\t\tconst currentDrawingX = currentPt.x\n\t\t\t\tconst currentDrawingY = currentPt.y\n\t\t\t\t\n\t\t\t\t// Calculate rectangle bounds in drawing coordinates\n\t\t\t\tconst x = Math.min(startDrawingX, currentDrawingX)\n\t\t\t\tconst y = Math.min(startDrawingY, currentDrawingY)\n\t\t\t\tconst width = Math.abs(currentDrawingX - startDrawingX)\n\t\t\t\tconst height = Math.abs(currentDrawingY - startDrawingY)\n\t\t\t\t\n\t\t\t\trect.setAttribute('x', String(x))\n\t\t\t\trect.setAttribute('y', String(y))\n\t\t\t\trect.setAttribute('width', String(width))\n\t\t\t\trect.setAttribute('height', String(height))\n\t\t\t},\n\t\t\tend(): DOMRect | null {\n\t\t\t\tif (!rect) return null\n\t\t\t\t\n\t\t\t\t// Get final rectangle bounds in drawing coordinates\n\t\t\t\tconst x = parseFloat(rect.getAttribute('x') || '0')\n\t\t\t\tconst y = parseFloat(rect.getAttribute('y') || '0')\n\t\t\t\tconst width = parseFloat(rect.getAttribute('width') || '0')\n\t\t\t\tconst height = parseFloat(rect.getAttribute('height') || '0')\n\t\t\t\t\n\t\t\t\trect.remove()\n\t\t\t\trect = null\n\t\t\t\t\n\t\t\t\t// Return drawing coordinates directly for boxSelection since we're now working in the same coordinate system\n\t\t\t\tif (width > 5 && height > 5) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tx: x, y: y, width: width, height: height,\n\t\t\t\t\t\tleft: x, top: y, right: x + width, bottom: y + height\n\t\t\t\t\t} as DOMRect\n\t\t\t\t}\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction getCurrentTransformLocal() {\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (!zoomGroup) return { x: 0, y: 0 }\n\t\t\n\t\tconst transform = zoomGroup.getAttribute('transform') || ''\n\t\tconst translateMatch = transform.match(/translate\\(([^,]+),([^)]+)\\)/)\n\t\tif (translateMatch) {\n\t\t\treturn {\n\t\t\t\tx: parseFloat(translateMatch[1]) || 0,\n\t\t\t\ty: parseFloat(translateMatch[2]) || 0\n\t\t\t}\n\t\t}\n\t\treturn { x: 0, y: 0 }\n\t}\n\n\tfunction setTransform(x: number, y: number) {\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (!zoomGroup) return\n\t\t\n\t\tconst zoom = getZoom()\n\t\tzoomGroup.setAttribute('transform', `translate(${x}, ${y}) scale(${zoom})`)\n\t}\n\n\tfunction onMouseDown(e: MouseEvent) {\n\t\te.preventDefault();\n\t\thasDragged = false\n\t\tpendingSelectionChange = null\n\t\tconst target = e.target\n\t\tconst link = target instanceof Element ? target.closest('a.nodeLink') : null\n\t\tpendingNavigation = e.shiftKey ? null : link?.getAttribute('href') || null\n\n\t\tconst node = conn.nodeFromEvent(e)\n\t\t\n\t\t// Determine effective mode: invert if shift is held\n\t\tconst effectiveMode = e.shiftKey ? (dragMode === 'pan' ? 'select' : 'pan') : dragMode\n\t\t\n\t\tif (!node) { // Clicked on empty space\n\t\t\tif (effectiveMode === 'pan') {\n\t\t\t\t// Pan mode: pan and deselect\n\t\t\t\tisPanning = true;\n\t\t\t\telastic = null;\n\t\t\t\tpanStartX = e.clientX;\n\t\t\t\tpanStartY = e.clientY;\n\t\t\t\tinitialTransform = getCurrentTransformLocal();\n\t\t\t\tini = [];\n\t\t\t\t// Deselect all elements when clicking empty space in pan mode\n\t\t\t\tconn.setSelection([]);\n\t\t\t} else {\n\t\t\t\t// Select mode: ONLY select, no panning\n\t\t\t\tisPanning = false;\n\t\t\t\telastic = createElastic();\n\t\t\t\tif (elastic) elastic.ini(e);\n\t\t\t\tini = [];\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Clicked on a node/vertex - behavior depends on effective mode\n\t\tif (effectiveMode === 'pan') {\n\t\t\t// Pan mode: select the element, don't pan\n\t\t\tisPanning = false;\n\t\t\telastic = null;\n\t\t\t\n\t\t\tconst nodes = conn.getSelection()\n\t\t\tif (conn.isSelected(node)) {\n\t\t\t\t// Clicking on a selected node - prepare to drag all selected elements\n\t\t\t\tini = nodes.map(n => ({ x: n.x, y: n.y, n }))\n\t\t\t\t// No selection change needed since we're clicking on already selected element\n\t\t\t} else {\n\t\t\t\t// Clicking on an unselected node - select only this element\n\t\t\t\tconn.setSelection([node]);\n\t\t\t\tini = [{ x: node.x, y: node.y, n: node }];\n\t\t\t}\n\t\t} else {\n\t\t\t// Select mode: selection/drag logic\n\t\t\tisPanning = false; // Ensure no panning if a node is clicked\n\t\t\telastic = null; // Ensure no selection box if a node is clicked\n\t\t\tconst nodes = conn.getSelection()\n\t\t\t\n\t\t\tif (e.shiftKey && dragMode === 'select') {\n\t\t\t\t// Shift+click in select mode: immediately change selection (no dragging expected)\n\t\t\t\tif (conn.isSelected(node)) {\n\t\t\t\t\tconst index = nodes.findIndex(n => n.id === node.id)\n\t\t\t\t\tif (index >= 0) nodes.splice(index, 1)\n\t\t\t\t} else {\n\t\t\t\t\tnodes.push(node)\n\t\t\t\t}\n\t\t\t\tconn.setSelection(nodes)\n\t\t\t\tini = nodes.map(n => ({ x: n.x, y: n.y, n }))\n\t\t\t} else {\n\t\t\t\t// Regular click: defer selection change until we know if it's a drag or click\n\t\t\t\tif (conn.isSelected(node)) {\n\t\t\t\t\t// Clicking on a selected node - prepare to drag all selected elements\n\t\t\t\t\tini = nodes.map(n => ({ x: n.x, y: n.y, n }))\n\t\t\t\t\t// No pending selection change needed since we're clicking on already selected element\n\t\t\t\t\tpendingSelectionChange = null\n\t\t\t\t} else {\n\t\t\t\t\t// Clicking on an unselected node - defer selection change until we determine if it's a click or drag\n\t\t\t\t\tpendingSelectionChange = { node, shiftKey: e.shiftKey }\n\t\t\t\t\t// For now, prepare to drag just the clicked node (we'll update selection when drag starts)\n\t\t\t\t\tini = [{ x: node.x, y: node.y, n: node }]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction onMouseMove(e: MouseEvent, dx: number, dy: number) {\n\t\t// Check if we've moved enough to consider this a drag\n\t\tconst dragThreshold = 3 // pixels\n\t\tif (!hasDragged && (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold)) {\n\t\t\thasDragged = true\n\t\t\tpendingNavigation = null\n\t\t\t\n\t\t\t// If we have a pending selection change and we're now dragging, apply it\n\t\t\tif (pendingSelectionChange) {\n\t\t\t\tconst nodes = conn.getSelection()\n\t\t\t\tnodes.length = 0\n\t\t\t\tnodes.push(pendingSelectionChange.node)\n\t\t\t\tconn.setSelection(nodes)\n\t\t\t\t// Update ini to drag only the newly selected node\n\t\t\t\tini = [{ x: pendingSelectionChange.node.x, y: pendingSelectionChange.node.y, n: pendingSelectionChange.node }]\n\t\t\t\tpendingSelectionChange = null\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (isPanning) {\n\t\t\t// Pan the view - apply mouse delta directly (no zoom division needed)\n\t\t\t// setTransform expects screen coordinates, dx/dy are already screen pixel deltas\n\t\t\tconst newX = initialTransform.x + dx\n\t\t\tconst newY = initialTransform.y + dy\n\t\t\tsetTransform(newX, newY)\n\t\t} else if (ini.length > 0 && hasDragged) {\n\t\t\t// Move selected nodes/vertices (only if we've actually started dragging)\n\t\t\t// dx, dy are screen pixel deltas, convert to drawing coordinate deltas\n\t\t\tconst zoom = conn.getZoom()\n\t\t\tconst drawingDx = dx / zoom\n\t\t\tconst drawingDy = dy / zoom\n\t\t\tini.forEach(item => {\n\t\t\t\t// item.x, item.y are initial drawing coordinates\n\t\t\t\t// Add the drawing coordinate delta to get new position\n\t\t\t\tconn.moveNode(item.n, item.x + drawingDx, item.y + drawingDy)\n\t\t\t})\n\t\t\tconn.setDragging(true)\n\t\t} else if (elastic) {\n\t\t\t// Update selection box\n\t\t\telastic.update(e)\n\t\t\tconn.setDragging(true)\n\t\t}\n\t}\n\n\tfunction onMouseUp(e: MouseEvent) {\n\t\tconn.setDragging(false)\n\t\tconst navigation = hasDragged ? null : pendingNavigation\n\t\tif (hasDragged) {\n\t\t\tsuppressLinkClick = true\n\t\t\twindow.setTimeout(() => {\n\t\t\t\tsuppressLinkClick = false\n\t\t\t}, 0)\n\t\t}\n\t\t\n\t\t// If we have a pending selection change and didn't drag, apply it now (it was just a click)\n\t\tif (pendingSelectionChange && !hasDragged) {\n\t\t\tconst nodes = conn.getSelection()\n\t\t\tnodes.length = 0\n\t\t\tnodes.push(pendingSelectionChange.node)\n\t\t\tconn.setSelection(nodes)\n\t\t}\n\t\t\n\t\tif (elastic) {\n\t\t\tconst box = elastic.end()\n\t\t\tif (box) {\n\t\t\t\tconn.boxSelection(box, e.shiftKey)\n\t\t\t} else if (!ini.length) {\n\t\t\t\t// Deselect if no box was drawn\n\t\t\t\tconn.setSelection([])\n\t\t\t}\n\t\t\telastic = null\n\t\t}\n\t\t\n\t\t// Save view state if user was panning (user-initiated view change)\n\t\tif (isPanning && hasDragged) {\n\t\t\tconst graphData = (svg as any).__data as GraphData\n\t\t\tif (graphData && graphData.id) {\n\t\t\t\tsaveViewState(graphData.id)\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Reset state\n\t\tpendingSelectionChange = null\n\t\tpendingNavigation = null\n\t\thasDragged = false\n\t\tisPanning = false\n\t\tconn.updatePanning()\n\t\tif (navigation) {\n\t\t\twindow.location.href = navigation\n\t\t}\n\t}\n\n\tfunction onClick(e: MouseEvent) {\n\t\tconst target = e.target\n\t\tif (!(target instanceof Element) || !target.closest('a.nodeLink')) return\n\t\te.preventDefault()\n\t\tif (!suppressLinkClick) return\n\t\te.stopPropagation()\n\t\tsuppressLinkClick = false\n\t}\n\n\t// Add drag and drop functionality\n\tfunction addDnd(element: SVGSVGElement) {\n\t\tlet md: { ex: number; ey: number } | null = null\n\n\t\tfunction convertEvent(e: MouseEvent | TouchEvent): MouseEvent {\n\t\t\tif ('changedTouches' in e && e.changedTouches) {\n\t\t\t\treturn e.changedTouches[0] as any\n\t\t\t}\n\t\t\treturn e as MouseEvent\n\t\t}\n\n\t\tfunction onMouseMoveHandler(e: MouseEvent | TouchEvent) {\n\t\t\tif (!md) return\n\t\t\te = convertEvent(e)\n\t\t\tonMouseMove(e, e.clientX - md.ex, e.clientY - md.ey)\n\t\t}\n\n\t\tfunction removeListeners() {\n\t\t\tdocument.removeEventListener('touchmove', onMouseMoveHandler as any)\n\t\t\tdocument.removeEventListener('mousemove', onMouseMoveHandler as any)\n\t\t\tdocument.removeEventListener('mouseup', onMouseUpHandler)\n\t\t\tdocument.removeEventListener('touchend', onMouseUpHandler)\n\t\t}\n\n\t\tfunction onMouseUpHandler(e: MouseEvent | TouchEvent) {\n\t\t\tremoveListeners()\n\t\t\tonMouseUp(convertEvent(e))\n\t\t\tmd = null\n\t\t}\n\n\t\tfunction onMouseDownHandler(e: MouseEvent | TouchEvent) {\n\t\t\te = convertEvent(e)\n\t\t\tmd = { ex: e.clientX, ey: e.clientY }\n\t\t\tonMouseDown(e)\n\t\t\tdocument.addEventListener('touchmove', onMouseMoveHandler as any)\n\t\t\tdocument.addEventListener('mousemove', onMouseMoveHandler as any)\n\t\t\tdocument.addEventListener('mouseup', onMouseUpHandler)\n\t\t\tdocument.addEventListener('touchend', onMouseUpHandler)\n\t\t}\n\n\t\telement.addEventListener('mousedown', onMouseDownHandler as any)\n\t\telement.addEventListener('touchstart', onMouseDownHandler as any)\n\t\t\n\t\t// Track these listeners for cleanup\n\t\teventListeners.push(\n\t\t\t{ element, event: 'mousedown', handler: onMouseDownHandler as any },\n\t\t\t{ element, event: 'touchstart', handler: onMouseDownHandler as any }\n\t\t)\n\t}\n\n\taddDnd(svg)\n\tsvg.addEventListener('click', onClick)\n\teventListeners.push({ element: svg, event: 'click', handler: onClick })\n\t\n\t// Return cleanup function\n\treturn () => {\n\t\teventListeners.forEach(({ element, event, handler }) => {\n\t\t\telement.removeEventListener(event, handler)\n\t\t})\n\t}\n}\n\nexport function addCursorInteraction(svg: SVGSVGElement, dragMode: 'pan' | 'select') {\n\t// Clean up any existing event listeners to prevent conflicts\n\tconst existingCleanup = (svg as any).__cursorInteractionCleanup\n\tif (existingCleanup) {\n\t\texistingCleanup()\n\t}\n\n\tfunction getData(el: SVGElement) {\n\t\t// @ts-ignore\n\t\treturn el.__data\n\t}\n\n\tconst gd = () => (getData(svg) as GraphData)\n\t\n\t// Store event listeners for cleanup\n\tconst eventListeners: Array<{ element: Element | Window, event: string, handler: EventListener }> = []\n\n\tconst beforeUnloadHandler = (e: BeforeUnloadEvent) => {\n\t\tif (!gd().changed()) return\n\t\te.preventDefault()\n\t\te.returnValue = ''\n\t}\n\twindow.addEventListener(\"beforeunload\", beforeUnloadHandler)\n\teventListeners.push({ element: window, event: 'beforeunload', handler: beforeUnloadHandler })\n\n\tfunction setDotSelected(d: Handle, selected: boolean) {\n\t\td.selected = selected\n\t\tconst dotEl = svg.querySelector('#' + d.id)\n\t\td.selected ? dotEl.classList.add('selected') : dotEl.classList.remove('selected')\n\t}\n\n\t// show moving dot along edge when ALT is pressed\n\tconst mouseMoveHandler = (e: MouseEvent) => {\n\t\tif (!e.altKey) return\n\t\tconst fnd = findClosestSegment(gd(), mouseToDrawing(e))\n\t\tif (fnd) {\n\t\t\tconst {prj} = fnd\n\t\t\tconst parent = svg.querySelector('g.edges')\n\t\t\tlet dot = parent.querySelector('#prj')\n\t\t\tif (!dot) {\n\t\t\t\tdot = create.element('circle', {id: 'prj', cx: prj.x, cy: prj.y, r: 7})\n\t\t\t\tparent.append(dot)\n\t\t\t}\n\t\t\tdot.setAttribute('cx', String(prj.x))\n\t\t\tdot.setAttribute('cy', String(prj.y))\n\t\t} else {\n\t\t\tremovePrjDot()\n\t\t}\n\t}\n\tsvg.addEventListener('mousemove', mouseMoveHandler)\n\teventListeners.push({ element: svg, event: 'mousemove', handler: mouseMoveHandler })\n\n\tconst keyUpHandler = (e: KeyboardEvent) => {\n\t\tconst key = findShortcut(e, true)\n\t\tif (key == ADD_VERTEX || key == ADD_LABEL_VERTEX) return\n\t\tremovePrjDot()\n\t}\n\twindow.addEventListener('keyup', keyUpHandler)\n\teventListeners.push({ element: window, event: 'keyup', handler: keyUpHandler })\n\n\tfunction removePrjDot() {\n\t\tconst el = svg.querySelector('g.edges #prj')\n\t\tel && el.parentElement.removeChild(el)\n\t}\n\n\tconst clickHandler = (e: MouseEvent) => {\n\t\tconst key = findShortcut(e, true)\n\t\tif (key != ADD_LABEL_VERTEX && key != ADD_VERTEX) return\n\t\tconst fnd = findClosestSegment(gd(), mouseToDrawing(e))\n\t\tif (fnd) {\n\t\t\tconst {edge, pos, prj} = fnd\n\t\t\t// depending on keyboard modifier, make it label position\n\t\t\tgd().insertEdgeVertex(edge, prj, pos, key == ADD_LABEL_VERTEX)\n\t\t\tremovePrjDot()\n\t\t}\n\t}\n\tsvg.addEventListener('click', clickHandler)\n\teventListeners.push({ element: svg, event: 'click', handler: clickHandler })\n\n\tconst wheelHandler = (e: WheelEvent) => {\n\t\t// Handle wheel zoom directly without relying on shortcuts\n\t\t// deltaY > 0 means scrolling down (zoom out), deltaY < 0 means scrolling up (zoom in)\n\t\tconst delta = Math.sign(e.deltaY) * 0.1 // Normalize to 0.1 zoom steps\n\t\tconst currentZoom = getZoom()\n\t\tconst newZoom = Math.max(0.1, Math.min(5, currentZoom - delta)) // Clamp zoom between 0.1 and 5\n\t\t\n\t\tif (newZoom !== currentZoom) {\n\t\t\t// Convert absolute screen coordinates to SVG-relative coordinates\n\t\t\tconst rect = svg.getBoundingClientRect()\n\t\t\tconst svgX = e.clientX - rect.left\n\t\t\tconst svgY = e.clientY - rect.top\n\t\t\tsetZoomCentered(newZoom, svgX, svgY)\n\t\t\te.preventDefault()\n\t\t\t\n\t\t\t// Save view state after user wheel zoom\n\t\t\tconst graphData = (svg as any).__data as GraphData\n\t\t\tif (graphData && graphData.id) {\n\t\t\t\tsaveViewState(graphData.id)\n\t\t\t}\n\t\t}\n\t}\n\tsvg.addEventListener('wheel', wheelHandler)\n\teventListeners.push({ element: svg, event: 'wheel', handler: wheelHandler })\n\n\tconst keyDownHandler = (e: KeyboardEvent) => {\n\t\tconst shortcut = findShortcut(e)\n\t\t\n\t\t\n\t\tif (shortcut) {\n\t\t\te.preventDefault() // Prevent browser default for all recognized shortcuts\n\t\t}\n\t\t\n\t\tswitch (shortcut) {\n\t\t\tcase DEL_VERTEX:\n\t\t\t\tconst selectedVertices = Array.from(gd().edgeVertices.values()).filter(v => v.selected);\n\t\t\t\tselectedVertices.forEach(v => {\n\t\t\t\t\tgd().deleteEdgeVertex(v)\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\tcase UNDO:\n\t\t\t\tgd().undo()\n\t\t\t\tbreak\n\t\t\tcase REDO:\n\t\t\t\tgd().redo()\n\t\t\t\tbreak\n\t\t\tcase ZOOM_IN:\n\t\t\t\tconst newZoomIn = Math.min(5, getZoom() * 1.2)\n\t\t\t\t// Center zoom on viewport center like mouse wheel\n\t\t\t\tsetZoomCentered(newZoomIn)\n\t\t\t\tsaveViewState(gd().id) // Save after user keyboard zoom\n\t\t\t\tbreak\n\t\t\tcase ZOOM_OUT:\n\t\t\t\tconst newZoomOut = Math.max(0.1, getZoom() / 1.2)\n\t\t\t\t// Center zoom on viewport center like mouse wheel\n\t\t\t\tsetZoomCentered(newZoomOut)\n\t\t\t\tsaveViewState(gd().id) // Save after user keyboard zoom\n\t\t\t\tbreak\n\t\t\tcase ZOOM_100:\n\t\t\t\t// Center zoom on viewport center like mouse wheel\n\t\t\t\tsetZoomCentered(1)\n\t\t\t\tsaveViewState(gd().id) // Save after user keyboard zoom\n\t\t\t\tbreak\n\t\t\tcase ZOOM_FIT:\n\t\t\t\tgd().fitToView()\n\t\t\t\t// Don't save view state here - fitToView should not be persisted\n\t\t\t\tbreak\n\t\t\tcase SELECT_ALL:\n\t\t\t\tgd().nodes().forEach(n => gd().setNodeSelected(n, true))\n\t\t\t\tgd().edgeVertices.forEach(v => setDotSelected(v, true))\n\t\t\t\tbreak\n\t\t\tcase DESELECT:\n\t\t\t\tgd().nodes().forEach(n => gd().setNodeSelected(n, false))\n\t\t\t\tgd().edgeVertices.forEach(v => setDotSelected(v, false))\n\t\t\t\tbreak\n\t\t}\n\t}\n\twindow.addEventListener('keydown', keyDownHandler)\n\teventListeners.push({ element: window, event: 'keydown', handler: keyDownHandler })\n\n\t// Custom cursor interaction with pan-first behavior\n\tconst customInteractionCleanup = addCustomCursorInteraction(svg, {\n\t\tnodeFromEvent(e: MouseEvent): Handle {\n\t\t\te.preventDefault()\n\t\t\t// node clicked\n\t\t\tlet el = (e.target as SVGElement).closest('g.nodes g.node') as SVGElement\n\t\t\tif (el) return getData(el)\n\t\t\t// vertex dot clicked\n\t\t\tel = (e.target as SVGElement).closest('g.edges g.edge .v-dot') as SVGElement\n\t\t\tif (el) {\n\t\t\t\treturn gd().edgeVertices.get(el.id)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\t\tsetSelection(handles: Handle[]) {\n\t\t\t// nodes\n\t\t\tgd().nodes().forEach(n => gd().setNodeSelected(n, handles.some(h => h.id == n.id)))\n\t\t\t// dots\n\t\t\tgd().edgeVertices.forEach(d => setDotSelected(d, handles.some(h => h.id == d.id)))\n\t\t\tselectListener(gd().nodes().find(n => n.selected))\n\t\t},\n\t\tsetDragging(d: boolean) {\n\t\t\tdragging = d\n\t\t},\n\t\tisSelected(handle: Handle): boolean {\n\t\t\treturn handle.selected\n\t\t},\n\t\tgetSelection(): Handle[] {\n\t\t\tconst ret: Handle[] = gd().nodes().filter(n => n.selected)\n\t\t\tgd().edgeVertices.forEach(d => d.selected && ret.push(d))\n\t\t\treturn ret\n\t\t},\n\t\tgetZoom: getZoom,\n\t\tmoveNode(h: Handle, x: number, y: number) {\n\t\t\tif (gd().nodesMap.has(h.id))\n\t\t\t\tgd().moveNode(h as Node, x, y)\n\t\t\telse {\n\t\t\t\t(h as EdgeVertex).auto = false\n\t\t\t\tgd().moveEdgeVertex(h as EdgeVertex, x, y)\n\t\t\t}\n\t\t},\n\t\tboxSelection(box: DOMRect, add) {\n\t\t\t// Box is now already in drawing coordinates, no need to scale\n\t\t\t// nodes\n\t\t\tgd().nodesMap.forEach(n => {\n\t\t\t\tconst inBox = boxesOverlap(uncenterBox(n), box)\n\t\t\t\tif (inBox) {\n\t\t\t\t\t// Toggle selection for elements in the box\n\t\t\t\t\tgd().setNodeSelected(n, !n.selected)\n\t\t\t\t} else if (!add) {\n\t\t\t\t\t// If not holding shift and element is outside box, deselect it\n\t\t\t\t\tgd().setNodeSelected(n, false)\n\t\t\t\t}\n\t\t\t})\n\t\t\t// dots\n\t\t\tgd().edgeVertices.forEach(d => {\n\t\t\t\tconst inBox = insideBox(d, box, false)\n\t\t\t\tif (inBox) {\n\t\t\t\t\t// Toggle selection for elements in the box\n\t\t\t\t\tsetDotSelected(d, !d.selected)\n\t\t\t\t} else if (!add) {\n\t\t\t\t\t// If not holding shift and element is outside box, deselect it\n\t\t\t\t\tsetDotSelected(d, false)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tselectListener(gd().nodes().find(n => n.selected))\n\t\t},\n\t\tupdatePanning: updatePanning,\n\t}, dragMode)\n\t\n\t// Store cleanup function on the SVG element for later use\n\tconst cleanup = () => {\n\t\teventListeners.forEach(({ element, event, handler }) => {\n\t\t\telement.removeEventListener(event, handler)\n\t\t})\n\t\tif (customInteractionCleanup) {\n\t\t\tcustomInteractionCleanup()\n\t\t}\n\t}\n\t\n\t// Store cleanup function on SVG element\n\t;(svg as any).__cursorInteractionCleanup = cleanup\n}\n\nexport function getZoom() {\n\tif (!svg) return 1\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return 1\n\t\n\t// Parse zoom from transform attribute to match how we set it\n\tconst transform = el.getAttribute('transform') || ''\n\tconst scaleMatch = transform.match(/scale\\(([^)]+)\\)/)\n\tif (scaleMatch) {\n\t\treturn parseFloat(scaleMatch[1]) || 1\n\t}\n\treturn 1\n}\n\n// svgPadding is now imported as SVG_PADDING from constants.ts\n\nexport function setZoom(zoom: number) {\n\tif (!svg) return\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return\n\t\n\t// Preserve existing translation when setting zoom\n\tconst currentTransform = getCurrentTransform()\n\tel.setAttribute('transform', `translate(${currentTransform.x}, ${currentTransform.y}) scale(${zoom})`)\n\t\n\t// also set panning size\n\tupdatePanning()\n}\n\nexport function setZoomCentered(newZoom: number, centerX?: number, centerY?: number) {\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tconst oldZoom = getZoom()\n\t\n\t// If no center point provided, use viewport center\n\tif (centerX === undefined || centerY === undefined) {\n\t\t// Use the parent container's dimensions for the visible viewport\n\t\t// The SVG might be larger than the visible area due to overflow\n\t\tconst container = svg.parentElement\n\t\tif (container) {\n\t\t\tcenterX = container.clientWidth / 2\n\t\t\tcenterY = container.clientHeight / 2\n\t\t} else {\n\t\t\t// Fallback to SVG dimensions if no parent\n\t\t\tcenterX = svg.clientWidth / 2\n\t\t\tcenterY = svg.clientHeight / 2\n\t\t}\n\t}\n\t\n\t// Get current transform\n\tconst currentTransform = getCurrentTransform()\n\t\n\t// Convert screen coordinates to drawing coordinates\n\t// For transform order translate(tx, ty) scale(s):\n\t// screen_point = (drawing_point * scale) + translation\n\t// So: drawing_point = (screen_point - translation) / scale\n\tconst drawingX = (centerX - currentTransform.x) / oldZoom\n\tconst drawingY = (centerY - currentTransform.y) / oldZoom\n\t\n\t// Calculate new translation to keep the same drawing point at the same screen position\n\t// screen_point = (drawing_point * new_scale) + new_translation\n\t// So: new_translation = screen_point - (drawing_point * new_scale)\n\tconst newTranslateX = centerX - (drawingX * newZoom)\n\tconst newTranslateY = centerY - (drawingY * newZoom)\n\t\n\t// Apply the new transform\n\tel.setAttribute('transform', `translate(${newTranslateX}, ${newTranslateY}) scale(${newZoom})`)\n\t\n\t// Update panning\n\tupdatePanning()\n}\n\nfunction getCurrentTransform() {\n\tif (!svg) return { x: 0, y: 0 }\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return { x: 0, y: 0 }\n\t\n\tconst transform = el.getAttribute('transform') || ''\n\tconst translateMatch = transform.match(/translate\\(([^,]+),([^)]+)\\)/)\n\tif (translateMatch) {\n\t\treturn {\n\t\t\tx: parseFloat(translateMatch[1]) || 0,\n\t\t\ty: parseFloat(translateMatch[2]) || 0\n\t\t}\n\t}\n\treturn { x: 0, y: 0 }\n}\n\nfunction updatePanning() {\n\tif (!svg) return\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return\n\tconst bb = el.getBBox()\n\tconst zoom = getZoom()\n\tif (!svg.parentElement) return\n\tconst w = Math.max(svg.parentElement.clientWidth / zoom, bb.x + bb.width + SVG_PADDING)\n\tconst h = Math.max(svg.parentElement.clientHeight / zoom, bb.y + bb.height + SVG_PADDING)\n\tsvg.setAttribute('width', String(w * zoom))\n\tsvg.setAttribute('height', String(h * zoom))\n\t\n\t// Note: View state saving removed from here to prevent interference with reset/fit functions\n\t// View state is now only saved on user interactions and page unload\n}\n\n// Optimized version that uses pre-calculated content bounds instead of expensive getBBox()\nfunction updatePanningOptimized(graphData: GraphData) {\n\tconst bb = graphData.calculateContentBounds() // Use already calculated bounds\n\tconst zoom = getZoom()\n\tconst w = Math.max(svg.parentElement.clientWidth / zoom, bb.x + bb.width + SVG_PADDING)\n\tconst h = Math.max(svg.parentElement.clientHeight / zoom, bb.y + bb.height + SVG_PADDING)\n\tsvg.setAttribute('width', String(w * zoom))\n\tsvg.setAttribute('height', String(h * zoom))\n}\n\nexport const getZoomAuto = () => {\n\t// Get the graph data to calculate proper content bounds\n\tconst graphData = (svg as any).__data as GraphData\n\tif (!graphData) {\n\t\treturn 1 // Default zoom if no graph data\n\t}\n\t\n\t// Use proper content bounds calculation\n\tconst contentBounds = graphData.calculateContentBounds()\n\tconst viewportWidth = svg.parentElement?.clientWidth || 800\n\tconst viewportHeight = svg.parentElement?.clientHeight || 600\n\t\n\t// Add padding around content\n\tconst padding = 40\n\t\n\t// Calculate zoom to fit content with padding\n\tconst zoomX = (viewportWidth - padding * 2) / contentBounds.width\n\tconst zoomY = (viewportHeight - padding * 2) / contentBounds.height\n\tconst zoom = Math.min(zoomX, zoomY)\n\t\n\t// Clamp zoom between reasonable bounds\n\treturn Math.max(Math.min(zoom, 2), 0.1)\n}\n\nconst setBorderStyle = (el: SVGElement, style: string) => {\n\tif (style == 'Dashed') el.setAttribute('stroke-dasharray', '4')\n\telse if (style == 'Dotted') el.setAttribute('stroke-dasharray', '2')\n}\n\nconst styles = {\n\t//node styles\n\tnodeBorder: {\n\t\t// Don't set fill and stroke here - let tag-specific styles handle colors\n\t\tfilter: 'url(#shadow)',\n\t},\n\tnodeText: {\n\t\t'font-family': 'Arial, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\n\t//edge styles\n\tedgeText: {\n\t\t'font-family': 'Arial, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\n\tedgeRect: {\n\t\tfill: \"none\",\n\t\tstroke: \"none\",\n\t},\n\n\t//group styles\n\tgroupRect: {\n\t\t//fill: \"none\",\n\t\tfill: \"rgba(0, 0, 0, 0.02)\",\n\t\tstroke: \"#666\",\n\t\t'stroke-width': 3,\n\t\t\"stroke-dasharray\": 4,\n\t},\n\tgroupText: {\n\t\t'font-family': 'Arial, sans-serif',\n\t\tfill: \"#666\",\n\t\t\"font-size\": 22,\n\t\t\"font-weight\": \"bold\",\n\t\tcursor: \"default\"\n\t}\n}\n\n// View state persistence - similar to undo cache but for zoom/pan\nconst viewStateCache = new Map();\n\n// Save current view state (zoom and pan)\nexport function saveViewState(graphId: string) {\n\tif (!svg) return;\n\t\n\tconst zoom = getZoom();\n\tconst transform = getCurrentTransform();\n\t\n\tconst state = {\n\t\tzoom,\n\t\ttransform: { x: transform.x, y: transform.y }\n\t};\n\t\n\tviewStateCache.set(graphId, state);\n}\n\n// Restore view state if it exists\nexport function restoreViewState(graphId: string): boolean {\n\tif (!svg || !viewStateCache.has(graphId)) {\n\t\treturn false;\n\t}\n\t\n\tconst state = viewStateCache.get(graphId);\n\tif (!state) {\n\t\treturn false;\n\t}\n\t\n\t// Restore zoom and transform\n\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement;\n\tif (zoomGroup) {\n\t\tzoomGroup.setAttribute('transform', `scale(${state.zoom}) translate(${state.transform.x}, ${state.transform.y})`);\n\t\tupdatePanning();\n\t}\n\t\n\treturn true;\n}\n\n// Clear view state for a graph\nexport function clearViewState(graphId: string) {\n\tviewStateCache.delete(graphId);\n}\n\n/**\n * Find the optimal position to insert a label vertex into the routing path\n * to minimize disruption to the existing route\n */\nfunction findOptimalLabelPosition(vertices: Point[], labelPos: Point, fromNode: Point, toNode: Point): number {\n\t// If no existing vertices, insert at the beginning\n\tif (vertices.length === 0) {\n\t\treturn 0;\n\t}\n\t\n\t// Build the full routing path including start/end nodes\n\tconst fullPath = [fromNode, ...vertices, toNode];\n\t\n\t// Find the closest point on the path to the label position\n\tlet minDistance = Infinity;\n\tlet bestSegmentIndex = 0;\n\t\n\tfor (let i = 0; i < fullPath.length - 1; i++) {\n\t\tconst segmentStart = fullPath[i];\n\t\tconst segmentEnd = fullPath[i + 1];\n\t\t\n\t\t// Calculate distance from label position to this segment\n\t\tconst distance = distanceToSegment(labelPos, segmentStart, segmentEnd);\n\t\t\n\t\tif (distance < minDistance) {\n\t\t\tminDistance = distance;\n\t\t\tbestSegmentIndex = i;\n\t\t}\n\t}\n\t\n\t// Convert full path index to vertices array index\n\t// bestSegmentIndex 0 means between fromNode and vertices[0] -> insert at 0\n\t// bestSegmentIndex 1 means between vertices[0] and vertices[1] -> insert at 1\n\t// etc.\n\treturn bestSegmentIndex;\n}\n\n/**\n * Calculate distance from a point to a line segment\n */\nfunction distanceToSegment(point: Point, segmentStart: Point, segmentEnd: Point): number {\n\tconst A = point.x - segmentStart.x;\n\tconst B = point.y - segmentStart.y;\n\tconst C = segmentEnd.x - segmentStart.x;\n\tconst D = segmentEnd.y - segmentStart.y;\n\t\n\tconst dot = A * C + B * D;\n\tconst lenSq = C * C + D * D;\n\t\n\tif (lenSq === 0) {\n\t\t// Segment is actually a point\n\t\treturn Math.sqrt(A * A + B * B);\n\t}\n\t\n\tlet param = dot / lenSq;\n\t\n\tlet xx, yy;\n\t\n\tif (param < 0) {\n\t\txx = segmentStart.x;\n\t\tyy = segmentStart.y;\n\t} else if (param > 1) {\n\t\txx = segmentEnd.x;\n\t\tyy = segmentEnd.y;\n\t} else {\n\t\txx = segmentStart.x + param * C;\n\t\tyy = segmentStart.y + param * D;\n\t}\n\t\n\tconst dx = point.x - xx;\n\tconst dy = point.y - yy;\n\treturn Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Project a label position onto the line segment where it will be inserted\n */\nfunction projectLabelOntoSegment(vertices: Point[], labelPos: Point, insertPos: number, fromNode: Point, toNode: Point): Point {\n\t// Build the full routing path including start/end nodes\n\tconst fullPath = [fromNode, ...vertices, toNode];\n\t\n\t// The segment where we're inserting is between fullPath[insertPos] and fullPath[insertPos + 1]\n\tconst segmentStart = fullPath[insertPos];\n\tconst segmentEnd = fullPath[insertPos + 1];\n\t\n\t// Project the label position onto this line segment\n\treturn projectPointOntoSegment(labelPos, segmentStart, segmentEnd);\n}\n\n/**\n * Project a point onto a line segment (closest point on the segment)\n */\nfunction projectPointOntoSegment(point: Point, segmentStart: Point, segmentEnd: Point): Point {\n\tconst A = point.x - segmentStart.x;\n\tconst B = point.y - segmentStart.y;\n\tconst C = segmentEnd.x - segmentStart.x;\n\tconst D = segmentEnd.y - segmentStart.y;\n\t\n\tconst dot = A * C + B * D;\n\tconst lenSq = C * C + D * D;\n\t\n\tif (lenSq === 0) {\n\t\t// Segment is actually a point, return that point\n\t\treturn { x: segmentStart.x, y: segmentStart.y };\n\t}\n\t\n\tlet param = dot / lenSq;\n\t\n\t// Clamp to segment (don't extend beyond endpoints)\n\tparam = Math.max(0, Math.min(1, param));\n\t\n\treturn {\n\t\tx: segmentStart.x + param * C,\n\t\ty: segmentStart.y + param * D\n\t};\n}","import { Point, BBox, calculateDistance } from './constants';\nimport { intersectPolylineBox, Segment } from './intersect';\n\n// Define interfaces locally since they're not exported from graph.ts\ninterface NodeStyle {\n\tbackground?: string;\n\tstroke?: string;\n\topacity?: number;\n\tfontSize?: number;\n\tshape?: string;\n\tborder?: string;\n}\n\ninterface Node extends Point {\n\tid: string;\n\ttitle: string;\n\tsub: string;\n\tdescription: string;\n\twidth: number;\n\theight: number;\n\tref?: SVGGElement;\n\tselected?: boolean;\n\tintersect: (p: Point) => Point;\n\tstyle: NodeStyle;\n}\n\ninterface EdgeVertex extends Point {\n\tid: string;\n\tselected?: boolean;\n\tref?: SVGElement;\n\tlabel?: boolean;\n\tauto?: boolean;\n}\n\ninterface EdgeStyle {\n\tcolor?: string;\n\tthickness?: number;\n\tfontSize?: number;\n\tposition?: number;\n\tdashed?: boolean;\n}\n\ninterface Edge {\n\tid: string;\n\tlabel: string;\n\tfrom: Node;\n\tto: Node;\n\tvertices?: EdgeVertex[];\n\tref?: SVGGElement;\n\tstyle: EdgeStyle;\n\tinitVertex: (p: Point) => EdgeVertex;\n\tuserDeletedVertices?: boolean; // Track if user explicitly deleted vertices\n}\n\ninterface GraphData {\n\tid: string;\n\tname: string;\n\tnodesMap: Map;\n\tedges: Edge[];\n\tedgeVertices: Map;\n\tgroupsMap: Map;\n\tmetadata: any;\n}\n\nexport interface EdgeLabelPlacement extends Point {\n\torientation: 'horizontal' | 'vertical';\n}\n\n/**\n * Calculate edge vertices, handling multi-edge scenarios and auto-vertices\n */\nexport function calculateEdgeVertices(edge: Edge, data: GraphData): Point[] {\n\tconst n1 = edge.from, n2 = edge.to;\n\t\n\t\n\t// if vertices exists, follow them\n\tlet vertices: Point[] = edge.vertices ? edge.vertices.concat() : [];\n\t// Don't remove label vertices - they should be preserved for rendering\n\t// (The autoLayout process handles replacing old ones with new ones)\n\tconst tmp = (vertices as EdgeVertex[]);\n\n\tif (vertices.length == 0 && !edge.userDeletedVertices) {\n\t\t// Only create auto vertices if user hasn't explicitly deleted them\n\t\t// for edges with same \"from\" and \"to\", we must spread the labels so they don't overlap\n\t\t// lookup the other \"same\" edges\n\t\tconst sameEdges = data.edges.filter(e => e.from == edge.from && e.to == edge.to)\n\t\tlet spreadPos = 0\n\t\tif (sameEdges.length > 1) {\n\t\t\tconst idx = sameEdges.indexOf(edge) // my index in the list of same edges\n\t\t\tspreadPos = idx - (sameEdges.length - 1) / 2\n\n\t\t\tlet spreadX = 0, spreadY = 0;\n\t\t\tif (Math.abs(n1.x - n2.x) > Math.abs(n1.y - n2.y)) {\n\t\t\t\tspreadY = spreadPos * 70\n\t\t\t} else {\n\t\t\t\tspreadX = spreadPos * 200\n\t\t\t}\n\t\t\tconst v = edge.initVertex({\n\t\t\t\tx: (n1.x + n2.x) / 2 + spreadX,\n\t\t\t\ty: (n1.y + n2.y) / 2 + spreadY\n\t\t\t})\n\t\t\tv.label = true\n\t\t\tv.auto = true\n\t\t\tvertices.push(v)\n\t\t} else {\n\t\t\t// If there are no user-defined vertices and not a multi-edge scenario,\n\t\t\t// we don't create any auto-vertices here. AutoLayout will provide them.\n\t\t\t// The path will be a straight line between n1 and n2 (after intersection points are calculated).\n\t\t\t// ELK/autoLayout is responsible for providing bend points for non-straight lines.\n\t\t}\n\t}\n\n\tvertices.unshift(n1)\n\tvertices.push(n2)\n\n\t// Calculate intersection points with node boundaries\n\t// Find first non-label vertex for start intersection\n\tlet firstRoutingVertex = vertices[vertices.length - 1]; // Default to end node\n\tfor (let i = 1; i < vertices.length - 1; i++) {\n\t\tif (!(vertices[i] as any).label) {\n\t\t\tfirstRoutingVertex = vertices[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\t// Find last non-label vertex for end intersection \n\tlet lastRoutingVertex = vertices[0]; // Default to start node\n\tfor (let i = vertices.length - 2; i > 0; i--) {\n\t\tif (!(vertices[i] as any).label) {\n\t\t\tlastRoutingVertex = vertices[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\t// For connections without routing vertices, ensure we have proper direction\n\t// The defaults are already correct: firstRoutingVertex = n2, lastRoutingVertex = n1\n\t\n\t\n\t// Calculate proper node boundary intersection\n\tconst calculateNodeIntersection = (node: any, targetPoint: Point): Point => {\n\t\tconst nodeShape = node.style?.shape?.toLowerCase() || 'box';\n\t\tconst dx = targetPoint.x - node.x;\n\t\tconst dy = targetPoint.y - node.y;\n\t\tconst nodeCenter = { x: node.x, y: node.y };\n\t\t\n\t\t// If target is at center, default to right edge\n\t\tif (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01) {\n\t\t\treturn { x: node.x + node.width / 2, y: node.y };\n\t\t}\n\t\t\n\t\tif (nodeShape === 'cylinder') {\n\t\t\t// Cylinder shape intersection (same as shapes.ts)\n\t\t\tconst w = node.width;\n\t\t\tconst rx = w / 2;\n\t\t\tconst ry = rx / (5.5 + w / 70);\n\t\t\tconst halfHeight = node.height / 2;\n\t\t\t\n\t\t\t// First calculate rectangular bounds intersection\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\tconst cos = Math.cos(angle);\n\t\t\tconst sin = Math.sin(angle);\n\t\t\t\n\t\t\t// Check intersection with rectangular bounds\n\t\t\tlet t = Infinity;\n\t\t\tif (Math.abs(cos) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(rx / cos));\n\t\t\t}\n\t\t\tif (Math.abs(sin) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(halfHeight / sin));\n\t\t\t}\n\t\t\t\n\t\t\tconst rectX = node.x + cos * t;\n\t\t\tconst rectY = node.y + sin * t;\n\t\t\t\n\t\t\t// Check if we need elliptical intersection for top/bottom curves\n\t\t\tconst topCurveY = node.y - halfHeight + ry;\n\t\t\tconst bottomCurveY = node.y + halfHeight - ry;\n\t\t\t\n\t\t\tif (rectY < topCurveY || rectY > bottomCurveY) {\n\t\t\t\t// Use ellipse intersection for curved parts\n\t\t\t\tconst ellipseY = rectY < topCurveY ? node.y - halfHeight + ry : node.y + halfHeight - ry;\n\t\t\t\t// Solve for ellipse intersection\n\t\t\t\tconst a = 1 / (rx * rx);\n\t\t\t\tconst b = -2 * node.x / (rx * rx);\n\t\t\t\tconst c = (node.x * node.x) / (rx * rx) + ((ellipseY - node.y) * (ellipseY - node.y)) / (ry * ry) - 1;\n\t\t\t\t\n\t\t\t\tconst discriminant = b * b - 4 * a * c;\n\t\t\t\tif (discriminant >= 0) {\n\t\t\t\t\tconst sqrt_d = Math.sqrt(discriminant);\n\t\t\t\t\tconst x1 = (-b + sqrt_d) / (2 * a);\n\t\t\t\t\tconst x2 = (-b - sqrt_d) / (2 * a);\n\t\t\t\t\t\n\t\t\t\t\t// Choose the intersection in the direction of the target\n\t\t\t\t\tconst intersectX = dx > 0 ? Math.max(x1, x2) : Math.min(x1, x2);\n\t\t\t\t\treturn { x: intersectX, y: ellipseY };\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\treturn { x: rectX, y: rectY };\n\t\t\t\n\t\t} else if (nodeShape === 'circle') {\n\t\t\tconst radius = node.width / 2;\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\treturn {\n\t\t\t\tx: node.x + Math.cos(angle) * radius,\n\t\t\t\ty: node.y + Math.sin(angle) * radius\n\t\t\t};\n\t\t\t\n\t\t} else if (nodeShape === 'ellipse') {\n\t\t\tconst rx = node.width * 0.55;\n\t\t\tconst ry = node.width * 0.45;\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\tconst cos = Math.cos(angle);\n\t\t\tconst sin = Math.sin(angle);\n\t\t\t\n\t\t\t// Parametric ellipse intersection\n\t\t\tconst t = Math.sqrt((rx * rx * sin * sin) + (ry * ry * cos * cos));\n\t\t\treturn {\n\t\t\t\tx: node.x + (rx * cos * ry) / t,\n\t\t\t\ty: node.y + (ry * sin * rx) / t\n\t\t\t};\n\t\t\t\n\t\t} else {\n\t\t\t// Default rectangular intersection\n\t\t\tconst halfWidth = node.width / 2;\n\t\t\tconst halfHeight = node.height / 2;\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\tconst cos = Math.cos(angle);\n\t\t\tconst sin = Math.sin(angle);\n\t\t\t\n\t\t\t// Calculate which edge we hit first\n\t\t\tlet t = Infinity;\n\t\t\tif (Math.abs(cos) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(halfWidth / cos));\n\t\t\t}\n\t\t\tif (Math.abs(sin) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(halfHeight / sin));\n\t\t\t}\n\t\t\t\n\t\t\treturn {\n\t\t\t\tx: node.x + cos * t,\n\t\t\t\ty: node.y + sin * t\n\t\t\t};\n\t\t}\n\t};\n\t\n\t// Calculate intersections, but use the direction from center to NEXT vertex in sequence\n\t// This ensures the line exits the node in the direction it needs to go\n\t\n\t// For start intersection: use direction from node center to first routing vertex\n\tlet startIntersection = calculateNodeIntersection(n1, firstRoutingVertex);\n\t\n\t// For end intersection: use direction from node center to last routing vertex \n\tlet endIntersection = calculateNodeIntersection(n2, lastRoutingVertex);\n\t\n\t// Start intersection: NO offset needed - intersection already gives perfect boundary point\n\t// End intersection: NO offset needed - the arrow marker now has refX=\"0\" so the tip is at the endpoint\n\t\n\tvertices[0] = startIntersection;\n\tvertices[vertices.length - 1] = endIntersection;\n\t\n\treturn vertices;\n}\n\n/**\n * Calculate the label anchor and the orientation of the path segment that owns\n * it. Renderers use the orientation to place text beside the relationship line\n * instead of centering text over vertical segments.\n */\nexport function calculateLabelPlacement(\n\tvertices: Point[],\n\tposition: number,\n\tfallback: Point,\n): EdgeLabelPlacement {\n\tlet point = {x: fallback.x, y: fallback.y};\n\tlet segment: Segment | undefined;\n\tconst labelIndex = vertices.findIndex(vertex => (vertex as EdgeVertex).label);\n\n\tif (labelIndex >= 0) {\n\t\tpoint = vertices[labelIndex];\n\t\tconst adjacentSegments: Segment[] = [];\n\t\tif (labelIndex > 0) {\n\t\t\tadjacentSegments.push({p: vertices[labelIndex - 1], q: point});\n\t\t}\n\t\tif (labelIndex < vertices.length - 1) {\n\t\t\tadjacentSegments.push({p: point, q: vertices[labelIndex + 1]});\n\t\t}\n\t\tsegment = adjacentSegments.reduce((longest, candidate) => {\n\t\t\tif (!longest) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t\treturn calculateDistance(candidate.p, candidate.q) >\n\t\t\t\tcalculateDistance(longest.p, longest.q)\n\t\t\t\t? candidate\n\t\t\t\t: longest;\n\t\t}, undefined);\n\t} else {\n\t\tconst totalLength = vertices.slice(1).reduce(\n\t\t\t(sum, vertex, index) => sum + calculateDistance(vertices[index], vertex),\n\t\t\t0,\n\t\t);\n\t\tconst targetLength = totalLength * position;\n\t\tlet traversed = 0;\n\t\tfor (let index = 1; index < vertices.length; index++) {\n\t\t\tconst candidate = {p: vertices[index - 1], q: vertices[index]};\n\t\t\tconst length = calculateDistance(candidate.p, candidate.q);\n\t\t\tif (length > 0 && traversed + length >= targetLength) {\n\t\t\t\tconst segmentPosition = (targetLength - traversed) / length;\n\t\t\t\tpoint = {\n\t\t\t\t\tx: candidate.p.x + (candidate.q.x - candidate.p.x) * segmentPosition,\n\t\t\t\t\ty: candidate.p.y + (candidate.q.y - candidate.p.y) * segmentPosition,\n\t\t\t\t};\n\t\t\t\tsegment = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttraversed += length;\n\t\t}\n\t}\n\n\tconst horizontalDistance = segment ? Math.abs(segment.q.x - segment.p.x) : 0;\n\tconst verticalDistance = segment ? Math.abs(segment.q.y - segment.p.y) : 0;\n\treturn {\n\t\t...point,\n\t\torientation: verticalDistance > horizontalDistance ? 'vertical' : 'horizontal',\n\t};\n}\n\n/**\n * Create edge segments and generate SVG path\n */\nexport function createEdgeSegments(vertices: Point[], bbox: BBox, n1: Point, n2: Point): { segments: Segment[], path: string } {\n\tconst segments: Segment[] = []\n\tfor (let i = 1; i < vertices.length; i++) {\n\t\tsegments.push({p: vertices[i - 1], q: vertices[i]})\n\t}\n\t\n\t// Debug the final segment that will have the arrow\n\tif (segments.length > 0) {\n\t\tconst lastSegment = segments[segments.length - 1];\n\t}\n\t// splice edge over label box\n\tintersectPolylineBox(segments, bbox)\n\n\t// Generate path based on routing style - SIMPLIFIED\n\tlet path: string\n\t\n\t// Always draw a polyline through the segments.\n\t// If segments.length is 1 (meaning direct connection or only label vertex), it will be a straight line.\n\t// If autoLayout provided bend points, those will be in `segments`.\n\tif (segments.length > 0) {\n\t\tpath = `M${segments[0].p.x},${segments[0].p.y}`\n\t\tfor (let i = 0; i < segments.length; i++) {\n\t\t\tconst s = segments[i]\n\t\t\t// For polylines, we just draw line segments to each vertex point.\n\t\t\t// The ELK 'POLYLINE' routing should give us the necessary bend points.\n\t\t\tpath += ` L${s.q.x},${s.q.y}`\n\t\t}\n\t} else {\n\t\t// Fallback for edges with no segments (should ideally not happen if n1 and n2 are defined)\n\t\t// Draw a straight line between n1 and n2 directly if no vertices/segments exist.\n\t\t// Note: Intersection points are calculated before this, so n1/n2 are already adjusted.\n\t\tpath = `M${n1.x},${n1.y} L${n2.x},${n2.y}`\n\t}\n\t\n\treturn { segments, path };\n}","import {GraphData, Node, Group} from \"./graph\";\n\nexport interface LayoutOptions {\n\tdirection?: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT';\n\tnodeSpacing?: number;\n\tlayerSpacing?: number;\n\tcompactLayout?: boolean;\n}\n\n// Simplified spacing configuration\ninterface SpacingConfig {\n\tnodeSpacing: number;\n\tlayerSpacing: number;\n\tcomponentSpacing: number;\n\tpadding: number;\n\tgroupMultiplier: number;\n}\n\n// Spacing configuration - balanced for readability\nconst DEFAULT_SPACING: SpacingConfig = {\n\tnodeSpacing: 80, // Comfortable vertical spacing between nodes in same layer\n\tlayerSpacing: 60, // Layer spacing (between nodes in flow direction)\n\tcomponentSpacing: 80, // Separation between disconnected components\n\tpadding: 40, // Padding around the entire layout (for group labels)\n\tgroupMultiplier: 0.65, // Moderate compaction within groups\n};\n\n// Helper function to get effective spacing for a context\nfunction getEffectiveSpacing(\n\tuserOptions: LayoutOptions = {},\n\tisGroup: boolean = false\n): SpacingConfig {\n\t// Apply user overrides to base config\n\tconst effectiveConfig: SpacingConfig = {\n\t\tnodeSpacing: userOptions.nodeSpacing ?? DEFAULT_SPACING.nodeSpacing,\n\t\tlayerSpacing: userOptions.layerSpacing ?? DEFAULT_SPACING.layerSpacing,\n\t\tcomponentSpacing: DEFAULT_SPACING.componentSpacing,\n\t\tpadding: DEFAULT_SPACING.padding,\n\t\tgroupMultiplier: DEFAULT_SPACING.groupMultiplier,\n\t};\n\t\n\t// Apply group multiplier if in group context\n\tif (isGroup) {\n\t\teffectiveConfig.nodeSpacing = Math.max(\n\t\t\teffectiveConfig.nodeSpacing * effectiveConfig.groupMultiplier,\n\t\t\t30 // Minimum 30px spacing within groups\n\t\t);\n\t\teffectiveConfig.layerSpacing = Math.max(\n\t\t\teffectiveConfig.layerSpacing * effectiveConfig.groupMultiplier,\n\t\t\t35 // Minimum 35px layer spacing within groups\n\t\t);\n\t\teffectiveConfig.componentSpacing = Math.max(\n\t\t\teffectiveConfig.componentSpacing * effectiveConfig.groupMultiplier,\n\t\t\t25 // Minimum 25px component spacing within groups\n\t\t);\n\t\teffectiveConfig.padding = Math.max(\n\t\t\teffectiveConfig.padding * effectiveConfig.groupMultiplier,\n\t\t\t15 // Minimum 15px padding within groups\n\t\t);\n\t}\n\t\n\treturn effectiveConfig;\n}\n\n// Simplified ELK layout options builder\nfunction getELKOptions(\n\tspacing: SpacingConfig,\n\tuserOptions: LayoutOptions\n): Record {\n\tconst {\n\t\tdirection = 'DOWN',\n\t\tcompactLayout = false\n\t} = userOptions;\n\t\n\tconst baseOptions: Record = {\n\t\t'elk.algorithm': 'layered', // Back to layered for better orthogonal routing\n\t\t'elk.direction': direction,\n\t\t'elk.spacing.nodeNode': spacing.nodeSpacing.toString(),\n\t\t'elk.spacing.componentComponent': spacing.componentSpacing.toString(),\n\t\t'elk.padding': `[top=${spacing.padding},left=${spacing.padding},bottom=${spacing.padding},right=${spacing.padding}]`,\n\t\t\n\t\t// Layer spacing for compact layout\n\t\t'elk.layered.spacing.nodeNodeBetweenLayers': spacing.layerSpacing.toString(),\n\t\t'elk.layered.spacing.edgeNodeBetweenLayers': '10', // Minimal spacing around nodes\n\t\t'elk.layered.spacing.edgeEdgeBetweenLayers': '10', // Minimal space between edges\n\t\t\n\t\t// ORTHOGONAL edge routing for cleaner layout\n\t\t'elk.edgeRouting': 'POLYLINE',\n\t\t'elk.layered.unnecessaryBendpoints': 'false',\n\t\t\n\t\t// Minimal edge routing - straight lines where possible\n\t\t'elk.layered.edgeRouting.orthogonal.mode': 'DIRECTION_BASED',\n\t\t'elk.layered.edgeRouting.orthogonal.spacing': '5', // Minimal edge spacing\n\t\t'elk.layered.edgeRouting.orthogonal.nodeOverlapRatio': '0.1',\n\t\t\n\t\t// Compaction options\n\t\t'elk.layered.compaction.connectedComponents': 'true',\n\t\t'elk.layered.compaction.postCompaction.strategy': 'LEFT_RIGHT',\n\t\t\n\t\t// Separate components to reduce complexity\n\t\t'elk.separateConnectedComponents': 'true',\n\t\t\n\t\t// Node placement strategy for consistent vertical spacing\n\t\t'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',\n\t\t'elk.layered.nodePlacement.favorStraightEdges': 'true',\n\t\t\n\t\t// Crossing minimization - respect model order for consistent layout\n\t\t'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',\n\t\t'elk.layered.crossingMinimization.semiInteractive': 'true',\n\t\t\n\t\t// Flatten hierarchy for better edge routing\n\t\t'elk.hierarchyHandling': 'SEPARATE_CHILDREN',\n\t\t'elk.layered.considerModelOrder.strategy': 'NONE', // Ignore model ordering constraints\n\t\t\n\t\t// Edge label handling - minimal space, labels positioned above edges\n\t\t'elk.edgeLabels.placement': 'CENTER',\n\t\t'elk.edgeLabels.inline': 'true',\n\t\t'elk.spacing.edgeLabel': '5', // Minimal spacing for labels\n\t\t'elk.edgeLabels.avoidOverlap': 'false', // Disable collision avoidance\n\t\t'elk.edgeLabels.considerModelOrder': 'false',\n\t\t'elk.layered.edgeLabels.sideSelection': 'ALWAYS_UP', // Labels above edges\n\t};\n\t\n\t// Additional compact layout options if requested\n\tif (compactLayout) {\n\t\tbaseOptions['elk.spacing.nodeNode'] = Math.max(spacing.nodeSpacing * 0.7, 30).toString();\n\t\tbaseOptions['elk.layered.spacing.nodeNodeBetweenLayers'] = Math.max(spacing.layerSpacing * 0.7, 30).toString();\n\t}\n\t\n\treturn baseOptions;\n}\n\nexport async function autoLayout(graph: GraphData, options: LayoutOptions = {}): Promise<{\n\tnodes: Array<{id: string, x: number, y: number}>,\n\tedges: Array<{id: string, vertices: Array<{x: number, y: number}>, label?: {x: number, y: number}}>\n}> {\n\t// Dynamically import ELK only when auto-layout is used\n\tconst ELK = await import('elkjs/lib/elk.bundled.js').then(module => module.default);\n\tconst elk = new ELK();\n\t// Get systematic spacing configuration\n\tconst rootSpacing = getEffectiveSpacing(options, false);\n\t\n\t// Build ELK graph structure\n\tconst elkGraph = {\n\t\tid: \"root\",\n\t\tlayoutOptions: getELKOptions(rootSpacing, options),\n\t\tchildren: [] as any[],\n\t\tedges: [] as any[]\n\t};\n\n\t// Build actual ELK nodes first. Groups below take ownership of their direct\n\t// members so ELK reserves non-overlapping space for every boundary.\n\tconst nodeMap = new Map();\n\tconst elkNodes = new Map();\n\tgraph.nodesMap.forEach(node => {\n\t\tif (!node.id) return; // Skip nodes without IDs\n\t\t\n\t\tnodeMap.set(node.id, node);\n\t\t\n\t\t// Ensure minimum dimensions and validate node size data\n\t\t// Use larger height to account for shapes like Robot that extend above the center\n\t\tconst nodeWidth = Math.max(node.width || 200, 150); // Min width 150px\n\t\tconst nodeHeight = Math.max(node.height || 100, 250); // Min height 250px to account for robot shape\n\t\t\n\t\t// Add padding to node dimensions for ELK to account for arrow size\n\t\t// This makes ELK route edges to a slightly larger boundary so arrow tips stay outside\n\t\tconst arrowPadding = 25; // Padding for arrow clearance\n\t\t\n\t\telkNodes.set(node.id, {\n\t\t\tid: node.id,\n\t\t\t// Provide current position as hint to ELK\n\t\t\tx: node.x,\n\t\t\ty: node.y,\n\t\t\twidth: nodeWidth + (arrowPadding * 2),\n\t\t\theight: nodeHeight + (arrowPadding * 2),\n\t\t\tlayoutOptions: {\n\t\t\t\t// Allow ELK to move nodes but consider current positions\n\t\t\t\t'elk.position': '',\n\t\t\t\t// Force ELK to use our exact dimensions\n\t\t\t\t'elk.nodeSize.constraints': '[FIXED_SIZE]'\n\t\t\t}\n\t\t});\n\t});\n\n\tconst nodeParentGroup = new Map();\n\tconst groupParent = new Map();\n\tconst childGroupIDs = new Set();\n\n\tgraph.groupsMap.forEach(group => {\n\t\tgroup.nodes.forEach(member => {\n\t\t\tif (isGroup(member)) {\n\t\t\t\tif (!groupParent.has(member.id)) {\n\t\t\t\t\tgroupParent.set(member.id, group.id);\n\t\t\t\t}\n\t\t\t\tchildGroupIDs.add(member.id);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!nodeParentGroup.has(member.id)) {\n\t\t\t\tnodeParentGroup.set(member.id, group.id);\n\t\t\t}\n\t\t});\n\t});\n\n\tconst elkGroups = new Map();\n\tconst buildELKGroup = (group: Group): any => {\n\t\tconst existing = elkGroups.get(group.id);\n\t\tif (existing) return existing;\n\n\t\tconst children = group.nodes.flatMap(member => {\n\t\t\tif (isGroup(member)) {\n\t\t\t\treturn groupParent.get(member.id) === group.id ? [buildELKGroup(member)] : [];\n\t\t\t}\n\t\t\tconst node = elkNodes.get(member.id);\n\t\t\treturn node && nodeParentGroup.get(member.id) === group.id ? [node] : [];\n\t\t});\n\t\tconst elkGroup = {\n\t\t\tid: group.id,\n\t\t\tchildren,\n\t\t\tedges: [] as any[],\n\t\t\tlayoutOptions: getELKOptions(getEffectiveSpacing(options, true), options),\n\t\t};\n\t\telkGroups.set(group.id, elkGroup);\n\t\treturn elkGroup;\n\t};\n\n\tgraph.groupsMap.forEach(group => {\n\t\tif (!childGroupIDs.has(group.id)) {\n\t\t\tconst elkGroup = buildELKGroup(group);\n\t\t\tif (elkGroup.children.length > 0) {\n\t\t\t\telkGraph.children.push(elkGroup);\n\t\t\t}\n\t\t}\n\t});\n\telkNodes.forEach((node, id) => {\n\t\tif (!nodeParentGroup.has(id)) {\n\t\t\telkGraph.children.push(node);\n\t\t}\n\t});\n\n\tconst groupAncestors = (groupID?: string) => {\n\t\tconst ancestors: string[] = [];\n\t\tlet current = groupID;\n\t\twhile (current) {\n\t\t\tancestors.push(current);\n\t\t\tcurrent = groupParent.get(current);\n\t\t}\n\t\treturn ancestors;\n\t};\n\tconst lowestCommonGroup = (sourceID: string, destinationID: string) => {\n\t\tconst sourceAncestors = groupAncestors(nodeParentGroup.get(sourceID));\n\t\tconst destinationAncestors = new Set(groupAncestors(nodeParentGroup.get(destinationID)));\n\t\treturn sourceAncestors.find(groupID => destinationAncestors.has(groupID));\n\t};\n\n\tgraph.edges.forEach(edge => {\n\t\t// Skip edges without proper IDs\n\t\tif (!edge.id || !edge.from?.id || !edge.to?.id) return;\n\t\t\n\t\t// Verify source and target nodes exist in our node map\n\t\tif (!nodeMap.has(edge.from.id) || !nodeMap.has(edge.to.id)) {\n\t\t\tconsole.warn(`Skipping edge ${edge.id}: source ${edge.from.id} or target ${edge.to.id} not found in nodes`);\n\t\t\treturn;\n\t\t}\n\t\t\n\t\t// Calculate more accurate label dimensions for ELK\n\t\tconst labelWidth = edge.label && edge.label.trim() ? \n\t\t\tMath.min(edge.label.length * 7, 200) : 0; // More realistic width estimate\n\t\t\n\t\t\n\t\tconst elkEdge = {\n\t\t\tid: edge.id,\n\t\t\tsources: [edge.from.id],\n\t\t\ttargets: [edge.to.id],\n\t\t\t// Include label information with much smaller dimensions\n\t\t\tlabels: edge.label && edge.label.trim() ? [{\n\t\t\t\tid: `${edge.id}-label`,\n\t\t\t\ttext: edge.label,\n\t\t\t\t// Much more conservative label size estimates\n\t\t\t\twidth: labelWidth,\n\t\t\t\theight: 20, // More realistic label height\n\t\t\t\tlayoutOptions: {\n\t\t\t\t\t'elk.edgeLabels.placement': 'CENTER',\n\t\t\t\t\t'elk.edgeLabels.inline': 'true'\n\t\t\t\t\t// Remove the FIXED_SIZE constraint that might be forcing detours\n\t\t\t\t}\n\t\t\t}] : []\n\t\t};\n\n\t\tconst groupID = lowestCommonGroup(edge.from.id, edge.to.id);\n\t\tconst edgeContainer = groupID ? elkGroups.get(groupID) : elkGraph;\n\t\tedgeContainer.edges.push(elkEdge);\n\t});\n\n\t// Enhanced validation - ensure ELK gets complete data\n\tif (!elkGraph.id || !elkGraph.children) {\n\t\tthrow new Error('Invalid ELK graph structure');\n\t}\n\t\n\n\ttry {\n\t\tconst layoutedGraph = await elk.layout(elkGraph);\n\t\t\n\t\t// Extract results\n\t\tconst nodes: Array<{id: string, x: number, y: number}> = [];\n\t\tconst edges: Array<{id: string, vertices: Array<{x: number, y: number}>, label?: {x: number, y: number}}> = [];\n\n\n\t\t// Extract nodes from layout result\n\t\tconst extractNodes = (container: any, offsetX = 0, offsetY = 0) => {\n\t\t\tcontainer.children?.forEach((child: any) => {\n\t\t\t\tif (child.children) {\n\t\t\t\t\t// This is a group, recurse\n\t\t\t\t\textractNodes(child, offsetX + (child.x || 0), offsetY + (child.y || 0));\n\t\t\t\t} else {\n\t\t\t\t\t// This is a node\n\t\t\t\t\t// Adjust for the padding we added - ELK positioned based on padded size\n\t\t\t\t\t// So we need to shift by the padding amount to get the real center\n\t\t\t\t\tnodes.push({\n\t\t\t\t\t\tid: child.id,\n\t\t\t\t\t\tx: offsetX + (child.x || 0) + (child.width || 0) / 2,\n\t\t\t\t\t\ty: offsetY + (child.y || 0) + (child.height || 0) / 2\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t};\n\n\t\t// Process edges from ELK layout result to get routing information\n\t\tconst processEdgesFromELK = (container: any, offsetX = 0, offsetY = 0) => {\n\t\t\tcontainer.edges?.forEach((elkEdge: any) => {\n\t\t\t\tconst vertices: Array<{x: number, y: number}> = [];\n\t\t\t\tlet label: {x: number, y: number} | undefined;\n\n\t\t\t\t// Process edge sections to get bend points\n\t\t\t\tif (elkEdge.sections && elkEdge.sections.length > 0) {\n\t\t\t\t\telkEdge.sections.forEach((section: any) => {\n\t\t\t\t\t\t// Add start point if it exists\n\t\t\t\t\t\tif (section.startPoint) {\n\t\t\t\t\t\t\tvertices.push({\n\t\t\t\t\t\t\t\tx: offsetX + section.startPoint.x, \n\t\t\t\t\t\t\t\ty: offsetY + section.startPoint.y\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Add bend points (this is where ELK puts the routing vertices!)\n\t\t\t\t\t\tif (section.bendPoints && section.bendPoints.length > 0) {\n\t\t\t\t\t\t\tsection.bendPoints.forEach((bp: any) => {\n\t\t\t\t\t\t\t\tvertices.push({\n\t\t\t\t\t\t\t\t\tx: offsetX + bp.x, \n\t\t\t\t\t\t\t\t\ty: offsetY + bp.y\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Add end point if it exists\n\t\t\t\t\t\tif (section.endPoint) {\n\t\t\t\t\t\t\tvertices.push({\n\t\t\t\t\t\t\t\tx: offsetX + section.endPoint.x, \n\t\t\t\t\t\t\t\ty: offsetY + section.endPoint.y\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Extract ELK's calculated label positions (respect collision avoidance!)\n\t\t\t\tconst originalEdge = graph.edges.find(e => e.id === elkEdge.id);\n\t\t\t\tif (originalEdge?.label && originalEdge.label.trim()) {\n\t\t\t\t\tif (elkEdge.labels && elkEdge.labels.length > 0) {\n\t\t\t\t\t\tconst elkLabel = elkEdge.labels[0]; // Get first label\n\t\t\t\t\t\tif (elkLabel.x !== undefined && elkLabel.y !== undefined) {\n\t\t\t\t\t\t\tlabel = {\n\t\t\t\t\t\t\t\tx: offsetX + elkLabel.x + (elkLabel.width || 0) / 2, // Center of label\n\t\t\t\t\t\t\t\ty: offsetY + elkLabel.y + (elkLabel.height || 0) / 2\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (vertices.length >= 2) {\n\t\t\t\t\t\t// Fallback: use middle of edge if ELK didn't provide label position\n\t\t\t\t\t\tconst midIndex = Math.floor(vertices.length / 2);\n\t\t\t\t\t\tif (vertices.length % 2 === 0) {\n\t\t\t\t\t\t\tconst v1 = vertices[midIndex - 1];\n\t\t\t\t\t\t\tconst v2 = vertices[midIndex];\n\t\t\t\t\t\t\tlabel = { x: (v1.x + v2.x) / 2, y: (v1.y + v2.y) / 2 };\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlabel = vertices[midIndex];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\n\t\t\t\tedges.push({\n\t\t\t\t\tid: elkEdge.id,\n\t\t\t\t\tvertices,\n\t\t\t\t\tlabel\n\t\t\t\t});\n\t\t\t});\n\t\t\t\n\t\t\t// Also process edges in child containers (groups)\n\t\t\tcontainer.children?.forEach((child: any) => {\n\t\t\t\tif (child.edges && child.edges.length > 0) {\n\t\t\t\t\tprocessEdgesFromELK(child, offsetX + (child.x || 0), offsetY + (child.y || 0));\n\t\t\t\t}\n\t\t\t});\n\t\t};\n\n\n\t\textractNodes(layoutedGraph);\n\t\tprocessEdgesFromELK(layoutedGraph);\n\n\t\t// Normalize coordinates to start near (0,0) to prevent huge canvas sizes\n\t\t// while preserving relative positioning between elements\n\t\tif (nodes.length > 0) {\n\t\t\t// Find the minimum coordinates across all elements\n\t\t\tconst minX = Math.min(...nodes.map(n => n.x));\n\t\t\tconst minY = Math.min(...nodes.map(n => n.y));\n\t\t\t\n\t\t\t// Add some padding so content doesn't start at exact (0,0)\n\t\t\tconst padding = 50;\n\t\t\tconst offsetX = -minX + padding;\n\t\t\tconst offsetY = -minY + padding;\n\t\t\t\n\t\t\t// Normalize all node positions\n\t\t\tnodes.forEach(node => {\n\t\t\t\tnode.x += offsetX;\n\t\t\t\tnode.y += offsetY;\n\t\t\t});\n\t\t\t\n\t\t\t// Normalize all edge positions\n\t\t\tedges.forEach(edge => {\n\t\t\t\tedge.vertices.forEach(vertex => {\n\t\t\t\t\tvertex.x += offsetX;\n\t\t\t\t\tvertex.y += offsetY;\n\t\t\t\t});\n\t\t\t\tif (edge.label) {\n\t\t\t\t\tedge.label.x += offsetX;\n\t\t\t\t\tedge.label.y += offsetY;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn { nodes, edges };\n\n\t} catch (error) {\n\t\tconsole.warn('ELK layout failed, using fallback layout. Error:', error);\n\t\treturn createFallbackLayout(graph);\n\t}\n}\n\n// Simplified fallback layout\nfunction createFallbackLayout(graph: GraphData): {\n\tnodes: Array<{id: string, x: number, y: number}>,\n\tedges: Array<{id: string, vertices: Array<{x: number, y: number}>}>\n} {\n\tconst nodes: Array<{id: string, x: number, y: number}> = [];\n\tconst edges: Array<{id: string, vertices: Array<{x: number, y: number}>}> = [];\n\n\t// Simple grid layout for nodes\n\tlet x = 0, y = 0;\n\tconst spacing = 300;\n\tconst maxCols = Math.ceil(Math.sqrt(graph.nodesMap.size));\n\n\tlet col = 0;\n\tgraph.nodesMap.forEach(node => {\n\t\tnodes.push({\n\t\t\tid: node.id,\n\t\t\tx: x,\n\t\t\ty: y\n\t\t});\n\n\t\tcol++;\n\t\tif (col >= maxCols) {\n\t\t\tcol = 0;\n\t\t\tx = 0;\n\t\t\ty += spacing;\n\t\t} else {\n\t\t\tx += spacing;\n\t\t}\n\t});\n\n\t// Simple straight line edges\n\tgraph.edges.forEach(edge => {\n\t\tedges.push({\n\t\t\tid: edge.id,\n\t\t\tvertices: []\n\t\t});\n\t});\n\n\treturn { nodes, edges };\n}\n\nfunction isGroup(member: Node | Group): member is Group {\n\treturn \"nodes\" in member;\n}","\n import API from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import domAPI from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/styleDomAPI.js\";\n import insertFn from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/insertBySelector.js\";\n import setAttributes from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\";\n import insertStyleElement from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/insertStyleElement.js\";\n import styleTagTransformFn from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/styleTagTransform.js\";\n import content, * as namedExport from \"!!../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/cjs.js!./style.css\";\n \n \n\nvar options = {};\n\noptions.styleTagTransform = styleTagTransformFn;\noptions.setAttributes = setAttributes;\noptions.insert = insertFn.bind(null, \"head\");\noptions.domAPI = domAPI;\noptions.insertStyleElement = insertStyleElement;\n\nvar update = API(content, options);\n\n\n\nexport * from \"!!../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/cjs.js!./style.css\";\n export default content && content.locals ? content.locals : undefined;\n","interface LiveReloadOptions {\n\tminDelay: number;\n\tmaxDelay: number;\n\thandshakeTimeout: number;\n}\n\ninterface LiveReloadMessage {\n\tcommand: string;\n\tprotocols?: string[];\n\tver?: string;\n\tpath?: string;\n}\n\nclass Timer {\n\tprivate readonly callback: () => void;\n\tprivate readonly handler: () => void;\n\tprivate running: boolean = false;\n\tprivate timeoutId: ReturnType | null = null;\n\n\tconstructor(callback: () => void) {\n\t\tthis.callback = callback;\n\t\tthis.handler = () => {\n\t\t\tthis.running = false;\n\t\t\tthis.timeoutId = null;\n\t\t\tthis.callback();\n\t\t};\n\t}\n\n\tstart(timeout: number): void {\n\t\tif (this.running) {\n\t\t\tthis.stop();\n\t\t}\n\t\tthis.timeoutId = setTimeout(this.handler, timeout);\n\t\tthis.running = true;\n\t}\n\n\tstop(): void {\n\t\tif (this.running && this.timeoutId !== null) {\n\t\t\tclearTimeout(this.timeoutId);\n\t\t\tthis.running = false;\n\t\t\tthis.timeoutId = null;\n\t\t}\n\t}\n\n\tisRunning(): boolean {\n\t\treturn this.running;\n\t}\n}\n\n/**\n * RefreshConnector implements the livereload protocol to listen for file changes via WebSocket\n */\nexport class RefreshConnector {\n\tprivate static readonly DEFAULT_OPTIONS: LiveReloadOptions = {\n\t\tminDelay: 1000,\n\t\tmaxDelay: 60000,\n\t\thandshakeTimeout: 5000\n\t};\n\n\tprivate static readonly LIVERELOAD_PROTOCOLS = [\n\t\t'http://livereload.com/protocols/official-9',\n\t\t'http://livereload.com/protocols/2.x-remote-control'\n\t];\n\n\tprivate readonly uri: string;\n\tprivate readonly options: LiveReloadOptions;\n\tprivate readonly fileChangeHandler: (path: string) => void;\n\t\n\tprivate socket: WebSocket | null = null;\n\tprivate nextDelay: number;\n\tprivate connectionDesired: boolean = false;\n\tprivate disconnectionReason: string = '';\n\t\n\tprivate readonly handshakeTimeout: Timer;\n\tprivate readonly reconnectTimer: Timer;\n\n\tconstructor(fileChangeHandler: (file: string) => void, options?: Partial) {\n\t\tthis.fileChangeHandler = fileChangeHandler;\n\t\tthis.options = { ...RefreshConnector.DEFAULT_OPTIONS, ...options };\n\t\tthis.uri = 'ws://localhost:35729/livereload';\n\t\tthis.nextDelay = this.options.minDelay;\n\n\t\tthis.handshakeTimeout = new Timer(() => this.handleHandshakeTimeout());\n\t\tthis.reconnectTimer = new Timer(() => this.attemptReconnection());\n\t}\n\n\tconnect(): void {\n\t\tthis.connectionDesired = true;\n\n\t\tif (this.isSocketConnected()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.prepareForConnection();\n\t\tthis.createWebSocket();\n\t}\n\n\tdisconnect(): void {\n\t\tthis.connectionDesired = false;\n\t\tthis.reconnectTimer.stop();\n\n\t\tif (this.isSocketConnected()) {\n\t\t\tthis.disconnectionReason = 'manual';\n\t\t\tthis.socket!.close();\n\t\t}\n\t}\n\n\tprivate isSocketConnected(): boolean {\n\t\treturn this.socket !== null && this.socket.readyState === WebSocket.OPEN;\n\t}\n\n\tprivate prepareForConnection(): void {\n\t\tthis.reconnectTimer.stop();\n\t\tthis.disconnectionReason = 'cannot-connect';\n\t}\n\n\tprivate createWebSocket(): void {\n\t\tthis.socket = new WebSocket(this.uri);\n\t\tthis.socket.onopen = () => this.handleOpen();\n\t\tthis.socket.onclose = () => this.handleClose();\n\t\tthis.socket.onmessage = (event) => this.handleMessage(event);\n\t\tthis.socket.onerror = () => this.handleError();\n\t}\n\n\tprivate handleOpen(): void {\n\t\tthis.disconnectionReason = 'handshake-failed';\n\t\tthis.startHandshake();\n\t}\n\n\tprivate handleClose(): void {\n\t\tconsole.log(`WebSocket disconnected: ${this.disconnectionReason}. Retry in ${this.nextDelay}ms`);\n\t\tthis.scheduleReconnection();\n\t}\n\n\tprivate handleMessage(event: MessageEvent): void {\n\t\ttry {\n\t\t\tconst message: LiveReloadMessage = JSON.parse(event.data);\n\t\t\tthis.processMessage(message);\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to parse WebSocket message:', error);\n\t\t}\n\t}\n\n\tprivate handleError(): void {\n\t\t// Error handling is done in onclose\n\t}\n\n\tprivate processMessage(message: LiveReloadMessage): void {\n\t\tswitch (message.command) {\n\t\t\tcase 'hello':\n\t\t\t\tthis.handleHelloMessage();\n\t\t\t\tbreak;\n\t\t\tcase 'reload':\n\t\t\t\tthis.handleReloadMessage(message);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tconsole.log('Unknown WebSocket message received:', message);\n\t\t}\n\t}\n\n\tprivate handleHelloMessage(): void {\n\t\tthis.handshakeTimeout.stop();\n\t\tthis.nextDelay = this.options.minDelay;\n\t}\n\n\tprivate handleReloadMessage(message: LiveReloadMessage): void {\n\t\t// The livereload server closes connection after sending reload\n\t\t// We must reconnect\n\t\tthis.reconnectTimer.stop();\n\t\tthis.connect();\n\n\t\tif (message.path) {\n\t\t\tthis.fileChangeHandler(message.path);\n\t\t}\n\t}\n\n\tprivate startHandshake(): void {\n\t\tconst helloMessage: LiveReloadMessage = {\n\t\t\tcommand: 'hello',\n\t\t\tprotocols: RefreshConnector.LIVERELOAD_PROTOCOLS,\n\t\t\tver: '3.3.1'\n\t\t};\n\t\t\n\t\tthis.sendCommand(helloMessage);\n\t\tthis.handshakeTimeout.start(this.options.handshakeTimeout);\n\t}\n\n\tprivate handleHandshakeTimeout(): void {\n\t\tif (this.isSocketConnected()) {\n\t\t\tthis.disconnectionReason = 'handshake-timeout';\n\t\t\tthis.socket!.close();\n\t\t}\n\t}\n\n\tprivate attemptReconnection(): void {\n\t\tif (this.connectionDesired) {\n\t\t\tthis.connect();\n\t\t}\n\t}\n\n\tprivate scheduleReconnection(): void {\n\t\tif (!this.connectionDesired) {\n\t\t\treturn; // Don't reconnect after manual disconnection\n\t\t}\n\n\t\tif (!this.reconnectTimer.isRunning()) {\n\t\t\tthis.reconnectTimer.start(this.nextDelay);\n\t\t\tthis.nextDelay = Math.min(this.options.maxDelay, this.nextDelay * 2);\n\t\t}\n\t}\n\n\tprivate sendCommand(command: LiveReloadMessage): void {\n\t\tif (this.isSocketConnected()) {\n\t\t\tthis.socket!.send(JSON.stringify(command));\n\t\t}\n\t}\n}","import { createRoot } from 'react-dom/client';\nimport React, { Suspense, lazy, useEffect, useState } from 'react';\nimport { refreshGraph } from \"./Root\";\nimport './style.css';\nimport '@fortawesome/fontawesome-free/css/all.css';\nimport { RefreshConnector } from \"./websocket\";\n\nconst Root = lazy(() => import('./Root').then(module => ({ default: module.Root })));\n\ninterface ModelData {\n\tmodel: any;\n\tlayout: any;\n}\n\ninterface AppState {\n\tdata: ModelData | null;\n\terror: string | null;\n\tloading: boolean;\n}\n\nconst App: React.FC = () => {\n\tconst [state, setState] = useState({\n\t\tdata: null,\n\t\terror: null,\n\t\tloading: true\n\t});\n\n\tconst loadData = async () => {\n\t\tsetState(prev => ({ ...prev, loading: true, error: null }));\n\t\t\n\t\ttry {\n\t\t\tconst [modelResponse, layoutResponse] = await Promise.all([\n\t\t\t\tfetch('data/model.json'),\n\t\t\t\tfetch('data/layout.json')\n\t\t\t]);\n\n\t\t\tif (!modelResponse.ok) {\n\t\t\t\tthrow new Error(`Failed to fetch model: ${modelResponse.statusText}`);\n\t\t\t}\n\t\t\t\n\t\t\tif (!layoutResponse.ok) {\n\t\t\t\tthrow new Error(`Failed to fetch layout: ${layoutResponse.statusText}`);\n\t\t\t}\n\n\t\t\tconst [model, layout] = await Promise.all([\n\t\t\t\tmodelResponse.json(),\n\t\t\t\tlayoutResponse.json()\n\t\t\t]);\n\n\t\t\tsetState({\n\t\t\t\tdata: { model, layout },\n\t\t\t\terror: null,\n\t\t\t\tloading: false\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to load data:', error);\n\t\t\tsetState({\n\t\t\t\tdata: null,\n\t\t\t\terror: error instanceof Error ? error.message : 'Unknown error occurred',\n\t\t\t\tloading: false\n\t\t\t});\n\t\t}\n\t};\n\n\tconst handleFileChange = (path: string) => {\n\t\tif (path.endsWith('.svg')) {\n\t\t\treturn; // Ignore SVG changes to avoid infinite loops\n\t\t}\n\t\t\n\t\tconsole.log('File changed:', path);\n\t\trefreshGraph();\n\t\tloadData();\n\t};\n\n\tuseEffect(() => {\n\t\t// Setup refresh connector\n\t\tconst refreshConnector = new RefreshConnector(handleFileChange);\n\t\trefreshConnector.connect();\n\n\t\t// Initial data load\n\t\tloadData();\n\n\t\t// Cleanup function\n\t\treturn () => {\n\t\t\t// RefreshConnector cleanup would go here if it had a disconnect method\n\t\t};\n\t}, []);\n\n\tif (state.loading) {\n\t\treturn ;\n\t}\n\n\tif (state.error) {\n\t\treturn ;\n\t}\n\n\tif (!state.data) {\n\t\treturn ;\n\t}\n\n\treturn (\n\t\t}>\n\t\t\t\n\t\t\n\t);\n};\n\nconst LoadingScreen: React.FC = () => (\n\t
\n\t\t
Loading...
\n\t
\n);\n\nconst ErrorScreen: React.FC<{ error: string; onRetry: () => void }> = ({ error, onRetry }) => (\n\t
\n\t\t

Error loading application

\n\t\t

{error}

\n\t\t\n\t
\n);\n\n// Initialize the application\nconst container = document.getElementById('root');\nif (!container) {\n\tthrow new Error('Root container not found');\n}\n\nconst root = createRoot(container);\nroot.render();","import React, {FC} from \"react\";\nimport { isMac, getModifierKeyName, getModifierKeyProperty } from './utils/platform';\n\ninterface Combination {\n\tctrl?: boolean;\n\tshift?: boolean;\n\talt?: boolean;\n\twheel?: boolean\n\tkey?: string;\n\tclick?: boolean;\n}\n\ninterface Shortcut {\n\tid: string,\n\thelp: string,\n\tcombinations: Combination[]\n}\n\nexport const SAVE = 'save'\n\nexport const UNDO = 'undo'\nexport const REDO = 'redo'\nexport const ADD_VERTEX = 'add-vertex'\nexport const ADD_LABEL_VERTEX = 'add-label-vertex'\nexport const DEL_VERTEX = 'del-vertex'\n\nexport const ZOOM_IN = 'zoom-in'\nexport const ZOOM_OUT = 'zoom-out'\nexport const ZOOM_FIT = 'zoom-fit'\nexport const ZOOM_100 = 'zoom-100'\n\nexport const SELECT_ALL = 'select-all'\nexport const DESELECT = 'deselect'\n\nexport const MOVE_LEFT = 'move-left'\nexport const MOVE_RIGHT = 'move-right'\nexport const MOVE_UP = 'move-up'\nexport const MOVE_DOWN = 'move-down'\nexport const MOVE_LEFT_FINE = 'move-left-fine'\nexport const MOVE_RIGHT_FINE = 'move-right-fine'\nexport const MOVE_UP_FINE = 'move-up-fine'\nexport const MOVE_DOWN_FINE = 'move-down-fine'\n\nexport const PAN_VIEW = 'pan-view'\nexport const SELECT_ELEMENT = 'select-element'\nexport const MULTI_SELECT = 'multi-select'\nexport const BOX_SELECT = 'box-select'\nexport const MOVE_ELEMENTS = 'move-elements'\n\nexport const HELP = 'help'\n\n// New shortcuts for toolbar buttons\nexport const TOGGLE_DRAG_MODE = 'toggle_drag_mode'\nexport const ALIGN_HORIZONTAL = 'align_horizontal'\nexport const ALIGN_VERTICAL = 'align_vertical'\nexport const DISTRIBUTE_HORIZONTAL = 'distribute_horizontal'\nexport const DISTRIBUTE_VERTICAL = 'distribute_vertical'\nexport const AUTO_LAYOUT = 'auto_layout'\nexport const RESET_POSITION = 'reset_position'\nexport const TOGGLE_GRID = 'toggle_grid'\nexport const TOGGLE_SNAP_TO_GRID = 'toggle_snap_to_grid'\nexport const SNAP_ALL_TO_GRID = 'snap_all_to_grid'\n\nconst shortcuts: { name: string; list: Shortcut[] }[] = [\n\t{\n\t\tname: 'Help',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: HELP,\n\t\t\t\thelp: 'Show/hide this help',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{key: '?', shift: true},\n\t\t\t\t\t{key: 'F1', shift: true}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'File',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: SAVE,\n\t\t\t\thelp: 'Save',\n\t\t\t\tcombinations: [{key: 's', ctrl: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'History',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: UNDO,\n\t\t\t\thelp: 'Undo',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, key: 'z'},\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: REDO,\n\t\t\t\thelp: 'Redo',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, shift: true, key: 'z'},\n\t\t\t\t\t{ctrl: true, key: 'y'},\n\t\t\t\t]\n\t\t\t}\n\n\t\t],\n\t},\n\t{\n\t\tname: 'Relationship editing',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: ADD_VERTEX,\n\t\t\t\thelp: 'Add relationship vertex',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{alt: true, click: true},\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ADD_LABEL_VERTEX,\n\t\t\t\thelp: 'Add label anchor relationship vertex',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{alt: true, shift: true, click: true},\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DEL_VERTEX,\n\t\t\t\thelp: 'Remove relationship vertex',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{key: 'DELETE'},\n\t\t\t\t\t{key: 'BACKSPACE'}\n\t\t\t\t]\n\t\t\t},\n\t\t]\n\t},\n\t{\n\t\tname: 'Zoom',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: ZOOM_IN,\n\t\t\t\thelp: 'Zoom in',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, key: '='}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ZOOM_OUT,\n\t\t\t\thelp: 'Zoom out',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, key: '-'}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ZOOM_FIT,\n\t\t\t\thelp: 'Zoom - fit',\n\t\t\t\tcombinations: [{ctrl: true, key: '9'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ZOOM_100,\n\t\t\t\thelp: 'Zoom 100%',\n\t\t\t\tcombinations: [{ctrl: true, key: '0'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'wheel_zoom',\n\t\t\t\thelp: 'Zoom in/out with mouse wheel',\n\t\t\t\tcombinations: [{wheel: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Mouse Interactions',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: PAN_VIEW,\n\t\t\t\thelp: 'Pan view (drag empty space)',\n\t\t\t\tcombinations: [{click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: SELECT_ELEMENT,\n\t\t\t\thelp: 'Select element',\n\t\t\t\tcombinations: [{click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MULTI_SELECT,\n\t\t\t\thelp: 'Add/remove from selection',\n\t\t\t\tcombinations: [{shift: true, click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: BOX_SELECT,\n\t\t\t\thelp: 'Box selection (drag empty space)',\n\t\t\t\tcombinations: [{shift: true, click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_ELEMENTS,\n\t\t\t\thelp: 'Move selected elements',\n\t\t\t\tcombinations: [{click: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Select',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: SELECT_ALL,\n\t\t\t\thelp: 'Select All',\n\t\t\t\tcombinations: [{ctrl: true, key: 'a'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DESELECT,\n\t\t\t\thelp: 'Deselect',\n\t\t\t\tcombinations: [{key: 'ESC'}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Move',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: MOVE_UP,\n\t\t\t\thelp: 'Move up (grid increment)',\n\t\t\t\tcombinations: [{key: 'UP'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_UP_FINE,\n\t\t\t\thelp: 'Move up (1 pixel)',\n\t\t\t\tcombinations: [{key: 'UP', shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_RIGHT,\n\t\t\t\thelp: 'Move right (grid increment)',\n\t\t\t\tcombinations: [{key: 'RIGHT'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_RIGHT_FINE,\n\t\t\t\thelp: 'Move right (1 pixel)',\n\t\t\t\tcombinations: [{key: 'RIGHT', shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_DOWN,\n\t\t\t\thelp: 'Move down (grid increment)',\n\t\t\t\tcombinations: [{key: 'DOWN'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_DOWN_FINE,\n\t\t\t\thelp: 'Move down (1 pixel)',\n\t\t\t\tcombinations: [{key: 'DOWN', shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_LEFT,\n\t\t\t\thelp: 'Move left (grid increment)',\n\t\t\t\tcombinations: [{key: 'LEFT'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_LEFT_FINE,\n\t\t\t\thelp: 'Move left (1 pixel)',\n\t\t\t\tcombinations: [{key: 'LEFT', shift: true}]\n\t\t\t},\n\t\t]\n\t},\n\t\t\t{\n\t\t\tname: 'View',\n\t\t\tlist: [\n\t\t\t\t{\n\t\t\t\t\tid: TOGGLE_DRAG_MODE,\n\t\t\t\t\thelp: 'Toggle between pan and select mode',\n\t\t\t\t\tcombinations: [{key: 't'}]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: RESET_POSITION,\n\t\t\t\t\thelp: 'Reset position and view',\n\t\t\t\t\tcombinations: [{key: 'Home', ctrl: true}]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t{\n\t\tname: 'Alignment',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: ALIGN_HORIZONTAL,\n\t\t\t\thelp: 'Align selected elements horizontally',\n\t\t\t\tcombinations: [{key: 'h', ctrl: true, shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ALIGN_VERTICAL,\n\t\t\t\thelp: 'Align selected elements vertically',\n\t\t\t\tcombinations: [{key: 'a', ctrl: true, shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DISTRIBUTE_HORIZONTAL,\n\t\t\t\thelp: 'Distribute selected elements horizontally',\n\t\t\t\tcombinations: [{key: 'h', ctrl: true, alt: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DISTRIBUTE_VERTICAL,\n\t\t\t\thelp: 'Distribute selected elements vertically',\n\t\t\t\tcombinations: [{key: 'v', ctrl: true, alt: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Layout',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: AUTO_LAYOUT,\n\t\t\t\thelp: 'Auto layout all elements',\n\t\t\t\tcombinations: [{key: 'l', ctrl: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Grid',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: TOGGLE_GRID,\n\t\t\t\thelp: 'Toggle grid visibility',\n\t\t\t\tcombinations: [{key: 'g', ctrl: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: TOGGLE_SNAP_TO_GRID,\n\t\t\t\thelp: 'Toggle snap to grid',\n\t\t\t\tcombinations: [{key: 'g', ctrl: true, shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: SNAP_ALL_TO_GRID,\n\t\t\t\thelp: 'Snap all elements to grid',\n\t\t\t\tcombinations: [{key: 'g', ctrl: true, alt: true}]\n\t\t\t}\n\t\t]\n\t}\n]\n\nconst shortcutMap = shortcuts\n\t.reduce((lst, s) => lst.concat(s.list), [] as Shortcut[])\n\t.reduce<{ [k: string]: Shortcut }>((map, s) => {\n\t\tmap[s.id] = s;\n\t\treturn map\n\t}, {})\n\nconst checkKey = (e: KeyboardEvent | MouseEvent, shortcut: Shortcut, click: boolean, wheel: boolean) => {\n\treturn shortcut.combinations.some(c => {\n\t\tif (Boolean(c.shift) != e.shiftKey) return false\n\t\t// Use platform-appropriate modifier key\n\t\tif (c.ctrl) {\n\t\t\tconst modifierKey = getModifierKeyProperty(e as KeyboardEvent);\n\t\t\tif (!modifierKey) return false;\n\t\t}\n\t\tif (Boolean(c.alt) != e.altKey) return false\n\t\tif (click) return c.click\n\t\tif (wheel) return c.wheel\n\t\tif (c.key) {\n\t\t\tconst ke = e as KeyboardEvent\n\t\t\tif (c.key == 'DELETE') return ke.key == 'Delete'\n\t\t\tif (c.key == 'BACKSPACE') return ke.key == 'Backspace'\n\t\t\tif (c.key == 'ESC') return ke.key == 'Escape'\n\t\t\tif (c.key == 'UP') return ke.key == 'ArrowUp'\n\t\t\tif (c.key == 'DOWN') return ke.key == 'ArrowDown'\n\t\t\tif (c.key == 'LEFT') return ke.key == 'ArrowLeft'\n\t\t\tif (c.key == 'RIGHT') return ke.key == 'ArrowRight'\n\t\t\treturn c.key && ke.key && c.key.toLowerCase() == ke.key.toLowerCase()\n\t\t}\n\t\treturn false\n\t})\n}\n\nexport const findShortcut = (e: KeyboardEvent | MouseEvent, click = false, wheel = false) => {\n\t// Find all matching shortcuts\n\tconst matches = Object.keys(shortcutMap).filter(k => checkKey(e, shortcutMap[k], click, wheel))\n\t\n\tif (matches.length === 0) return undefined\n\tif (matches.length === 1) return matches[0]\n\t\n\t// If multiple matches, prefer the one with more specific modifiers\n\t// Sort by number of modifiers (shift, ctrl, alt) in descending order\n\tconst sortedMatches = matches.sort((a, b) => {\n\t\tconst aShortcut = shortcutMap[a]\n\t\tconst bShortcut = shortcutMap[b]\n\t\t\n\t\tconst aModifiers = aShortcut.combinations[0]\n\t\tconst bModifiers = bShortcut.combinations[0]\n\t\t\n\t\tconst aCount = (aModifiers.shift ? 1 : 0) + (aModifiers.ctrl ? 1 : 0) + (aModifiers.alt ? 1 : 0)\n\t\tconst bCount = (bModifiers.shift ? 1 : 0) + (bModifiers.ctrl ? 1 : 0) + (bModifiers.alt ? 1 : 0)\n\t\t\n\t\treturn bCount - aCount // Descending order (more modifiers first)\n\t})\n\t\n\treturn sortedMatches[0]\n}\n\nconst comboText = (c: Combination) => {\n\treturn [\n\t\tc.ctrl && getModifierKeyName().toUpperCase(),\n\t\tc.shift && 'SHIFT',\n\t\tc.alt && 'ALT',\n\t\tc.key && (c.key.length > 1 ? c.key : `\"${c.key.toUpperCase()}\"`),\n\t\tc.click && 'CLICK',\n\t\tc.wheel && 'WHEEL'\n\t].filter(Boolean).join(' + ')\n}\n\nexport const Help: FC = () => {\n\treturn
\n\t\t

Shortcuts

\n\t\t\n\t\t\t\n\t\t\t{\n\t\t\t\tshortcuts.map(section => <>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{section.list.map(item => \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t)\n\t\t\t}\n\t\t\t\n\t\t
{section.name}
{item.combinations.map(comboText).join(', ')}{item.help}
\n\t
\n}\n","/**\n * Robust platform detection utilities\n */\n\n/**\n * Detects if the user is on a Mac platform using multiple detection methods\n * for maximum compatibility across browsers and future-proofing.\n */\nexport const isMac = (): boolean => {\n if (typeof navigator === 'undefined') return false;\n \n // Method 1: Check userAgentData (modern browsers, most reliable)\n if ('userAgentData' in navigator && (navigator as any).userAgentData) {\n const platform = (navigator as any).userAgentData.platform;\n if (platform && platform.toLowerCase().includes('mac')) {\n return true;\n }\n }\n \n // Method 2: Check userAgent string (widely supported)\n const userAgent = navigator.userAgent.toLowerCase();\n if (userAgent.includes('mac os') || userAgent.includes('macintosh')) {\n return true;\n }\n \n // Method 3: Check platform (fallback, deprecated but still widely supported)\n if (navigator.platform) {\n const platform = navigator.platform.toLowerCase();\n if (platform.includes('mac') || platform.includes('darwin')) {\n return true;\n }\n }\n \n // Method 4: Check for Mac-specific features as additional validation\n try {\n const testEvent = new KeyboardEvent('keydown', { metaKey: true });\n if (testEvent.metaKey !== undefined) {\n // Additional heuristic: Mac typically has different key layouts\n return /mac|darwin|os x/i.test(navigator.userAgent);\n }\n } catch (e) {\n // Ignore errors in older browsers\n }\n \n return false;\n};\n\n/**\n * Gets the appropriate modifier key name for the current platform\n */\nexport const getModifierKeyName = (): string => {\n return isMac() ? 'Cmd' : 'Ctrl';\n};\n\n/**\n * Gets the appropriate modifier key property for keyboard events\n */\nexport const getModifierKeyProperty = (event: KeyboardEvent): boolean => {\n return isMac() ? event.metaKey : event.ctrlKey;\n};\n\n/**\n * Gets the appropriate Alt key name for the current platform\n */\nexport const getAltKeyName = (): string => {\n return isMac() ? 'Option' : 'Alt';\n}; ","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/runtime/sourceMaps.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `html, body, #root {\n height: 100%;\n}\n\nbody {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #666;\n margin: 0;\n}\n\n#root {\n display: flex;\n flex-direction: column;\n}\n#root > div.graph {\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 10px;\n background-color: #f0f0f0;\n border-bottom: 1px solid #cccccc;\n}\n\n.toolbar > div {\n display: flex;\n align-items: center;\n}\n\n.toolbar button {\n padding: 5px 8px;\n margin: 0 2px;\n cursor: pointer;\n}\n\n.toolbar button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Drag mode toggle button styles */\n.toolbar button.mode-toggle {\n position: relative;\n border: 1px solid #8f9fc9;\n width: 40px;\n min-height: 28px;\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\n border-color: #8f9fc9;\n color: white;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n}\n\n.toolbar button.mode-toggle:hover {\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\n border-color: #abb8db;\n}\n\n.toolbar button.mode-toggle.select-mode:active {\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\n}\n\n/* Pan mode and active toggle - darker blue */\n.toolbar button.mode-toggle.pan-mode,\n.toolbar button.active-toggle {\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n.toolbar button.mode-toggle.pan-mode:hover,\n.toolbar button.active-toggle:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\n.toolbar button.mode-toggle.pan-mode:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n/* Toggle buttons when inactive - gray styling */\n.toolbar button.inactive-toggle {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n.toolbar button.inactive-toggle:hover {\n background: #b1b1b1;\n}\n\n.toolbar button.inactive-toggle:active {\n background: #a1a1a1;\n}\n\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\n.toolbar button .fas {\n font-size: 1em;\n vertical-align: middle;\n}\n\n/* Zoom percentage display */\n.toolbar button.zoom-display {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\n padding: 6px 8px;\n text-align: center;\n}\n\n.toolbar-group {\n display: flex;\n align-items: center;\n margin-right: 25px; /* Large space between groups */\n}\n\n.toolbar-group:last-child {\n margin-right: 0; /* Remove right margin from the last group (help button) */\n}\n\nbutton {\n border: none;\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n border-radius: 3px;\n padding: 6px 10px;\n color: #fff;\n outline: none;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n margin-right: 5px;\n min-width: 32px;\n min-height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n line-height: 1;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n position: relative;\n}\n\nbutton:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\nbutton:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n}\n\nbutton:last-child {\n margin-right: 0;\n}\n\n/* Save button when no changes - gray */\nbutton.action {\n background: #c1c1c1;\n color: #999;\n}\n\nbutton.action:active {\n background: #b1b1b1;\n}\n\n/* Save button when there are changes - orange */\nbutton.grp {\n background: linear-gradient(to bottom, #ee9564, #de7d48);\n margin-right: 0;\n}\n\nbutton.grp:active {\n background: #d67540;\n}\n\n/* Auto-arrange button with AI purple to blue gradient */\nbutton.auto-arrange {\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\n border-color: #4a4c93;\n}\n\nbutton.auto-arrange:hover {\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\n border-color: #5a5ca3;\n}\n\nbutton.auto-arrange:active {\n background: linear-gradient(135deg, #593B83, #357abd);\n border-color: #3a3c83;\n}\n\nselect {\n border: 1px solid #ccc;\n background: white;\n border-radius: 3px;\n padding: 3px 7px;\n color: #666;\n outline: none;\n margin-right: 5px;\n font-size: 12px;\n}\n\nselect:disabled {\n background: #f5f5f5;\n color: #999;\n}\n\nbutton:disabled {\n background: #c1c1c1;\n color: #999;\n}\n\n#root > div > svg {\n position: absolute;\n user-select: none;\n}\n\n\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\n stroke: #29c229;\n}\n.edge .v-dot {\n fill: transparent;\n stroke: transparent;\n stroke-width: 3px;\n cursor: pointer;\n transition: stroke 0.15s ease;\n}\n.edge .v-dot:hover {\n stroke: #999;\n fill: rgba(153, 153, 153, 0.1);\n}\n.edge .v-dot.selected {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.1);\n}\n.edge .v-dot.selected:hover {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.2);\n}\n.edge .v-dot.auto.selected {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.1);\n}\n.edge .v-dot.auto.selected:hover {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.2);\n}\ncircle#prj {\n fill: none;\n stroke: #777;\n}\n\n.nodeShadow {\n fill: none;\n stroke-width: 4px;\n stroke: rgba(0, 0, 0, 0.13);\n}\n\ng.node {\n user-select: none;\n cursor: default;\n}\n\ng.node.linked {\n cursor: pointer;\n}\n\ng.node text {\n pointer-events: none;\n}\n\n.icon {\n fill: #aaa;\n stroke: #fff;\n}\n#icon-cube {\n fill: #aaa;\n}\n\n/* Ensure all button icons are uncolored */\nbutton .icon,\nbutton svg,\nbutton path {\n fill: currentColor !important;\n stroke: none !important;\n}\n\n/* Font Awesome icon styling in buttons */\nbutton i {\n font-size: 12px;\n color: inherit;\n}\n\nrect.elastic {\n pointer-events: none;\n stroke: none;\n fill: #3bd8281f;\n display: none;\n}\nrect.elastic.on {\n display: block;\n}\n\n.popover {\n position: absolute;\n top: 50px;\n bottom: 10px;\n overflow: auto;\n right: 10px;\n background: ghostwhite;\n padding: 30px;\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\n border: solid 1px #eee;\n}\n\n.popover th {\n text-align: left;\n padding: 20px 0px 10px;\n}\n.popover td {\n padding-right: 20px;\n font-size: 14px;\n}\n\n/* Simple tooltip system with smart positioning */\n[data-tooltip] {\n position: relative;\n}\n\n[data-tooltip]:hover::after {\n content: attr(data-tooltip);\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.9);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n font-weight: normal !important;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n margin-top: 5px;\n animation: tooltip-appear 0.1s ease-out;\n min-width: 120px;\n max-width: calc(100vw - 20px);\n box-sizing: border-box;\n}\n\n[data-tooltip]:hover::before {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-bottom-color: rgba(0, 0, 0, 0.9);\n z-index: 1000;\n pointer-events: none;\n margin-top: 1px;\n animation: tooltip-appear 0.1s ease-out;\n}\n\n/* Special handling for rightmost elements that might overflow */\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 0;\n transform: none;\n}\n\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 16px;\n transform: none;\n}\n\n@keyframes tooltip-appear {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(-5px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\nselect {\n position: relative;\n}\n\n/* Robot shape internal elements - inherit stroke from parent node */\n.node .robot-eye-socket {\n fill: none;\n stroke: inherit;\n stroke-width: 2;\n}\n\n.node .robot-eye {\n fill: currentColor;\n stroke: none;\n}\n\n.node .robot-mouth {\n stroke: inherit;\n}\n\n.node .robot-antenna {\n stroke: inherit;\n}\n\n.node .robot-antenna-ball {\n fill: currentColor;\n stroke: inherit;\n stroke-width: 1.5;\n}\n\n.node .robot-panel {\n stroke: inherit;\n}\n\n.node .robot-indicator {\n fill: currentColor;\n stroke: none;\n}`, \"\",{\"version\":3,\"sources\":[\"webpack://./src/style.css\"],\"names\":[],\"mappings\":\"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\",\"sourcesContent\":[\"html, body, #root {\\n height: 100%;\\n}\\n\\nbody {\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\\n color: #666;\\n margin: 0;\\n}\\n\\n#root {\\n display: flex;\\n flex-direction: column;\\n}\\n#root > div.graph {\\n flex: 1;\\n overflow: auto;\\n position: relative;\\n}\\n\\n.toolbar {\\n display: flex;\\n justify-content: space-between;\\n align-items: center;\\n padding: 4px 10px;\\n background-color: #f0f0f0;\\n border-bottom: 1px solid #cccccc;\\n}\\n\\n.toolbar > div {\\n display: flex;\\n align-items: center;\\n}\\n\\n.toolbar button {\\n padding: 5px 8px;\\n margin: 0 2px;\\n cursor: pointer;\\n}\\n\\n.toolbar button:disabled {\\n opacity: 0.5;\\n cursor: not-allowed;\\n}\\n\\n/* Drag mode toggle button styles */\\n.toolbar button.mode-toggle {\\n position: relative;\\n border: 1px solid #8f9fc9;\\n width: 40px;\\n min-height: 28px;\\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\\n border-color: #8f9fc9;\\n color: white;\\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\\n}\\n\\n.toolbar button.mode-toggle:hover {\\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\\n border-color: #abb8db;\\n}\\n\\n.toolbar button.mode-toggle.select-mode:active {\\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\\n}\\n\\n/* Pan mode and active toggle - darker blue */\\n.toolbar button.mode-toggle.pan-mode,\\n.toolbar button.active-toggle {\\n background: linear-gradient(to bottom, #4a90e2, #357abd);\\n border-color: #2968a3;\\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\\n}\\n\\n.toolbar button.mode-toggle.pan-mode:hover,\\n.toolbar button.active-toggle:hover {\\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\\n}\\n\\n.toolbar button.mode-toggle.pan-mode:active {\\n background: linear-gradient(to bottom, #357abd, #2968a3);\\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\\n}\\n\\n/* Toggle buttons when inactive - gray styling */\\n.toolbar button.inactive-toggle {\\n background: #c1c1c1;\\n color: #999;\\n border-color: #c1c1c1;\\n box-shadow: none;\\n}\\n\\n.toolbar button.inactive-toggle:hover {\\n background: #b1b1b1;\\n}\\n\\n.toolbar button.inactive-toggle:active {\\n background: #a1a1a1;\\n}\\n\\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\\n background: #c1c1c1;\\n color: #999;\\n border-color: #c1c1c1;\\n box-shadow: none;\\n}\\n\\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\\n.toolbar button .fas {\\n font-size: 1em;\\n vertical-align: middle;\\n}\\n\\n/* Zoom percentage display */\\n.toolbar button.zoom-display {\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\\n font-size: 11px;\\n font-weight: 600;\\n font-variant-numeric: tabular-nums;\\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\\n padding: 6px 8px;\\n text-align: center;\\n}\\n\\n.toolbar-group {\\n display: flex;\\n align-items: center;\\n margin-right: 25px; /* Large space between groups */\\n}\\n\\n.toolbar-group:last-child {\\n margin-right: 0; /* Remove right margin from the last group (help button) */\\n}\\n\\nbutton {\\n border: none;\\n background: linear-gradient(to bottom, #4a90e2, #357abd);\\n border-color: #2968a3;\\n border-radius: 3px;\\n padding: 6px 10px;\\n color: #fff;\\n outline: none;\\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\\n margin-right: 5px;\\n min-width: 32px;\\n min-height: 28px;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n font-size: 14px;\\n line-height: 1;\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\\n position: relative;\\n}\\n\\nbutton:hover {\\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\\n}\\n\\nbutton:active {\\n background: linear-gradient(to bottom, #357abd, #2968a3);\\n}\\n\\nbutton:last-child {\\n margin-right: 0;\\n}\\n\\n/* Save button when no changes - gray */\\nbutton.action {\\n background: #c1c1c1;\\n color: #999;\\n}\\n\\nbutton.action:active {\\n background: #b1b1b1;\\n}\\n\\n/* Save button when there are changes - orange */\\nbutton.grp {\\n background: linear-gradient(to bottom, #ee9564, #de7d48);\\n margin-right: 0;\\n}\\n\\nbutton.grp:active {\\n background: #d67540;\\n}\\n\\n/* Auto-arrange button with AI purple to blue gradient */\\nbutton.auto-arrange {\\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\\n border-color: #4a4c93;\\n}\\n\\nbutton.auto-arrange:hover {\\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\\n border-color: #5a5ca3;\\n}\\n\\nbutton.auto-arrange:active {\\n background: linear-gradient(135deg, #593B83, #357abd);\\n border-color: #3a3c83;\\n}\\n\\nselect {\\n border: 1px solid #ccc;\\n background: white;\\n border-radius: 3px;\\n padding: 3px 7px;\\n color: #666;\\n outline: none;\\n margin-right: 5px;\\n font-size: 12px;\\n}\\n\\nselect:disabled {\\n background: #f5f5f5;\\n color: #999;\\n}\\n\\nbutton:disabled {\\n background: #c1c1c1;\\n color: #999;\\n}\\n\\n#root > div > svg {\\n position: absolute;\\n user-select: none;\\n}\\n\\n\\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\\n stroke: #29c229;\\n}\\n.edge .v-dot {\\n fill: transparent;\\n stroke: transparent;\\n stroke-width: 3px;\\n cursor: pointer;\\n transition: stroke 0.15s ease;\\n}\\n.edge .v-dot:hover {\\n stroke: #999;\\n fill: rgba(153, 153, 153, 0.1);\\n}\\n.edge .v-dot.selected {\\n stroke: #29c229;\\n fill: rgba(41, 194, 41, 0.1);\\n}\\n.edge .v-dot.selected:hover {\\n stroke: #29c229;\\n fill: rgba(41, 194, 41, 0.2);\\n}\\n.edge .v-dot.auto.selected {\\n stroke: #777;\\n fill: rgba(119, 119, 119, 0.1);\\n}\\n.edge .v-dot.auto.selected:hover {\\n stroke: #777;\\n fill: rgba(119, 119, 119, 0.2);\\n}\\ncircle#prj {\\n fill: none;\\n stroke: #777;\\n}\\n\\n.nodeShadow {\\n fill: none;\\n stroke-width: 4px;\\n stroke: rgba(0, 0, 0, 0.13);\\n}\\n\\ng.node {\\n user-select: none;\\n cursor: default;\\n}\\n\\ng.node.linked {\\n cursor: pointer;\\n}\\n\\ng.node text {\\n pointer-events: none;\\n}\\n\\n.icon {\\n fill: #aaa;\\n stroke: #fff;\\n}\\n#icon-cube {\\n fill: #aaa;\\n}\\n\\n/* Ensure all button icons are uncolored */\\nbutton .icon,\\nbutton svg,\\nbutton path {\\n fill: currentColor !important;\\n stroke: none !important;\\n}\\n\\n/* Font Awesome icon styling in buttons */\\nbutton i {\\n font-size: 12px;\\n color: inherit;\\n}\\n\\nrect.elastic {\\n pointer-events: none;\\n stroke: none;\\n fill: #3bd8281f;\\n display: none;\\n}\\nrect.elastic.on {\\n display: block;\\n}\\n\\n.popover {\\n position: absolute;\\n top: 50px;\\n bottom: 10px;\\n overflow: auto;\\n right: 10px;\\n background: ghostwhite;\\n padding: 30px;\\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\\n border: solid 1px #eee;\\n}\\n\\n.popover th {\\n text-align: left;\\n padding: 20px 0px 10px;\\n}\\n.popover td {\\n padding-right: 20px;\\n font-size: 14px;\\n}\\n\\n/* Simple tooltip system with smart positioning */\\n[data-tooltip] {\\n position: relative;\\n}\\n\\n[data-tooltip]:hover::after {\\n content: attr(data-tooltip);\\n position: absolute;\\n top: 100%;\\n left: 50%;\\n transform: translateX(-50%);\\n background: rgba(0, 0, 0, 0.9);\\n color: white;\\n padding: 6px 12px;\\n border-radius: 4px;\\n font-size: 12px !important;\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\\n font-weight: normal !important;\\n white-space: nowrap;\\n z-index: 1000;\\n pointer-events: none;\\n margin-top: 5px;\\n animation: tooltip-appear 0.1s ease-out;\\n min-width: 120px;\\n max-width: calc(100vw - 20px);\\n box-sizing: border-box;\\n}\\n\\n[data-tooltip]:hover::before {\\n content: '';\\n position: absolute;\\n top: 100%;\\n left: 50%;\\n transform: translateX(-50%);\\n border: 4px solid transparent;\\n border-bottom-color: rgba(0, 0, 0, 0.9);\\n z-index: 1000;\\n pointer-events: none;\\n margin-top: 1px;\\n animation: tooltip-appear 0.1s ease-out;\\n}\\n\\n/* Special handling for rightmost elements that might overflow */\\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\\n /* Apply to last 2 toolbar groups (save and help buttons) */\\n left: auto;\\n right: 0;\\n transform: none;\\n}\\n\\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\\n /* Apply to last 2 toolbar groups (save and help buttons) */\\n left: auto;\\n right: 16px;\\n transform: none;\\n}\\n\\n@keyframes tooltip-appear {\\n from {\\n opacity: 0;\\n transform: translateX(-50%) translateY(-5px);\\n }\\n to {\\n opacity: 1;\\n transform: translateX(-50%) translateY(0);\\n }\\n}\\n\\nselect {\\n position: relative;\\n}\\n\\n/* Robot shape internal elements - inherit stroke from parent node */\\n.node .robot-eye-socket {\\n fill: none;\\n stroke: inherit;\\n stroke-width: 2;\\n}\\n\\n.node .robot-eye {\\n fill: currentColor;\\n stroke: none;\\n}\\n\\n.node .robot-mouth {\\n stroke: inherit;\\n}\\n\\n.node .robot-antenna {\\n stroke: inherit;\\n}\\n\\n.node .robot-antenna-ball {\\n fill: currentColor;\\n stroke: inherit;\\n stroke-width: 1.5;\\n}\\n\\n.node .robot-panel {\\n stroke: inherit;\\n}\\n\\n.node .robot-indicator {\\n fill: currentColor;\\n stroke: none;\\n}\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n"],"names":["listViews","model","viewsList","Object","keys","views","filter","section","endsWith","forEach","s","v","push","key","title","graphs","camelToWords","camel","split","replace","charAt","toUpperCase","slice","Toolbar","currentID","onViewChange","graph","onAutoLayout","onSave","onToggleHelp","saving","layouting","dragMode","setDragMode","jsx_runtime","jsxs","className","children","jsx","ViewSelector","ToolbarActions","length","onChange","e","target","value","disabled","hidden","map","view","style","marginLeft","fontWeight","display","alignItems","DragModeButton","UndoRedoButtons","AlignmentButtons","LayoutControls","GridControls","ZoomControls","SaveButton","HelpButton","onClick","modKey","platform","sy","Fragment","undo","redo","alignSelectionH","alignSelectionV","transform","distributeSelectionH","distributeSelectionV","gridVisible","setGridVisible","react","useState","isGridVisible","snapToGrid","setSnapToGrid","isSnapToGrid","useEffect","updateGridState","window","addEventListener","removeEventListener","toggleGrid","toggleSnapToGrid","snapAllToGrid","ZoomDisplay","zoom","setZoomState","updateZoom","currentZoom","Math","round","graph_view_graph","IX","interval","setInterval","clearInterval","a_","max","min","fitToView","hasChanges","setHasChanges","checkChanges","changed","Help","lazy","Promise","resolve","then","__webpack_require__","bind","module","default","Graph","Root","layout","chunk_62JRHF6Z","Kd","BV","qh","path","element","ModelPane","layouts","refreshGraph","URLSearchParams","document","location","search","get","searchParams","setSearchParams","ok","decodeURI","helpVisible","setHelpVisible","viewKey","elements","Map","relations","collectRels","el","Array","isArray","relationships","rel","set","id","people","softwareSystems","containers","el1","parent","components","el2","deploymentNodes","containerInstances","item","containerId","recAddNodes","infrastructureNodes","some","getView","jg","metadata","name","description","version","groupingIDs","ref","softwareSystemId","find","p","enterprise","styles","cssClassName","tag","toLowerCase","varPrefix","background","colorToVarMap","color","stroke","elementViewKey","containerViews","candidate","lookupContainerViewKey","undefined","sub","tags","technology","addNode","encodedViewKey","encodeURIComponent","href","exportHref","url","nodeLink","properties","nodesMap","has","sourceId","console","warn","destinationId","routing","addEdge","vertices","level","i","sort","a","b","groupMembers","addGroup","init","parseView","useGraph","handleAutoLayout","setLayouting","useCallback","async","options","direction","opts","autoLayout","error","alert","useAutoLayout","handleSave","setSaving","fetch","method","body","exportSVG","status","setSaved","useSave","ViewRedirect","handleToggleHelp","params","fromEntries","entries","auto","save","compact","layoutOpts","includes","compactLayout","cancelled","toggleHelp","saveLayout","handleKeyDown","shortcut","shortcuts","Yp","preventDefault","aX","t9","Jk","DE","Vy","Hd","_t","resetView","Op","hZ","OE","Gg","moveSelected","getGridSize","J8","b3","iD","uK","l8","rB","mt","useKeyboardShortcuts","handleViewChange","handleSelect","m","log","obj","JSON","parse","stringify","Suspense","fallback","data","onSelect","svgTextWrap","text","width","attrs","svg","createElementNS","appendChild","measure","node","setAttribute","attr","createTextNode","height","getBBox","removeChild","clean","textMeasure","maxW","ret","trim","words","lines","currentLine","word","join","brokenParts","maxWidth","parts","currentPart","testPart","breakLongWord","newLine","size","reduce","concat","create","type","k","String","classList","add","use","this","setAttributeNS","d","t","textContent","textArea","fontSize","bold","x","y","anchor","txt","line","span","dy","append","rect","r","rx","ry","icon","expand","expanded","g","setPosition","insideBox","centeredBox","intersectRectFull","p1","p2","box","w","h","q","q1","q2","denominator","numerator1","numerator2","result","onLine1","onLine2","segmentIntersection","intersectRect","intersectEllipse","ellCenter","nodeCenter","point","si","c","radicand_sqrt","sqrt","pos","project","atob","len","dot","cabDistance","abs","cylinderRadiusY","shapeLabelOffsetY","shape","D3Element","_el","constructor","insert","insertBefore","querySelector","bbox","rounded","shapeSvg","intersect","_ellipse","mobiledeviceportrait","shapes","roundedbox","component","dx","cylinder","cy","person","circle","ellipse","hexagon","sz","n","folder","mobiledevicelandscape","mobiledevice","pipe","robot","headSize","headR","antennaH","antennaR","eyeR","eyeSpacing","earW","earH","bodyW","bodyTop","bodyH","headTop","eyeY","mouthY","mouthW","webbrowser","Undo","versions","lastSavedPos","exportDoc","importDoc","change","tmpPreviousState","func","timeout","context","clearTimeout","setTimeout","apply","debounce","saveNow","beforeChange","deepClone","currentState","Error","splice","doc","structuredClone","SVG_STYLES","applyStyle","setProperty","toString","calculateDistance","textBlock","gapAfter","field","wrapped","lineHeight","defaultEdgeStyle","thickness","opacity","dashed","defaultNodeStyle","GraphData","edges","edgeVertices","groupsMap","_undo","_gridVisible","_snapToGrid","_gridSize","_skipAutoFit","exportLayout","lo","importLayout","label","link","nodeStyle","minimumHeight","contentLayout","subtitle","nodeWidth","textWidth","HORIZONTAL_PADDING","blocks","textHeight","block","VERTICAL_PADDING","layoutNodeContent","requiredHeight","nodes","from","values","fromNode","toNode","edge","to","initVertex","edgeID","input","charCodeAt","imul","fnv1a36","stableVertexID","userDeletedVertices","nodesOrGroups","group","Boolean","setNodeSelected","selected","remove","updateEdgesSel","moveNode","disableSnap","skipUndo","snapped","redrawEdges","redrawGroups","moveEdgeVertex","redrawEdge","insertEdgeVertex","isLabel","deleteEdgeVertex","index","indexOf","delete","alignTopLeft","contentBounds","calculateContentBounds","offsetX","offsetY","vertex","resetPanTransform","getZoom","zoomGroup","graphData","bb","parentElement","clientWidth","clientHeight","updatePanningOptimized","clearViewState","shouldSkipAutoFit","updatePanning","buildEdge","buildGroup","originalSvg","exportSvg","cloneNode","querySelectorAll","getAttribute","removeAttribute","exportElastic","exportWidth","padding","exportHeight","exportZoomGroup","convertStylesToCustomProperties","script","createElement","firstChild","outerHTML","fill","minX","Infinity","minY","maxX","maxY","left","right","top","bottom","centerX","centerY","approxLabelSize","full","lst","rerender","coordinates","startsWith","normalizedPoint","assign","edgeId","an","ae","labelVertex","insertPos","labelPos","fullPath","minDistance","bestSegmentIndex","distance","distanceToSegment","findOptimalLabelPosition","projectedPos","segmentStart","segmentEnd","A","B","C","D","lenSq","param","projectPointOntoSegment","projectLabelOntoSegment","selectedNodes","selectedVertices","allElements","spacing","newX","newY","setEdgeSelected","viewportWidth","viewportHeight","zoomX","zoomY","optimalZoom","finalZoom","translateX","translateY","saveViewState","saveLayoutState","restoreLayoutState","state","updateGridDisplay","dispatchEvent","CustomEvent","snappedX","snappedY","existingGrid","existingGridRect","defs","pattern","clickListener","selectListener","dragging","buildGraph","onNodeSelect","innerHTML","__data","_buildGraph","elasticEl","addCursorInteraction","setZoom","zoomG","nodesG","edgesG","groupsG","gdata","content","shapeType","nodeBorder","setBorderStyle","border","tg","buildNodeContent","Number","buildNode","n1","n2","position","sameEdges","spreadPos","spreadX","spreadY","unshift","firstRoutingVertex","lastRoutingVertex","calculateNodeIntersection","targetPoint","nodeShape","halfHeight","angle","atan2","cos","sin","rectX","rectY","topCurveY","bottomCurveY","ellipseY","discriminant","sqrt_d","x1","x2","radius","halfWidth","startIntersection","endIntersection","calculateEdgeVertices","labelPlacement","segment","labelIndex","findIndex","adjacentSegments","longest","targetLength","sum","traversed","segmentPosition","horizontalDistance","verticalDistance","orientation","calculateLabelPlacement","bg","placement","edgeText","edgeRect","buildEdgeLabel","segments","reverse","s2","intersectPolylineBox","createEdgeSegments","cx","p0","topExtension","bottomExtension","hexHeight","pad","groupRect","groupText","findClosestSegment","fnd","dst","POSITIVE_INFINITY","prj","pts","mouseToDrawing","getBoundingClientRect","z","currentTransform","getCurrentTransform","clientX","clientY","addCustomCursorInteraction","conn","ini","elastic","isPanning","panStartX","panStartY","initialTransform","pendingSelectionChange","pendingNavigation","hasDragged","suppressLinkClick","eventListeners","Element","closest","stopPropagation","md","convertEvent","changedTouches","onMouseMoveHandler","getSelection","setSelection","setTransform","drawingDx","drawingDy","setDragging","update","onMouseMove","ex","ey","onMouseUpHandler","navigation","end","boxSelection","shiftKey","onMouseUp","onMouseDownHandler","nodeFromEvent","effectiveMode","isSelected","translateMatch","match","parseFloat","getCurrentTransformLocal","startDrawingX","startDrawingY","pt","currentPt","currentDrawingX","currentDrawingY","createElastic","onMouseDown","event","handler","addDnd","existingCleanup","__cursorInteractionCleanup","getData","gd","beforeUnloadHandler","returnValue","setDotSelected","dotEl","mouseMoveHandler","altKey","removePrjDot","keyUpHandler","Zj","_s","clickHandler","wheelHandler","delta","sign","deltaY","newZoom","setZoomCentered","keyDownHandler","bl","Ur","hU","i1","mD","F","Gn","customInteractionCleanup","handles","handle","b1","b2","scaleMatch","oldZoom","container","newTranslateX","newTranslateY","nodeText","cursor","viewStateCache","graphId","restoreViewState","xx","yy","getEffectiveSpacing","userOptions","isGroup","effectiveConfig","nodeSpacing","layerSpacing","componentSpacing","groupMultiplier","getELKOptions","baseOptions","elk","elkGraph","layoutOptions","nodeMap","elkNodes","nodeHeight","arrowPadding","nodeParentGroup","groupParent","childGroupIDs","Set","member","elkGroups","buildELKGroup","existing","flatMap","elkGroup","groupAncestors","groupID","ancestors","current","labelWidth","elkEdge","sources","targets","labels","sourceID","destinationID","sourceAncestors","destinationAncestors","lowestCommonGroup","layoutedGraph","extractNodes","child","processEdgesFromELK","sections","startPoint","bendPoints","bp","endPoint","originalEdge","elkLabel","midIndex","floor","v1","v2","maxCols","ceil","col","createFallbackLayout","styleTagTransform","styleTagTransform_default","setAttributes","setAttributesWithoutAttributes_default","insertBySelector_default","domAPI","styleDomAPI_default","insertStyleElement","insertStyleElement_default","injectStylesIntoStyleTag_default","locals","Timer","callback","running","timeoutId","start","stop","isRunning","RefreshConnector","static","minDelay","maxDelay","handshakeTimeout","uri","fileChangeHandler","socket","nextDelay","connectionDesired","disconnectionReason","reconnectTimer","DEFAULT_OPTIONS","handleHandshakeTimeout","attemptReconnection","connect","isSocketConnected","prepareForConnection","createWebSocket","disconnect","close","readyState","WebSocket","OPEN","onopen","handleOpen","onclose","handleClose","onmessage","handleMessage","onerror","handleError","startHandshake","scheduleReconnection","message","processMessage","command","handleHelloMessage","handleReloadMessage","helloMessage","protocols","LIVERELOAD_PROTOCOLS","ver","sendCommand","send","src_Root","App","setState","loading","loadData","prev","modelResponse","layoutResponse","all","statusText","json","handleFileChange","S","LoadingScreen","ErrorScreen","onRetry","justifyContent","fontFamily","whiteSpace","flexDirection","backgroundColor","borderRadius","getElementById","client","createRoot","render","ADD_VERTEX","ADD_LABEL_VERTEX","DEL_VERTEX","ZOOM_IN","ZOOM_OUT","ZOOM_FIT","ZOOM_100","SELECT_ALL","DESELECT","MOVE_LEFT","MOVE_RIGHT","MOVE_UP","MOVE_DOWN","MOVE_LEFT_FINE","MOVE_RIGHT_FINE","MOVE_UP_FINE","MOVE_DOWN_FINE","TOGGLE_DRAG_MODE","ALIGN_HORIZONTAL","ALIGN_VERTICAL","DISTRIBUTE_HORIZONTAL","DISTRIBUTE_VERTICAL","AUTO_LAYOUT","RESET_POSITION","TOGGLE_GRID","TOGGLE_SNAP_TO_GRID","SNAP_ALL_TO_GRID","list","help","combinations","shift","ctrl","alt","click","wheel","shortcutMap","comboText","_utils_platform__WEBPACK_IMPORTED_MODULE_1__","react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__","colSpan","matches","SA","ke","checkKey","aShortcut","bShortcut","aModifiers","bModifiers","aCount","isMac","navigator","userAgentData","userAgent","KeyboardEvent","metaKey","test","ctrlKey","___CSS_LOADER_EXPORT___","_node_modules_pnpm_css_loader_7_1_4_webpack_5_109_2_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default","_node_modules_pnpm_css_loader_7_1_4_webpack_5_109_2_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default","names","mappings","sourcesContent","sourceRoot","__WEBPACK_DEFAULT_EXPORT__"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"main.js","mappings":"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","sources":["webpack://app/./src/parseModel.ts","webpack://app/./src/hooks.ts","webpack://app/./src/utils.ts","webpack://app/./src/components/Toolbar.tsx","webpack://app/./src/Root.tsx","webpack://app/./src/graph-view/defs.ts","webpack://app/./src/graph-view/svg-text.ts","webpack://app/./src/graph-view/svg-create.ts","webpack://app/./src/graph-view/intersect.ts","webpack://app/./src/graph-view/shapes.ts","webpack://app/./src/graph-view/undo.ts","webpack://app/./src/graph-view/constants.ts","webpack://app/./src/graph-view/node-content.ts","webpack://app/./src/graph-view/graph.ts","webpack://app/./src/graph-view/edge-utils.ts","webpack://app/./src/graph-view/layout.ts","webpack://app/./src/style.css?dd02","webpack://app/./src/websocket.ts","webpack://app/./src/index.tsx","webpack://app/./src/shortcuts.tsx","webpack://app/./src/utils/platform.ts","webpack://app/./src/style.css"],"sourcesContent":["import {GraphData, LayoutDirection, NodeLink} from \"./graph-view/graph\";\n\n\ninterface Model {\n\tname: string\n\tdescription: string\n\tversion: string\n\tmodel: {\n\t\tenterprise: {\n\t\t\tname: string\n\t\t}\n\t\tpeople: Element[]\n\t\tsoftwareSystems: Element[]\n\t\tdeploymentNodes: Element[]\n\t}\n\tviews: {\n\t\tsystemLandscapeViews: View[]\n\t\tcontainerViews: View[]\n\t\tcomponentViews: View[]\n\t\tdynamicViews: View[]\n\t\tdeploymentViews: View[]\n\t\tstyles: {\n\t\t\telements: {\n\t\t\t\t[key: string]: string\n\t\t\t}[];\n\t\t\trelationships: {\n\t\t\t\t[key: string]: string\n\t\t\t}[]\n\t\t}\n\t}\n}\n\ninterface Layouts {\n\t[key: string]: { // keyed by view key\n\t\t[key: string]: { x: number; y: number } // keyed by element id\n\t}\n}\n\ninterface Element {\n\tid: string;\n\tname: string;\n\ttechnology?: string;\n\tdescription?: string;\n\turl?: string;\n\tparent?: Element;\n\ttags?: string;\n\tlocation?: string;\n\tcontainers?: Element[];\n\tcomponents?: Element[];\n\trelationships?: Relation[];\n\tproperties?: { [key: string]: string }\n\tchildren?: Element[];\n\tinfrastructureNodes?: Element[];\n}\n\ninterface Relation {\n\tid: string;\n\tdescription: string;\n\ttags: string;\n\tsourceId: string;\n\tdestinationId: string;\n\ttechnology: string;\n\tinteractionStyle: string;\n}\n\ntype RankDirection = 'TopBottom' | 'BottomTop' | 'LeftRight' | 'RightLeft';\n\ninterface View {\n\tkey: string;\n\ttitle: string;\n\tdescription: string\n\tautomaticLayout?: {\n\t\trankDirection?: RankDirection;\n\t};\n\telements: {\n\t\tid: string\n\t}[];\n\trelationships: {\n\t\tid: string;\n\t\tvertices: { x: number; y: number }[];\n\t\trouting: string; // takes priority over style\n\t}[];\n\tsoftwareSystemId: string;\n}\n\nconst layoutDirections: Record = {\n\tTopBottom: 'DOWN',\n\tBottomTop: 'UP',\n\tLeftRight: 'RIGHT',\n\tRightLeft: 'LEFT',\n};\n\ninterface Metadata {\n\tname: string\n\tdescription: string\n\tversion: string\n\telements: {\n\t\tid: string\n\t\ttags?: string;\n\t\tlocation?: string;\n\t\tproperties?: { [key: string]: string };\n\t\telementViewKey?: string;\n\t\ttechnology?: string;\n\t\turl?: string;\n\t}[]\n}\n\nexport type ViewsList = {\n\tkey: string;\n\ttitle: string;\n\tsection: string;\n}[]\n\nexport const parseView = (model: Model, layouts: Layouts, viewKey: string) => {\n\n\tconst elements = new Map();\n\tconst relations = new Map();\n\n\tconst collectRels = (el: Element) => {\n\t\tif (Array.isArray(el.relationships)) {\n\t\t\tel.relationships.forEach(rel => {\n\t\t\t\trelations.set(rel.id, rel)\n\t\t\t})\n\t\t}\n\t}\n\n\t// People\n\tmodel.model.people && model.model.people.forEach((el: Element) => {\n\t\telements.set(el.id, el)\n\t\tif (Array.isArray(el.relationships)) {\n\t\t\tel.relationships.forEach(rel => {\n\t\t\t\trelations.set(rel.id, rel)\n\t\t\t})\n\t\t}\n\t})\n\t// Software Systems\n\tmodel.model.softwareSystems && model.model.softwareSystems.forEach((el: Element) => {\n\t\telements.set(el.id, el)\n\t\tcollectRels(el)\n\n\t\tif (Array.isArray(el.containers)) {\n\t\t\tel.containers.forEach((el1: Element) => {\n\t\t\t\tel1.parent = el;\n\t\t\t\telements.set(el1.id, el1)\n\t\t\t\tcollectRels(el1)\n\t\t\t\tif (Array.isArray(el1.components)) {\n\t\t\t\t\tel1.components.forEach((el2: Element) => {\n\t\t\t\t\t\tel2.parent = el1;\n\t\t\t\t\t\telements.set(el2.id, el2)\n\t\t\t\t\t\tcollectRels(el2)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\t// Deployment Nodes\n\tif (model.model.deploymentNodes) {\n\t\tconst containerInstances = (el: any) => {\n\t\t\tel.containerInstances && el.containerInstances.forEach((item: any) => {\n\t\t\t\tconst el1 = {...elements.get(item.containerId), id: item.id}\n\t\t\t\telements.set(el1.id, el1)\n\t\t\t\tel1.parent = el\n\t\t\t\tcollectRels(item)\n\t\t\t})\n\t\t}\n\n\t\tconst recAddNodes = (el: Element, parent: Element) => {\n\t\t\tel.parent = parent;\n\t\t\telements.set(el.id, el)\n\t\t\tcollectRels(el)\n\t\t\tcontainerInstances(el)\n\t\t\tel.children && el.children.forEach((el1: Element) => recAddNodes(el1, el))\n\t\t\tel.infrastructureNodes && el.infrastructureNodes.forEach((el1: Element) => recAddNodes(el1, el))\n\t\t}\n\n\t\tmodel.model.deploymentNodes.forEach((el: Element) => recAddNodes(el, null))\n\t}\n\n\t// Create graph from selected view\n\tconst {view, section} = getView(model, viewKey)\n\n\tif (!view) return null\n\n\tconst graph = new GraphData(view.key, view.title || view.key)\n\tconst rankDirection = view.automaticLayout?.rankDirection\n\tgraph.layoutDirection = rankDirection ? layoutDirections[rankDirection] : undefined\n\tconst metadata: Metadata = {name: graph.name, description: view.description, version: model.version, elements: []}\n\tgraph.metadata = metadata\n\n\tif (!view.elements) return graph\n\n\t//grouping rules - elements that are groups will not be nodes\n\tconst groupingIDs: { [key: string]: boolean } = {}\n\tif (section == 'deploymentViews' || section == 'containerViews') {\n\t\tview.elements.forEach(ref => {\n\t\t\tconst el = elements.get(ref.id)\n\t\t\tif (el?.parent) {\n\t\t\t\tgroupingIDs[el.parent.id] = true\n\t\t\t}\n\t\t})\n\t} else if (view.softwareSystemId) {\n\t\t//don't show grouping if the element is listed in the view\n\t\tif (!view.elements.find(ref => ref.id == view.softwareSystemId))\n\t\t\tgroupingIDs[view.softwareSystemId] = true\n\t} else if (section == 'systemLandscapeViews') {\n\t\t// create a virtual parent element from enterprise\n\t\tconst p: Element = {id: '__enterprise__', ...model.model.enterprise}\n\t\telements.set(p.id, p)\n\t\tif (model.model.people) model.model.people.filter(el => el.location != 'External').forEach(el => el.parent = p)\n\t\tif (model.model.softwareSystems) model.model.softwareSystems.filter(el => el.location != 'External').forEach(el => el.parent = p)\n\t\tgroupingIDs[p.id] = true\n\t}\n\n\tconst styles = model.views.styles\n\n\t// Build color-to-variable mapping for CSS custom properties theming\n\tconst cssClassName = (tag: string) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')\n\t\n\tif (styles?.elements) {\n\t\tstyles.elements.forEach(s => {\n\t\t\tif (s.tag) {\n\t\t\t\tconst varPrefix = `--mdl-${cssClassName(s.tag)}`\n\t\t\t\tif (s.background) graph.colorToVarMap.set(s.background as string, `${varPrefix}-bg`)\n\t\t\t\tif (s.color) graph.colorToVarMap.set(s.color as string, `${varPrefix}-color`)\n\t\t\t\tif (s.stroke) graph.colorToVarMap.set(s.stroke as string, `${varPrefix}-stroke`)\n\t\t\t}\n\t\t})\n\t}\n\n\tif (styles?.relationships) {\n\t\tstyles.relationships.forEach(s => {\n\t\t\tif (s.tag) {\n\t\t\t\tconst varPrefix = `--mdl-rel-${cssClassName(s.tag)}`\n\t\t\t\tif (s.color) graph.colorToVarMap.set(s.color as string, `${varPrefix}-color`)\n\t\t\t}\n\t\t})\n\t}\n\n\t//nodes\n\tview.elements.forEach((ref) => {\n\t\t// except grouping elements\n\t\tif (groupingIDs[ref.id]) return\n\n\t\tconst el = elements.get(ref.id)\n\t\tconst elementViewKey = el ? lookupContainerViewKey(model, el.id) : undefined\n\n\t\tlet sub = ''\n\t\tlet style = {}\n\t\tif (el) {\n\t\t\tconst tags = el.tags.split(',')\n\t\t\tsub = tags[tags.length - 1] // subtitle is []\n\t\t\tif (el.technology)\n\t\t\t\tsub += ': ' + el.technology // or [: ]\n\n\t\t\ttags.forEach(tag => {\n\t\t\t\tconst s = styles && styles.elements && styles.elements.find(s => s.tag == tag)\n\t\t\t\ts && (style = {...style, ...s})\n\t\t\t})\n\t\t}\n\n\t\tgraph.addNode(\n\t\t\tref.id,\n\t\t\tel ? (el.name || ref.id) : ref.id,\n\t\t\tsub,\n\t\t\t(el && el.description) ? el.description : '',\n\t\t\tstyle,\n\t\t\tnodeLink(el, elementViewKey)\n\t\t)\n\t\tel && metadata.elements.push({\n\t\t\tid: el.id,\n\t\t\ttags: el.tags,\n\t\t\tlocation: el.location,\n\t\t\tproperties: el.properties,\n\t\t\telementViewKey,\n\t\t\ttechnology: el.technology,\n\t\t\turl: el.url\n\t\t})\n\t})\n\t//edges\n\tif (Array.isArray(view.relationships)) {\n\t\tview.relationships.forEach(ref => {\n\t\t\tconst rel = relations.get(ref.id)\n\t\t\tif (!rel) return;\n\n\t\t\tif (!graph.nodesMap.has(rel.sourceId)) {\n\t\t\t\tif (elements.has(rel.sourceId)) {\n\t\t\t\t\tconst el = elements.get(rel.sourceId)\n\t\t\t\t\tconsole.warn('Element not found in this view: ', el.id, el.name)\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn('Element not found: ', rel.sourceId)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!graph.nodesMap.has(rel.destinationId)) {\n\t\t\t\tif (elements.has(rel.destinationId)) {\n\t\t\t\t\tconst el = elements.get(rel.destinationId)\n\t\t\t\t\tconsole.warn('Element not found in this view: ', el.id, el.name)\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn('Element not found: ', rel.destinationId)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet style: any = {}\n\t\t\trel.tags.split(',').forEach(tag => {\n\t\t\t\tconst s = styles && styles.relationships && styles.relationships.find(s => s.tag == tag)\n\t\t\t\ts && (style = {...style, ...s})\n\t\t\t})\n\t\t\tif (ref.routing) style.routing = ref.routing\n\n\t\t\tgraph.addEdge(rel.id, rel.sourceId, rel.destinationId, rel.description, ref.vertices, style)\n\t\t})\n\t}\n\n\t//groups\n\t//sort by depth to solve dependency\n\tconst level = (el: Element) => {\n\t\tlet i = 0\n\t\tfor (let p = el.parent; p; p = p.parent) i++;\n\t\treturn i\n\t}\n\tconst gElements = Object.keys(groupingIDs)\n\t\t.map(id => elements.get(id))\n\t\t.sort((a, b) => level(a) > level(b) ? -1 : 1)\n\n\tgElements.forEach(parent => {\n\t\tlet style = {}\n\t\tif (section == 'deploymentViews') {\n\t\t\tconst el = elements.get(parent.id)\n\t\t\tconst tags = el.tags.split(',')\n\t\t\ttags.forEach(tag => {\n\t\t\t\tconst s = styles && styles.elements && styles.elements.find(s => s.tag == tag)\n\t\t\t\ts && (style = {...style, ...s})\n\t\t\t})\n\t\t}\n\t\t\n\t\t// Filter group members more carefully to respect boundaries\n\t\tconst groupMembers = view.elements\n\t\t\t.map(ref => elements.get(ref.id))\n\t\t\t.filter(el => {\n\t\t\t\tif (!el || el.parent !== parent) return false;\n\t\t\t\t\n\t\t\t\t// For system landscape views, respect the location-based grouping\n\t\t\t\tif (section === 'systemLandscapeViews' && parent.id === '__enterprise__') {\n\t\t\t\t\t// Only include elements that are explicitly non-external\n\t\t\t\t\treturn el.location !== 'External';\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// For other view types, include if parent matches\n\t\t\t\treturn true;\n\t\t\t})\n\t\t\t.map(el => el.id);\n\t\t\n\t\t// Only create group if it has members\n\t\tif (groupMembers.length > 0) {\n\t\t\tgraph.addGroup(\n\t\t\t\tparent.id,\n\t\t\t\tparent.name,\n\t\t\t\tgroupMembers,\n\t\t\t\tstyle\n\t\t\t)\n\t\t}\n\t})\n\n\t//layout if any and init graph\n\tgraph.init(layouts[graph.id])\n\treturn graph\n}\n\nfunction nodeLink(element: Element | undefined, elementViewKey: string | undefined): NodeLink | undefined {\n\tif (elementViewKey) {\n\t\tconst encodedViewKey = encodeURIComponent(elementViewKey)\n\t\treturn {\n\t\t\thref: `?id=${encodedViewKey}`,\n\t\t\texportHref: `${encodedViewKey}.svg`,\n\t\t}\n\t}\n\tif (element?.url) {\n\t\treturn {\n\t\t\thref: element.url,\n\t\t\texportHref: element.url,\n\t\t}\n\t}\n\treturn undefined\n}\n\n// lookup the view in all Views sections in the model. return the view and the section\nfunction getView(model: Model, viewKey: string) {\n\tlet view: View = null, section: string = ''\n\tObject.keys(model.views).filter(s => s.endsWith('Views')).some((s: string) => {\n\t\treturn ((model.views as any)[s]).some((v: View) => {\n\t\t\tif (v.key == viewKey) {\n\t\t\t\tview = v\n\t\t\t\tsection = s\n\t\t\t\treturn true\n\t\t\t}\n\t\t})\n\t})\n\treturn {view, section}\n}\n\n\nfunction lookupContainerViewKey(model: Model, softwareSystemId: string) {\n\tconst view = model.views.containerViews?.find(candidate => candidate.softwareSystemId == softwareSystemId)\n\treturn view?.key\n}\n\nexport const listViews = (model: any) => {\n\tconst viewsList: ViewsList = []\n\tconst sections = Object.keys(model.views).filter(section => section.endsWith('Views'))\n\tsections.forEach(s => {\n\t\tmodel.views[s].forEach((v: View) => {\n\t\t\tviewsList.push({key: v.key, title: v.title || v.key, section: s})\n\t\t})\n\t})\n\treturn viewsList;\n}\n","import { useState, useCallback, useEffect } from 'react';\nimport { GraphData } from './graph-view/graph';\nimport { parseView } from './parseModel';\nimport { LayoutOptions } from './graph-view/layout';\nimport { \n findShortcut, \n HELP, \n SAVE, \n TOGGLE_DRAG_MODE,\n ALIGN_HORIZONTAL,\n ALIGN_VERTICAL,\n DISTRIBUTE_HORIZONTAL,\n DISTRIBUTE_VERTICAL,\n AUTO_LAYOUT,\n RESET_POSITION,\n TOGGLE_GRID,\n TOGGLE_SNAP_TO_GRID,\n SNAP_ALL_TO_GRID,\n MOVE_LEFT,\n MOVE_RIGHT,\n MOVE_UP,\n MOVE_DOWN,\n MOVE_LEFT_FINE,\n MOVE_RIGHT_FINE,\n MOVE_UP_FINE,\n MOVE_DOWN_FINE\n} from './shortcuts';\n\n// Global state for graphs to preserve edits\nconst graphs: { [key: string]: GraphData } = {};\n\n// Custom hook for graph management\nexport const useGraph = (model: any, layouts: any, currentID: string): GraphData | null => {\n if (graphs[currentID]) {\n return graphs[currentID];\n }\n \n const graph = parseView(model, layouts, currentID);\n if (graph) {\n graphs[currentID] = graph;\n }\n \n return graph;\n};\n\n// Custom hook for auto layout functionality\nexport const useAutoLayout = (graph: GraphData) => {\n const [layouting, setLayouting] = useState(false);\n\n const handleAutoLayout = useCallback(async (opts?: LayoutOptions) => {\n setLayouting(true);\n try {\n const options: LayoutOptions = {\n direction: graph.layoutDirection || 'DOWN',\n ...(opts || {})\n };\n await graph.autoLayout(options);\n } finally {\n setLayouting(false);\n }\n }, [graph]);\n\n return { layouting, handleAutoLayout };\n};\n\n// Custom hook for save functionality\nexport const useSave = (graph: GraphData, currentID: string) => {\n const [saving, setSaving] = useState(false);\n\n const handleSave = useCallback(async () => {\n setSaving(true);\n \n try {\n const response = await fetch('data/save?id=' + encodeURIComponent(currentID), {\n method: 'post',\n body: graph.exportSVG()\n });\n \n if (response.status !== 202) {\n const detail = (await response.text()).trim();\n throw new Error(detail || `save failed with HTTP ${response.status}`);\n }\n graph.setSaved();\n } finally {\n setSaving(false);\n }\n }, [graph, currentID]);\n\n return { saving, handleSave };\n};\n\n// Custom hook for keyboard shortcuts\nexport const useKeyboardShortcuts = (\n toggleHelp: () => void,\n saveLayout: () => void,\n graph?: GraphData,\n dragMode?: 'pan' | 'select',\n setDragMode?: (mode: 'pan' | 'select') => void,\n onAutoLayout?: () => void\n) => {\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const shortcut = findShortcut(e);\n \n // Prevent browser default for all recognized shortcuts\n if (shortcut) {\n e.preventDefault();\n }\n \n if (shortcut === HELP) {\n toggleHelp();\n } else if (shortcut === SAVE) {\n saveLayout();\n } else if (shortcut === TOGGLE_DRAG_MODE && setDragMode && dragMode) {\n setDragMode(dragMode === 'pan' ? 'select' : 'pan');\n } else if (graph) {\n // Graph-dependent shortcuts\n if (shortcut === ALIGN_HORIZONTAL) {\n graph.alignSelectionH();\n } else if (shortcut === ALIGN_VERTICAL) {\n graph.alignSelectionV();\n } else if (shortcut === DISTRIBUTE_HORIZONTAL) {\n graph.distributeSelectionH();\n } else if (shortcut === DISTRIBUTE_VERTICAL) {\n graph.distributeSelectionV();\n } else if (shortcut === AUTO_LAYOUT && onAutoLayout) {\n onAutoLayout();\n } else if (shortcut === RESET_POSITION) {\n graph.resetView();\n } else if (shortcut === TOGGLE_GRID) {\n graph.toggleGrid();\n } else if (shortcut === TOGGLE_SNAP_TO_GRID) {\n graph.toggleSnapToGrid();\n } else if (shortcut === SNAP_ALL_TO_GRID) {\n graph.snapAllToGrid();\n } else if (shortcut === MOVE_LEFT) {\n graph.moveSelected(-graph.getGridSize(), 0);\n } else if (shortcut === MOVE_LEFT_FINE) {\n graph.moveSelected(-1, 0, true); // Disable snap for fine movement\n } else if (shortcut === MOVE_RIGHT) {\n graph.moveSelected(graph.getGridSize(), 0);\n } else if (shortcut === MOVE_RIGHT_FINE) {\n graph.moveSelected(1, 0, true); // Disable snap for fine movement\n } else if (shortcut === MOVE_UP) {\n graph.moveSelected(0, -graph.getGridSize());\n } else if (shortcut === MOVE_UP_FINE) {\n graph.moveSelected(0, -1, true); // Disable snap for fine movement\n } else if (shortcut === MOVE_DOWN) {\n graph.moveSelected(0, graph.getGridSize());\n } else if (shortcut === MOVE_DOWN_FINE) {\n graph.moveSelected(0, 1, true); // Disable snap for fine movement\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [toggleHelp, saveLayout, graph, dragMode, setDragMode, onAutoLayout]);\n};\n\n// Utility function to clear graph cache\nexport const clearGraphCache = (currentID?: string) => {\n if (currentID) {\n delete graphs[currentID];\n } else {\n Object.keys(graphs).forEach(key => delete graphs[key]);\n }\n};","// Helper functions for the application\n\nexport function removeEmptyProps(obj: any) {\n return JSON.parse(JSON.stringify(obj));\n}\n\nexport function camelToWords(camel: string) {\n const split = camel.replace(/([A-Z])/g, \" $1\");\n return split.charAt(0).toUpperCase() + split.slice(1);\n}\n\nexport function getCurrentViewID() {\n const params = new URLSearchParams(document.location.search);\n return params.get('id') || '';\n} ","import React, { FC, useState, useEffect } from 'react';\nimport { getZoomAuto, GraphData, setZoom, getZoom, setZoomCentered } from '../graph-view/graph';\nimport { listViews } from '../parseModel';\nimport { camelToWords } from '../utils';\nimport { getModifierKeyName } from '../utils/platform';\n\n// Types\ninterface ToolbarProps {\n model: any;\n currentID: string;\n onViewChange: (id: string) => void;\n graph: GraphData;\n onAutoLayout: () => void;\n onSave: () => void;\n onToggleHelp: () => void;\n saving: boolean;\n layouting: boolean;\n dragMode: 'pan' | 'select';\n setDragMode: (mode: 'pan' | 'select') => void;\n}\n\nexport const Toolbar: FC = ({\n model, currentID, onViewChange, graph, \n onAutoLayout, onSave, onToggleHelp, saving, layouting,\n dragMode, setDragMode\n}) => {\n const views = listViews(model);\n \n return (\n
\n \n \n
\n );\n};\n\nconst ViewSelector: FC<{\n views: any[];\n currentID: string;\n onViewChange: (id: string) => void;\n}> = ({ views, currentID, onViewChange }) => (\n
\n View:\n {views.length > 1 ? (\n \n ) : (\n \n {views[0] ? camelToWords(views[0].section) + ': ' + views[0].title : 'No views available'}\n \n )}\n
\n);\n\nconst ToolbarActions: FC<{\n graph: GraphData;\n onAutoLayout: () => void;\n onSave: () => void;\n onToggleHelp: () => void;\n saving: boolean;\n layouting: boolean;\n dragMode: 'pan' | 'select';\n setDragMode: (mode: 'pan' | 'select') => void;\n}> = ({\n graph, onAutoLayout, onSave, onToggleHelp, saving, layouting,\n dragMode, setDragMode\n}) => (\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n);\n\nconst DragModeButton: FC<{\n dragMode: 'pan' | 'select';\n setDragMode: (mode: 'pan' | 'select') => void;\n}> = ({ dragMode, setDragMode }) => (\n \n);\n\nconst UndoRedoButtons: FC<{ graph: GraphData }> = ({ graph }) => {\n const modKey = getModifierKeyName();\n return (\n <>\n \n \n \n );\n};\n\nconst AlignmentButtons: FC<{ graph: GraphData }> = ({ graph }) => {\n const modKey = getModifierKeyName();\n return (\n <>\n \n \n \n \n \n );\n};\n\nconst LayoutControls: FC<{\n onAutoLayout: () => void;\n layouting: boolean;\n}> = ({ onAutoLayout, layouting }) => {\n const modKey = getModifierKeyName();\n return (\n \n );\n};\n\nconst GridControls: FC<{ graph: GraphData }> = ({ graph }) => {\n const [gridVisible, setGridVisible] = useState(graph.isGridVisible());\n const [snapToGrid, setSnapToGrid] = useState(graph.isSnapToGrid());\n const modKey = getModifierKeyName();\n \n // Update state when graph changes or when grid state changes via shortcuts\n React.useEffect(() => {\n const updateGridState = () => {\n setGridVisible(graph.isGridVisible());\n setSnapToGrid(graph.isSnapToGrid());\n };\n \n // Initial update\n updateGridState();\n \n // Listen for grid state changes from keyboard shortcuts\n window.addEventListener('gridStateChanged', updateGridState);\n \n return () => {\n window.removeEventListener('gridStateChanged', updateGridState);\n };\n }, [graph]);\n \n const handleToggleGrid = () => {\n graph.toggleGrid();\n setGridVisible(graph.isGridVisible());\n };\n \n const handleToggleSnap = () => {\n graph.toggleSnapToGrid();\n setSnapToGrid(graph.isSnapToGrid());\n };\n \n const handleSnapAll = () => {\n graph.snapAllToGrid();\n };\n \n return (\n <>\n \n \n \n \n );\n};\n\nconst ZoomDisplay: FC = () => {\n const [zoom, setZoomState] = useState(100);\n\n useEffect(() => {\n const updateZoom = () => {\n const currentZoom = Math.round(getZoom() * 100);\n setZoomState(currentZoom);\n };\n\n // Update zoom initially\n updateZoom();\n\n // Update zoom every 100ms to catch changes from wheel/keyboard/etc\n const interval = setInterval(updateZoom, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n return (\n \n );\n};\n\nconst ZoomControls: FC<{ graph: GraphData }> = ({ graph }) => {\n const modKey = getModifierKeyName();\n return (\n <>\n \n \n \n \n \n );\n};\n\nconst SaveButton: FC<{\n onSave: () => void;\n saving: boolean;\n graph: GraphData;\n}> = ({ onSave, saving, graph }) => {\n const [hasChanges, setHasChanges] = useState(false);\n const modKey = getModifierKeyName();\n \n // Check for changes periodically\n useEffect(() => {\n const checkChanges = () => {\n setHasChanges(graph.changed());\n };\n \n // Initial check\n checkChanges();\n \n // Check every 100ms for changes\n const interval = setInterval(checkChanges, 100);\n \n return () => clearInterval(interval);\n }, [graph]);\n \n return (\n \n );\n};\n\nconst HelpButton: FC<{\n onToggleHelp: () => void;\n}> = ({ onToggleHelp }) => {\n return (\n \n );\n};","import React, { FC, useState, useCallback, useEffect, useRef, Suspense, lazy } from \"react\";\nimport { GraphData, LayoutDirection } from \"./graph-view/graph\";\nimport { LayoutOptions } from \"./graph-view/layout\";\nimport { BrowserRouter as Router, Routes, Route, useSearchParams } from 'react-router-dom';\nimport { listViews } from \"./parseModel\";\nimport { useGraph, useAutoLayout, useSave, useKeyboardShortcuts, clearGraphCache } from \"./hooks\";\nimport { Toolbar } from \"./components/Toolbar\";\nimport { removeEmptyProps, getCurrentViewID } from \"./utils\";\n\nconst Help = lazy(() => import(\"./shortcuts\").then(module => ({ default: module.Help })));\nconst Graph = lazy(() => import(\"./graph-view/graph-react\").then(module => ({ default: module.Graph })));\n\n// Types\ninterface ModelData {\n model: any;\n layout: any;\n}\n\ntype AutomationStatus = 'running' | 'complete' | 'error';\n\nconst setAutomationStatus = (status: AutomationStatus | null, error?: string) => {\n const root = document.documentElement;\n if (!status) {\n delete root.dataset.mdlAutomationStatus;\n delete root.dataset.mdlAutomationError;\n return;\n }\n root.dataset.mdlAutomationStatus = status;\n if (error) {\n root.dataset.mdlAutomationError = error;\n } else {\n delete root.dataset.mdlAutomationError;\n }\n};\n\nconst errorMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error);\n\nconst reportInteractiveError = (action: string, error: unknown) => {\n console.error(`${action} failed:`, error);\n alert(`${action} failed. See console for details.`);\n};\n\nexport const Root: FC = ({ model, layout }) => (\n \n \n } />\n \n \n);\n\nexport const refreshGraph = () => {\n const currentID = getCurrentViewID();\n clearGraphCache(currentID);\n};\n\nconst ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => {\n const [searchParams, setSearchParams] = useSearchParams();\n const currentID = decodeURI(searchParams.get('id') || '');\n \n // UI State\n const [helpVisible, setHelpVisible] = useState(false);\n const [dragMode, setDragMode] = useState<'pan' | 'select'>('pan');\n const [readyGraphID, setReadyGraphID] = useState(null);\n const automationKey = useRef(null);\n const automationRun = useRef(0);\n \n // Get or create graph for current view\n const graph = useGraph(model, layouts, currentID);\n \n // Custom hooks for functionality\n const { layouting, handleAutoLayout } = useAutoLayout(graph || ({} as GraphData));\n const { saving, handleSave } = useSave(graph || ({} as GraphData), currentID);\n \n if (!graph) {\n return ;\n }\n\n const handleToggleHelp = useCallback(() => {\n setHelpVisible(!helpVisible);\n }, [helpVisible]);\n\n const handleGraphReady = useCallback(() => {\n setReadyGraphID(currentID);\n }, [currentID]);\n\n const handleInteractiveAutoLayout = useCallback(() => {\n void handleAutoLayout().catch(error => reportInteractiveError('Layout', error));\n }, [handleAutoLayout]);\n\n const handleInteractiveSave = useCallback(() => {\n void handleSave().catch(error => reportInteractiveError('Save', error));\n }, [handleSave]);\n\n // Update document title when view changes\n useEffect(() => {\n if (graph && graph.name) {\n document.title = `${graph.name} - Model`;\n }\n }, [graph]);\n\n // Headless automation: support query params to auto-layout and save\n useEffect(() => {\n const params = Object.fromEntries(searchParams.entries());\n const auto = params['auto'] === '1' || params['auto'] === 'true';\n const save = params['save'] === '1' || params['save'] === 'true';\n if (!auto && !save) {\n automationKey.current = null;\n automationRun.current++;\n setAutomationStatus(null);\n return;\n }\n if (readyGraphID !== currentID) {\n return;\n }\n\n const key = `${currentID}:${searchParams.toString()}`;\n if (automationKey.current === key) {\n return;\n }\n automationKey.current = key;\n const run = ++automationRun.current;\n setAutomationStatus('running');\n\n const direction = (params['direction'] || '').toUpperCase();\n const compact = params['compact'] === '1' || params['compact'] === 'true';\n\n const validDirections: LayoutDirection[] = ['UP', 'DOWN', 'LEFT', 'RIGHT'];\n const layoutOpts: LayoutOptions = {};\n if (validDirections.includes(direction as LayoutDirection)) {\n layoutOpts.direction = direction as LayoutDirection;\n }\n if (compact) {\n layoutOpts.compactLayout = true;\n }\n\n (async () => {\n try {\n if (auto) {\n await handleAutoLayout(layoutOpts);\n }\n if (save) {\n await handleSave();\n }\n if (automationRun.current === run) {\n setAutomationStatus('complete');\n }\n } catch (error) {\n const message = errorMessage(error);\n console.error('Automation failed:', error);\n if (automationRun.current === run) {\n setAutomationStatus('error', message);\n }\n }\n })();\n }, [currentID, graph, handleAutoLayout, handleSave, readyGraphID, searchParams]);\n\n // Setup keyboard shortcuts\n useKeyboardShortcuts(\n handleToggleHelp,\n handleInteractiveSave,\n graph,\n dragMode,\n setDragMode,\n handleInteractiveAutoLayout,\n );\n\n const handleViewChange = useCallback((id: string) => {\n setSearchParams({ id: encodeURIComponent(id) });\n }, [setSearchParams]);\n\n const handleSelect = useCallback((id: string | null) => {\n if (id) {\n const element = graph.metadata.elements.find((m: any) => m.id === id);\n console.log(removeEmptyProps(element));\n }\n }, [graph]);\n\n\treturn (\n\t\t<>\n\t\t\t\n\t\t\tLoading graph...}>\n\t\t\t\t\n\t\t\t\n\t\t\t{helpVisible && (\n\t\t\t\tLoading help...}>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t\n\t);\n};\n\nconst ViewRedirect: FC<{ model: any }> = ({ model }) => {\n const views = listViews(model);\n \n React.useEffect(() => {\n // Set default title when no view is selected\n document.title = 'Model - Architecture Diagrams as Code';\n \n if (views.length > 0) {\n document.location.href = '?id=' + views[0].key;\n }\n }, [views]);\n\n if (views.length > 0) {\n return <>Redirecting to {views[0].title};\n }\n return <>No views available;\n};\n\n","export const defs = `\n\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n`\n","const textMeasure = () => {\n\tconst svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n\tdocument.body.appendChild(svg);\n\n\treturn {\n\t\tmeasure: (text: string, attrs: { [key: string]: string }) => {\n\t\t\tconst node = document.createElementNS('http://www.w3.org/2000/svg', 'text')\n\t\t\tnode.setAttribute('x', '0');\n\t\t\tnode.setAttribute('y', '0');\n\t\t\tfor (let attr in attrs) {\n\t\t\t\tnode.setAttribute(attr, attrs[attr]);\n\t\t\t}\n\t\t\tnode.appendChild(document.createTextNode(text));\n\n\t\t\tsvg.appendChild(node);\n\t\t\tconst {width, height} = node.getBBox();\n\t\t\tsvg.removeChild(node);\n\t\t\treturn {width, height};\n\t\t},\n\t\tclean: () => {\n\t\t\tdocument.body.removeChild(svg);\n\t\t}\n\t}\n}\n\n// Helper function to break long words that exceed width\nconst breakLongWord = (word: string, maxWidth: number, attrs: { [key: string]: string }, mt: any): string[] => {\n\tconst parts: string[] = [];\n\tlet currentPart = '';\n\t\n\tfor (let i = 0; i < word.length; i++) {\n\t\tconst testPart = currentPart + word[i];\n\t\tconst size = mt.measure(testPart, attrs);\n\t\t\n\t\tif (size.width > maxWidth && currentPart.length > 0) {\n\t\t\tparts.push(currentPart);\n\t\t\tcurrentPart = word[i];\n\t\t} else {\n\t\t\tcurrentPart = testPart;\n\t\t}\n\t}\n\t\n\tif (currentPart.length > 0) {\n\t\tparts.push(currentPart);\n\t}\n\t\n\treturn parts;\n}\n\n// split a text in lines wrapped at a certain width\nexport const svgTextWrap = (text: string, width: number, attrs: { [key: string]: string }) => {\n\tconst mt = textMeasure()\n\tlet maxW = 0;\n\t\n\tconst ret = text.trim().split('\\n').map(text => { //split paragraphs\n\t\t//do one paragraph\n\t\tconst words = text.trim().split(/\\s+/);\n\t\tlet lines: string[] = [];\n\t\tlet currentLine: string[] = [];\n\t\t\n\t\twords.forEach(word => {\n\t\t\t// First check if the single word exceeds the width\n\t\t\tconst wordSize = mt.measure(word, attrs);\n\t\t\tif (wordSize.width > width) {\n\t\t\t\t// If we have content in current line, finish it first\n\t\t\t\tif (currentLine.length > 0) {\n\t\t\t\t\tlines.push(currentLine.join(' '));\n\t\t\t\t\tcurrentLine = [];\n\t\t\t\t}\n\t\t\t\t// Break the long word into smaller parts\n\t\t\t\tconst brokenParts = breakLongWord(word, width, attrs, mt);\n\t\t\t\t// Add all but the last part as complete lines\n\t\t\t\tfor (let i = 0; i < brokenParts.length - 1; i++) {\n\t\t\t\t\tlines.push(brokenParts[i]);\n\t\t\t\t\tmaxW = Math.max(maxW, mt.measure(brokenParts[i], attrs).width);\n\t\t\t\t}\n\t\t\t\t// Start new line with the last part\n\t\t\t\tif (brokenParts.length > 0) {\n\t\t\t\t\tcurrentLine = [brokenParts[brokenParts.length - 1]];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Normal word processing\n\t\t\t\tconst newLine = [...currentLine, word];\n\t\t\t\tconst size = mt.measure(newLine.join(' '), attrs);\n\t\t\t\tif (size.width > width && currentLine.length > 0) {\n\t\t\t\t\tlines.push(currentLine.join(' '));\n\t\t\t\t\tcurrentLine = [word];\n\t\t\t\t} else {\n\t\t\t\t\tmaxW = Math.max(maxW, size.width)\n\t\t\t\t\tcurrentLine = newLine;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tif (currentLine.length > 0) {\n\t\t\tlines.push(currentLine.join(' '));\n\t\t}\n\t\treturn lines;\n\t}).reduce((a, v) => a.concat(v), []) //flatten\n\n\tmt.clean()\n\treturn {lines: ret, maxW};\n};\n\n\n","import {svgTextWrap} from \"./svg-text\";\n\nexport const create = {\n\telement(type: string, attrs: Record = {}, className?: string) {\n\t\tconst el = document.createElementNS('http://www.w3.org/2000/svg', type);\n\t\tObject.entries(attrs).forEach(([k, v]) => el.setAttribute(k, String(v)));\n\t\tif (className) el.classList.add(className);\n\t\treturn el;\n\t},\n\n\tuse(id: string, attrs: Record = {}) {\n\t\tconst el = this.element('use', attrs);\n\t\tel.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#' + id);\n\t\treturn el;\n\t},\n\n\tpath(path: string, attrs: Record = {}, className?: string) {\n\t\tconst p = this.element(\"path\", {...attrs, d: path}, className);\n\t\treturn p;\n\t},\n\n\ttext(text: string, attrs: Record = {}) {\n\t\tconst t = this.element('text', attrs) as SVGTextElement;\n\t\tif (text) t.textContent = text;\n\t\treturn t;\n\t},\n\n\ttextArea(text: string, width: number, fontSize: number, bold: boolean, x = 0, y = 0, anchor = '') {\n\t\tconst attrs: Record = {\n\t\t\t'font-size': `${fontSize}px`,\n\t\t\t'font-weight': bold ? 'bold' : 'normal'\n\t\t};\n\t\tconst {lines, maxW} = svgTextWrap(text, width, attrs);\n\t\tconst txt = this.text('', {x: 0, y, 'text-anchor': anchor || undefined});\n\t\t\n\t\tlines.forEach((line, i) => {\n\t\t\tconst span = this.element('tspan', {x, dy: `${fontSize + 2}px`, ...attrs});\n\t\t\tspan.textContent = line;\n\t\t\ttxt.append(span);\n\t\t});\n\t\t\n\t\treturn {txt, dy: (lines.length + 1) * (fontSize + 2), maxW};\n\t},\n\n\trect(width: number, height: number, x = 0, y = 0, r = 0, className?: string) {\n\t\treturn this.element('rect', {x, y, rx: r, ry: r, width, height}, className) as SVGRectElement;\n\t},\n\n\ticon(icon: string, x = 0, y = 0) {\n\t\treturn this.use(icon, {x, y});\n\t},\n\n\texpand(x: number, y: number, expanded: boolean) {\n\t\tconst g = this.element('g', {transform: `translate(${x},${y})`}, 'expand') as SVGGElement;\n\t\tg.append(\n\t\t\tthis.rect(19, 19, 0, 0, 1),\n\t\t\tthis.text(expanded ? '-' : '+', {x: 10, y: 14, 'text-anchor': 'middle'})\n\t\t);\n\t\treturn g;\n\t}\n};\n\nexport function setPosition(g: SVGGElement, x: number, y: number) {\n\tg.setAttribute('transform', `translate(${x},${y})`);\n}","interface Point {\n\tx: number;\n\ty: number;\n}\n\ninterface BBox extends Point {\n\twidth: number;\n\theight: number;\n}\n\n\nexport function insideBox(p: Point, b: BBox, centeredBox = true): boolean {\n\treturn centeredBox ?\n\t\t(p.x > b.x - b.width / 2 && p.x < b.x + b.width / 2 && p.y > b.y - b.height / 2 && p.y < b.y + b.height / 2) :\n\t\t(p.x > b.x && p.x < b.x + b.width && p.y > b.y && p.y < b.y + b.height)\n}\n\nexport function boxesOverlap(b1: BBox, b2: BBox): boolean {\n\treturn b1.x < b2.x + b2.width && b1.y < b2.y + b2.height && b1.x + b1.width > b2.x && b1.y + b1.height > b2.y\n}\n\nexport function uncenterBox(b: BBox): BBox {\n\treturn {x: b.x - b.width / 2, y: b.y - b.height / 2, width: b.width, height: b.height}\n}\n\nexport function scaleBox(b: BBox, sc: number): BBox {\n\treturn {x: b.x * sc, y: b.y * sc, width: b.width * sc, height: b.height * sc}\n}\n\n// intersect 2 segments (p1->q1) with (p2, q2)\n// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite)\n// and booleans for whether line segment 1 or line segment 2 contain the point\nfunction segmentIntersection(p1: Point, q1: Point, p2: Point, q2: Point) {\n\tlet denominator, a, b, numerator1, numerator2,\n\t\tresult: { x: number, y: number, onLine1: boolean, onLine2: boolean } = {\n\t\t\tx: null,\n\t\t\ty: null,\n\t\t\tonLine1: false,\n\t\t\tonLine2: false\n\t\t};\n\tdenominator = (q2.y - p2.y) * (q1.x - p1.x) - (q2.x - p2.x) * (q1.y - p1.y);\n\tif (denominator == 0) {\n\t\treturn result;\n\t}\n\ta = p1.y - p2.y;\n\tb = p1.x - p2.x;\n\tnumerator1 = ((q2.x - p2.x) * a) - ((q2.y - p2.y) * b);\n\tnumerator2 = ((q1.x - p1.x) * a) - ((q1.y - p1.y) * b);\n\ta = numerator1 / denominator;\n\tb = numerator2 / denominator;\n\n\t// if we cast these lines infinitely in both directions, they intersect here:\n\tresult.x = p1.x + (a * (q1.x - p1.x));\n\tresult.y = p1.y + (a * (q1.y - p1.y));\n\n\t// if line1 is a segment and line2 is infinite, they intersect if:\n\tif (a > 0 && a < 1) {\n\t\tresult.onLine1 = true;\n\t}\n\t// if line2 is a segment and line1 is infinite, they intersect if:\n\tif (b >= 0 && b <= 1) {\n\t\tresult.onLine2 = true;\n\t}\n\t// if line1 and line2 are segments, they intersect if both of the above are true\n\treturn result;\n}\n\n// intersects a segment (p1->p2) with a box\nexport function intersectRectFull(p1: Point, p2: Point, box: BBox): Point[] {\n\tconst w = box.width / 2\n\tconst h = box.height / 2\n\tconst segs: { p: Point; q: Point }[] = [\n\t\t{p: {x: box.x - w, y: box.y - h}, q: {x: box.x - w, y: box.y + h}},\n\t\t{p: {x: box.x - w, y: box.y - h}, q: {x: box.x + w, y: box.y - h}},\n\t\t{p: {x: box.x + w, y: box.y - h}, q: {x: box.x + w, y: box.y + h}},\n\t\t{p: {x: box.x - w, y: box.y + h}, q: {x: box.x + w, y: box.y + h}},\n\t]\n\treturn segs.map(s => segmentIntersection(p1, p2, s.p, s.q)).filter(ret => ret.onLine1 && ret.onLine2)\n}\n\n// intersects a line that goes from p to the center of the box\nexport function intersectRect(box: BBox, p: Point): Point {\n\tif (insideBox(p, box)) return {x: box.x, y: box.y}\n\treturn intersectRectFull(box, p, box)[0] || {x: box.x, y: box.y}\n}\n\nexport function intersectEllipse(ellCenter: Point, rx: number, ry: number, nodeCenter: Point, point: Point) {\n\n\t//translate all to center ellipse\n\tconst p1 = {x: point.x - ellCenter.x, y: point.y - ellCenter.y}\n\tconst p2 = {x: nodeCenter.x - ellCenter.x, y: nodeCenter.y - ellCenter.y}\n\n\tif (p2.x == p1.x) { //hack to avoid singularity\n\t\tp1.x += .0000001\n\t}\n\n\tconst s = (p2.y - p1.y) / (p2.x - p1.x);\n\tconst si = p2.y - (s * p2.x);\n\tconst a = (ry * ry) + (rx * rx * s * s);\n\tconst b = 2 * rx * rx * si * s;\n\tconst c = rx * rx * si * si - rx * rx * ry * ry;\n\n\tconst radicand_sqrt = Math.sqrt((b * b) - (4 * a * c));\n\tconst x = p1.x > p2.x ?\n\t\t(-b + radicand_sqrt) / (2 * a) :\n\t\t(-b - radicand_sqrt) / (2 * a)\n\tconst pos = {\n\t\tx: x,\n\t\ty: s * x + si\n\t}\n\t//translate back\n\tpos.x += ellCenter.x;\n\tpos.y += ellCenter.y\n\n\treturn pos;\n}\n\nexport interface Segment {\n\tp: Point;\n\tq: Point;\n}\n\n// given a polyline as a list of segments, interrupt it over the box so no line is inside the box\nexport function intersectPolylineBox(segments: Segment[], box: BBox) {\n\tfor (let i = 0; i < segments.length; i++) {\n\t\tconst s = segments[i]\n\t\tif (insideBox(s.p, box)) {\n\t\t\tif (insideBox(s.q, box)) { // segment both ends inside box\n\t\t\t\tsegments.splice(i, 1)\n\t\t\t\ti -= 1\n\t\t\t} else { // segment start inside box\n\t\t\t\ts.p = intersectRectFull(s.p, s.q, box)[0]\n\t\t\t}\n\t\t} else {\n\t\t\tif (insideBox(s.q, box)) { // segment end inside box\n\t\t\t\ts.q = intersectRectFull(s.p, s.q, box)[0]\n\t\t\t} else { // both ends outside\n\t\t\t\tconst ret = intersectRectFull(s.p, s.q, box)\n\t\t\t\tif (ret.length == 2) { // intersects the box, splice segment\n\t\t\t\t\t// order the intersection points, closest first\n\t\t\t\t\tconst dst1 = Math.abs(ret[0].x - s.p.x) + Math.abs(ret[0].y - s.p.y)\n\t\t\t\t\tconst dst2 = Math.abs(ret[1].x - s.p.x) + Math.abs(ret[1].y - s.p.y)\n\t\t\t\t\tif (dst1 > dst2) ret.reverse()\n\t\t\t\t\t// split the segment in 2\n\t\t\t\t\tconst s2 = {p: ret[1], q: s.q}\n\t\t\t\t\ts.q = ret[0]\n\t\t\t\t\tsegments.splice(i + 1, 0, s2)\n\t\t\t\t\ti += 1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport function project(p: Point, a: Point, b: Point): Point {\n\tlet atob = {x: b.x - a.x, y: b.y - a.y};\n\tlet atop = {x: p.x - a.x, y: p.y - a.y};\n\tlet len = atob.x * atob.x + atob.y * atob.y;\n\tlet dot = atop.x * atob.x + atop.y * atob.y;\n\tlet t = Math.min(1, Math.max(0, dot / len));\n\treturn {\n\t\tx: a.x + atob.x * t,\n\t\ty: a.y + atob.y * t\n\t};\n}\n\nexport function cabDistance(p1: Point, p2: Point): number {\n\treturn Math.abs(p2.x - p1.x) + Math.abs(p2.y - p1.y)\n}","import {intersectEllipse, intersectRect} from \"./intersect\";\n\ninterface Point {\n\tx: number;\n\ty: number;\n}\n\ninterface BBox extends Point {\n\twidth: number;\n\theight: number;\n}\n\ninterface D3Node extends BBox {\n\tintersect: (p: Point) => Point\n}\n\nfunction cylinderRadiusY(width: number) {\n\treturn width / 2 / (5.5 + width / 70);\n}\n\nexport function shapeLabelOffsetY(shape: string, width: number, height: number) {\n\tswitch (shape.toLowerCase()) {\n\t\tcase \"cylinder\":\n\t\t\treturn 2 * cylinderRadiusY(width);\n\t\tcase \"person\":\n\t\t\treturn height * 0.4;\n\t\tcase \"folder\":\n\t\t\treturn width / 10;\n\t\tcase \"robot\":\n\t\t\treturn height * 0.35;\n\t\tcase \"webbrowser\":\n\t\t\treturn height / 8;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nclass D3Element {\n\tprivate readonly _el: SVGElement\n\n\tconstructor(el: SVGElement) {\n\t\tthis._el = el;\n\t}\n\n\tnode() {\n\t\treturn this._el;\n\t}\n\n\tattr(name: string, value: string | number) {\n\t\tthis._el.setAttribute(name, String(value))\n\t\treturn this;\n\t}\n\n\tinsert(type: string, pos: string) {\n\t\tconst el = document.createElementNS('http://www.w3.org/2000/svg', type)\n\t\tconst el2 = this._el.insertBefore(el, this._el.querySelector(pos))\n\t\treturn new D3Element(el2)\n\t}\n}\n\n\nfunction rect(parent: D3Element, bbox: BBox, node: D3Node, rounded = false) {\n\tconst shapeSvg = parent.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", rounded ? node.width / 8 : 3)\n\t\t.attr(\"ry\", rounded ? node.width / 8 : 3)\n\t\t.attr(\"x\", -bbox.width / 2)\n\t\t.attr(\"y\", -bbox.height / 2)\n\t\t.attr(\"width\", bbox.width)\n\t\t.attr(\"height\", bbox.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect(node, point);\n\t};\n\n\treturn shapeSvg;\n}\n\n\nfunction cylinder(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = bbox.width;\n\tconst rx = w / 2;\n\tconst ry = cylinderRadiusY(w);\n\tconst h = bbox.height;\n\n\tconst shape =\n\t\t`M 0,${ry} a${rx},${ry} 0,0,0 ${w} 0 a ${rx},${ry} 0,0,0 ${-w} 0 l 0,${h - 2 * ry} a ${rx},${ry} 0,0,0 ${w} 0 l 0,${-h + 2 * ry}`;\n\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"cylinder\", w, h))\n\t\t.insert('path', ':first-child')\n\t\t.attr('d', shape)\n\t\t.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2) + ')');\n\n\tnode.intersect = function (point: Point) {\n\t\tconst pos = intersectRect(node, point)\n\t\tlet cy = node.y + node.height / 2 - ry\n\t\tif (pos.y > cy)\n\t\t\treturn intersectEllipse({x: node.x, y: cy}, rx, ry, node, point)\n\n\t\tcy = node.y - node.height / 2 + ry\n\t\tif (pos.y < cy)\n\t\t\treturn intersectEllipse({x: node.x, y: cy}, rx, ry, node, point)\n\n\t\treturn pos;\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction person(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = bbox.width;\n\tconst h = bbox.height;\n\n\tconst shape =\n\t\t`M ${.38 * w},${h / 3} A${w / 2},${h / 2} 0,0,0 0 ${h / 2}\n\t\tL${w / 11},${h} L${w - w / 11},${h} L${w},${h / 2}\n\t\tA${w / 2},${h / 2} 0,0,0 ${w - .38 * w} ${h / 3} \n\t\tA${w / 6},${w / 6} 0,1,0 ${.38 * w} ${h / 3}`;\n\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"person\", w, h))\n\t\t.insert('path', ':first-child')\n\t\t.attr('d', shape)\n\t\t.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2) + ')');\n\n\tnode.intersect = function (point: Point) {\n\t\tconst pos = intersectRect(node, point)\n\t\treturn pos;\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction _ellipse(parent: D3Element, bbox: BBox, node: D3Node, rx: number, ry: number) {\n\tconst shapeSvg = parent.insert(\"ellipse\", \":first-child\")\n\t\t.attr(\"cx\", 0)\n\t\t.attr(\"cy\", 0)\n\t\t.attr('rx', rx)\n\t\t.attr('ry', ry)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectEllipse(node, rx, ry, node, point)\n\t};\n\treturn shapeSvg;\n}\n\nfunction circle(parent: D3Element, bbox: BBox, node: D3Node) {\n\treturn _ellipse(parent, bbox, node, node.width / 2, node.width / 2)\n}\n\nfunction ellipse(parent: D3Element, bbox: BBox, node: D3Node) {\n\treturn _ellipse(parent, bbox, node, node.width * .55, node.width * .45)\n}\n\nfunction hexagon(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst sz = node.width / 2\n\t// drawing a hexagon from polar coords\n\t// [0,1,2,3,4,5,6].map(i=>`${Math.sin(Math.PI/3*i+Math.PI/6).toFixed(4)},${Math.cos(Math.PI/3*i+Math.PI/6).toFixed(4)}`).join(',')\n\tconst shapeSvg = parent.insert(\"polygon\", \":first-child\")\n\t\t.attr(\"points\",\n\t\t\t[0.5000, 0.8660, 1.0000, 0.0000, 0.5000, -0.8660, -0.5000, -0.8660, -1.0000, -0.0000, -0.5000, 0.8660, 0.5000, 0.8660].map(n => n * sz).join(','))\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectEllipse(node, node.width / 2, node.width / 2, node, point)\n\t};\n\treturn shapeSvg;\n}\n\nfunction component(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dx = node.width / 10\n\tconst shapeSvg = parent.insert('g', ':first-child')\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2 - dx)\n\t\t.attr(\"y\", -node.height / 2 + dx)\n\t\t.attr(\"width\", dx * 2)\n\t\t.attr(\"height\", dx);\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2 - dx)\n\t\t.attr(\"y\", -node.height / 2 + dx * 2.5)\n\t\t.attr(\"width\", dx * 2)\n\t\t.attr(\"height\", dx);\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2)\n\t\t.attr(\"y\", -node.height / 2)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x - dx / 2, y: node.y, width: node.width + dx, height: node.height}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction folder(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dy = node.width / 20\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"folder\", node.width, node.height))\n\t\t.insert('g', ':first-child')\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2)\n\t\t.attr(\"y\", -node.height / 2 + dy * 2)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height - dy * 2);\n\tshapeSvg.insert(\"path\", \":first-child\")\n\t\t.attr('d', `M0,${-node.height / 2 + 2 * dy} l${dy},${-2 * dy} h${node.width / 2 - dy * 2} v${dy * 2}`)\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x, y: node.y + dy / 2, width: node.width, height: node.height + dy}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction mobiledevicelandscape(parent: D3Element, bbox: BBox, node: D3Node, rounded = false) {\n\tconst dx = node.width / 8\n\tconst r = node.width / 14\n\tconst shapeSvg = parent.insert('g', ':first-child')\n\tshapeSvg.insert('path', ':first-child')\n\t\t.attr('d', `M${-node.width / 2},${-node.height / 2} l0,${node.height} M${node.width / 2},${-node.height / 2} l0,${node.height}`)\n\tshapeSvg.insert('circle', ':first-child')\n\t\t.attr('cx', -node.width / 2 - dx / 2)\n\t\t.attr('cy', 0)\n\t\t.attr('r', r * .4)\n\tshapeSvg.insert('rect', ':first-child')\n\t\t.attr('x', node.width / 2 + dx / 2 - r * .2)\n\t\t.attr('y', -r)\n\t\t.attr('width', r * .4)\n\t\t.attr('height', r * 2)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", r)\n\t\t.attr(\"ry\", r)\n\t\t.attr(\"x\", -bbox.width / 2 - dx)\n\t\t.attr(\"y\", -bbox.height / 2)\n\t\t.attr(\"width\", bbox.width + 2 * dx)\n\t\t.attr(\"height\", bbox.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x, y: node.y, width: node.width + 2 * dx, height: node.height}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction mobiledeviceportrait(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dy = node.width / 8\n\tconst r = node.width / 14\n\tconst shapeSvg = parent.insert('g', ':first-child')\n\tshapeSvg.insert('path', ':first-child')\n\t\t.attr('d', `M${-node.width / 2},${-node.height / 2} l${node.width},0 M${-node.width / 2},${node.height / 2} l${node.width},0`)\n\tshapeSvg.insert('circle', ':first-child')\n\t\t.attr('cx', 0)\n\t\t.attr('cy', node.height / 2 + dy / 2)\n\t\t.attr('r', r * .4)\n\tshapeSvg.insert('rect', ':first-child')\n\t\t.attr('x', -r)\n\t\t.attr('y', -node.height / 2 - dy / 2 - r * .2)\n\t\t.attr('width', r * 2)\n\t\t.attr('height', r * .4)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", r)\n\t\t.attr(\"ry\", r)\n\t\t.attr(\"x\", -bbox.width / 2)\n\t\t.attr(\"y\", -bbox.height / 2 - dy)\n\t\t.attr(\"width\", bbox.width)\n\t\t.attr(\"height\", bbox.height + 2 * dy);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect({x: node.x, y: node.y, width: node.width, height: node.height + 2 * dy}, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction pipe(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = node.width;\n\tconst h = node.height;\n\tconst ry = h / 2;\n\tconst rx = ry / (2.5 + w / 70);\n\n\tconst shape =\n\t\t`M${-rx},0\n\t\ta${rx},${ry} 0,0,1 0,${h}\n\t\ta${rx},${ry} 0,0,1 0,${-h}\n\t\tl${w},0\n\t\ta${rx},${ry} 0,0,1 0,${h}\n\t\tl${-w},0`;\n\n\tconst shapeSvg = parent\n\t\t.insert('path', ':first-child')\n\t\t.attr('d', shape)\n\t\t.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2) + ')');\n\n\tnode.intersect = function (point: Point) {\n\t\treturn intersectRect({x: node.x - rx, y: node.y, width: node.width + 2 * rx, height: node.height}, point)\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction robot(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst w = node.width\n\tconst h = node.height\n\t\n\t// Small head at top (like person shape but robot-styled)\n\tconst headSize = Math.min(w * 0.28, h * 0.25)\n\tconst headR = headSize * 0.2\n\tconst antennaH = headSize * 0.25\n\tconst antennaR = headSize * 0.08\n\t\n\t// Eye dimensions\n\tconst eyeR = headSize * 0.12\n\tconst eyeSpacing = headSize * 0.22\n\t\n\t// Ear dimensions \n\tconst earW = headSize * 0.12\n\tconst earH = headSize * 0.3\n\t\n\t// Body fills most of the space for text\n\tconst bodyW = w\n\tconst bodyTop = -h / 2 + antennaH + headSize\n\tconst bodyH = h - antennaH - headSize\n\tconst bodyR = 3\n\t\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"robot\", w, h))\n\t\t.insert('g', ':first-child')\n\t\n\t// Body - main rectangle for text (draw first so it's behind)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", bodyR)\n\t\t.attr(\"ry\", bodyR)\n\t\t.attr('x', -bodyW / 2)\n\t\t.attr('y', bodyTop)\n\t\t.attr('width', bodyW)\n\t\t.attr('height', bodyH)\n\t\n\t// Head\n\tconst headTop = -h / 2 + antennaH\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", headR)\n\t\t.attr(\"ry\", headR)\n\t\t.attr('x', -headSize / 2)\n\t\t.attr('y', headTop)\n\t\t.attr('width', headSize)\n\t\t.attr('height', headSize)\n\t\n\t// Antenna\n\tshapeSvg.insert(\"line\", \":first-child\")\n\t\t.attr('class', 'robot-antenna')\n\t\t.attr('x1', 0)\n\t\t.attr('y1', headTop)\n\t\t.attr('x2', 0)\n\t\t.attr('y2', -h / 2 + antennaR * 2)\n\t\t.attr('stroke-width', antennaR * 0.6)\n\t\t.attr('stroke-linecap', 'round')\n\t\n\t// Antenna ball\n\tshapeSvg.insert(\"circle\", \":first-child\")\n\t\t.attr('class', 'robot-antenna-ball')\n\t\t.attr('cx', 0)\n\t\t.attr('cy', -h / 2 + antennaR * 2)\n\t\t.attr('r', antennaR * 1.2)\n\t\n\t// Eyes\n\tconst eyeY = headTop + headSize * 0.4\n\tshapeSvg.insert(\"circle\", \":first-child\")\n\t\t.attr('class', 'robot-eye')\n\t\t.attr('cx', -eyeSpacing)\n\t\t.attr('cy', eyeY)\n\t\t.attr('r', eyeR)\n\tshapeSvg.insert(\"circle\", \":first-child\")\n\t\t.attr('class', 'robot-eye')\n\t\t.attr('cx', eyeSpacing)\n\t\t.attr('cy', eyeY)\n\t\t.attr('r', eyeR)\n\t\n\t// Mouth - simple smile\n\tconst mouthY = headTop + headSize * 0.7\n\tconst mouthW = headSize * 0.28\n\tshapeSvg.insert(\"path\", \":first-child\")\n\t\t.attr('class', 'robot-mouth')\n\t\t.attr('d', `M${-mouthW / 2},${mouthY} Q0,${mouthY + mouthW * 0.3} ${mouthW / 2},${mouthY}`)\n\t\t.attr('fill', 'none')\n\t\t.attr('stroke-width', antennaR * 0.5)\n\t\t.attr('stroke-linecap', 'round')\n\t\n\t// Ears\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", earW * 0.25)\n\t\t.attr(\"ry\", earW * 0.25)\n\t\t.attr('x', -headSize / 2 - earW - 1)\n\t\t.attr('y', eyeY - earH / 2)\n\t\t.attr('width', earW)\n\t\t.attr('height', earH)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", earW * 0.25)\n\t\t.attr(\"ry\", earW * 0.25)\n\t\t.attr('x', headSize / 2 + 1)\n\t\t.attr('y', eyeY - earH / 2)\n\t\t.attr('width', earW)\n\t\t.attr('height', earH)\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect(node, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nfunction webbrowser(parent: D3Element, bbox: BBox, node: D3Node) {\n\tconst dy = node.height / 8\n\tconst shapeSvg = parent\n\t\t.attr('label-offset-y', shapeLabelOffsetY(\"webbrowser\", node.width, node.height))\n\t\t.insert('g', ':first-child')\n\tshapeSvg.insert(\"path\", \":first-child\")\n\t\t.attr('d', `\n\t\t\tM${-node.width / 2},${-node.height / 2 + dy} h${node.width}\n\t\t\tM${-node.width / 2 + dy / 4},${-node.height / 2 + dy / 4} h${dy / 2} v${dy / 2} h${-dy / 2} z\n\t\t\tM${-node.width / 2 + dy},${-node.height / 2 + dy / 4} h${node.width - dy - dy / 4} v${dy / 2} h${-node.width + dy + dy / 4} z\n\t\t`)\n\tshapeSvg.insert(\"rect\", \":first-child\")\n\t\t.attr(\"rx\", 3).attr(\"ry\", 3)\n\t\t.attr(\"x\", -node.width / 2)\n\t\t.attr(\"y\", -node.height / 2)\n\t\t.attr(\"width\", node.width)\n\t\t.attr(\"height\", node.height);\n\n\tnode.intersect = function (point) {\n\t\treturn intersectRect(node, point);\n\t};\n\n\treturn shapeSvg;\n}\n\nexport const shapes: { [key: string]: (parent: SVGElement, node: D3Node) => SVGElement } = {\n\tbox: (parent: SVGElement, node: D3Node) => rect(new D3Element(parent), node, node).node(),\n\troundedbox: (parent: SVGElement, node: D3Node) => rect(new D3Element(parent), node, node, true).node(),\n\tcomponent: (parent: SVGElement, node: D3Node) => component(new D3Element(parent), node, node).node(),\n\tcylinder: (parent: SVGElement, node: D3Node) => cylinder(new D3Element(parent), node, node).node(),\n\tperson: (parent: SVGElement, node: D3Node) => person(new D3Element(parent), node, node).node(),\n\tcircle: (parent: SVGElement, node: D3Node) => circle(new D3Element(parent), node, node).node(),\n\tellipse: (parent: SVGElement, node: D3Node) => ellipse(new D3Element(parent), node, node).node(),\n\thexagon: (parent: SVGElement, node: D3Node) => hexagon(new D3Element(parent), node, node).node(),\n\tfolder: (parent: SVGElement, node: D3Node) => folder(new D3Element(parent), node, node).node(),\n\tmobiledevicelandscape: (parent: SVGElement, node: D3Node) => mobiledevicelandscape(new D3Element(parent), node, node).node(),\n\tmobiledeviceportrait: (parent: SVGElement, node: D3Node) => mobiledeviceportrait(new D3Element(parent), node, node).node(),\n\tmobiledevice: (parent: SVGElement, node: D3Node) => mobiledeviceportrait(new D3Element(parent), node, node).node(),\n\tpipe: (parent: SVGElement, node: D3Node) => pipe(new D3Element(parent), node, node).node(),\n\trobot: (parent: SVGElement, node: D3Node) => robot(new D3Element(parent), node, node).node(),\n\twebbrowser: (parent: SVGElement, node: D3Node) => webbrowser(new D3Element(parent), node, node).node(),\n}\n","/**\n * Undo functionality\n * at every change in the document, Undo can save a new version\n * so the user can \"undo\" and \"redo\" changes by reverting to an\n * older version of the document\n */\n\nexport class Undo {\n\tprivate readonly versions: Doc[] = [];\n\tprivate pos: number = 0;\n\tprivate lastSavedPos: number = 0;\n\tprivate readonly exportDoc: () => Doc;\n\tprivate readonly importDoc: (d: Doc) => void;\n\tchange: () => void;\n\tprivate tmpPreviousState: Doc | null = null;\n\n\tconstructor(id: string, exportDoc: () => Doc, importDoc: (d: Doc) => void) {\n\t\tthis.exportDoc = exportDoc;\n\t\tthis.importDoc = importDoc;\n\t\tthis.change = debounce(() => this.saveNow(), 300);\n\t}\n\n\t// Store the state previous to the changes collected in the debounce period\n\tbeforeChange() {\n\t\tif (!this.tmpPreviousState) {\n\t\t\tthis.tmpPreviousState = this.deepClone(this.exportDoc());\n\t\t}\n\t}\n\n\tlength() {\n\t\treturn this.versions.length;\n\t}\n\n\tcurrentState() {\n\t\treturn this.deepClone(this.versions[this.pos - 1]);\n\t}\n\n\tprivate saveNow() {\n\t\tif (!this.tmpPreviousState) {\n\t\t\tthrow Error(\"undo.change() was called without previously calling undo.beforeChange()!\");\n\t\t}\n\t\t\n\t\tthis.versions[this.pos] = this.deepClone(this.exportDoc());\n\t\tthis.versions[this.pos - 1] = this.tmpPreviousState;\n\t\tthis.tmpPreviousState = null;\n\t\tthis.pos += 1;\n\t\t\n\t\t// Remove anything that might be on top of this version\n\t\tthis.versions.splice(this.pos);\n\t}\n\n\tprivate deepClone(doc: Doc): Doc {\n\t\t// Use modern structuredClone if available, fallback to JSON\n\t\tif (typeof structuredClone !== 'undefined') {\n\t\t\treturn structuredClone(doc);\n\t\t}\n\t\treturn JSON.parse(JSON.stringify(doc));\n\t}\n\n\tundo() {\n\t\tif (this.pos < 2) return;\n\t\tthis.pos -= 1;\n\t\tconst doc = this.versions[this.pos - 1];\n\t\tthis.importDoc(this.deepClone(doc));\n\t}\n\n\tredo() {\n\t\tif (this.pos > this.versions.length - 1) return;\n\t\tconst doc = this.versions[this.pos];\n\t\tthis.importDoc(this.deepClone(doc));\n\t\tthis.pos += 1;\n\t}\n\n\tchanged() {\n\t\treturn this.pos !== this.lastSavedPos;\n\t}\n\n\tsetSaved() {\n\t\tthis.lastSavedPos = this.pos;\n\t}\n}\n\nfunction debounce(func: () => void, wait: number) {\n\tlet timeout: ReturnType;\n\treturn function () {\n\t\tconst context = this;\n\t\tconst later = function () {\n\t\t\ttimeout = null;\n\t\t\tfunc.apply(context);\n\t\t};\n\t\tclearTimeout(timeout);\n\t\ttimeout = setTimeout(later, wait);\n\t};\n}","// Constants and default configurations for the graph view\n\nexport interface Point {\n\tx: number;\n\ty: number;\n}\n\nexport interface BBox extends Point {\n\twidth: number;\n\theight: number;\n}\n\nexport interface NodeStyle {\n\t// Width of element, in pixels.\n\twidth?: number\n\t// Height of element, in pixels.\n\theight?: number\n\t// Background color of element as HTML RGB hex string (e.g. \"#ffffff\")\n\tbackground?: string\n\t// Stroke color of element as HTML RGB hex string (e.g. \"#000000\")\n\tstroke?: string\n\t// Foreground (text) color of element as HTML RGB hex string (e.g. \"#ffffff\")\n\tcolor?: string\n\t// Standard font size used to render text, in pixels.\n\tfontSize?: number\n\t// Shape used to render element.\n\tshape?: string\n\t// URL of PNG/JPG/GIF file or Base64 data URI representation.\n\ticon?: string\n\t// Type of border used to render element.\n\tborder?: string\n\t// Opacity used to render element; 0-100.\n\topacity?: number\n\t// Whether element metadata should be shown.\n\tmetadata?: boolean\n\t// Whether element description should be shown.\n\tdescription?: boolean\n}\n\nexport interface EdgeStyle {\n\t// Thickness of line, in pixels.\n\tthickness?: number\n\t// Color of line as HTML RGB hex string (e.g. \"#ffffff\").\n\tcolor?: string\n\t// Standard font size used to render relationship annotation, in pixels.\n\tfontSize?: number\n\t// Width of relationship annotation, in pixels.\n\twidth?: number\n\t// Whether line is dashed.\n\tdashed?: boolean\n\t// Position of label along edge (0-100).\n\tposition?: number\n\t// Opacity used to render relationship; 0-100.\n\topacity?: number\n\t// Arrow style for the edge.\n\tarrowStyle?: 'normal' | 'large' | 'small' | 'none'\n}\n\n// Default styles\nexport const DEFAULT_EDGE_STYLE: EdgeStyle = {\n\tthickness: 3,\n\tcolor: '#999',\n\topacity: 1,\n\tfontSize: 22,\n\tdashed: true,\n};\n\nexport const DEFAULT_NODE_STYLE: NodeStyle = {\n\twidth: 280,\n\theight: 180,\n\tbackground: 'rgba(255, 255, 255, .9)',\n\tcolor: '#666',\n\topacity: .9,\n\tstroke: '#999',\n\tfontSize: 22,\n\tshape: 'Box'\n};\n\n// SVG styles\nexport const SVG_STYLES = {\n\tnodeBorder: {\n\t\tfill: \"rgba(255, 255, 255, 0.86)\",\n\t\tstroke: \"#aaa\",\n\t\tfilter: 'url(#shadow)',\n\t},\n\tnodeText: {\n\t\t'font-family': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\tedgeText: {\n\t\t'font-family': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\tedgeRect: {\n\t\tfill: \"none\",\n\t\tstroke: \"none\",\n\t},\n\tgroupRect: {\n\t\tfill: \"rgba(0, 0, 0, 0.02)\",\n\t\tstroke: \"#666\",\n\t\t'stroke-width': 3,\n\t\t\"stroke-dasharray\": 4,\n\t},\n\tgroupText: {\n\t\tfill: \"#666\",\n\t\t\"font-size\": 22,\n\t\t\"font-weight\": \"500\",\n\t\t'font-family': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',\n\t\tcursor: \"default\"\n\t}\n};\n\n// Configuration constants\nexport const SVG_PADDING = 20;\nexport const DEFAULT_GRID_SIZE = 25;\nexport const EDGE_SPREAD_DISTANCE = 70;\nexport const EDGE_SPREAD_DISTANCE_X = 200;\n\n// Utility function to apply styles to SVG elements\nexport const applyStyle = (el: SVGElement, style: { [key: string]: string | number }) => {\n\tObject.keys(style).forEach(key => {\n\t\tconst value = style[key];\n\t\tif (typeof value === 'number') {\n\t\t\tel.style.setProperty(key, value.toString());\n\t\t} else {\n\t\t\tel.style.setProperty(key, value);\n\t\t}\n\t});\n};\n\n// Utility function to calculate distance between two points\nexport const calculateDistance = (p1: Point, p2: Point): number => {\n\treturn Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));\n};","import {applyStyle, SVG_STYLES} from \"./constants\";\nimport {create} from \"./svg-create\";\nimport {svgTextWrap} from \"./svg-text\";\n\ninterface TextBlockLayout {\n\tlines: string[];\n\tfontSize: number;\n\tlineHeight: number;\n\tbold: boolean;\n\tfield?: string;\n\tgapAfter: number;\n}\n\nexport interface NodeContentLayout {\n\tblocks: TextBlockLayout[];\n\ttextHeight: number;\n\tminimumHeight: number;\n}\n\nconst HORIZONTAL_PADDING = 18;\nconst VERTICAL_PADDING = 18;\nconst TITLE_GAP = 6;\nconst METADATA_GAP = 10;\n\nfunction textBlock(\n\ttext: string,\n\twidth: number,\n\tfontSize: number,\n\tbold: boolean,\n\tgapAfter: number,\n\tfield?: string,\n): TextBlockLayout {\n\tconst attrs = {\n\t\t\"font-family\": String(SVG_STYLES.nodeText[\"font-family\"]),\n\t\t\"font-size\": `${fontSize}px`,\n\t\t\"font-weight\": bold ? \"bold\" : \"normal\",\n\t};\n\tconst wrapped = svgTextWrap(text, width, attrs);\n\tconst lines = wrapped.lines.length > 0 ? wrapped.lines : [\"\"];\n\n\treturn {\n\t\tlines,\n\t\tfontSize,\n\t\tlineHeight: fontSize + 2,\n\t\tbold,\n\t\tfield,\n\t\tgapAfter,\n\t};\n}\n\nexport function layoutNodeContent(\n\ttitle: string,\n\tsubtitle: string,\n\tdescription: string,\n\tnodeWidth: number,\n\tfontSize: number,\n): NodeContentLayout {\n\tconst textWidth = Math.max(nodeWidth - HORIZONTAL_PADDING * 2, 80);\n\tconst blocks = [\n\t\ttextBlock(title, textWidth, fontSize, true, TITLE_GAP, \"name\"),\n\t\ttextBlock(`[${subtitle}]`, textWidth, fontSize * 0.75, false, METADATA_GAP),\n\t\ttextBlock(description, textWidth, Math.min(fontSize * 0.8, 16), false, 0, \"description\"),\n\t];\n\tconst textHeight = blocks.reduce(\n\t\t(height, block) => height + block.lines.length * block.lineHeight + block.gapAfter,\n\t\t0,\n\t);\n\n\treturn {\n\t\tblocks,\n\t\ttextHeight,\n\t\tminimumHeight: textHeight + VERTICAL_PADDING * 2,\n\t};\n}\n\nexport function buildNodeContent(layout: NodeContentLayout, color?: string): SVGGElement {\n\tconst group = create.element(\"g\") as SVGGElement;\n\tlet top = -layout.textHeight / 2;\n\n\tlayout.blocks.forEach((block) => {\n\t\tconst text = create.text(\"\", {\"text-anchor\": \"middle\"});\n\t\tapplyStyle(text, SVG_STYLES.nodeText);\n\t\tif (color) {\n\t\t\ttext.setAttribute(\"fill\", color);\n\t\t}\n\t\tif (block.field) {\n\t\t\ttext.setAttribute(\"data-field\", block.field);\n\t\t}\n\n\t\tblock.lines.forEach((line, index) => {\n\t\t\tconst span = create.element(\"tspan\", {\n\t\t\t\tx: 0,\n\t\t\t\ty: top + block.fontSize + index * block.lineHeight,\n\t\t\t\t\"font-size\": `${block.fontSize}px`,\n\t\t\t\t\"font-weight\": block.bold ? \"bold\" : \"normal\",\n\t\t\t});\n\t\t\tspan.textContent = line;\n\t\t\ttext.append(span);\n\t\t});\n\n\t\tgroup.append(text);\n\t\ttop += block.lines.length * block.lineHeight + block.gapAfter;\n\t});\n\n\treturn group;\n}\n","import {defs} from \"./defs\";\nimport {create, setPosition} from \"./svg-create\";\nimport {cursorInteraction} from \"svg-editor-tools/lib/cursor-interaction\";\nimport {shapeLabelOffsetY, shapes} from \"./shapes\";\nimport {\n\tboxesOverlap,\n\tcabDistance,\n\tinsideBox,\n\tintersectPolylineBox,\n\tproject,\n\tscaleBox,\n\tSegment,\n\tuncenterBox\n} from \"./intersect\";\nimport {autoLayout, LayoutOptions} from \"./layout\";\nimport {Undo} from \"./undo\";\nimport {\n\tADD_LABEL_VERTEX,\n\tADD_VERTEX,\n\tDEL_VERTEX,\n\tDESELECT,\n\tfindShortcut,\n\tREDO,\n\tSELECT_ALL,\n\tUNDO,\n\tZOOM_100,\n\tZOOM_FIT,\n\tZOOM_IN,\n\tZOOM_OUT\n} from \"../shortcuts\";\nimport {\n\tPoint,\n\tBBox,\n\tNodeStyle,\n\tEdgeStyle,\n\tDEFAULT_EDGE_STYLE,\n\tDEFAULT_NODE_STYLE,\n\tSVG_STYLES,\n\tSVG_PADDING,\n\tDEFAULT_GRID_SIZE,\n\tapplyStyle,\n\tcalculateDistance\n} from \"./constants\";\nimport {\n\tcalculateEdgeVertices,\n\tcalculateLabelPlacement,\n\tcreateEdgeSegments,\n\tEdgeLabelPlacement\n} from \"./edge-utils\";\nimport {\n\tbuildNodeContent,\n\tlayoutNodeContent,\n\tNodeContentLayout\n} from \"./node-content\";\n\n\n// Point and BBox interfaces are now imported from constants\n\nexport interface Group extends BBox {\n\tid: string;\n\tname: string;\n\tnodes: (Node | Group)[];\n\tref?: SVGGElement;\n\tstyle: NodeStyle;\n}\n\nexport interface NodeLink {\n\thref: string;\n\texportHref: string;\n}\n\nexport type LayoutDirection = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT';\n\nexport interface Node extends BBox {\n\tid: string;\n\ttitle: string;\n\tsub: string;\n\tdescription: string;\n\n\tref?: SVGGElement;\n\tselected?: boolean;\n\n\tintersect: (p: Point) => Point\n\n\tstyle: NodeStyle\n\tlink?: NodeLink\n\tcontentLayout: NodeContentLayout\n}\n\n// NodeStyle interface is now imported from constants\n\n// EdgeStyle interface is now imported from constants\n\n// Default styles are now imported from constants\nconst defaultEdgeStyle = DEFAULT_EDGE_STYLE;\nconst defaultNodeStyle = DEFAULT_NODE_STYLE;\n\n// Edge and EdgeVertex interfaces are now defined in edge-utils.ts\n// Using local interfaces for compatibility with existing code\ninterface Edge {\n\tid: string;\n\tlabel: string;\n\tfrom: Node;\n\tto: Node;\n\tvertices?: EdgeVertex[];\n\tref?: SVGGElement;\n\tstyle: EdgeStyle;\n\tinitVertex: (p: Point) => EdgeVertex;\n\tuserDeletedVertices?: boolean; // Track if user explicitly deleted vertices\n\tlabelVertex?: EdgeVertex; // ELK-calculated label position (separate from routing vertices)\n\tlabelBounds?: BBox;\n}\n\ninterface EdgeVertex extends Point {\n\tid: string\n\tselected?: boolean\n\tedge: Edge\n\tref?: SVGElement\n\tlabel?: boolean\n\tauto?: boolean\n}\n\ninterface Layout {\n\t[k: string]: Point | (Point & { label: boolean })[] | boolean\n}\n\nexport class GraphData {\n\tid: string;\n\tname: string;\n\tnodesMap: Map;\n\tedges: Edge[];\n\tedgeVertices: Map\n\tgroupsMap: Map;\n\tmetadata: any;\n\tlayoutDirection?: LayoutDirection;\n\tcolorToVarMap: Map = new Map(); // For CSS custom properties theming\n\tprivate _undo: Undo;\n\tprivate _gridVisible: boolean = false;\n\tprivate _snapToGrid: boolean = true;\n\tprivate _gridSize: number = 25;\n\tprivate _skipAutoFit: boolean = false;\n\n\tconstructor(id?: string, name?: string) {\n\t\tthis.id = id;\n\t\tthis.name = name;\n\n\t\tthis.edges = [];\n\t\tthis.edgeVertices = new Map;\n\t\tthis.nodesMap = new Map;\n\t\tthis.groupsMap = new Map;\n\n\t\tthis._undo = new Undo(\n\t\t\tthis.id,\n\t\t\t() => this.exportLayout(true),\n\t\t\t(lo) => this.importLayout(lo, true)\n\t\t)\n\n\t\t// @ts-ignore\n\t\twindow.graph = this\n\t}\n\n\t// after the graph model is build using addNode, addEdge etc, initialize\n\tinit(layout?: Layout) {\n\t\tlayout && this.importLayout(layout)\n\t\tthis._undo = new Undo(\n\t\t\tthis.id,\n\t\t\t() => this.exportLayout(true),\n\t\t\t(lo) => this.importLayout(lo, true)\n\t\t)\n\t\tif (this._undo.length()) {\n\t\t\tthis.importLayout(this._undo.currentState())\n\t\t}\n\t\t\n\t\t// Save initial state so first action is undoable\n\t\tthis._undo.beforeChange()\n\t\tthis._undo.change()\n\t}\n\n\taddNode(id: string, label: string, sub: string, description: string, style: NodeStyle, link?: NodeLink) {\n\t\tif (this.nodesMap.has(id)) throw Error('duplicate node: ' + id)\n\t\tconst nodeStyle = {...defaultNodeStyle, ...style};\n\t\tconst shape = (nodeStyle.shape || 'Box').toLowerCase();\n\t\tconst minimumWidth = 280;\n\t\tconst minimumHeight = shape === 'person' ? 240 : 180;\n\t\tconst width = Math.max(minimumWidth, nodeStyle.width || 0);\n\t\tconst fontSize = nodeStyle.fontSize || 22;\n\t\tconst contentLayout = layoutNodeContent(label, sub, description, width, fontSize);\n\t\tlet height = Math.max(minimumHeight, nodeStyle.height || 0, contentLayout.minimumHeight);\n\n\t\t// Some shapes shift labels down to reserve visual space for an icon,\n\t\t// header, or curved edge. Grow until the shifted content also fits.\n\t\tfor (let i = 0; i < 20; i++) {\n\t\t\tconst requiredHeight = contentLayout.minimumHeight +\n\t\t\t\tMath.abs(shapeLabelOffsetY(shape, width, height));\n\t\t\tif (requiredHeight <= height + 0.1) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\theight = requiredHeight;\n\t\t}\n\n\t\tconst n: Node = {\n\t\t\tid, title: label, sub, description, style: nodeStyle,\n\t\t\tx: 0, y: 0, width, height, intersect: null, link, contentLayout\n\t\t}\n\t\tthis.nodesMap.set(n.id, n)\n\t}\n\n\tnodes() {\n\t\treturn Array.from(this.nodesMap.values())\n\t}\n\n\taddEdge(id: string, fromNode: string, toNode: string, label: string, vertices: Point[], style: EdgeStyle) {\n\t\tvertices && vertices.forEach((p, i) => {\n\t\t\tconst v = p as EdgeVertex\n\t\t\tv.id = `v-${id}-${i}`\n\t\t\tthis.edgeVertices.set(v.id, v)\n\t\t})\n\t\t// Deterministic vertex IDs.\n\t\t//\n\t\t// `mdl svg` renders diagrams headlessly and saves the resulting SVG. If we\n\t\t// generate random vertex IDs, the SVG changes on every run even when the\n\t\t// underlying model and layout are unchanged. Use a stable hash instead.\n\t\tconst fnv1a36 = (input: string) => {\n\t\t\tlet h = 0x811c9dc5\n\t\t\tfor (let i = 0; i < input.length; i++) {\n\t\t\t\th ^= input.charCodeAt(i)\n\t\t\t\th = Math.imul(h, 0x01000193)\n\t\t\t}\n\t\t\treturn (h >>> 0).toString(36)\n\t\t}\n\t\tconst stableVertexID = (edgeID: string, p: Point) => {\n\t\t\tconst x = (p as any).x\n\t\t\tconst y = (p as any).y\n\t\t\treturn `v-${edgeID}-a-${fnv1a36(`${edgeID}:${x}:${y}`)}`\n\t\t}\n\t\tconst initVertex = (p: Point) => {\n\t\t\tconst v = p as EdgeVertex\n\t\t\tif (!v.id) {\n\t\t\t\tv.id = stableVertexID(edge.id, p)\n\t\t\t\tthis.edgeVertices.set(v.id, v)\n\t\t\t}\n\t\t\tv.edge = edge\n\t\t\treturn p as EdgeVertex\n\t\t}\n\t\tconst edge = {\n\t\t\tid,\n\t\t\tfrom: this.nodesMap.get(fromNode),\n\t\t\tto: this.nodesMap.get(toNode),\n\t\t\tlabel,\n\t\t\tvertices: null as EdgeVertex[],\n\t\t\tstyle: {...defaultEdgeStyle, ...style},\n\t\t\tinitVertex,\n\t\t\tuserDeletedVertices: false\n\t\t}\n\t\tthis.edges.push(edge)\n\t\tif (vertices) {\n\t\t\tedge.vertices = vertices.map(p => edge.initVertex(p))\n\t\t}\n\t}\n\n\taddGroup(id: string, name: string, nodesOrGroups: string[], style: NodeStyle) {\n\t\tif (this.groupsMap.has(id)) {\n\t\t\tconsole.error(`Group exists: ${id} ${name}`)\n\t\t\treturn\n\t\t}\n\t\tconst group: Group = {\n\t\t\tid, name, x: null, y: null, width: null, height: null,\n\t\t\tnodes: nodesOrGroups.map(k => {\n\t\t\t\tconst n = this.nodesMap.get(k) || this.groupsMap.get(k)\n\t\t\t\tif (!n) console.error(`Node or group ${k} not found for group ${id} \"${name}\"`)\n\t\t\t\treturn n\n\t\t\t}).filter(Boolean),\n\t\t\tstyle\n\t\t}\n\t\tthis.groupsMap.set(id, group)\n\t}\n\n\t// private rebuildNode(node: Node) {\n\t// \tconst p = node.ref.parentElement;\n\t// \tp.removeChild(node.ref)\n\t// \tnode.ref = buildNode(node, this)\n\t// \tp.appendChild(node.ref)\n\t// \tthis.redrawEdges(node)\n\t// \tthis.redrawGroups(node)\n\t// }\n\n\tsetNodeSelected(node: Node, selected: boolean) {\n\t\tnode.selected = selected\n\t\tselected ?\n\t\t\tnode.ref.classList.add('selected') :\n\t\t\tnode.ref.classList.remove('selected')\n\t\tthis.updateEdgesSel()\n\t}\n\n\tprivate updateEdgesSel() {\n\t\tthis.edges.forEach(e => {\n\t\t\tif (e.to.selected || e.from.selected) {\n\t\t\t\te.ref.classList.add('selected')\n\t\t\t} else {\n\t\t\t\te.ref.classList.remove('selected')\n\t\t\t}\n\t\t})\n\t}\n\n\tmoveNode(n: Node, x: number, y: number, disableSnap: boolean = false, skipUndo: boolean = false) {\n\t\tif (!n) return\n\t\t\n\t\t// Apply snap-to-grid if enabled and not explicitly disabled\n\t\tif (this._snapToGrid && !disableSnap) {\n\t\t\tconst snapped = this.snapToGrid(x, y);\n\t\t\tx = snapped.x;\n\t\t\ty = snapped.y;\n\t\t}\n\t\t\n\t\tif (n.x == x && n.y == y) return\n\t\t\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.beforeChange()\n\t\t}\n\t\tn.x = x;\n\t\tn.y = y;\n\t\tsetPosition(n.ref, x, y)\n\t\tthis.redrawEdges(n);\n\t\tthis.redrawGroups(n)\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.change()\n\t\t}\n\t}\n\n\tmoveEdgeVertex(v: EdgeVertex, x: number, y: number, disableSnap: boolean = false, skipUndo: boolean = false) {\n\t\t\n\t\tif (this._snapToGrid && !disableSnap) {\n\t\t\tconst snapped = this.snapToGrid(x, y);\n\t\t\tx = snapped.x;\n\t\t\ty = snapped.y;\n\t\t}\n\t\t// Use exact coordinates (no rounding needed with modern grid system)\n\t\t\n\t\tif (v.x == x && v.y == y) return\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.beforeChange()\n\t\t}\n\t\tv.x = x;\n\t\tv.y = y;\n\t\tthis.redrawEdge(v.edge)\n\t\tif (!skipUndo) {\n\t\t\tthis._undo.change()\n\t\t}\n\t}\n\n\tmoveSelected(dx: number, dy: number, disableSnap: boolean = false) {\n\t\tthis.nodes().forEach(n => n.selected && this.moveNode(n, n.x + dx, n.y + dy, disableSnap, false))\n\t\tthis.edgeVertices.forEach(v => v.selected && this.moveEdgeVertex(v, v.x + dx, v.y + dy, disableSnap, false))\n\t}\n\n\tinsertEdgeVertex(edge: Edge, p: Point, pos: number, isLabel: boolean) {\n\t\tthis._undo.beforeChange()\n\t\tconst v = edge.initVertex(p)\n\t\tv.selected = true\n\t\tif (isLabel) { // when shift down, make it label position\n\t\t\tedge.vertices.forEach(v => v.label = false)\n\t\t\tv.label = true\n\t\t}\n\t\tedge.vertices.splice(pos - 1, 0, v)\n\t\tthis.redrawEdge(edge)\n\t\tthis._undo.change()\n\t}\n\n\tdeleteEdgeVertex(v: EdgeVertex) {\n\t\tthis._undo.beforeChange()\n\t\t\n\t\tconst index = v.edge.vertices.indexOf(v)\n\t\tif (index >= 0) {\n\t\t\tv.edge.vertices.splice(index, 1)\n\t\t\tthis.edgeVertices.delete(v.id)\n\t\t\t\n\t\t\t// Mark that user explicitly deleted vertices from this edge\n\t\t\tv.edge.userDeletedVertices = true\n\t\t}\n\t\t\n\t\tthis.redrawEdge(v.edge)\n\t\tthis._undo.change()\n\t}\n\n\tchanged() {\n\t\treturn this._undo.changed()\n\t}\n\n\tundo() {\n\t\tthis._undo.undo()\n\t}\n\n\tredo() {\n\t\tthis._undo.redo()\n\t}\n\n\t// moves the entire graph to be aligned top-left of the drawing area\n\t// used to bring back to visible the nodes that end up at negative coordinates\n\talignTopLeft() {\n\t\tconst contentBounds = this.calculateContentBounds()\n\t\tconst padding = 100 // Reasonable padding for viewport\n\t\t\n\t\tconst offsetX = -contentBounds.x + padding\n\t\tconst offsetY = -contentBounds.y + padding\n\t\t\n\t\t// Set flag to prevent React useEffect from calling fitToView during this operation\n\t\tthis._skipAutoFit = true\n\t\t\n\t\tthis._undo.beforeChange()\n\t\t\n\t\tthis.nodesMap.forEach(node => {\n\t\t\tthis.moveNode(node, node.x + offsetX, node.y + offsetY, true, true) // Disable snap and undo during reset\n\t\t})\n\t\t\n\t\tthis.edgeVertices.forEach(vertex => {\n\t\t\tthis.moveEdgeVertex(vertex, vertex.x + offsetX, vertex.y + offsetY, true, true) // Disable snap and undo during reset\n\t\t})\n\t\t\n\t\tthis._undo.change()\n\t\t\n\t\t// DON'T clear view state here - let resetPanTransform handle it to avoid React useEffect recursion\n\t}\n\t\n\t// Reset pan transform to (0,0) while preserving zoom\n\tresetPanTransform() {\n\t\tconst currentZoom = getZoom()\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (zoomGroup) {\n\t\t\tzoomGroup.setAttribute('transform', `scale(${currentZoom}) translate(0, 0)`)\n\t\t\tupdatePanningOptimized(this)\n\t\t}\n\t\t\n\t\t// Clear view state so this reset is not overridden\n\t\tclearViewState(this.id)\n\t\t\n\t\t// Reset the skip auto fit flag after reset is complete\n\t\tthis._skipAutoFit = false\n\t}\n\t\n\t// Check if auto-fit should be skipped (used by React useEffect)\n\tshouldSkipAutoFit(): boolean {\n\t\treturn this._skipAutoFit\n\t}\n\n\t// Reset view to default state: 100% zoom, centered at origin\n\tresetView() {\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (zoomGroup) {\n\t\t\t// Reset to 100% zoom, centered at origin\n\t\t\tzoomGroup.setAttribute('transform', 'scale(1) translate(0, 0)')\n\t\t\tupdatePanning()\n\t\t}\n\t\t\n\t\t// Clear any saved view state so this reset position is not overridden\n\t\tclearViewState(this.id)\n\t}\n\n\t//redraw connected edges\n\tprivate redrawEdges(n: Node) {\n\t\tthis.edges.forEach(e => (n == e.from || n == e.to) && this.redrawEdge(e))\n\t\tthis.updateEdgesSel()\n\t}\n\n\tredrawEdge(e: Edge) {\n\t\tconst p = e.ref.parentElement;\n\t\tp.removeChild(e.ref)\n\t\te.ref = buildEdge(this, e)\n\t\tp.append(e.ref)\n\t}\n\n\tprivate redrawGroups(node: Node) {\n\t\tthis.groupsMap.forEach(group => {\n\t\t\t//if (group.nodes.indexOf(node) == -1) return\n\t\t\tconst p = group.ref.parentElement\n\t\t\tp.removeChild(group.ref)\n\t\t\tbuildGroup(group)\n\t\t\tp.append(group.ref)\n\t\t})\n\t}\n\n\texportSVG() {\n\t\t// Get the original SVG\n\t\tconst originalSvg: SVGSVGElement = document.querySelector('svg#graph')\n\t\tconst elastic = originalSvg.querySelector('rect.elastic')\n\t\t\n\t\t// Clone the SVG for export (completely separate from the live one)\n\t\tconst exportSvg = originalSvg.cloneNode(true) as SVGSVGElement\n\n\t\t// Standalone SVGs navigate to sibling view files instead of editor routes.\n\t\texportSvg.querySelectorAll('a.nodeLink[data-export-href]').forEach(link => {\n\t\t\tlink.setAttribute('href', link.getAttribute('data-export-href') || '')\n\t\t\tlink.removeAttribute('data-export-href')\n\t\t})\n\t\t\n\t\t// Remove elastic element from export\n\t\tconst exportElastic = exportSvg.querySelector('rect.elastic')\n\t\tif (exportElastic) {\n\t\t\texportElastic.remove()\n\t\t}\n\t\t\n\t\t// Calculate actual content bounds including all elements\n\t\tconst contentBounds = this.calculateContentBounds()\n\t\t\n\t\t// Add padding around content\n\t\tconst padding = 50\n\t\t\n\t\t// Calculate final export dimensions (always positive)\n\t\tconst exportWidth = contentBounds.width + (padding * 2)\n\t\tconst exportHeight = contentBounds.height + (padding * 2)\n\t\t\n\t\t// Calculate offset to move content to start at (padding, padding) within the export area\n\t\tconst offsetX = -contentBounds.x + padding\n\t\tconst offsetY = -contentBounds.y + padding\n\t\t\n\t\t// Apply export positioning to the cloned SVG elements\n\t\tconst exportZoomGroup = exportSvg.querySelector('g.zoom') as SVGGElement\n\t\tif (exportZoomGroup) {\n\t\t\t// Reset zoom to 1 and apply offset transform to position content properly\n\t\t\texportZoomGroup.setAttribute('transform', `scale(1) translate(${offsetX}, ${offsetY})`)\n\t\t}\n\t\t\n\t\t// Set proper viewBox and dimensions for export - viewBox always starts at (0,0)\n\t\texportSvg.setAttribute('viewBox', `0 0 ${exportWidth} ${exportHeight}`)\n\t\texportSvg.setAttribute('width', String(exportWidth))\n\t\texportSvg.setAttribute('height', String(exportHeight))\n\t\t\n\t\t// Add required SVG namespace for browser compatibility\n\t\texportSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')\n\t\t\n\t\t// Convert inline styles to CSS custom properties for theming support\n\t\tthis.convertStylesToCustomProperties(exportSvg)\n\t\t\n\t\t// Inject metadata with current layout\n\t\tconst script = document.createElement('script')\n\t\tscript.setAttribute('type', 'application/json')\n\t\tthis.metadata.layout = this.exportLayout()\n\t\tscript.append('')\n\t\texportSvg.insertBefore(script, exportSvg.firstChild)\n\t\t\n\t\t// Get the export SVG as string\n\t\tconst src = exportSvg.outerHTML\n\t\t\n\t\t// No restoration needed since we never touched the original SVG!\n\t\treturn src\n\t}\n\n\t// Convert inline fill/stroke attributes to CSS custom properties with fallbacks.\n\t// This enables theming: container pages can override colors via CSS variables.\n\tprivate convertStylesToCustomProperties(svg: SVGSVGElement) {\n\t\tif (this.colorToVarMap.size === 0) return\n\n\t\t// Process all elements with fill attribute\n\t\tsvg.querySelectorAll('[fill]').forEach(el => {\n\t\t\tconst fill = el.getAttribute('fill')\n\t\t\tif (fill && this.colorToVarMap.has(fill)) {\n\t\t\t\tel.setAttribute('fill', `var(${this.colorToVarMap.get(fill)}, ${fill})`)\n\t\t\t}\n\t\t})\n\n\t\t// Process all elements with stroke attribute\n\t\tsvg.querySelectorAll('[stroke]').forEach(el => {\n\t\t\tconst stroke = el.getAttribute('stroke')\n\t\t\tif (stroke && this.colorToVarMap.has(stroke)) {\n\t\t\t\tel.setAttribute('stroke', `var(${this.colorToVarMap.get(stroke)}, ${stroke})`)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Calculate the actual bounds of all content including nodes, edges, and groups\n\tcalculateContentBounds(): BBox {\n\t\tlet minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n\t\t\n\t\t// Process nodes (including their actual dimensions)\n\t\tthis.nodes().forEach(node => {\n\t\t\tconst left = node.x - node.width / 2\n\t\t\tconst right = node.x + node.width / 2\n\t\t\tconst top = node.y - node.height / 2\n\t\t\tconst bottom = node.y + node.height / 2\n\t\t\t\n\t\t\tminX = Math.min(minX, left)\n\t\t\tmaxX = Math.max(maxX, right)\n\t\t\tminY = Math.min(minY, top)\n\t\t\tmaxY = Math.max(maxY, bottom)\n\t\t})\n\t\t\n\t\t// Process edge vertices (much faster than complex label calculations)\n\t\tthis.edgeVertices.forEach(vertex => {\n\t\t\tminX = Math.min(minX, vertex.x - 5)\n\t\t\tmaxX = Math.max(maxX, vertex.x + 5)\n\t\t\tminY = Math.min(minY, vertex.y - 5)\n\t\t\tmaxY = Math.max(maxY, vertex.y + 5)\n\t\t})\n\t\t\n\t\t// Process groups\n\t\tthis.groupsMap.forEach(group => {\n\t\t\tconst left = group.x - group.width / 2\n\t\t\tconst right = group.x + group.width / 2\n\t\t\tconst top = group.y - group.height / 2\n\t\t\tconst bottom = group.y + group.height / 2\n\t\t\t\n\t\t\tminX = Math.min(minX, left)\n\t\t\tmaxX = Math.max(maxX, right)\n\t\t\tminY = Math.min(minY, top)\n\t\t\tmaxY = Math.max(maxY, bottom)\n\t\t})\n\t\t\n\t\t// Process edges (simplified - just endpoints and vertices, skip complex label calculations)\n\t\tthis.edges.forEach(edge => {\n\t\t\t// Edge endpoints\n\t\t\tminX = Math.min(minX, edge.from.x - 10, edge.to.x - 10)\n\t\t\tmaxX = Math.max(maxX, edge.from.x + 10, edge.to.x + 10)\n\t\t\tminY = Math.min(minY, edge.from.y - 10, edge.to.y - 10)\n\t\t\tmaxY = Math.max(maxY, edge.from.y + 10, edge.to.y + 10)\n\t\t\t\n\t\t\t// Edge vertices (if any)\n\t\t\tif (edge.vertices) {\n\t\t\t\tedge.vertices.forEach(vertex => {\n\t\t\t\t\tminX = Math.min(minX, vertex.x - 10)\n\t\t\t\t\tmaxX = Math.max(maxX, vertex.x + 10)\n\t\t\t\t\tminY = Math.min(minY, vertex.y - 10)\n\t\t\t\t\tmaxY = Math.max(maxY, vertex.y + 10)\n\t\t\t\t})\n\t\t\t}\n\t\t\t\n\t\t\t// Simplified label bounds (avoid expensive path calculations)\n\t\t\tif (edge.label && edge.label.trim()) {\n\t\t\t\t// Just use approximate center between from and to nodes\n\t\t\t\tconst centerX = (edge.from.x + edge.to.x) / 2\n\t\t\t\tconst centerY = (edge.from.y + edge.to.y) / 2\n\t\t\t\tconst approxLabelSize = edge.label.length * 10 + 50 // Rough estimate\n\t\t\t\t\n\t\t\t\tminX = Math.min(minX, centerX - approxLabelSize)\n\t\t\t\tmaxX = Math.max(maxX, centerX + approxLabelSize)\n\t\t\t\tminY = Math.min(minY, centerY - 25)\n\t\t\t\tmaxY = Math.max(maxY, centerY + 25)\n\t\t\t}\n\t\t})\n\t\t\n\t\t// Handle empty graph\n\t\tif (minX === Infinity) {\n\t\t\treturn { x: 0, y: 0, width: 100, height: 100 }\n\t\t}\n\t\t\n\t\treturn {\n\t\t\tx: minX,\n\t\t\ty: minY,\n\t\t\twidth: maxX - minX,\n\t\t\theight: maxY - minY\n\t\t}\n\t}\n\n\n\n\t/**\n\t * @param full when true, the edges without vertices are saved too, used for undo buffer\n\t * for saving, full is false\n\t */\n\texportLayout(full = false) {\n\t\tconst ret: Layout = {}\n\t\tthis.nodes().forEach(n => ret[n.id] = {x: n.x, y: n.y})\n\t\tthis.edges.forEach(e => {\n\t\t\tif (!e.vertices) return\n\t\t\t// Save all vertices (both user and auto-generated), preserving their properties\n\t\t\tconst lst = e.vertices.map(v => ({\n\t\t\t\tx: v.x, \n\t\t\t\ty: v.y, \n\t\t\t\tlabel: v.label,\n\t\t\t\tauto: v.auto // Preserve auto flag so we know which are ELK-generated\n\t\t\t}));\n\t\t\tif (lst.length || full) {\n\t\t\t\tret[`e-${e.id}`] = lst\n\t\t\t}\n\t\t\t// Also save the userDeletedVertices flag as metadata\n\t\t\tif (e.userDeletedVertices) {\n\t\t\t\tret[`e-${e.id}-deleted`] = true\n\t\t\t}\n\t\t})\n\t\treturn ret\n\t}\n\n\tsetSaved() {\n\t\tthis._undo.setSaved()\n\t}\n\n\timportLayout(layout: { [key: string]: any }, rerender = false) {\n\t\t// First pass: collect all coordinate values to find bounds\n\t\tconst coordinates: Array<{x: number, y: number}> = [];\n\t\t\n\t\tObject.entries(layout).forEach(([k, v]) => {\n\t\t\tif (!k.startsWith('e-') && v.x !== undefined && v.y !== undefined) {\n\t\t\t\t// Node coordinates\n\t\t\t\tcoordinates.push({x: v.x, y: v.y});\n\t\t\t} else if (k.startsWith('e-') && Array.isArray(v)) {\n\t\t\t\t// Edge vertex coordinates\n\t\t\t\tv.forEach((vertex: any) => {\n\t\t\t\t\tif (vertex.x !== undefined && vertex.y !== undefined) {\n\t\t\t\t\t\tcoordinates.push({x: vertex.x, y: vertex.y});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\t\n\t\t// Calculate normalization offset if we have coordinates\n\t\tlet offsetX = 0;\n\t\tlet offsetY = 0;\n\t\t\n\t\tif (coordinates.length > 0) {\n\t\t\tconst minX = Math.min(...coordinates.map(c => c.x));\n\t\t\tconst minY = Math.min(...coordinates.map(c => c.y));\n\t\t\t\n\t\t\t// Only normalize if coordinates are problematic (negative or very large)\n\t\t\tif (minX < -100 || minY < -100 || Math.max(...coordinates.map(c => c.x)) > 3000 || Math.max(...coordinates.map(c => c.y)) > 2000) {\n\t\t\t\tconst padding = 50;\n\t\t\t\toffsetX = -minX + padding;\n\t\t\t\toffsetY = -minY + padding;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Second pass: apply coordinates with normalization\n\t\tObject.entries(layout).forEach(([k, v]) => {\n\t\t\t// nodes\n\t\t\tconst n = this.nodesMap.get(k)\n\t\t\tif (n) {\n\t\t\t\tn.x = v.x + offsetX\n\t\t\t\tn.y = v.y + offsetY\n\t\t\t} else\n\t\t\t\t// edge vertices\n\t\t\tif (k.startsWith('e-') && !k.endsWith('-deleted')) {\n\t\t\t\tconst edge = this.edges.find(e => e.id == k.slice(2))\n\t\t\t\tif (!edge) return;\n\t\t\t\tedge.vertices && edge.vertices.forEach(v => this.edgeVertices.delete(v.id))\n\t\t\t\tedge.vertices = v.map((p: Point) => {\n\t\t\t\t\tconst normalizedPoint = { \n\t\t\t\t\t\tx: p.x + offsetX, \n\t\t\t\t\t\ty: p.y + offsetY \n\t\t\t\t\t} as Point;\n\t\t\t\t\t// Preserve any additional properties like 'label' and 'auto'\n\t\t\t\t\tObject.assign(normalizedPoint, p, { x: p.x + offsetX, y: p.y + offsetY });\n\t\t\t\t\tconst vertex = edge.initVertex(normalizedPoint);\n\t\t\t\t\t// Ensure auto flag is preserved after initVertex\n\t\t\t\t\tif ((p as any).auto) {\n\t\t\t\t\t\tvertex.auto = true;\n\t\t\t\t\t}\n\t\t\t\t\treturn vertex;\n\t\t\t\t})\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (k.endsWith('-deleted')) {\n\t\t\t\tconst edgeId = k.slice(2, -8); // Remove 'e-' prefix and '-deleted' suffix\n\t\t\t\tconst edge = this.edges.find(e => e.id == edgeId)\n\t\t\t\tif (edge && v === true) {\n\t\t\t\t\tedge.userDeletedVertices = true\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t})\n\t\tif (rerender) {\n\t\t\tthis.nodes().forEach(n => setPosition(n.ref, n.x, n.y))\n\t\t\tthis.edges.forEach(e => this.redrawEdge(e))\n\t\t\tthis.updateEdgesSel()\n\t\t\tthis.redrawGroups(null)\n\t\t}\n\t}\n\n\tasync autoLayout(options?: LayoutOptions) {\n\t\ttry {\n\t\t\tconst auto = await autoLayout(this, options)\n\t\t\t\n\t\t\tthis._undo.beforeChange()\n\t\t\t\n\t\t\t// Apply node positions\n\t\t\tauto.nodes.forEach(an => {\n\t\t\t\tconst n = this.nodesMap.get(an.id)\n\t\t\t\tif (n) {\n\t\t\t\t\tthis.moveNode(n, an.x, an.y, false, true) // Skip undo for individual moves\n\t\t\t\t}\n\t\t\t})\n\t\t\t\n\t\t\t// Apply edge routing from ELK layout\n\t\t\tauto.edges.forEach(ae => {\n\t\t\t\tconst edge = this.edges.find(e => e.id == ae.id)\n\t\t\t\tif (edge) {\n\t\t\t\t\t// Clear existing vertices for this edge only\n\t\t\t\t\tif (edge.vertices) {\n\t\t\t\t\t\tedge.vertices.forEach(v => {\n\t\t\t\t\t\t\tif (v.id) {\n\t\t\t\t\t\t\t\tthis.edgeVertices.delete(v.id)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tedge.vertices = []\n\t\t\t\t\tedge.userDeletedVertices = false\n\t\t\t\t\t\n\t\t\t\t\t// Add routing vertices from ELK (these are proper bend points, not nodes)\n\t\t\t\t\tif (ae.vertices && ae.vertices.length > 0) {\n\t\t\t\t\t\tedge.vertices = ae.vertices.map(p => {\n\t\t\t\t\t\t\tconst vertex = edge.initVertex(p)\n\t\t\t\t\t\t\tvertex.auto = true // Mark as auto-generated\n\t\t\t\t\t\t\treturn vertex\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Handle edge label positioning - create proper interactive label vertices\n\t\t\t\t\tif (ae.label) {\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Remove any existing label vertices (ELK or user-created)\n\t\t\t\t\t\tif (edge.vertices) {\n\t\t\t\t\t\t\tedge.vertices.forEach(v => {\n\t\t\t\t\t\t\t\tif (v.label) {\n\t\t\t\t\t\t\t\t\tthis.edgeVertices.delete(v.id)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tedge.vertices = edge.vertices.filter(v => !v.label)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Create a proper label vertex that behaves like a user-created vertex\n\t\t\t\t\t\tconst labelVertex = edge.initVertex(ae.label)\n\t\t\t\t\t\tlabelVertex.label = true\n\t\t\t\t\t\tlabelVertex.auto = true // Mark as auto-generated so it can be cleaned up\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Insert label vertex at the optimal position in the routing path\n\t\t\t\t\t\t// Find the best position to insert it and project it onto that line segment\n\t\t\t\t\t\tedge.vertices = edge.vertices || []\n\t\t\t\t\t\tconst insertPos = findOptimalLabelPosition(edge.vertices, ae.label, edge.from, edge.to)\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Project the label position onto the line segment where it will be inserted\n\t\t\t\t\t\tconst projectedPos = projectLabelOntoSegment(edge.vertices, ae.label, insertPos, edge.from, edge.to)\n\t\t\t\t\t\tlabelVertex.x = projectedPos.x\n\t\t\t\t\t\tlabelVertex.y = projectedPos.y\n\t\t\t\t\t\t\n\t\t\t\t\t\tedge.vertices.splice(insertPos, 0, labelVertex)\n\t\t\t\t\t\tthis.edgeVertices.set(labelVertex.id, labelVertex)\n\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Redraw the edge with new routing\n\t\t\t\t\tthis.redrawEdge(edge)\n\t\t\t\t}\n\t\t\t})\n\t\t\t\n\t\t\t// Fit the layout to the viewport with optimal positioning\n\t\t\tthis.fitToView()\n\t\t\t\n\t\t\tthis._undo.change()\n\t\t\t\n\t\t} catch (error) {\n\t\t\tconsole.error('Auto layout failed:', error)\n\t\t\t// Could show user notification here\n\t\t}\n\t}\n\n\talignSelectionV() {\n\t\tconst lst: Point[] = this.nodes().filter(n => n.selected)\n\t\tlst.push(...Array.from(this.edgeVertices.values()).filter(v => v.selected))\n\t\tlet minY = Math.min(...lst.map(p => p.y))\n\t\tthis.nodesMap.forEach(n => n.selected && this.moveNode(n, n.x, minY, false, false))\n\t\tthis.edgeVertices.forEach(v => v.selected && this.moveEdgeVertex(v, v.x, minY, false, false))\n\t}\n\n\talignSelectionH() {\n\t\tconst lst: Point[] = this.nodes().filter(n => n.selected)\n\t\tlst.push(...Array.from(this.edgeVertices.values()).filter(v => v.selected))\n\t\tlet minX = Math.min(...lst.map(p => p.x))\n\t\tthis.nodesMap.forEach(n => n.selected && this.moveNode(n, minX, n.y, false, false))\n\t\tthis.edgeVertices.forEach(v => v.selected && this.moveEdgeVertex(v, minX, v.y, false, false))\n\t}\n\n\tdistributeSelectionH() {\n\t\tconst selectedNodes = this.nodes().filter(n => n.selected)\n\t\tconst selectedVertices = Array.from(this.edgeVertices.values()).filter(v => v.selected)\n\t\t\n\t\tif (selectedNodes.length + selectedVertices.length < 3) return // Need at least 3 elements to distribute\n\t\t\n\t\tthis._undo.beforeChange()\n\t\t\n\t\t// Combine and sort by X coordinate\n\t\tconst allElements = [...selectedNodes, ...selectedVertices]\n\t\tallElements.sort((a, b) => a.x - b.x)\n\t\t\n\t\tconst minX = allElements[0].x\n\t\tconst maxX = allElements[allElements.length - 1].x\n\t\tconst spacing = (maxX - minX) / (allElements.length - 1)\n\t\t\n\t\t// Distribute elements evenly between leftmost and rightmost\n\t\tallElements.forEach((element, index) => {\n\t\t\tconst newX = minX + (index * spacing)\n\t\t\tif ('title' in element) {\n\t\t\t\t// It's a Node\n\t\t\t\tthis.moveNode(element as Node, newX, element.y, false, true)\n\t\t\t} else {\n\t\t\t\t// It's an EdgeVertex\n\t\t\t\tthis.moveEdgeVertex(element as EdgeVertex, newX, element.y, false, true)\n\t\t\t}\n\t\t})\n\t\t\n\t\tthis._undo.change()\n\t}\n\n\tdistributeSelectionV() {\n\t\tconst selectedNodes = this.nodes().filter(n => n.selected)\n\t\tconst selectedVertices = Array.from(this.edgeVertices.values()).filter(v => v.selected)\n\t\t\n\t\tif (selectedNodes.length + selectedVertices.length < 3) return // Need at least 3 elements to distribute\n\t\t\n\t\tthis._undo.beforeChange()\n\t\t\n\t\t// Combine and sort by Y coordinate\n\t\tconst allElements = [...selectedNodes, ...selectedVertices]\n\t\tallElements.sort((a, b) => a.y - b.y)\n\t\t\n\t\tconst minY = allElements[0].y\n\t\tconst maxY = allElements[allElements.length - 1].y\n\t\tconst spacing = (maxY - minY) / (allElements.length - 1)\n\t\t\n\t\t// Distribute elements evenly between topmost and bottommost\n\t\tallElements.forEach((element, index) => {\n\t\t\tconst newY = minY + (index * spacing)\n\t\t\tif ('title' in element) {\n\t\t\t\t// It's a Node\n\t\t\t\tthis.moveNode(element as Node, element.x, newY, false, true)\n\t\t\t} else {\n\t\t\t\t// It's an EdgeVertex\n\t\t\t\tthis.moveEdgeVertex(element as EdgeVertex, element.x, newY, false, true)\n\t\t\t}\n\t\t})\n\t\t\n\t\tthis._undo.change()\n\t}\n\n\t// Set edge selection state\n\tsetEdgeSelected(edge: Edge, selected: boolean) {\n\t\t// Mark the edge as selected by selecting its connected nodes\n\t\tif (selected) {\n\t\t\tthis.setNodeSelected(edge.from, true)\n\t\t\tthis.setNodeSelected(edge.to, true)\n\t\t}\n\t\t// Update visual selection\n\t\tthis.updateEdgesSel()\n\t}\n\n\t// Fit the entire graph to the current viewport\n\tfitToView() {\n\t\tconst contentBounds = this.calculateContentBounds()\n\t\t\n\t\t// Handle edge case where there's no content\n\t\tif (contentBounds.width === 0 || contentBounds.height === 0) {\n\t\t\treturn\n\t\t}\n\t\t\n\t\t// Get viewport dimensions\n\t\tconst viewportWidth = svg.parentElement?.clientWidth || 800\n\t\tconst viewportHeight = svg.parentElement?.clientHeight || 600\n\t\t\n\t\t// Add padding around content\n\t\tconst padding = 40\n\t\t\n\t\t// Calculate zoom to fit content with padding\n\t\tconst zoomX = (viewportWidth - padding * 2) / contentBounds.width\n\t\tconst zoomY = (viewportHeight - padding * 2) / contentBounds.height\n\t\tconst optimalZoom = Math.min(zoomX, zoomY)\n\t\t\n\t\t// Clamp zoom between reasonable bounds\n\t\tconst finalZoom = Math.max(Math.min(optimalZoom, 2), 0.1)\n\t\t\n\t\t// Calculate content center in drawing coordinates\n\t\tconst contentCenterX = contentBounds.x + contentBounds.width / 2\n\t\tconst contentCenterY = contentBounds.y + contentBounds.height / 2\n\t\t\n\t\t// Calculate viewport center in screen coordinates\n\t\tconst viewportCenterX = viewportWidth / 2\n\t\tconst viewportCenterY = viewportHeight / 2\n\t\t\n\t\t// Calculate translation needed to center content in viewport\n\t\t// With translate(x,y) scale(zoom), translation is in screen coordinates\n\t\tconst translateX = viewportCenterX - (contentCenterX * finalZoom)\n\t\tconst translateY = viewportCenterY - (contentCenterY * finalZoom)\n\t\t\n\t\t// Apply zoom and translation transform\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (zoomGroup) {\n\t\t\tzoomGroup.setAttribute('transform', `translate(${translateX}, ${translateY}) scale(${finalZoom})`)\n\t\t}\n\t\t\n\t\t// Update panning\n\t\tupdatePanning()\n\t\t\n\t\t// Save view state so this fit position is preserved after reload\n\t\tsaveViewState(this.id)\n\t}\n\n\t// Save current layout state for restoration\n\tprivate saveLayoutState(): Layout {\n\t\treturn this.exportLayout(true) // Include all vertices for complete state\n\t}\n\n\t// Restore layout state\n\tprivate restoreLayoutState(state: Layout) {\n\t\tthis.importLayout(state, true) // Rerender after restoring\n\t}\n\n\t// Grid functionality\n\tisGridVisible(): boolean {\n\t\treturn this._gridVisible;\n\t}\n\n\tisSnapToGrid(): boolean {\n\t\treturn this._snapToGrid;\n\t}\n\n\tgetGridSize(): number {\n\t\treturn this._gridSize;\n\t}\n\n\ttoggleGrid() {\n\t\tthis._gridVisible = !this._gridVisible;\n\t\tthis.updateGridDisplay();\n\t\t// Force toolbar update by dispatching a custom event\n\t\twindow.dispatchEvent(new CustomEvent('gridStateChanged'));\n\t}\n\n\ttoggleSnapToGrid() {\n\t\tthis._snapToGrid = !this._snapToGrid;\n\t\t// Force toolbar update by dispatching a custom event\n\t\twindow.dispatchEvent(new CustomEvent('gridStateChanged'));\n\t}\n\n\tsnapAllToGrid() {\n\t\tif (!this._snapToGrid) return;\n\t\t\n\t\tthis._undo.beforeChange();\n\t\tthis.nodes().forEach(node => {\n\t\t\tconst snappedX = Math.round(node.x / this._gridSize) * this._gridSize;\n\t\t\tconst snappedY = Math.round(node.y / this._gridSize) * this._gridSize;\n\t\t\tthis.moveNode(node, snappedX, snappedY, false, true);\n\t\t});\n\t\tthis._undo.change();\n\t}\n\n\t// Helper method to snap a point to grid\n\tprivate snapToGrid(x: number, y: number): { x: number, y: number } {\n\t\treturn {\n\t\t\tx: Math.round(x / this._gridSize) * this._gridSize,\n\t\t\ty: Math.round(y / this._gridSize) * this._gridSize\n\t\t};\n\t}\n\n\tupdateGridDisplay() {\n\t\tif (!svg) return;\n\n\t\t// Remove existing grid pattern and background\n\t\tconst existingGrid = svg.querySelector('#grid-pattern');\n\t\tif (existingGrid) {\n\t\t\texistingGrid.remove();\n\t\t}\n\n\t\tconst existingGridRect = svg.querySelector('#grid-background');\n\t\tif (existingGridRect) {\n\t\t\texistingGridRect.remove();\n\t\t}\n\n\t\tif (!this._gridVisible) return;\n\n\t\t// Create grid pattern in defs\n\t\tlet defs = svg.querySelector('defs');\n\t\tif (!defs) {\n\t\t\tdefs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');\n\t\t\tsvg.insertBefore(defs, svg.firstChild);\n\t\t}\n\n\t\tconst pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');\n\t\tpattern.id = 'grid-pattern';\n\t\tpattern.setAttribute('width', this._gridSize.toString());\n\t\tpattern.setAttribute('height', this._gridSize.toString());\n\t\tpattern.setAttribute('patternUnits', 'userSpaceOnUse');\n\n\t\tconst path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n\t\tpath.setAttribute('d', `M ${this._gridSize} 0 L 0 0 0 ${this._gridSize}`);\n\t\tpath.setAttribute('fill', 'none');\n\t\tpath.setAttribute('stroke', '#d0d0d0');\n\t\tpath.setAttribute('stroke-width', '1');\n\t\tpath.setAttribute('opacity', '0.8');\n\n\t\tpattern.appendChild(path);\n\t\tdefs.appendChild(pattern);\n\n\t\t// Create grid background rectangle\n\t\tconst rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n\t\trect.id = 'grid-background';\n\t\trect.setAttribute('x', '-10000');\n\t\trect.setAttribute('y', '-10000');\n\t\trect.setAttribute('width', '20000');\n\t\trect.setAttribute('height', '20000');\n\t\trect.setAttribute('fill', 'url(#grid-pattern)');\n\t\trect.setAttribute('pointer-events', 'none');\n\n\t\t// Insert grid as first child of zoom group so it transforms with content\n\t\tconst zoomGroup = svg.querySelector('g.zoom');\n\t\tif (zoomGroup) {\n\t\t\tzoomGroup.insertBefore(rect, zoomGroup.firstChild);\n\t\t}\n\t}\n}\n\nfunction escapeCdata(code: string) {\n\treturn code.replace(/]]>/g, ']]]>]> clickListener(e))\n\t// addCursorInteraction(svg) // Call will be updated in buildGraph\n}\nsvg.setAttribute('width', '100%')\nsvg.setAttribute('height', '100%')\n\nlet clickListener: (e: MouseEvent) => void\nlet dragging = false;\nlet selectListener: (n: Node) => void\n\n\nexport const buildGraph = (data: GraphData, onNodeSelect: (n: Node) => void, dragMode: 'pan' | 'select') => {\n\t// empty svg\n\tsvg.innerHTML = defs\n\tdocument.body.append(svg) // make sure svg element is connected, we will measure texts sizes\n\t// @ts-ignore\n\tsvg.__data = data\n\n\tselectListener = onNodeSelect\n\n\t//use event delegation\n\tclickListener = e => {\n\t\tif (dragging) {\n\t\t\treturn;\n\t\t}\n\t\t// const el = (e.target as any).closest('.node > .expand');\n\t}\n\n\t_buildGraph(data)\n\tconst elasticEl = create.rect(300, 300, 50, 50, 0, 'elastic')\n\tsvg.append(elasticEl)\n\n\t// Initialize grid display now that the zoom group exists\n\tdata.updateGridDisplay()\n\n\t// Call addCursorInteraction with dragMode\n\taddCursorInteraction(svg, dragMode)\n\n\treturn {\n\t\tsvg,\n\t\tsetZoom,\n\t}\n}\n\nexport const buildGraphView = (data: GraphData) => {\n\tsvg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n\tsvg.setAttribute('id', 'graph')\n\t_buildGraph(data)\n\treturn svg\n}\n\nconst _buildGraph = (data: GraphData) => {\n\t//toplevel groups\n\tconst zoomG = create.element('g', {}, 'zoom') as SVGGElement\n\tconst nodesG = create.element('g', {}, 'nodes') as SVGGElement\n\tconst edgesG = create.element('g', {}, 'edges') as SVGGElement\n\tconst groupsG = create.element('g', {}, 'groups') as SVGGElement\n\tzoomG.append(groupsG, edgesG, nodesG)\n\n\n\tdata.nodesMap.forEach((n) => {\n\t\tbuildNode(n, data)\n\t\tnodesG.append(n.ref)\n\t})\n\n\tdata.edges.forEach(edge => {\n\t\tedge.labelBounds = undefined\n\t})\n\tdata.edges.forEach(e => {\n\t\tbuildEdge(data, e)\n\t\tedgesG.append(e.ref)\n\t})\n\n\tdata.groupsMap.forEach((group) => {\n\t\tbuildGroup(group)\n\t\tgroupsG.append(group.ref)\n\t})\n\n\tsvg.append(zoomG)\n}\n\nfunction buildEdge(data: GraphData, edge: Edge) {\n\tconst n1 = edge.from, n2 = edge.to;\n\n\tconst g = create.element('g', {}, 'edge') as SVGGElement\n\tg.setAttribute('id', edge.id)\n\tg.setAttribute('data-from', edge.from.id)\n\tg.setAttribute('data-to', edge.to.id)\n\n\tconst position = (edge.style.position || 50) / 100\n\n\t// Calculate edge vertices using utility function\n\tconst vertices = calculateEdgeVertices(edge, data)\n\n\tconst labelPlacement = calculateLabelPlacement(vertices, position, n1)\n\n\tconst {bg, txt, bbox} = buildEdgeLabel(labelPlacement, edge, data)\n\tg.append(bg, txt)\n\n\t// Create edge segments and path using utility function\n\tconst {segments, path} = createEdgeSegments(vertices, bbox, n1, n2)\n\n\tconst p = create.path(path, {'marker-end': 'url(#arrow)'}, 'edge')\n\tp.setAttribute('fill', 'none')\n\tp.setAttribute('stroke', edge.style.color)\n\tp.setAttribute('stroke-width', String(edge.style.thickness))\n\tp.setAttribute('stroke-linecap', 'round')\n\tedge.style.dashed && p.setAttribute('stroke-dasharray', '8')\n\tg.append(p)\n\t\n\t// Debug visualization removed - arrow issue fixed\n\n\t// drag handlers\n\tedge.vertices = vertices.slice(1, -1).map(p => {\n\t\t// Preserve existing EdgeVertex objects to maintain IDs and selection state\n\t\tif ('id' in p && 'edge' in p) {\n\t\t\t// This is already an EdgeVertex, preserve it\n\t\t\tconst v = p as EdgeVertex;\n\t\t\tv.edge = edge; // Ensure edge reference is correct\n\t\t\treturn v;\n\t\t} else {\n\t\t\t// This is a new Point, convert to EdgeVertex\n\t\t\treturn edge.initVertex(p);\n\t\t}\n\t})\n\tedge.vertices.forEach((p, i) => {\n\t\tconst v = p as EdgeVertex\n\t\tv.ref = create.element('circle', {id: v.id, cx: p.x, cy: p.y, r: 7, fill: 'none'}, 'v-dot')\n\t\tv.selected && v.ref.classList.add('selected')\n\t\tv.auto && v.ref.classList.add('auto')\n\t\tg.append(v.ref)\n\t})\n\n\tedge.ref = g\n\treturn g\n}\n\nfunction buildEdgeLabel(placement: EdgeLabelPlacement, edge: Edge, data: GraphData) {\n\tconst labelGap = 12;\n\tconst collisionPadding = 8;\n\tconst fontSize = edge.style.fontSize\n\tlet {txt, dy, maxW} = create.textArea(edge.label, 200, fontSize, false, placement.x, placement.y, 'middle')\n\tdy -= fontSize / 2\n\tmaxW += fontSize\n\n\tconst anchors: Point[] = [{x: placement.x, y: placement.y}]\n\tif (placement.movable && placement.segment) {\n\t\tfor (const fraction of [0.5, 0.35, 0.65, 0.2, 0.8]) {\n\t\t\tconst anchor = {\n\t\t\t\tx: placement.segment.p.x +\n\t\t\t\t\t(placement.segment.q.x - placement.segment.p.x) * fraction,\n\t\t\t\ty: placement.segment.p.y +\n\t\t\t\t\t(placement.segment.q.y - placement.segment.p.y) * fraction,\n\t\t\t}\n\t\t\tif (!anchors.some(existing =>\n\t\t\t\tMath.abs(existing.x - anchor.x) < 0.1 &&\n\t\t\t\tMath.abs(existing.y - anchor.y) < 0.1\n\t\t\t)) {\n\t\t\t\tanchors.push(anchor)\n\t\t\t}\n\t\t}\n\t}\n\n\tconst occupied = [\n\t\t...data.nodes().map(node => expandBox(uncenterBox(node), collisionPadding)),\n\t\t...data.edges\n\t\t\t.filter(other => other !== edge && other.labelBounds)\n\t\t\t.map(other => expandBox(other.labelBounds, collisionPadding)),\n\t]\n\tconst candidates = anchors.flatMap(anchor => {\n\t\tif (placement.orientation === 'vertical') {\n\t\t\treturn [1, -1].map(side => edgeLabelCandidate(\n\t\t\t\tanchor.x + side * (maxW / 2 + labelGap),\n\t\t\t\tanchor.y,\n\t\t\t\tmaxW,\n\t\t\t\tdy,\n\t\t\t\tanchor,\n\t\t\t\tplacement,\n\t\t\t\toccupied,\n\t\t\t))\n\t\t}\n\t\treturn [-1, 1].map(side => edgeLabelCandidate(\n\t\t\tanchor.x,\n\t\t\tanchor.y + side * (dy / 2 + labelGap),\n\t\t\tmaxW,\n\t\t\tdy,\n\t\t\tanchor,\n\t\t\tplacement,\n\t\t\toccupied,\n\t\t))\n\t})\n\tconst selected = candidates.reduce((best, candidate) =>\n\t\tcandidate.score < best.score ? candidate : best\n\t)\n\tconst {centerX, centerY, bounds} = selected\n\ttxt.querySelectorAll('tspan').forEach((span: SVGTSpanElement) => {\n\t\tspan.setAttribute('x', String(centerX))\n\t})\n\ttxt.setAttribute('y', String(centerY - dy / 2))\n\n\tapplyStyle(txt, styles.edgeText)\n\ttxt.setAttribute('stroke', 'none')\n\ttxt.setAttribute('font-size', String(edge.style.fontSize))\n\ttxt.setAttribute('fill', edge.style.color)\n\n\tconst bbox = {...bounds}\n\tconst bg = create.rect(bbox.width, bbox.height, bbox.x, bbox.y)\n\tapplyStyle(bg, styles.edgeRect)\n\ttxt.setAttribute('data-field', 'label')\n\tedge.labelBounds = bounds\n\n\tbbox.x += bbox.width / 2\n\tbbox.y += bbox.height / 2\n\treturn {bg, txt, bbox}\n}\n\nfunction edgeLabelCandidate(\n\tcenterX: number,\n\tcenterY: number,\n\twidth: number,\n\theight: number,\n\tanchor: Point,\n\tplacement: EdgeLabelPlacement,\n\toccupied: BBox[],\n) {\n\tconst bounds = {\n\t\tx: centerX - width / 2,\n\t\ty: centerY - height / 2,\n\t\twidth,\n\t\theight,\n\t}\n\tconst overlap = occupied.reduce(\n\t\t(total, box) => total + boxOverlapArea(bounds, box),\n\t\t0,\n\t)\n\treturn {\n\t\tcenterX,\n\t\tcenterY,\n\t\tbounds,\n\t\tscore: overlap * 1000 + calculateDistance(anchor, placement),\n\t}\n}\n\nfunction expandBox(box: BBox, padding: number): BBox {\n\treturn {\n\t\tx: box.x - padding,\n\t\ty: box.y - padding,\n\t\twidth: box.width + padding * 2,\n\t\theight: box.height + padding * 2,\n\t}\n}\n\nfunction boxOverlapArea(first: BBox, second: BBox): number {\n\tconst width = Math.max(\n\t\t0,\n\t\tMath.min(first.x + first.width, second.x + second.width) -\n\t\t\tMath.max(first.x, second.x),\n\t)\n\tconst height = Math.max(\n\t\t0,\n\t\tMath.min(first.y + first.height, second.y + second.height) -\n\t\t\tMath.max(first.y, second.y),\n\t)\n\treturn width * height\n}\n\n\nfunction buildNode(n: Node, data: GraphData) {\n\t// @ts-ignore\n\twindow.gdata = data\n\n\tconst g = create.element('g', {}, 'node') as SVGGElement\n\tg.setAttribute('id', n.id)\n\tn.selected && g.classList.add('selected')\n\tsetPosition(g, n.x, n.y)\n\tconst link = n.link\n\t\t? create.element('a', {\n\t\t\thref: n.link.href,\n\t\t\t'data-export-href': n.link.exportHref,\n\t\t\t'aria-label': `Open ${n.title}`,\n\t\t}, 'nodeLink') as SVGAElement\n\t\t: null\n\tconst content = link || g\n\tif (link) {\n\t\tg.classList.add('linked')\n\t\tg.append(link)\n\t}\n\n\t// Ensure we use the correct shape from style, defaulting to Box\n\tconst shapeType = n.style.shape || 'Box';\n\tconst shapeFn = shapes[shapeType.toLowerCase()] || shapes.box\n\tconst shape: SVGElement = shapeFn(content, n);\n\n\tshape.classList.add('nodeBorder')\n\n\t// Apply generic styles first\n\tapplyStyle(shape, styles.nodeBorder)\n\t// Then apply tag-specific styles to override generic ones\n\tshape.setAttribute('fill', n.style.background)\n\tshape.setAttribute('stroke', n.style.stroke)\n\t// Consistent border width for all elements\n\tshape.setAttribute('stroke-width', '3')\n\tshape.setAttribute('opacity', String(n.style.opacity))\n\tsetBorderStyle(shape, n.style.border)\n\n\tconst tg = buildNodeContent(n.contentLayout, n.style.color)\n\tconst labelOffsetY = Number(content.getAttribute('label-offset-y')) || 0\n\tsetPosition(tg, 0, labelOffsetY / 2)\n\tcontent.append(tg)\n\n\t// @ts-ignore\n\tg.__data = n;\n\tn.ref = g;\n\n\treturn g\n}\n\n\nfunction buildGroup(group: Group) {\n\tif (group.nodes.length == 0) {\n\t\treturn\n\t}\n\tconst g = create.element('g', {}, 'group') as SVGGElement\n\n\tlet p0: Point = {x: 1e100, y: 1e100}, p1: Point = {x: 0, y: 0}\n\tgroup.nodes.forEach(n => {\n\t\t// Calculate visual bounds accounting for shapes that extend beyond center\n\t\tconst shape = (n as Node).style?.shape?.toLowerCase() || 'box'\n\t\t\n\t\tlet topExtension = n.height / 2\n\t\tlet bottomExtension = n.height / 2\n\t\t\n\t\tif (shape === 'robot') {\n\t\t\t// Robot shape extends above with antenna\n\t\t\t// Account for antenna height (h * 0.08) plus some margin\n\t\t\tconst antennaH = n.height * 0.12\n\t\t\ttopExtension = n.height / 2 + antennaH\n\t\t\tbottomExtension = n.height / 2\n\t\t} else if (shape === 'hexagon') {\n\t\t\t// Hexagon extends to ±0.866 * (width/2) vertically\n\t\t\t// For width=280, that's ±121.24px from center\n\t\t\tconst hexHeight = n.width / 2 * 0.866\n\t\t\ttopExtension = hexHeight\n\t\t\tbottomExtension = hexHeight\n\t\t}\n\t\t\n\t\tconst b = {\n\t\t\tx: n.x - n.width / 2,\n\t\t\ty: n.y - topExtension,\n\t\t\twidth: n.width,\n\t\t\theight: topExtension + bottomExtension\n\t\t}\n\t\tp0.x = Math.min(p0.x, b.x)\n\t\tp0.y = Math.min(p0.y, b.y)\n\t\tp1.x = Math.max(p1.x, b.x + b.width)\n\t\tp1.y = Math.max(p1.y, b.y + b.height)\n\t})\n\tconst pad = 25 // Padding around content\n\tconst labelHeight = 30 // Space for the group label at bottom\n\tconst w = Math.max(p1.x - p0.x, 200)\n\tconst h = p1.y - p0.y\n\tconst bb = {\n\t\tx: p0.x - pad,\n\t\ty: p0.y - pad,\n\t\twidth: w + pad * 2,\n\t\theight: h + pad * 2 + labelHeight,\n\t}\n\tconst r = create.rect(bb.width, bb.height, bb.x, bb.y)\n\tgroup.x = bb.x + bb.width / 2\n\tgroup.y = bb.y + bb.height / 2\n\tgroup.width = bb.width\n\tgroup.height = bb.height\n\tapplyStyle(r, styles.groupRect)\n\tgroup.style.stroke && r.setAttribute('stroke', group.style.stroke)\n\tgroup.style.background && r.setAttribute('fill', group.style.background)\n\n\tconst txt = create.text(group.name, {x: p0.x, y: bb.y + bb.height - styles.groupText[\"font-size\"]})\n\tapplyStyle(txt, styles.groupText)\n\tgroup.style.color && txt.setAttribute('fill', group.style.color)\n\n\tg.append(r, txt)\n\tgroup.ref = g\n}\n\nfunction findClosestSegment(graph: GraphData, p: Point) {\n\t// find the closest point on a segment\n\tlet fnd = {dst: Number.POSITIVE_INFINITY, pos: -1, edge: null as Edge, prj: null as Point}\n\tgraph.edges.forEach(edge => {\n\t\tconst vertices = edge.vertices || []\n\t\tconst pts = [edge.from, ...vertices, edge.to]\n\t\tfor (let i = 1; i < pts.length; i++) {\n\t\t\tconst prj = project(p, pts[i - 1], pts[i])\n\t\t\tconst dst = cabDistance(p, prj)\n\t\t\tif (dst > 50) continue\n\t\t\tif (dst < fnd.dst) {\n\t\t\t\tfnd = {dst, pos: i, prj, edge}\n\t\t\t}\n\t\t}\n\t})\n\treturn fnd.edge ? fnd : null\n}\n\nfunction mouseToDrawing(e: MouseEvent): Point {\n\t// transform event coords to drawing coords\n\tconst b = svg.getBoundingClientRect()\n\tconst z = getZoom()\n\t\n\t// Get current pan transform\n\tconst currentTransform = getCurrentTransform()\n\t\n\t// Convert screen coordinates to drawing coordinates accounting for zoom and pan\n\t// The transform values are in screen coordinates, so we need to divide by zoom and subtract\n\treturn {\n\t\tx: (e.clientX - b.x - currentTransform.x) / z,\n\t\ty: (e.clientY - b.y - currentTransform.y) / z\n\t}\n}\n\ninterface Handle extends Point {\n\tid: string\n\tselected?: boolean\n\tref?: SVGElement\n}\n\n// Custom cursor interaction that prioritizes panning over selection\nfunction addCustomCursorInteraction(svg: SVGSVGElement, conn: {\n\tnodeFromEvent(e: MouseEvent): Handle | null;\n\tsetSelection(handles: Handle[]): void;\n\tsetDragging(dragging: boolean): void;\n\tisSelected(handle: Handle): boolean;\n\tgetSelection(): Handle[];\n\tgetZoom(): number;\n\tmoveNode(h: Handle, x: number, y: number): void;\n\tboxSelection(box: DOMRect, add: boolean): void;\n\tupdatePanning(): void;\n}, dragMode: 'pan' | 'select') {\n\tlet ini: { x: number; y: number; n: Handle }[] = []\n\tlet elastic: any = null\n\tlet isPanning = false\n\tlet panStartX = 0\n\tlet panStartY = 0\n\tlet initialTransform = { x: 0, y: 0 }\n\tlet pendingSelectionChange: { node: Handle; shiftKey: boolean } | null = null\n\tlet pendingNavigation: string | null = null\n\tlet hasDragged = false\n\tlet suppressLinkClick = false\n\t\n\t// Store event listeners for cleanup\n\tconst eventListeners: Array<{ element: Element | Window, event: string, handler: EventListener }> = []\n\n\t// Simple elastic selection box implementation - use mouseToDrawing for proper coordinate conversion\n\tfunction createElastic() {\n\t\tlet startDrawingX = 0, startDrawingY = 0, rect: SVGRectElement | null = null\n\t\t\n\t\treturn {\n\t\t\tini(e: MouseEvent) {\n\t\t\t\t// Use mouseToDrawing to get proper drawing coordinates (accounts for zoom and pan)\n\t\t\t\tconst pt = mouseToDrawing(e)\n\t\t\t\tstartDrawingX = pt.x\n\t\t\t\tstartDrawingY = pt.y\n\t\t\t\t\n\t\t\t\trect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\")\n\t\t\t\trect.setAttribute('fill', 'rgba(0, 100, 255, 0.1)')\n\t\t\t\trect.setAttribute('stroke', 'rgba(0, 100, 255, 0.5)')\n\t\t\t\trect.setAttribute('stroke-width', '1')\n\t\t\t\trect.setAttribute('stroke-dasharray', '3,3')\n\t\t\t\trect.setAttribute('x', String(startDrawingX))\n\t\t\t\trect.setAttribute('y', String(startDrawingY))\n\t\t\t\trect.setAttribute('width', '0')\n\t\t\t\trect.setAttribute('height', '0')\n\t\t\t\t\n\t\t\t\t// Add to the zoom group so it transforms with the content\n\t\t\t\tconst zoomGroup = svg.querySelector('g.zoom')\n\t\t\t\tif (zoomGroup) {\n\t\t\t\t\tzoomGroup.appendChild(rect)\n\t\t\t\t} else {\n\t\t\t\t\tsvg.appendChild(rect)\n\t\t\t\t}\n\t\t\t},\n\t\t\tupdate(e: MouseEvent) {\n\t\t\t\tif (!rect) return\n\t\t\t\t\n\t\t\t\t// Convert current mouse position to drawing coordinates (accounts for zoom and pan)\n\t\t\t\tconst currentPt = mouseToDrawing(e)\n\t\t\t\tconst currentDrawingX = currentPt.x\n\t\t\t\tconst currentDrawingY = currentPt.y\n\t\t\t\t\n\t\t\t\t// Calculate rectangle bounds in drawing coordinates\n\t\t\t\tconst x = Math.min(startDrawingX, currentDrawingX)\n\t\t\t\tconst y = Math.min(startDrawingY, currentDrawingY)\n\t\t\t\tconst width = Math.abs(currentDrawingX - startDrawingX)\n\t\t\t\tconst height = Math.abs(currentDrawingY - startDrawingY)\n\t\t\t\t\n\t\t\t\trect.setAttribute('x', String(x))\n\t\t\t\trect.setAttribute('y', String(y))\n\t\t\t\trect.setAttribute('width', String(width))\n\t\t\t\trect.setAttribute('height', String(height))\n\t\t\t},\n\t\t\tend(): DOMRect | null {\n\t\t\t\tif (!rect) return null\n\t\t\t\t\n\t\t\t\t// Get final rectangle bounds in drawing coordinates\n\t\t\t\tconst x = parseFloat(rect.getAttribute('x') || '0')\n\t\t\t\tconst y = parseFloat(rect.getAttribute('y') || '0')\n\t\t\t\tconst width = parseFloat(rect.getAttribute('width') || '0')\n\t\t\t\tconst height = parseFloat(rect.getAttribute('height') || '0')\n\t\t\t\t\n\t\t\t\trect.remove()\n\t\t\t\trect = null\n\t\t\t\t\n\t\t\t\t// Return drawing coordinates directly for boxSelection since we're now working in the same coordinate system\n\t\t\t\tif (width > 5 && height > 5) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tx: x, y: y, width: width, height: height,\n\t\t\t\t\t\tleft: x, top: y, right: x + width, bottom: y + height\n\t\t\t\t\t} as DOMRect\n\t\t\t\t}\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction getCurrentTransformLocal() {\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (!zoomGroup) return { x: 0, y: 0 }\n\t\t\n\t\tconst transform = zoomGroup.getAttribute('transform') || ''\n\t\tconst translateMatch = transform.match(/translate\\(([^,]+),([^)]+)\\)/)\n\t\tif (translateMatch) {\n\t\t\treturn {\n\t\t\t\tx: parseFloat(translateMatch[1]) || 0,\n\t\t\t\ty: parseFloat(translateMatch[2]) || 0\n\t\t\t}\n\t\t}\n\t\treturn { x: 0, y: 0 }\n\t}\n\n\tfunction setTransform(x: number, y: number) {\n\t\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement\n\t\tif (!zoomGroup) return\n\t\t\n\t\tconst zoom = getZoom()\n\t\tzoomGroup.setAttribute('transform', `translate(${x}, ${y}) scale(${zoom})`)\n\t}\n\n\tfunction onMouseDown(e: MouseEvent) {\n\t\te.preventDefault();\n\t\thasDragged = false\n\t\tpendingSelectionChange = null\n\t\tconst target = e.target\n\t\tconst link = target instanceof Element ? target.closest('a.nodeLink') : null\n\t\tpendingNavigation = e.shiftKey ? null : link?.getAttribute('href') || null\n\n\t\tconst node = conn.nodeFromEvent(e)\n\t\t\n\t\t// Determine effective mode: invert if shift is held\n\t\tconst effectiveMode = e.shiftKey ? (dragMode === 'pan' ? 'select' : 'pan') : dragMode\n\t\t\n\t\tif (!node) { // Clicked on empty space\n\t\t\tif (effectiveMode === 'pan') {\n\t\t\t\t// Pan mode: pan and deselect\n\t\t\t\tisPanning = true;\n\t\t\t\telastic = null;\n\t\t\t\tpanStartX = e.clientX;\n\t\t\t\tpanStartY = e.clientY;\n\t\t\t\tinitialTransform = getCurrentTransformLocal();\n\t\t\t\tini = [];\n\t\t\t\t// Deselect all elements when clicking empty space in pan mode\n\t\t\t\tconn.setSelection([]);\n\t\t\t} else {\n\t\t\t\t// Select mode: ONLY select, no panning\n\t\t\t\tisPanning = false;\n\t\t\t\telastic = createElastic();\n\t\t\t\tif (elastic) elastic.ini(e);\n\t\t\t\tini = [];\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Clicked on a node/vertex - behavior depends on effective mode\n\t\tif (effectiveMode === 'pan') {\n\t\t\t// Pan mode: select the element, don't pan\n\t\t\tisPanning = false;\n\t\t\telastic = null;\n\t\t\t\n\t\t\tconst nodes = conn.getSelection()\n\t\t\tif (conn.isSelected(node)) {\n\t\t\t\t// Clicking on a selected node - prepare to drag all selected elements\n\t\t\t\tini = nodes.map(n => ({ x: n.x, y: n.y, n }))\n\t\t\t\t// No selection change needed since we're clicking on already selected element\n\t\t\t} else {\n\t\t\t\t// Clicking on an unselected node - select only this element\n\t\t\t\tconn.setSelection([node]);\n\t\t\t\tini = [{ x: node.x, y: node.y, n: node }];\n\t\t\t}\n\t\t} else {\n\t\t\t// Select mode: selection/drag logic\n\t\t\tisPanning = false; // Ensure no panning if a node is clicked\n\t\t\telastic = null; // Ensure no selection box if a node is clicked\n\t\t\tconst nodes = conn.getSelection()\n\t\t\t\n\t\t\tif (e.shiftKey && dragMode === 'select') {\n\t\t\t\t// Shift+click in select mode: immediately change selection (no dragging expected)\n\t\t\t\tif (conn.isSelected(node)) {\n\t\t\t\t\tconst index = nodes.findIndex(n => n.id === node.id)\n\t\t\t\t\tif (index >= 0) nodes.splice(index, 1)\n\t\t\t\t} else {\n\t\t\t\t\tnodes.push(node)\n\t\t\t\t}\n\t\t\t\tconn.setSelection(nodes)\n\t\t\t\tini = nodes.map(n => ({ x: n.x, y: n.y, n }))\n\t\t\t} else {\n\t\t\t\t// Regular click: defer selection change until we know if it's a drag or click\n\t\t\t\tif (conn.isSelected(node)) {\n\t\t\t\t\t// Clicking on a selected node - prepare to drag all selected elements\n\t\t\t\t\tini = nodes.map(n => ({ x: n.x, y: n.y, n }))\n\t\t\t\t\t// No pending selection change needed since we're clicking on already selected element\n\t\t\t\t\tpendingSelectionChange = null\n\t\t\t\t} else {\n\t\t\t\t\t// Clicking on an unselected node - defer selection change until we determine if it's a click or drag\n\t\t\t\t\tpendingSelectionChange = { node, shiftKey: e.shiftKey }\n\t\t\t\t\t// For now, prepare to drag just the clicked node (we'll update selection when drag starts)\n\t\t\t\t\tini = [{ x: node.x, y: node.y, n: node }]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction onMouseMove(e: MouseEvent, dx: number, dy: number) {\n\t\t// Check if we've moved enough to consider this a drag\n\t\tconst dragThreshold = 3 // pixels\n\t\tif (!hasDragged && (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold)) {\n\t\t\thasDragged = true\n\t\t\tpendingNavigation = null\n\t\t\t\n\t\t\t// If we have a pending selection change and we're now dragging, apply it\n\t\t\tif (pendingSelectionChange) {\n\t\t\t\tconst nodes = conn.getSelection()\n\t\t\t\tnodes.length = 0\n\t\t\t\tnodes.push(pendingSelectionChange.node)\n\t\t\t\tconn.setSelection(nodes)\n\t\t\t\t// Update ini to drag only the newly selected node\n\t\t\t\tini = [{ x: pendingSelectionChange.node.x, y: pendingSelectionChange.node.y, n: pendingSelectionChange.node }]\n\t\t\t\tpendingSelectionChange = null\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (isPanning) {\n\t\t\t// Pan the view - apply mouse delta directly (no zoom division needed)\n\t\t\t// setTransform expects screen coordinates, dx/dy are already screen pixel deltas\n\t\t\tconst newX = initialTransform.x + dx\n\t\t\tconst newY = initialTransform.y + dy\n\t\t\tsetTransform(newX, newY)\n\t\t} else if (ini.length > 0 && hasDragged) {\n\t\t\t// Move selected nodes/vertices (only if we've actually started dragging)\n\t\t\t// dx, dy are screen pixel deltas, convert to drawing coordinate deltas\n\t\t\tconst zoom = conn.getZoom()\n\t\t\tconst drawingDx = dx / zoom\n\t\t\tconst drawingDy = dy / zoom\n\t\t\tini.forEach(item => {\n\t\t\t\t// item.x, item.y are initial drawing coordinates\n\t\t\t\t// Add the drawing coordinate delta to get new position\n\t\t\t\tconn.moveNode(item.n, item.x + drawingDx, item.y + drawingDy)\n\t\t\t})\n\t\t\tconn.setDragging(true)\n\t\t} else if (elastic) {\n\t\t\t// Update selection box\n\t\t\telastic.update(e)\n\t\t\tconn.setDragging(true)\n\t\t}\n\t}\n\n\tfunction onMouseUp(e: MouseEvent) {\n\t\tconn.setDragging(false)\n\t\tconst navigation = hasDragged ? null : pendingNavigation\n\t\tif (hasDragged) {\n\t\t\tsuppressLinkClick = true\n\t\t\twindow.setTimeout(() => {\n\t\t\t\tsuppressLinkClick = false\n\t\t\t}, 0)\n\t\t}\n\t\t\n\t\t// If we have a pending selection change and didn't drag, apply it now (it was just a click)\n\t\tif (pendingSelectionChange && !hasDragged) {\n\t\t\tconst nodes = conn.getSelection()\n\t\t\tnodes.length = 0\n\t\t\tnodes.push(pendingSelectionChange.node)\n\t\t\tconn.setSelection(nodes)\n\t\t}\n\t\t\n\t\tif (elastic) {\n\t\t\tconst box = elastic.end()\n\t\t\tif (box) {\n\t\t\t\tconn.boxSelection(box, e.shiftKey)\n\t\t\t} else if (!ini.length) {\n\t\t\t\t// Deselect if no box was drawn\n\t\t\t\tconn.setSelection([])\n\t\t\t}\n\t\t\telastic = null\n\t\t}\n\t\t\n\t\t// Save view state if user was panning (user-initiated view change)\n\t\tif (isPanning && hasDragged) {\n\t\t\tconst graphData = (svg as any).__data as GraphData\n\t\t\tif (graphData && graphData.id) {\n\t\t\t\tsaveViewState(graphData.id)\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Reset state\n\t\tpendingSelectionChange = null\n\t\tpendingNavigation = null\n\t\thasDragged = false\n\t\tisPanning = false\n\t\tconn.updatePanning()\n\t\tif (navigation) {\n\t\t\twindow.location.href = navigation\n\t\t}\n\t}\n\n\tfunction onClick(e: MouseEvent) {\n\t\tconst target = e.target\n\t\tif (!(target instanceof Element) || !target.closest('a.nodeLink')) return\n\t\te.preventDefault()\n\t\tif (!suppressLinkClick) return\n\t\te.stopPropagation()\n\t\tsuppressLinkClick = false\n\t}\n\n\t// Add drag and drop functionality\n\tfunction addDnd(element: SVGSVGElement) {\n\t\tlet md: { ex: number; ey: number } | null = null\n\n\t\tfunction convertEvent(e: MouseEvent | TouchEvent): MouseEvent {\n\t\t\tif ('changedTouches' in e && e.changedTouches) {\n\t\t\t\treturn e.changedTouches[0] as any\n\t\t\t}\n\t\t\treturn e as MouseEvent\n\t\t}\n\n\t\tfunction onMouseMoveHandler(e: MouseEvent | TouchEvent) {\n\t\t\tif (!md) return\n\t\t\te = convertEvent(e)\n\t\t\tonMouseMove(e, e.clientX - md.ex, e.clientY - md.ey)\n\t\t}\n\n\t\tfunction removeListeners() {\n\t\t\tdocument.removeEventListener('touchmove', onMouseMoveHandler as any)\n\t\t\tdocument.removeEventListener('mousemove', onMouseMoveHandler as any)\n\t\t\tdocument.removeEventListener('mouseup', onMouseUpHandler)\n\t\t\tdocument.removeEventListener('touchend', onMouseUpHandler)\n\t\t}\n\n\t\tfunction onMouseUpHandler(e: MouseEvent | TouchEvent) {\n\t\t\tremoveListeners()\n\t\t\tonMouseUp(convertEvent(e))\n\t\t\tmd = null\n\t\t}\n\n\t\tfunction onMouseDownHandler(e: MouseEvent | TouchEvent) {\n\t\t\te = convertEvent(e)\n\t\t\tmd = { ex: e.clientX, ey: e.clientY }\n\t\t\tonMouseDown(e)\n\t\t\tdocument.addEventListener('touchmove', onMouseMoveHandler as any)\n\t\t\tdocument.addEventListener('mousemove', onMouseMoveHandler as any)\n\t\t\tdocument.addEventListener('mouseup', onMouseUpHandler)\n\t\t\tdocument.addEventListener('touchend', onMouseUpHandler)\n\t\t}\n\n\t\telement.addEventListener('mousedown', onMouseDownHandler as any)\n\t\telement.addEventListener('touchstart', onMouseDownHandler as any)\n\t\t\n\t\t// Track these listeners for cleanup\n\t\teventListeners.push(\n\t\t\t{ element, event: 'mousedown', handler: onMouseDownHandler as any },\n\t\t\t{ element, event: 'touchstart', handler: onMouseDownHandler as any }\n\t\t)\n\t}\n\n\taddDnd(svg)\n\tsvg.addEventListener('click', onClick)\n\teventListeners.push({ element: svg, event: 'click', handler: onClick })\n\t\n\t// Return cleanup function\n\treturn () => {\n\t\teventListeners.forEach(({ element, event, handler }) => {\n\t\t\telement.removeEventListener(event, handler)\n\t\t})\n\t}\n}\n\nexport function addCursorInteraction(svg: SVGSVGElement, dragMode: 'pan' | 'select') {\n\t// Clean up any existing event listeners to prevent conflicts\n\tconst existingCleanup = (svg as any).__cursorInteractionCleanup\n\tif (existingCleanup) {\n\t\texistingCleanup()\n\t}\n\n\tfunction getData(el: SVGElement) {\n\t\t// @ts-ignore\n\t\treturn el.__data\n\t}\n\n\tconst gd = () => (getData(svg) as GraphData)\n\t\n\t// Store event listeners for cleanup\n\tconst eventListeners: Array<{ element: Element | Window, event: string, handler: EventListener }> = []\n\n\tconst beforeUnloadHandler = (e: BeforeUnloadEvent) => {\n\t\tif (!gd().changed()) return\n\t\te.preventDefault()\n\t\te.returnValue = ''\n\t}\n\twindow.addEventListener(\"beforeunload\", beforeUnloadHandler)\n\teventListeners.push({ element: window, event: 'beforeunload', handler: beforeUnloadHandler })\n\n\tfunction setDotSelected(d: Handle, selected: boolean) {\n\t\td.selected = selected\n\t\tconst dotEl = svg.querySelector('#' + d.id)\n\t\td.selected ? dotEl.classList.add('selected') : dotEl.classList.remove('selected')\n\t}\n\n\t// show moving dot along edge when ALT is pressed\n\tconst mouseMoveHandler = (e: MouseEvent) => {\n\t\tif (!e.altKey) return\n\t\tconst fnd = findClosestSegment(gd(), mouseToDrawing(e))\n\t\tif (fnd) {\n\t\t\tconst {prj} = fnd\n\t\t\tconst parent = svg.querySelector('g.edges')\n\t\t\tlet dot = parent.querySelector('#prj')\n\t\t\tif (!dot) {\n\t\t\t\tdot = create.element('circle', {id: 'prj', cx: prj.x, cy: prj.y, r: 7})\n\t\t\t\tparent.append(dot)\n\t\t\t}\n\t\t\tdot.setAttribute('cx', String(prj.x))\n\t\t\tdot.setAttribute('cy', String(prj.y))\n\t\t} else {\n\t\t\tremovePrjDot()\n\t\t}\n\t}\n\tsvg.addEventListener('mousemove', mouseMoveHandler)\n\teventListeners.push({ element: svg, event: 'mousemove', handler: mouseMoveHandler })\n\n\tconst keyUpHandler = (e: KeyboardEvent) => {\n\t\tconst key = findShortcut(e, true)\n\t\tif (key == ADD_VERTEX || key == ADD_LABEL_VERTEX) return\n\t\tremovePrjDot()\n\t}\n\twindow.addEventListener('keyup', keyUpHandler)\n\teventListeners.push({ element: window, event: 'keyup', handler: keyUpHandler })\n\n\tfunction removePrjDot() {\n\t\tconst el = svg.querySelector('g.edges #prj')\n\t\tel && el.parentElement.removeChild(el)\n\t}\n\n\tconst clickHandler = (e: MouseEvent) => {\n\t\tconst key = findShortcut(e, true)\n\t\tif (key != ADD_LABEL_VERTEX && key != ADD_VERTEX) return\n\t\tconst fnd = findClosestSegment(gd(), mouseToDrawing(e))\n\t\tif (fnd) {\n\t\t\tconst {edge, pos, prj} = fnd\n\t\t\t// depending on keyboard modifier, make it label position\n\t\t\tgd().insertEdgeVertex(edge, prj, pos, key == ADD_LABEL_VERTEX)\n\t\t\tremovePrjDot()\n\t\t}\n\t}\n\tsvg.addEventListener('click', clickHandler)\n\teventListeners.push({ element: svg, event: 'click', handler: clickHandler })\n\n\tconst wheelHandler = (e: WheelEvent) => {\n\t\t// Handle wheel zoom directly without relying on shortcuts\n\t\t// deltaY > 0 means scrolling down (zoom out), deltaY < 0 means scrolling up (zoom in)\n\t\tconst delta = Math.sign(e.deltaY) * 0.1 // Normalize to 0.1 zoom steps\n\t\tconst currentZoom = getZoom()\n\t\tconst newZoom = Math.max(0.1, Math.min(5, currentZoom - delta)) // Clamp zoom between 0.1 and 5\n\t\t\n\t\tif (newZoom !== currentZoom) {\n\t\t\t// Convert absolute screen coordinates to SVG-relative coordinates\n\t\t\tconst rect = svg.getBoundingClientRect()\n\t\t\tconst svgX = e.clientX - rect.left\n\t\t\tconst svgY = e.clientY - rect.top\n\t\t\tsetZoomCentered(newZoom, svgX, svgY)\n\t\t\te.preventDefault()\n\t\t\t\n\t\t\t// Save view state after user wheel zoom\n\t\t\tconst graphData = (svg as any).__data as GraphData\n\t\t\tif (graphData && graphData.id) {\n\t\t\t\tsaveViewState(graphData.id)\n\t\t\t}\n\t\t}\n\t}\n\tsvg.addEventListener('wheel', wheelHandler)\n\teventListeners.push({ element: svg, event: 'wheel', handler: wheelHandler })\n\n\tconst keyDownHandler = (e: KeyboardEvent) => {\n\t\tconst shortcut = findShortcut(e)\n\t\t\n\t\t\n\t\tif (shortcut) {\n\t\t\te.preventDefault() // Prevent browser default for all recognized shortcuts\n\t\t}\n\t\t\n\t\tswitch (shortcut) {\n\t\t\tcase DEL_VERTEX:\n\t\t\t\tconst selectedVertices = Array.from(gd().edgeVertices.values()).filter(v => v.selected);\n\t\t\t\tselectedVertices.forEach(v => {\n\t\t\t\t\tgd().deleteEdgeVertex(v)\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\tcase UNDO:\n\t\t\t\tgd().undo()\n\t\t\t\tbreak\n\t\t\tcase REDO:\n\t\t\t\tgd().redo()\n\t\t\t\tbreak\n\t\t\tcase ZOOM_IN:\n\t\t\t\tconst newZoomIn = Math.min(5, getZoom() * 1.2)\n\t\t\t\t// Center zoom on viewport center like mouse wheel\n\t\t\t\tsetZoomCentered(newZoomIn)\n\t\t\t\tsaveViewState(gd().id) // Save after user keyboard zoom\n\t\t\t\tbreak\n\t\t\tcase ZOOM_OUT:\n\t\t\t\tconst newZoomOut = Math.max(0.1, getZoom() / 1.2)\n\t\t\t\t// Center zoom on viewport center like mouse wheel\n\t\t\t\tsetZoomCentered(newZoomOut)\n\t\t\t\tsaveViewState(gd().id) // Save after user keyboard zoom\n\t\t\t\tbreak\n\t\t\tcase ZOOM_100:\n\t\t\t\t// Center zoom on viewport center like mouse wheel\n\t\t\t\tsetZoomCentered(1)\n\t\t\t\tsaveViewState(gd().id) // Save after user keyboard zoom\n\t\t\t\tbreak\n\t\t\tcase ZOOM_FIT:\n\t\t\t\tgd().fitToView()\n\t\t\t\t// Don't save view state here - fitToView should not be persisted\n\t\t\t\tbreak\n\t\t\tcase SELECT_ALL:\n\t\t\t\tgd().nodes().forEach(n => gd().setNodeSelected(n, true))\n\t\t\t\tgd().edgeVertices.forEach(v => setDotSelected(v, true))\n\t\t\t\tbreak\n\t\t\tcase DESELECT:\n\t\t\t\tgd().nodes().forEach(n => gd().setNodeSelected(n, false))\n\t\t\t\tgd().edgeVertices.forEach(v => setDotSelected(v, false))\n\t\t\t\tbreak\n\t\t}\n\t}\n\twindow.addEventListener('keydown', keyDownHandler)\n\teventListeners.push({ element: window, event: 'keydown', handler: keyDownHandler })\n\n\t// Custom cursor interaction with pan-first behavior\n\tconst customInteractionCleanup = addCustomCursorInteraction(svg, {\n\t\tnodeFromEvent(e: MouseEvent): Handle {\n\t\t\te.preventDefault()\n\t\t\t// node clicked\n\t\t\tlet el = (e.target as SVGElement).closest('g.nodes g.node') as SVGElement\n\t\t\tif (el) return getData(el)\n\t\t\t// vertex dot clicked\n\t\t\tel = (e.target as SVGElement).closest('g.edges g.edge .v-dot') as SVGElement\n\t\t\tif (el) {\n\t\t\t\treturn gd().edgeVertices.get(el.id)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\t\tsetSelection(handles: Handle[]) {\n\t\t\t// nodes\n\t\t\tgd().nodes().forEach(n => gd().setNodeSelected(n, handles.some(h => h.id == n.id)))\n\t\t\t// dots\n\t\t\tgd().edgeVertices.forEach(d => setDotSelected(d, handles.some(h => h.id == d.id)))\n\t\t\tselectListener(gd().nodes().find(n => n.selected))\n\t\t},\n\t\tsetDragging(d: boolean) {\n\t\t\tdragging = d\n\t\t},\n\t\tisSelected(handle: Handle): boolean {\n\t\t\treturn handle.selected\n\t\t},\n\t\tgetSelection(): Handle[] {\n\t\t\tconst ret: Handle[] = gd().nodes().filter(n => n.selected)\n\t\t\tgd().edgeVertices.forEach(d => d.selected && ret.push(d))\n\t\t\treturn ret\n\t\t},\n\t\tgetZoom: getZoom,\n\t\tmoveNode(h: Handle, x: number, y: number) {\n\t\t\tif (gd().nodesMap.has(h.id))\n\t\t\t\tgd().moveNode(h as Node, x, y)\n\t\t\telse {\n\t\t\t\t(h as EdgeVertex).auto = false\n\t\t\t\tgd().moveEdgeVertex(h as EdgeVertex, x, y)\n\t\t\t}\n\t\t},\n\t\tboxSelection(box: DOMRect, add) {\n\t\t\t// Box is now already in drawing coordinates, no need to scale\n\t\t\t// nodes\n\t\t\tgd().nodesMap.forEach(n => {\n\t\t\t\tconst inBox = boxesOverlap(uncenterBox(n), box)\n\t\t\t\tif (inBox) {\n\t\t\t\t\t// Toggle selection for elements in the box\n\t\t\t\t\tgd().setNodeSelected(n, !n.selected)\n\t\t\t\t} else if (!add) {\n\t\t\t\t\t// If not holding shift and element is outside box, deselect it\n\t\t\t\t\tgd().setNodeSelected(n, false)\n\t\t\t\t}\n\t\t\t})\n\t\t\t// dots\n\t\t\tgd().edgeVertices.forEach(d => {\n\t\t\t\tconst inBox = insideBox(d, box, false)\n\t\t\t\tif (inBox) {\n\t\t\t\t\t// Toggle selection for elements in the box\n\t\t\t\t\tsetDotSelected(d, !d.selected)\n\t\t\t\t} else if (!add) {\n\t\t\t\t\t// If not holding shift and element is outside box, deselect it\n\t\t\t\t\tsetDotSelected(d, false)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tselectListener(gd().nodes().find(n => n.selected))\n\t\t},\n\t\tupdatePanning: updatePanning,\n\t}, dragMode)\n\t\n\t// Store cleanup function on the SVG element for later use\n\tconst cleanup = () => {\n\t\teventListeners.forEach(({ element, event, handler }) => {\n\t\t\telement.removeEventListener(event, handler)\n\t\t})\n\t\tif (customInteractionCleanup) {\n\t\t\tcustomInteractionCleanup()\n\t\t}\n\t}\n\t\n\t// Store cleanup function on SVG element\n\t;(svg as any).__cursorInteractionCleanup = cleanup\n}\n\nexport function getZoom() {\n\tif (!svg) return 1\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return 1\n\t\n\t// Parse zoom from transform attribute to match how we set it\n\tconst transform = el.getAttribute('transform') || ''\n\tconst scaleMatch = transform.match(/scale\\(([^)]+)\\)/)\n\tif (scaleMatch) {\n\t\treturn parseFloat(scaleMatch[1]) || 1\n\t}\n\treturn 1\n}\n\n// svgPadding is now imported as SVG_PADDING from constants.ts\n\nexport function setZoom(zoom: number) {\n\tif (!svg) return\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return\n\t\n\t// Preserve existing translation when setting zoom\n\tconst currentTransform = getCurrentTransform()\n\tel.setAttribute('transform', `translate(${currentTransform.x}, ${currentTransform.y}) scale(${zoom})`)\n\t\n\t// also set panning size\n\tupdatePanning()\n}\n\nexport function setZoomCentered(newZoom: number, centerX?: number, centerY?: number) {\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tconst oldZoom = getZoom()\n\t\n\t// If no center point provided, use viewport center\n\tif (centerX === undefined || centerY === undefined) {\n\t\t// Use the parent container's dimensions for the visible viewport\n\t\t// The SVG might be larger than the visible area due to overflow\n\t\tconst container = svg.parentElement\n\t\tif (container) {\n\t\t\tcenterX = container.clientWidth / 2\n\t\t\tcenterY = container.clientHeight / 2\n\t\t} else {\n\t\t\t// Fallback to SVG dimensions if no parent\n\t\t\tcenterX = svg.clientWidth / 2\n\t\t\tcenterY = svg.clientHeight / 2\n\t\t}\n\t}\n\t\n\t// Get current transform\n\tconst currentTransform = getCurrentTransform()\n\t\n\t// Convert screen coordinates to drawing coordinates\n\t// For transform order translate(tx, ty) scale(s):\n\t// screen_point = (drawing_point * scale) + translation\n\t// So: drawing_point = (screen_point - translation) / scale\n\tconst drawingX = (centerX - currentTransform.x) / oldZoom\n\tconst drawingY = (centerY - currentTransform.y) / oldZoom\n\t\n\t// Calculate new translation to keep the same drawing point at the same screen position\n\t// screen_point = (drawing_point * new_scale) + new_translation\n\t// So: new_translation = screen_point - (drawing_point * new_scale)\n\tconst newTranslateX = centerX - (drawingX * newZoom)\n\tconst newTranslateY = centerY - (drawingY * newZoom)\n\t\n\t// Apply the new transform\n\tel.setAttribute('transform', `translate(${newTranslateX}, ${newTranslateY}) scale(${newZoom})`)\n\t\n\t// Update panning\n\tupdatePanning()\n}\n\nfunction getCurrentTransform() {\n\tif (!svg) return { x: 0, y: 0 }\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return { x: 0, y: 0 }\n\t\n\tconst transform = el.getAttribute('transform') || ''\n\tconst translateMatch = transform.match(/translate\\(([^,]+),([^)]+)\\)/)\n\tif (translateMatch) {\n\t\treturn {\n\t\t\tx: parseFloat(translateMatch[1]) || 0,\n\t\t\ty: parseFloat(translateMatch[2]) || 0\n\t\t}\n\t}\n\treturn { x: 0, y: 0 }\n}\n\nfunction updatePanning() {\n\tif (!svg) return\n\tconst el = svg.querySelector('g.zoom') as SVGGElement\n\tif (!el) return\n\tconst bb = el.getBBox()\n\tconst zoom = getZoom()\n\tif (!svg.parentElement) return\n\tconst w = Math.max(svg.parentElement.clientWidth / zoom, bb.x + bb.width + SVG_PADDING)\n\tconst h = Math.max(svg.parentElement.clientHeight / zoom, bb.y + bb.height + SVG_PADDING)\n\tsvg.setAttribute('width', String(w * zoom))\n\tsvg.setAttribute('height', String(h * zoom))\n\t\n\t// Note: View state saving removed from here to prevent interference with reset/fit functions\n\t// View state is now only saved on user interactions and page unload\n}\n\n// Optimized version that uses pre-calculated content bounds instead of expensive getBBox()\nfunction updatePanningOptimized(graphData: GraphData) {\n\tconst bb = graphData.calculateContentBounds() // Use already calculated bounds\n\tconst zoom = getZoom()\n\tconst w = Math.max(svg.parentElement.clientWidth / zoom, bb.x + bb.width + SVG_PADDING)\n\tconst h = Math.max(svg.parentElement.clientHeight / zoom, bb.y + bb.height + SVG_PADDING)\n\tsvg.setAttribute('width', String(w * zoom))\n\tsvg.setAttribute('height', String(h * zoom))\n}\n\nexport const getZoomAuto = () => {\n\t// Get the graph data to calculate proper content bounds\n\tconst graphData = (svg as any).__data as GraphData\n\tif (!graphData) {\n\t\treturn 1 // Default zoom if no graph data\n\t}\n\t\n\t// Use proper content bounds calculation\n\tconst contentBounds = graphData.calculateContentBounds()\n\tconst viewportWidth = svg.parentElement?.clientWidth || 800\n\tconst viewportHeight = svg.parentElement?.clientHeight || 600\n\t\n\t// Add padding around content\n\tconst padding = 40\n\t\n\t// Calculate zoom to fit content with padding\n\tconst zoomX = (viewportWidth - padding * 2) / contentBounds.width\n\tconst zoomY = (viewportHeight - padding * 2) / contentBounds.height\n\tconst zoom = Math.min(zoomX, zoomY)\n\t\n\t// Clamp zoom between reasonable bounds\n\treturn Math.max(Math.min(zoom, 2), 0.1)\n}\n\nconst setBorderStyle = (el: SVGElement, style: string) => {\n\tif (style == 'Dashed') el.setAttribute('stroke-dasharray', '4')\n\telse if (style == 'Dotted') el.setAttribute('stroke-dasharray', '2')\n}\n\nconst styles = {\n\t//node styles\n\tnodeBorder: {\n\t\t// Don't set fill and stroke here - let tag-specific styles handle colors\n\t\tfilter: 'url(#shadow)',\n\t},\n\tnodeText: {\n\t\t'font-family': 'Arial, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\n\t//edge styles\n\tedgeText: {\n\t\t'font-family': 'Arial, sans-serif',\n\t\tstroke: \"none\"\n\t},\n\n\tedgeRect: {\n\t\tfill: \"none\",\n\t\tstroke: \"none\",\n\t},\n\n\t//group styles\n\tgroupRect: {\n\t\t//fill: \"none\",\n\t\tfill: \"rgba(0, 0, 0, 0.02)\",\n\t\tstroke: \"#666\",\n\t\t'stroke-width': 3,\n\t\t\"stroke-dasharray\": 4,\n\t},\n\tgroupText: {\n\t\t'font-family': 'Arial, sans-serif',\n\t\tfill: \"#666\",\n\t\t\"font-size\": 22,\n\t\t\"font-weight\": \"bold\",\n\t\tcursor: \"default\"\n\t}\n}\n\n// View state persistence - similar to undo cache but for zoom/pan\nconst viewStateCache = new Map();\n\n// Save current view state (zoom and pan)\nexport function saveViewState(graphId: string) {\n\tif (!svg) return;\n\t\n\tconst zoom = getZoom();\n\tconst transform = getCurrentTransform();\n\t\n\tconst state = {\n\t\tzoom,\n\t\ttransform: { x: transform.x, y: transform.y }\n\t};\n\t\n\tviewStateCache.set(graphId, state);\n}\n\n// Restore view state if it exists\nexport function restoreViewState(graphId: string): boolean {\n\tif (!svg || !viewStateCache.has(graphId)) {\n\t\treturn false;\n\t}\n\t\n\tconst state = viewStateCache.get(graphId);\n\tif (!state) {\n\t\treturn false;\n\t}\n\t\n\t// Restore zoom and transform\n\tconst zoomGroup = svg.querySelector('g.zoom') as SVGGElement;\n\tif (zoomGroup) {\n\t\tzoomGroup.setAttribute('transform', `scale(${state.zoom}) translate(${state.transform.x}, ${state.transform.y})`);\n\t\tupdatePanning();\n\t}\n\t\n\treturn true;\n}\n\n// Clear view state for a graph\nexport function clearViewState(graphId: string) {\n\tviewStateCache.delete(graphId);\n}\n\n/**\n * Find the optimal position to insert a label vertex into the routing path\n * to minimize disruption to the existing route\n */\nfunction findOptimalLabelPosition(vertices: Point[], labelPos: Point, fromNode: Point, toNode: Point): number {\n\t// If no existing vertices, insert at the beginning\n\tif (vertices.length === 0) {\n\t\treturn 0;\n\t}\n\t\n\t// Build the full routing path including start/end nodes\n\tconst fullPath = [fromNode, ...vertices, toNode];\n\t\n\t// Find the closest point on the path to the label position\n\tlet minDistance = Infinity;\n\tlet bestSegmentIndex = 0;\n\t\n\tfor (let i = 0; i < fullPath.length - 1; i++) {\n\t\tconst segmentStart = fullPath[i];\n\t\tconst segmentEnd = fullPath[i + 1];\n\t\t\n\t\t// Calculate distance from label position to this segment\n\t\tconst distance = distanceToSegment(labelPos, segmentStart, segmentEnd);\n\t\t\n\t\tif (distance < minDistance) {\n\t\t\tminDistance = distance;\n\t\t\tbestSegmentIndex = i;\n\t\t}\n\t}\n\t\n\t// Convert full path index to vertices array index\n\t// bestSegmentIndex 0 means between fromNode and vertices[0] -> insert at 0\n\t// bestSegmentIndex 1 means between vertices[0] and vertices[1] -> insert at 1\n\t// etc.\n\treturn bestSegmentIndex;\n}\n\n/**\n * Calculate distance from a point to a line segment\n */\nfunction distanceToSegment(point: Point, segmentStart: Point, segmentEnd: Point): number {\n\tconst A = point.x - segmentStart.x;\n\tconst B = point.y - segmentStart.y;\n\tconst C = segmentEnd.x - segmentStart.x;\n\tconst D = segmentEnd.y - segmentStart.y;\n\t\n\tconst dot = A * C + B * D;\n\tconst lenSq = C * C + D * D;\n\t\n\tif (lenSq === 0) {\n\t\t// Segment is actually a point\n\t\treturn Math.sqrt(A * A + B * B);\n\t}\n\t\n\tlet param = dot / lenSq;\n\t\n\tlet xx, yy;\n\t\n\tif (param < 0) {\n\t\txx = segmentStart.x;\n\t\tyy = segmentStart.y;\n\t} else if (param > 1) {\n\t\txx = segmentEnd.x;\n\t\tyy = segmentEnd.y;\n\t} else {\n\t\txx = segmentStart.x + param * C;\n\t\tyy = segmentStart.y + param * D;\n\t}\n\t\n\tconst dx = point.x - xx;\n\tconst dy = point.y - yy;\n\treturn Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Project a label position onto the line segment where it will be inserted\n */\nfunction projectLabelOntoSegment(vertices: Point[], labelPos: Point, insertPos: number, fromNode: Point, toNode: Point): Point {\n\t// Build the full routing path including start/end nodes\n\tconst fullPath = [fromNode, ...vertices, toNode];\n\t\n\t// The segment where we're inserting is between fullPath[insertPos] and fullPath[insertPos + 1]\n\tconst segmentStart = fullPath[insertPos];\n\tconst segmentEnd = fullPath[insertPos + 1];\n\t\n\t// Project the label position onto this line segment\n\treturn projectPointOntoSegment(labelPos, segmentStart, segmentEnd);\n}\n\n/**\n * Project a point onto a line segment (closest point on the segment)\n */\nfunction projectPointOntoSegment(point: Point, segmentStart: Point, segmentEnd: Point): Point {\n\tconst A = point.x - segmentStart.x;\n\tconst B = point.y - segmentStart.y;\n\tconst C = segmentEnd.x - segmentStart.x;\n\tconst D = segmentEnd.y - segmentStart.y;\n\t\n\tconst dot = A * C + B * D;\n\tconst lenSq = C * C + D * D;\n\t\n\tif (lenSq === 0) {\n\t\t// Segment is actually a point, return that point\n\t\treturn { x: segmentStart.x, y: segmentStart.y };\n\t}\n\t\n\tlet param = dot / lenSq;\n\t\n\t// Clamp to segment (don't extend beyond endpoints)\n\tparam = Math.max(0, Math.min(1, param));\n\t\n\treturn {\n\t\tx: segmentStart.x + param * C,\n\t\ty: segmentStart.y + param * D\n\t};\n}","import { Point, BBox, calculateDistance } from './constants';\nimport { intersectPolylineBox, Segment } from './intersect';\n\n// Define interfaces locally since they're not exported from graph.ts\ninterface NodeStyle {\n\tbackground?: string;\n\tstroke?: string;\n\topacity?: number;\n\tfontSize?: number;\n\tshape?: string;\n\tborder?: string;\n}\n\ninterface Node extends Point {\n\tid: string;\n\ttitle: string;\n\tsub: string;\n\tdescription: string;\n\twidth: number;\n\theight: number;\n\tref?: SVGGElement;\n\tselected?: boolean;\n\tintersect: (p: Point) => Point;\n\tstyle: NodeStyle;\n}\n\ninterface EdgeVertex extends Point {\n\tid: string;\n\tselected?: boolean;\n\tref?: SVGElement;\n\tlabel?: boolean;\n\tauto?: boolean;\n}\n\ninterface EdgeStyle {\n\tcolor?: string;\n\tthickness?: number;\n\tfontSize?: number;\n\tposition?: number;\n\tdashed?: boolean;\n}\n\ninterface Edge {\n\tid: string;\n\tlabel: string;\n\tfrom: Node;\n\tto: Node;\n\tvertices?: EdgeVertex[];\n\tref?: SVGGElement;\n\tstyle: EdgeStyle;\n\tinitVertex: (p: Point) => EdgeVertex;\n\tuserDeletedVertices?: boolean; // Track if user explicitly deleted vertices\n}\n\ninterface GraphData {\n\tid: string;\n\tname: string;\n\tnodesMap: Map;\n\tedges: Edge[];\n\tedgeVertices: Map;\n\tgroupsMap: Map;\n\tmetadata: any;\n}\n\nexport interface EdgeLabelPlacement extends Point {\n\torientation: 'horizontal' | 'vertical';\n\tsegment?: Segment;\n\tmovable: boolean;\n}\n\n/**\n * Calculate edge vertices, handling multi-edge scenarios and auto-vertices\n */\nexport function calculateEdgeVertices(edge: Edge, data: GraphData): Point[] {\n\tconst n1 = edge.from, n2 = edge.to;\n\t\n\t\n\t// if vertices exists, follow them\n\tlet vertices: Point[] = edge.vertices ? edge.vertices.concat() : [];\n\t// Don't remove label vertices - they should be preserved for rendering\n\t// (The autoLayout process handles replacing old ones with new ones)\n\tconst tmp = (vertices as EdgeVertex[]);\n\n\tif (vertices.length == 0 && !edge.userDeletedVertices) {\n\t\t// Only create auto vertices if user hasn't explicitly deleted them\n\t\t// for edges with same \"from\" and \"to\", we must spread the labels so they don't overlap\n\t\t// lookup the other \"same\" edges\n\t\tconst sameEdges = data.edges.filter(e => e.from == edge.from && e.to == edge.to)\n\t\tlet spreadPos = 0\n\t\tif (sameEdges.length > 1) {\n\t\t\tconst idx = sameEdges.indexOf(edge) // my index in the list of same edges\n\t\t\tspreadPos = idx - (sameEdges.length - 1) / 2\n\n\t\t\tlet spreadX = 0, spreadY = 0;\n\t\t\tif (Math.abs(n1.x - n2.x) > Math.abs(n1.y - n2.y)) {\n\t\t\t\tspreadY = spreadPos * 70\n\t\t\t} else {\n\t\t\t\tspreadX = spreadPos * 200\n\t\t\t}\n\t\t\tconst v = edge.initVertex({\n\t\t\t\tx: (n1.x + n2.x) / 2 + spreadX,\n\t\t\t\ty: (n1.y + n2.y) / 2 + spreadY\n\t\t\t})\n\t\t\tv.label = true\n\t\t\tv.auto = true\n\t\t\tvertices.push(v)\n\t\t} else {\n\t\t\t// If there are no user-defined vertices and not a multi-edge scenario,\n\t\t\t// we don't create any auto-vertices here. AutoLayout will provide them.\n\t\t\t// The path will be a straight line between n1 and n2 (after intersection points are calculated).\n\t\t\t// ELK/autoLayout is responsible for providing bend points for non-straight lines.\n\t\t}\n\t}\n\n\tvertices.unshift(n1)\n\tvertices.push(n2)\n\n\t// Calculate intersection points with node boundaries\n\t// Find first non-label vertex for start intersection\n\tlet firstRoutingVertex = vertices[vertices.length - 1]; // Default to end node\n\tfor (let i = 1; i < vertices.length - 1; i++) {\n\t\tif (!(vertices[i] as any).label) {\n\t\t\tfirstRoutingVertex = vertices[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\t// Find last non-label vertex for end intersection \n\tlet lastRoutingVertex = vertices[0]; // Default to start node\n\tfor (let i = vertices.length - 2; i > 0; i--) {\n\t\tif (!(vertices[i] as any).label) {\n\t\t\tlastRoutingVertex = vertices[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\t// For connections without routing vertices, ensure we have proper direction\n\t// The defaults are already correct: firstRoutingVertex = n2, lastRoutingVertex = n1\n\t\n\t\n\t// Calculate proper node boundary intersection\n\tconst calculateNodeIntersection = (node: any, targetPoint: Point): Point => {\n\t\tconst nodeShape = node.style?.shape?.toLowerCase() || 'box';\n\t\tconst dx = targetPoint.x - node.x;\n\t\tconst dy = targetPoint.y - node.y;\n\t\tconst nodeCenter = { x: node.x, y: node.y };\n\t\t\n\t\t// If target is at center, default to right edge\n\t\tif (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01) {\n\t\t\treturn { x: node.x + node.width / 2, y: node.y };\n\t\t}\n\t\t\n\t\tif (nodeShape === 'cylinder') {\n\t\t\t// Cylinder shape intersection (same as shapes.ts)\n\t\t\tconst w = node.width;\n\t\t\tconst rx = w / 2;\n\t\t\tconst ry = rx / (5.5 + w / 70);\n\t\t\tconst halfHeight = node.height / 2;\n\t\t\t\n\t\t\t// First calculate rectangular bounds intersection\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\tconst cos = Math.cos(angle);\n\t\t\tconst sin = Math.sin(angle);\n\t\t\t\n\t\t\t// Check intersection with rectangular bounds\n\t\t\tlet t = Infinity;\n\t\t\tif (Math.abs(cos) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(rx / cos));\n\t\t\t}\n\t\t\tif (Math.abs(sin) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(halfHeight / sin));\n\t\t\t}\n\t\t\t\n\t\t\tconst rectX = node.x + cos * t;\n\t\t\tconst rectY = node.y + sin * t;\n\t\t\t\n\t\t\t// Check if we need elliptical intersection for top/bottom curves\n\t\t\tconst topCurveY = node.y - halfHeight + ry;\n\t\t\tconst bottomCurveY = node.y + halfHeight - ry;\n\t\t\t\n\t\t\tif (rectY < topCurveY || rectY > bottomCurveY) {\n\t\t\t\t// Use ellipse intersection for curved parts\n\t\t\t\tconst ellipseY = rectY < topCurveY ? node.y - halfHeight + ry : node.y + halfHeight - ry;\n\t\t\t\t// Solve for ellipse intersection\n\t\t\t\tconst a = 1 / (rx * rx);\n\t\t\t\tconst b = -2 * node.x / (rx * rx);\n\t\t\t\tconst c = (node.x * node.x) / (rx * rx) + ((ellipseY - node.y) * (ellipseY - node.y)) / (ry * ry) - 1;\n\t\t\t\t\n\t\t\t\tconst discriminant = b * b - 4 * a * c;\n\t\t\t\tif (discriminant >= 0) {\n\t\t\t\t\tconst sqrt_d = Math.sqrt(discriminant);\n\t\t\t\t\tconst x1 = (-b + sqrt_d) / (2 * a);\n\t\t\t\t\tconst x2 = (-b - sqrt_d) / (2 * a);\n\t\t\t\t\t\n\t\t\t\t\t// Choose the intersection in the direction of the target\n\t\t\t\t\tconst intersectX = dx > 0 ? Math.max(x1, x2) : Math.min(x1, x2);\n\t\t\t\t\treturn { x: intersectX, y: ellipseY };\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\treturn { x: rectX, y: rectY };\n\t\t\t\n\t\t} else if (nodeShape === 'circle') {\n\t\t\tconst radius = node.width / 2;\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\treturn {\n\t\t\t\tx: node.x + Math.cos(angle) * radius,\n\t\t\t\ty: node.y + Math.sin(angle) * radius\n\t\t\t};\n\t\t\t\n\t\t} else if (nodeShape === 'ellipse') {\n\t\t\tconst rx = node.width * 0.55;\n\t\t\tconst ry = node.width * 0.45;\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\tconst cos = Math.cos(angle);\n\t\t\tconst sin = Math.sin(angle);\n\t\t\t\n\t\t\t// Parametric ellipse intersection\n\t\t\tconst t = Math.sqrt((rx * rx * sin * sin) + (ry * ry * cos * cos));\n\t\t\treturn {\n\t\t\t\tx: node.x + (rx * cos * ry) / t,\n\t\t\t\ty: node.y + (ry * sin * rx) / t\n\t\t\t};\n\t\t\t\n\t\t} else {\n\t\t\t// Default rectangular intersection\n\t\t\tconst halfWidth = node.width / 2;\n\t\t\tconst halfHeight = node.height / 2;\n\t\t\tconst angle = Math.atan2(dy, dx);\n\t\t\tconst cos = Math.cos(angle);\n\t\t\tconst sin = Math.sin(angle);\n\t\t\t\n\t\t\t// Calculate which edge we hit first\n\t\t\tlet t = Infinity;\n\t\t\tif (Math.abs(cos) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(halfWidth / cos));\n\t\t\t}\n\t\t\tif (Math.abs(sin) > 0.01) {\n\t\t\t\tt = Math.min(t, Math.abs(halfHeight / sin));\n\t\t\t}\n\t\t\t\n\t\t\treturn {\n\t\t\t\tx: node.x + cos * t,\n\t\t\t\ty: node.y + sin * t\n\t\t\t};\n\t\t}\n\t};\n\t\n\t// Calculate intersections, but use the direction from center to NEXT vertex in sequence\n\t// This ensures the line exits the node in the direction it needs to go\n\t\n\t// For start intersection: use direction from node center to first routing vertex\n\tlet startIntersection = calculateNodeIntersection(n1, firstRoutingVertex);\n\t\n\t// For end intersection: use direction from node center to last routing vertex \n\tlet endIntersection = calculateNodeIntersection(n2, lastRoutingVertex);\n\t\n\t// Start intersection: NO offset needed - intersection already gives perfect boundary point\n\t// End intersection: NO offset needed - the arrow marker now has refX=\"0\" so the tip is at the endpoint\n\t\n\tvertices[0] = startIntersection;\n\tvertices[vertices.length - 1] = endIntersection;\n\t\n\treturn vertices;\n}\n\n/**\n * Calculate the label anchor and the orientation of the path segment that owns\n * it. Renderers use the orientation to place text beside the relationship line\n * instead of centering text over vertical segments.\n */\nexport function calculateLabelPlacement(\n\tvertices: Point[],\n\tposition: number,\n\tfallback: Point,\n): EdgeLabelPlacement {\n\tlet point = {x: fallback.x, y: fallback.y};\n\tlet segment: Segment | undefined;\n\tconst labelIndex = vertices.findIndex(vertex => (vertex as EdgeVertex).label);\n\tlet movable = true;\n\n\tif (labelIndex >= 0) {\n\t\tconst labelVertex = vertices[labelIndex] as EdgeVertex;\n\t\tpoint = labelVertex;\n\t\tmovable = labelVertex.auto === true;\n\t\tconst adjacentSegments: Segment[] = [];\n\t\tif (labelIndex > 0) {\n\t\t\tadjacentSegments.push({p: vertices[labelIndex - 1], q: point});\n\t\t}\n\t\tif (labelIndex < vertices.length - 1) {\n\t\t\tadjacentSegments.push({p: point, q: vertices[labelIndex + 1]});\n\t\t}\n\t\tsegment = adjacentSegments.reduce((longest, candidate) => {\n\t\t\tif (!longest) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t\treturn calculateDistance(candidate.p, candidate.q) >\n\t\t\t\tcalculateDistance(longest.p, longest.q)\n\t\t\t\t? candidate\n\t\t\t\t: longest;\n\t\t}, undefined);\n\t} else {\n\t\tconst totalLength = vertices.slice(1).reduce(\n\t\t\t(sum, vertex, index) => sum + calculateDistance(vertices[index], vertex),\n\t\t\t0,\n\t\t);\n\t\tconst targetLength = totalLength * position;\n\t\tlet traversed = 0;\n\t\tfor (let index = 1; index < vertices.length; index++) {\n\t\t\tconst candidate = {p: vertices[index - 1], q: vertices[index]};\n\t\t\tconst length = calculateDistance(candidate.p, candidate.q);\n\t\t\tif (length > 0 && traversed + length >= targetLength) {\n\t\t\t\tconst segmentPosition = (targetLength - traversed) / length;\n\t\t\t\tpoint = {\n\t\t\t\t\tx: candidate.p.x + (candidate.q.x - candidate.p.x) * segmentPosition,\n\t\t\t\t\ty: candidate.p.y + (candidate.q.y - candidate.p.y) * segmentPosition,\n\t\t\t\t};\n\t\t\t\tsegment = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttraversed += length;\n\t\t}\n\t}\n\n\tconst horizontalDistance = segment ? Math.abs(segment.q.x - segment.p.x) : 0;\n\tconst verticalDistance = segment ? Math.abs(segment.q.y - segment.p.y) : 0;\n\treturn {\n\t\t...point,\n\t\torientation: verticalDistance > horizontalDistance ? 'vertical' : 'horizontal',\n\t\tsegment,\n\t\tmovable,\n\t};\n}\n\n/**\n * Create edge segments and generate SVG path\n */\nexport function createEdgeSegments(vertices: Point[], bbox: BBox, n1: Point, n2: Point): { segments: Segment[], path: string } {\n\tconst segments: Segment[] = []\n\tfor (let i = 1; i < vertices.length; i++) {\n\t\tsegments.push({p: vertices[i - 1], q: vertices[i]})\n\t}\n\t\n\t// Debug the final segment that will have the arrow\n\tif (segments.length > 0) {\n\t\tconst lastSegment = segments[segments.length - 1];\n\t}\n\t// splice edge over label box\n\tintersectPolylineBox(segments, bbox)\n\n\t// Generate path based on routing style - SIMPLIFIED\n\tlet path: string\n\t\n\t// Always draw a polyline through the segments.\n\t// If segments.length is 1 (meaning direct connection or only label vertex), it will be a straight line.\n\t// If autoLayout provided bend points, those will be in `segments`.\n\tif (segments.length > 0) {\n\t\tpath = `M${segments[0].p.x},${segments[0].p.y}`\n\t\tfor (let i = 0; i < segments.length; i++) {\n\t\t\tconst s = segments[i]\n\t\t\t// For polylines, we just draw line segments to each vertex point.\n\t\t\t// The ELK 'POLYLINE' routing should give us the necessary bend points.\n\t\t\tpath += ` L${s.q.x},${s.q.y}`\n\t\t}\n\t} else {\n\t\t// Fallback for edges with no segments (should ideally not happen if n1 and n2 are defined)\n\t\t// Draw a straight line between n1 and n2 directly if no vertices/segments exist.\n\t\t// Note: Intersection points are calculated before this, so n1/n2 are already adjusted.\n\t\tpath = `M${n1.x},${n1.y} L${n2.x},${n2.y}`\n\t}\n\t\n\treturn { segments, path };\n}","import {GraphData, Node, Group, LayoutDirection} from \"./graph\";\n\nexport interface LayoutOptions {\n\tdirection?: LayoutDirection;\n\tnodeSpacing?: number;\n\tlayerSpacing?: number;\n\tcompactLayout?: boolean;\n}\n\n// Simplified spacing configuration\ninterface SpacingConfig {\n\tnodeSpacing: number;\n\tlayerSpacing: number;\n\tcomponentSpacing: number;\n\tpadding: number;\n\tgroupMultiplier: number;\n}\n\n// Spacing configuration - balanced for readability\nconst DEFAULT_SPACING: SpacingConfig = {\n\tnodeSpacing: 80, // Comfortable vertical spacing between nodes in same layer\n\tlayerSpacing: 60, // Layer spacing (between nodes in flow direction)\n\tcomponentSpacing: 80, // Separation between disconnected components\n\tpadding: 40, // Padding around the entire layout (for group labels)\n\tgroupMultiplier: 0.65, // Moderate compaction within groups\n};\n\n// Helper function to get effective spacing for a context\nfunction getEffectiveSpacing(\n\tuserOptions: LayoutOptions = {},\n\tisGroup: boolean = false\n): SpacingConfig {\n\t// Apply user overrides to base config\n\tconst effectiveConfig: SpacingConfig = {\n\t\tnodeSpacing: userOptions.nodeSpacing ?? DEFAULT_SPACING.nodeSpacing,\n\t\tlayerSpacing: userOptions.layerSpacing ?? DEFAULT_SPACING.layerSpacing,\n\t\tcomponentSpacing: DEFAULT_SPACING.componentSpacing,\n\t\tpadding: DEFAULT_SPACING.padding,\n\t\tgroupMultiplier: DEFAULT_SPACING.groupMultiplier,\n\t};\n\t\n\t// Apply group multiplier if in group context\n\tif (isGroup) {\n\t\teffectiveConfig.nodeSpacing = Math.max(\n\t\t\teffectiveConfig.nodeSpacing * effectiveConfig.groupMultiplier,\n\t\t\t30 // Minimum 30px spacing within groups\n\t\t);\n\t\teffectiveConfig.layerSpacing = Math.max(\n\t\t\teffectiveConfig.layerSpacing * effectiveConfig.groupMultiplier,\n\t\t\t35 // Minimum 35px layer spacing within groups\n\t\t);\n\t\teffectiveConfig.componentSpacing = Math.max(\n\t\t\teffectiveConfig.componentSpacing * effectiveConfig.groupMultiplier,\n\t\t\t25 // Minimum 25px component spacing within groups\n\t\t);\n\t\teffectiveConfig.padding = Math.max(\n\t\t\teffectiveConfig.padding * effectiveConfig.groupMultiplier,\n\t\t\t15 // Minimum 15px padding within groups\n\t\t);\n\t}\n\t\n\treturn effectiveConfig;\n}\n\n// Simplified ELK layout options builder\nfunction getELKOptions(\n\tspacing: SpacingConfig,\n\tuserOptions: LayoutOptions\n): Record {\n\tconst {\n\t\tdirection = 'DOWN',\n\t\tcompactLayout = false\n\t} = userOptions;\n\t\n\tconst baseOptions: Record = {\n\t\t'elk.algorithm': 'layered', // Back to layered for better orthogonal routing\n\t\t'elk.direction': direction,\n\t\t'elk.spacing.nodeNode': spacing.nodeSpacing.toString(),\n\t\t'elk.spacing.componentComponent': spacing.componentSpacing.toString(),\n\t\t'elk.padding': `[top=${spacing.padding},left=${spacing.padding},bottom=${spacing.padding},right=${spacing.padding}]`,\n\t\t\n\t\t// Layer spacing for compact layout\n\t\t'elk.layered.spacing.nodeNodeBetweenLayers': spacing.layerSpacing.toString(),\n\t\t'elk.layered.spacing.edgeNodeBetweenLayers': '10', // Minimal spacing around nodes\n\t\t'elk.layered.spacing.edgeEdgeBetweenLayers': '10', // Minimal space between edges\n\t\t\n\t\t// ORTHOGONAL edge routing for cleaner layout\n\t\t'elk.edgeRouting': 'POLYLINE',\n\t\t'elk.layered.unnecessaryBendpoints': 'false',\n\t\t\n\t\t// Minimal edge routing - straight lines where possible\n\t\t'elk.layered.edgeRouting.orthogonal.mode': 'DIRECTION_BASED',\n\t\t'elk.layered.edgeRouting.orthogonal.spacing': '5', // Minimal edge spacing\n\t\t'elk.layered.edgeRouting.orthogonal.nodeOverlapRatio': '0.1',\n\t\t\n\t\t// Compaction options\n\t\t'elk.layered.compaction.connectedComponents': 'true',\n\t\t'elk.layered.compaction.postCompaction.strategy': 'LEFT_RIGHT',\n\t\t\n\t\t// Separate components to reduce complexity\n\t\t'elk.separateConnectedComponents': 'true',\n\t\t\n\t\t// Node placement strategy for consistent vertical spacing\n\t\t'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',\n\t\t'elk.layered.nodePlacement.favorStraightEdges': 'true',\n\t\t\n\t\t// Crossing minimization - respect model order for consistent layout\n\t\t'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',\n\t\t'elk.layered.crossingMinimization.semiInteractive': 'true',\n\t\t\n\t\t// Flatten hierarchy for better edge routing\n\t\t'elk.hierarchyHandling': 'SEPARATE_CHILDREN',\n\t\t'elk.layered.considerModelOrder.strategy': 'NONE', // Ignore model ordering constraints\n\t\t\n\t\t// Edge label handling - minimal space, labels positioned above edges\n\t\t'elk.edgeLabels.placement': 'CENTER',\n\t\t'elk.edgeLabels.inline': 'true',\n\t\t'elk.spacing.edgeLabel': '5', // Minimal spacing for labels\n\t\t'elk.edgeLabels.avoidOverlap': 'false', // Disable collision avoidance\n\t\t'elk.edgeLabels.considerModelOrder': 'false',\n\t\t'elk.layered.edgeLabels.sideSelection': 'ALWAYS_UP', // Labels above edges\n\t};\n\t\n\t// Additional compact layout options if requested\n\tif (compactLayout) {\n\t\tbaseOptions['elk.spacing.nodeNode'] = Math.max(spacing.nodeSpacing * 0.7, 30).toString();\n\t\tbaseOptions['elk.layered.spacing.nodeNodeBetweenLayers'] = Math.max(spacing.layerSpacing * 0.7, 30).toString();\n\t}\n\t\n\treturn baseOptions;\n}\n\nexport async function autoLayout(graph: GraphData, options: LayoutOptions = {}): Promise<{\n\tnodes: Array<{id: string, x: number, y: number}>,\n\tedges: Array<{id: string, vertices: Array<{x: number, y: number}>, label?: {x: number, y: number}}>\n}> {\n\t// Dynamically import ELK only when auto-layout is used\n\tconst ELK = await import('elkjs/lib/elk.bundled.js').then(module => module.default);\n\tconst elk = new ELK();\n\t// Get systematic spacing configuration\n\tconst rootSpacing = getEffectiveSpacing(options, false);\n\t\n\t// Build ELK graph structure\n\tconst elkGraph = {\n\t\tid: \"root\",\n\t\tlayoutOptions: getELKOptions(rootSpacing, options),\n\t\tchildren: [] as any[],\n\t\tedges: [] as any[]\n\t};\n\n\t// Build actual ELK nodes first. Groups below take ownership of their direct\n\t// members so ELK reserves non-overlapping space for every boundary.\n\tconst nodeMap = new Map();\n\tconst elkNodes = new Map();\n\tgraph.nodesMap.forEach(node => {\n\t\tif (!node.id) return; // Skip nodes without IDs\n\t\t\n\t\tnodeMap.set(node.id, node);\n\t\t\n\t\t// Ensure minimum dimensions and validate node size data\n\t\t// Use larger height to account for shapes like Robot that extend above the center\n\t\tconst nodeWidth = Math.max(node.width || 200, 150); // Min width 150px\n\t\tconst nodeHeight = Math.max(node.height || 100, 250); // Min height 250px to account for robot shape\n\t\t\n\t\t// Add padding to node dimensions for ELK to account for arrow size\n\t\t// This makes ELK route edges to a slightly larger boundary so arrow tips stay outside\n\t\tconst arrowPadding = 25; // Padding for arrow clearance\n\t\t\n\t\telkNodes.set(node.id, {\n\t\t\tid: node.id,\n\t\t\t// Provide current position as hint to ELK\n\t\t\tx: node.x,\n\t\t\ty: node.y,\n\t\t\twidth: nodeWidth + (arrowPadding * 2),\n\t\t\theight: nodeHeight + (arrowPadding * 2),\n\t\t\tlayoutOptions: {\n\t\t\t\t// Allow ELK to move nodes but consider current positions\n\t\t\t\t'elk.position': '',\n\t\t\t\t// Force ELK to use our exact dimensions\n\t\t\t\t'elk.nodeSize.constraints': '[FIXED_SIZE]'\n\t\t\t}\n\t\t});\n\t});\n\n\tconst nodeParentGroup = new Map();\n\tconst groupParent = new Map();\n\tconst childGroupIDs = new Set();\n\n\tgraph.groupsMap.forEach(group => {\n\t\tgroup.nodes.forEach(member => {\n\t\t\tif (isGroup(member)) {\n\t\t\t\tif (!groupParent.has(member.id)) {\n\t\t\t\t\tgroupParent.set(member.id, group.id);\n\t\t\t\t}\n\t\t\t\tchildGroupIDs.add(member.id);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!nodeParentGroup.has(member.id)) {\n\t\t\t\tnodeParentGroup.set(member.id, group.id);\n\t\t\t}\n\t\t});\n\t});\n\n\tconst elkGroups = new Map();\n\tconst buildELKGroup = (group: Group): any => {\n\t\tconst existing = elkGroups.get(group.id);\n\t\tif (existing) return existing;\n\n\t\tconst children = group.nodes.flatMap(member => {\n\t\t\tif (isGroup(member)) {\n\t\t\t\treturn groupParent.get(member.id) === group.id ? [buildELKGroup(member)] : [];\n\t\t\t}\n\t\t\tconst node = elkNodes.get(member.id);\n\t\t\treturn node && nodeParentGroup.get(member.id) === group.id ? [node] : [];\n\t\t});\n\t\tconst elkGroup = {\n\t\t\tid: group.id,\n\t\t\tchildren,\n\t\t\tedges: [] as any[],\n\t\t\tlayoutOptions: getELKOptions(getEffectiveSpacing(options, true), options),\n\t\t};\n\t\telkGroups.set(group.id, elkGroup);\n\t\treturn elkGroup;\n\t};\n\n\tgraph.groupsMap.forEach(group => {\n\t\tif (!childGroupIDs.has(group.id)) {\n\t\t\tconst elkGroup = buildELKGroup(group);\n\t\t\tif (elkGroup.children.length > 0) {\n\t\t\t\telkGraph.children.push(elkGroup);\n\t\t\t}\n\t\t}\n\t});\n\telkNodes.forEach((node, id) => {\n\t\tif (!nodeParentGroup.has(id)) {\n\t\t\telkGraph.children.push(node);\n\t\t}\n\t});\n\n\tconst groupAncestors = (groupID?: string) => {\n\t\tconst ancestors: string[] = [];\n\t\tlet current = groupID;\n\t\twhile (current) {\n\t\t\tancestors.push(current);\n\t\t\tcurrent = groupParent.get(current);\n\t\t}\n\t\treturn ancestors;\n\t};\n\tconst lowestCommonGroup = (sourceID: string, destinationID: string) => {\n\t\tconst sourceAncestors = groupAncestors(nodeParentGroup.get(sourceID));\n\t\tconst destinationAncestors = new Set(groupAncestors(nodeParentGroup.get(destinationID)));\n\t\treturn sourceAncestors.find(groupID => destinationAncestors.has(groupID));\n\t};\n\n\tgraph.edges.forEach(edge => {\n\t\t// Skip edges without proper IDs\n\t\tif (!edge.id || !edge.from?.id || !edge.to?.id) return;\n\t\t\n\t\t// Verify source and target nodes exist in our node map\n\t\tif (!nodeMap.has(edge.from.id) || !nodeMap.has(edge.to.id)) {\n\t\t\tconsole.warn(`Skipping edge ${edge.id}: source ${edge.from.id} or target ${edge.to.id} not found in nodes`);\n\t\t\treturn;\n\t\t}\n\t\t\n\t\t// Calculate more accurate label dimensions for ELK\n\t\tconst labelWidth = edge.label && edge.label.trim() ? \n\t\t\tMath.min(edge.label.length * 7, 200) : 0; // More realistic width estimate\n\t\t\n\t\t\n\t\tconst elkEdge = {\n\t\t\tid: edge.id,\n\t\t\tsources: [edge.from.id],\n\t\t\ttargets: [edge.to.id],\n\t\t\t// Include label information with much smaller dimensions\n\t\t\tlabels: edge.label && edge.label.trim() ? [{\n\t\t\t\tid: `${edge.id}-label`,\n\t\t\t\ttext: edge.label,\n\t\t\t\t// Much more conservative label size estimates\n\t\t\t\twidth: labelWidth,\n\t\t\t\theight: 20, // More realistic label height\n\t\t\t\tlayoutOptions: {\n\t\t\t\t\t'elk.edgeLabels.placement': 'CENTER',\n\t\t\t\t\t'elk.edgeLabels.inline': 'true'\n\t\t\t\t\t// Remove the FIXED_SIZE constraint that might be forcing detours\n\t\t\t\t}\n\t\t\t}] : []\n\t\t};\n\n\t\tconst groupID = lowestCommonGroup(edge.from.id, edge.to.id);\n\t\tconst edgeContainer = groupID ? elkGroups.get(groupID) : elkGraph;\n\t\tedgeContainer.edges.push(elkEdge);\n\t});\n\n\t// Enhanced validation - ensure ELK gets complete data\n\tif (!elkGraph.id || !elkGraph.children) {\n\t\tthrow new Error('Invalid ELK graph structure');\n\t}\n\t\n\n\ttry {\n\t\tconst layoutedGraph = await elk.layout(elkGraph);\n\t\t\n\t\t// Extract results\n\t\tconst nodes: Array<{id: string, x: number, y: number}> = [];\n\t\tconst edges: Array<{id: string, vertices: Array<{x: number, y: number}>, label?: {x: number, y: number}}> = [];\n\n\n\t\t// Extract nodes from layout result\n\t\tconst extractNodes = (container: any, offsetX = 0, offsetY = 0) => {\n\t\t\tcontainer.children?.forEach((child: any) => {\n\t\t\t\tif (child.children) {\n\t\t\t\t\t// This is a group, recurse\n\t\t\t\t\textractNodes(child, offsetX + (child.x || 0), offsetY + (child.y || 0));\n\t\t\t\t} else {\n\t\t\t\t\t// This is a node\n\t\t\t\t\t// Adjust for the padding we added - ELK positioned based on padded size\n\t\t\t\t\t// So we need to shift by the padding amount to get the real center\n\t\t\t\t\tnodes.push({\n\t\t\t\t\t\tid: child.id,\n\t\t\t\t\t\tx: offsetX + (child.x || 0) + (child.width || 0) / 2,\n\t\t\t\t\t\ty: offsetY + (child.y || 0) + (child.height || 0) / 2\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t};\n\n\t\t// Process edges from ELK layout result to get routing information\n\t\tconst processEdgesFromELK = (container: any, offsetX = 0, offsetY = 0) => {\n\t\t\tcontainer.edges?.forEach((elkEdge: any) => {\n\t\t\t\tconst vertices: Array<{x: number, y: number}> = [];\n\t\t\t\tlet label: {x: number, y: number} | undefined;\n\n\t\t\t\t// Process edge sections to get bend points\n\t\t\t\tif (elkEdge.sections && elkEdge.sections.length > 0) {\n\t\t\t\t\telkEdge.sections.forEach((section: any) => {\n\t\t\t\t\t\t// Add start point if it exists\n\t\t\t\t\t\tif (section.startPoint) {\n\t\t\t\t\t\t\tvertices.push({\n\t\t\t\t\t\t\t\tx: offsetX + section.startPoint.x, \n\t\t\t\t\t\t\t\ty: offsetY + section.startPoint.y\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Add bend points (this is where ELK puts the routing vertices!)\n\t\t\t\t\t\tif (section.bendPoints && section.bendPoints.length > 0) {\n\t\t\t\t\t\t\tsection.bendPoints.forEach((bp: any) => {\n\t\t\t\t\t\t\t\tvertices.push({\n\t\t\t\t\t\t\t\t\tx: offsetX + bp.x, \n\t\t\t\t\t\t\t\t\ty: offsetY + bp.y\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Add end point if it exists\n\t\t\t\t\t\tif (section.endPoint) {\n\t\t\t\t\t\t\tvertices.push({\n\t\t\t\t\t\t\t\tx: offsetX + section.endPoint.x, \n\t\t\t\t\t\t\t\ty: offsetY + section.endPoint.y\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Extract ELK's calculated label positions (respect collision avoidance!)\n\t\t\t\tconst originalEdge = graph.edges.find(e => e.id === elkEdge.id);\n\t\t\t\tif (originalEdge?.label && originalEdge.label.trim()) {\n\t\t\t\t\tif (elkEdge.labels && elkEdge.labels.length > 0) {\n\t\t\t\t\t\tconst elkLabel = elkEdge.labels[0]; // Get first label\n\t\t\t\t\t\tif (elkLabel.x !== undefined && elkLabel.y !== undefined) {\n\t\t\t\t\t\t\tlabel = {\n\t\t\t\t\t\t\t\tx: offsetX + elkLabel.x + (elkLabel.width || 0) / 2, // Center of label\n\t\t\t\t\t\t\t\ty: offsetY + elkLabel.y + (elkLabel.height || 0) / 2\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (vertices.length >= 2) {\n\t\t\t\t\t\t// Fallback: use middle of edge if ELK didn't provide label position\n\t\t\t\t\t\tconst midIndex = Math.floor(vertices.length / 2);\n\t\t\t\t\t\tif (vertices.length % 2 === 0) {\n\t\t\t\t\t\t\tconst v1 = vertices[midIndex - 1];\n\t\t\t\t\t\t\tconst v2 = vertices[midIndex];\n\t\t\t\t\t\t\tlabel = { x: (v1.x + v2.x) / 2, y: (v1.y + v2.y) / 2 };\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlabel = vertices[midIndex];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\n\t\t\t\tedges.push({\n\t\t\t\t\tid: elkEdge.id,\n\t\t\t\t\tvertices,\n\t\t\t\t\tlabel\n\t\t\t\t});\n\t\t\t});\n\t\t\t\n\t\t\t// Also process edges in child containers (groups)\n\t\t\tcontainer.children?.forEach((child: any) => {\n\t\t\t\tif (child.edges && child.edges.length > 0) {\n\t\t\t\t\tprocessEdgesFromELK(child, offsetX + (child.x || 0), offsetY + (child.y || 0));\n\t\t\t\t}\n\t\t\t});\n\t\t};\n\n\n\t\textractNodes(layoutedGraph);\n\t\tprocessEdgesFromELK(layoutedGraph);\n\n\t\t// Normalize coordinates to start near (0,0) to prevent huge canvas sizes\n\t\t// while preserving relative positioning between elements\n\t\tif (nodes.length > 0) {\n\t\t\t// Find the minimum coordinates across all elements\n\t\t\tconst minX = Math.min(...nodes.map(n => n.x));\n\t\t\tconst minY = Math.min(...nodes.map(n => n.y));\n\t\t\t\n\t\t\t// Add some padding so content doesn't start at exact (0,0)\n\t\t\tconst padding = 50;\n\t\t\tconst offsetX = -minX + padding;\n\t\t\tconst offsetY = -minY + padding;\n\t\t\t\n\t\t\t// Normalize all node positions\n\t\t\tnodes.forEach(node => {\n\t\t\t\tnode.x += offsetX;\n\t\t\t\tnode.y += offsetY;\n\t\t\t});\n\t\t\t\n\t\t\t// Normalize all edge positions\n\t\t\tedges.forEach(edge => {\n\t\t\t\tedge.vertices.forEach(vertex => {\n\t\t\t\t\tvertex.x += offsetX;\n\t\t\t\t\tvertex.y += offsetY;\n\t\t\t\t});\n\t\t\t\tif (edge.label) {\n\t\t\t\t\tedge.label.x += offsetX;\n\t\t\t\t\tedge.label.y += offsetY;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn { nodes, edges };\n\n\t} catch (error) {\n\t\tconsole.warn('ELK layout failed, using fallback layout. Error:', error);\n\t\treturn createFallbackLayout(graph);\n\t}\n}\n\n// Simplified fallback layout\nfunction createFallbackLayout(graph: GraphData): {\n\tnodes: Array<{id: string, x: number, y: number}>,\n\tedges: Array<{id: string, vertices: Array<{x: number, y: number}>}>\n} {\n\tconst nodes: Array<{id: string, x: number, y: number}> = [];\n\tconst edges: Array<{id: string, vertices: Array<{x: number, y: number}>}> = [];\n\n\t// Simple grid layout for nodes\n\tlet x = 0, y = 0;\n\tconst spacing = 300;\n\tconst maxCols = Math.ceil(Math.sqrt(graph.nodesMap.size));\n\n\tlet col = 0;\n\tgraph.nodesMap.forEach(node => {\n\t\tnodes.push({\n\t\t\tid: node.id,\n\t\t\tx: x,\n\t\t\ty: y\n\t\t});\n\n\t\tcol++;\n\t\tif (col >= maxCols) {\n\t\t\tcol = 0;\n\t\t\tx = 0;\n\t\t\ty += spacing;\n\t\t} else {\n\t\t\tx += spacing;\n\t\t}\n\t});\n\n\t// Simple straight line edges\n\tgraph.edges.forEach(edge => {\n\t\tedges.push({\n\t\t\tid: edge.id,\n\t\t\tvertices: []\n\t\t});\n\t});\n\n\treturn { nodes, edges };\n}\n\nfunction isGroup(member: Node | Group): member is Group {\n\treturn \"nodes\" in member;\n}","\n import API from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import domAPI from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/styleDomAPI.js\";\n import insertFn from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/insertBySelector.js\";\n import setAttributes from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\";\n import insertStyleElement from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/insertStyleElement.js\";\n import styleTagTransformFn from \"!../node_modules/.pnpm/style-loader@4.0.0_webpack@5.109.2/node_modules/style-loader/dist/runtime/styleTagTransform.js\";\n import content, * as namedExport from \"!!../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/cjs.js!./style.css\";\n \n \n\nvar options = {};\n\noptions.styleTagTransform = styleTagTransformFn;\noptions.setAttributes = setAttributes;\noptions.insert = insertFn.bind(null, \"head\");\noptions.domAPI = domAPI;\noptions.insertStyleElement = insertStyleElement;\n\nvar update = API(content, options);\n\n\n\nexport * from \"!!../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/cjs.js!./style.css\";\n export default content && content.locals ? content.locals : undefined;\n","interface LiveReloadOptions {\n\tminDelay: number;\n\tmaxDelay: number;\n\thandshakeTimeout: number;\n}\n\ninterface LiveReloadMessage {\n\tcommand: string;\n\tprotocols?: string[];\n\tver?: string;\n\tpath?: string;\n}\n\nclass Timer {\n\tprivate readonly callback: () => void;\n\tprivate readonly handler: () => void;\n\tprivate running: boolean = false;\n\tprivate timeoutId: ReturnType | null = null;\n\n\tconstructor(callback: () => void) {\n\t\tthis.callback = callback;\n\t\tthis.handler = () => {\n\t\t\tthis.running = false;\n\t\t\tthis.timeoutId = null;\n\t\t\tthis.callback();\n\t\t};\n\t}\n\n\tstart(timeout: number): void {\n\t\tif (this.running) {\n\t\t\tthis.stop();\n\t\t}\n\t\tthis.timeoutId = setTimeout(this.handler, timeout);\n\t\tthis.running = true;\n\t}\n\n\tstop(): void {\n\t\tif (this.running && this.timeoutId !== null) {\n\t\t\tclearTimeout(this.timeoutId);\n\t\t\tthis.running = false;\n\t\t\tthis.timeoutId = null;\n\t\t}\n\t}\n\n\tisRunning(): boolean {\n\t\treturn this.running;\n\t}\n}\n\n/**\n * RefreshConnector implements the livereload protocol to listen for file changes via WebSocket\n */\nexport class RefreshConnector {\n\tprivate static readonly DEFAULT_OPTIONS: LiveReloadOptions = {\n\t\tminDelay: 1000,\n\t\tmaxDelay: 60000,\n\t\thandshakeTimeout: 5000\n\t};\n\n\tprivate static readonly LIVERELOAD_PROTOCOLS = [\n\t\t'http://livereload.com/protocols/official-9',\n\t\t'http://livereload.com/protocols/2.x-remote-control'\n\t];\n\n\tprivate readonly uri: string;\n\tprivate readonly options: LiveReloadOptions;\n\tprivate readonly fileChangeHandler: (path: string) => void;\n\t\n\tprivate socket: WebSocket | null = null;\n\tprivate nextDelay: number;\n\tprivate connectionDesired: boolean = false;\n\tprivate disconnectionReason: string = '';\n\t\n\tprivate readonly handshakeTimeout: Timer;\n\tprivate readonly reconnectTimer: Timer;\n\n\tconstructor(fileChangeHandler: (file: string) => void, options?: Partial) {\n\t\tthis.fileChangeHandler = fileChangeHandler;\n\t\tthis.options = { ...RefreshConnector.DEFAULT_OPTIONS, ...options };\n\t\tthis.uri = 'ws://localhost:35729/livereload';\n\t\tthis.nextDelay = this.options.minDelay;\n\n\t\tthis.handshakeTimeout = new Timer(() => this.handleHandshakeTimeout());\n\t\tthis.reconnectTimer = new Timer(() => this.attemptReconnection());\n\t}\n\n\tconnect(): void {\n\t\tthis.connectionDesired = true;\n\n\t\tif (this.isSocketConnected()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.prepareForConnection();\n\t\tthis.createWebSocket();\n\t}\n\n\tdisconnect(): void {\n\t\tthis.connectionDesired = false;\n\t\tthis.reconnectTimer.stop();\n\n\t\tif (this.isSocketConnected()) {\n\t\t\tthis.disconnectionReason = 'manual';\n\t\t\tthis.socket!.close();\n\t\t}\n\t}\n\n\tprivate isSocketConnected(): boolean {\n\t\treturn this.socket !== null && this.socket.readyState === WebSocket.OPEN;\n\t}\n\n\tprivate prepareForConnection(): void {\n\t\tthis.reconnectTimer.stop();\n\t\tthis.disconnectionReason = 'cannot-connect';\n\t}\n\n\tprivate createWebSocket(): void {\n\t\tthis.socket = new WebSocket(this.uri);\n\t\tthis.socket.onopen = () => this.handleOpen();\n\t\tthis.socket.onclose = () => this.handleClose();\n\t\tthis.socket.onmessage = (event) => this.handleMessage(event);\n\t\tthis.socket.onerror = () => this.handleError();\n\t}\n\n\tprivate handleOpen(): void {\n\t\tthis.disconnectionReason = 'handshake-failed';\n\t\tthis.startHandshake();\n\t}\n\n\tprivate handleClose(): void {\n\t\tconsole.log(`WebSocket disconnected: ${this.disconnectionReason}. Retry in ${this.nextDelay}ms`);\n\t\tthis.scheduleReconnection();\n\t}\n\n\tprivate handleMessage(event: MessageEvent): void {\n\t\ttry {\n\t\t\tconst message: LiveReloadMessage = JSON.parse(event.data);\n\t\t\tthis.processMessage(message);\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to parse WebSocket message:', error);\n\t\t}\n\t}\n\n\tprivate handleError(): void {\n\t\t// Error handling is done in onclose\n\t}\n\n\tprivate processMessage(message: LiveReloadMessage): void {\n\t\tswitch (message.command) {\n\t\t\tcase 'hello':\n\t\t\t\tthis.handleHelloMessage();\n\t\t\t\tbreak;\n\t\t\tcase 'reload':\n\t\t\t\tthis.handleReloadMessage(message);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tconsole.log('Unknown WebSocket message received:', message);\n\t\t}\n\t}\n\n\tprivate handleHelloMessage(): void {\n\t\tthis.handshakeTimeout.stop();\n\t\tthis.nextDelay = this.options.minDelay;\n\t}\n\n\tprivate handleReloadMessage(message: LiveReloadMessage): void {\n\t\t// The livereload server closes connection after sending reload\n\t\t// We must reconnect\n\t\tthis.reconnectTimer.stop();\n\t\tthis.connect();\n\n\t\tif (message.path) {\n\t\t\tthis.fileChangeHandler(message.path);\n\t\t}\n\t}\n\n\tprivate startHandshake(): void {\n\t\tconst helloMessage: LiveReloadMessage = {\n\t\t\tcommand: 'hello',\n\t\t\tprotocols: RefreshConnector.LIVERELOAD_PROTOCOLS,\n\t\t\tver: '3.3.1'\n\t\t};\n\t\t\n\t\tthis.sendCommand(helloMessage);\n\t\tthis.handshakeTimeout.start(this.options.handshakeTimeout);\n\t}\n\n\tprivate handleHandshakeTimeout(): void {\n\t\tif (this.isSocketConnected()) {\n\t\t\tthis.disconnectionReason = 'handshake-timeout';\n\t\t\tthis.socket!.close();\n\t\t}\n\t}\n\n\tprivate attemptReconnection(): void {\n\t\tif (this.connectionDesired) {\n\t\t\tthis.connect();\n\t\t}\n\t}\n\n\tprivate scheduleReconnection(): void {\n\t\tif (!this.connectionDesired) {\n\t\t\treturn; // Don't reconnect after manual disconnection\n\t\t}\n\n\t\tif (!this.reconnectTimer.isRunning()) {\n\t\t\tthis.reconnectTimer.start(this.nextDelay);\n\t\t\tthis.nextDelay = Math.min(this.options.maxDelay, this.nextDelay * 2);\n\t\t}\n\t}\n\n\tprivate sendCommand(command: LiveReloadMessage): void {\n\t\tif (this.isSocketConnected()) {\n\t\t\tthis.socket!.send(JSON.stringify(command));\n\t\t}\n\t}\n}","import { createRoot } from 'react-dom/client';\nimport React, { Suspense, lazy, useEffect, useState } from 'react';\nimport { refreshGraph } from \"./Root\";\nimport './style.css';\nimport '@fortawesome/fontawesome-free/css/all.css';\nimport { RefreshConnector } from \"./websocket\";\n\nconst Root = lazy(() => import('./Root').then(module => ({ default: module.Root })));\n\ninterface ModelData {\n\tmodel: any;\n\tlayout: any;\n}\n\ninterface AppState {\n\tdata: ModelData | null;\n\terror: string | null;\n\tloading: boolean;\n}\n\nconst App: React.FC = () => {\n\tconst [state, setState] = useState({\n\t\tdata: null,\n\t\terror: null,\n\t\tloading: true\n\t});\n\n\tconst loadData = async () => {\n\t\tsetState(prev => ({ ...prev, loading: true, error: null }));\n\t\t\n\t\ttry {\n\t\t\tconst [modelResponse, layoutResponse] = await Promise.all([\n\t\t\t\tfetch('data/model.json'),\n\t\t\t\tfetch('data/layout.json')\n\t\t\t]);\n\n\t\t\tif (!modelResponse.ok) {\n\t\t\t\tthrow new Error(`Failed to fetch model: ${modelResponse.statusText}`);\n\t\t\t}\n\t\t\t\n\t\t\tif (!layoutResponse.ok) {\n\t\t\t\tthrow new Error(`Failed to fetch layout: ${layoutResponse.statusText}`);\n\t\t\t}\n\n\t\t\tconst [model, layout] = await Promise.all([\n\t\t\t\tmodelResponse.json(),\n\t\t\t\tlayoutResponse.json()\n\t\t\t]);\n\n\t\t\tsetState({\n\t\t\t\tdata: { model, layout },\n\t\t\t\terror: null,\n\t\t\t\tloading: false\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to load data:', error);\n\t\t\tsetState({\n\t\t\t\tdata: null,\n\t\t\t\terror: error instanceof Error ? error.message : 'Unknown error occurred',\n\t\t\t\tloading: false\n\t\t\t});\n\t\t}\n\t};\n\n\tconst handleFileChange = (path: string) => {\n\t\tif (path.endsWith('.svg')) {\n\t\t\treturn; // Ignore SVG changes to avoid infinite loops\n\t\t}\n\t\t\n\t\tconsole.log('File changed:', path);\n\t\trefreshGraph();\n\t\tloadData();\n\t};\n\n\tuseEffect(() => {\n\t\t// Setup refresh connector\n\t\tconst refreshConnector = new RefreshConnector(handleFileChange);\n\t\trefreshConnector.connect();\n\n\t\t// Initial data load\n\t\tloadData();\n\n\t\t// Cleanup function\n\t\treturn () => {\n\t\t\t// RefreshConnector cleanup would go here if it had a disconnect method\n\t\t};\n\t}, []);\n\n\tif (state.loading) {\n\t\treturn ;\n\t}\n\n\tif (state.error) {\n\t\treturn ;\n\t}\n\n\tif (!state.data) {\n\t\treturn ;\n\t}\n\n\treturn (\n\t\t}>\n\t\t\t\n\t\t\n\t);\n};\n\nconst LoadingScreen: React.FC = () => (\n\t
\n\t\t
Loading...
\n\t
\n);\n\nconst ErrorScreen: React.FC<{ error: string; onRetry: () => void }> = ({ error, onRetry }) => (\n\t
\n\t\t

Error loading application

\n\t\t

{error}

\n\t\t\n\t
\n);\n\n// Initialize the application\nconst container = document.getElementById('root');\nif (!container) {\n\tthrow new Error('Root container not found');\n}\n\nconst root = createRoot(container);\nroot.render();","import React, {FC} from \"react\";\nimport { isMac, getModifierKeyName, getModifierKeyProperty } from './utils/platform';\n\ninterface Combination {\n\tctrl?: boolean;\n\tshift?: boolean;\n\talt?: boolean;\n\twheel?: boolean\n\tkey?: string;\n\tclick?: boolean;\n}\n\ninterface Shortcut {\n\tid: string,\n\thelp: string,\n\tcombinations: Combination[]\n}\n\nexport const SAVE = 'save'\n\nexport const UNDO = 'undo'\nexport const REDO = 'redo'\nexport const ADD_VERTEX = 'add-vertex'\nexport const ADD_LABEL_VERTEX = 'add-label-vertex'\nexport const DEL_VERTEX = 'del-vertex'\n\nexport const ZOOM_IN = 'zoom-in'\nexport const ZOOM_OUT = 'zoom-out'\nexport const ZOOM_FIT = 'zoom-fit'\nexport const ZOOM_100 = 'zoom-100'\n\nexport const SELECT_ALL = 'select-all'\nexport const DESELECT = 'deselect'\n\nexport const MOVE_LEFT = 'move-left'\nexport const MOVE_RIGHT = 'move-right'\nexport const MOVE_UP = 'move-up'\nexport const MOVE_DOWN = 'move-down'\nexport const MOVE_LEFT_FINE = 'move-left-fine'\nexport const MOVE_RIGHT_FINE = 'move-right-fine'\nexport const MOVE_UP_FINE = 'move-up-fine'\nexport const MOVE_DOWN_FINE = 'move-down-fine'\n\nexport const PAN_VIEW = 'pan-view'\nexport const SELECT_ELEMENT = 'select-element'\nexport const MULTI_SELECT = 'multi-select'\nexport const BOX_SELECT = 'box-select'\nexport const MOVE_ELEMENTS = 'move-elements'\n\nexport const HELP = 'help'\n\n// New shortcuts for toolbar buttons\nexport const TOGGLE_DRAG_MODE = 'toggle_drag_mode'\nexport const ALIGN_HORIZONTAL = 'align_horizontal'\nexport const ALIGN_VERTICAL = 'align_vertical'\nexport const DISTRIBUTE_HORIZONTAL = 'distribute_horizontal'\nexport const DISTRIBUTE_VERTICAL = 'distribute_vertical'\nexport const AUTO_LAYOUT = 'auto_layout'\nexport const RESET_POSITION = 'reset_position'\nexport const TOGGLE_GRID = 'toggle_grid'\nexport const TOGGLE_SNAP_TO_GRID = 'toggle_snap_to_grid'\nexport const SNAP_ALL_TO_GRID = 'snap_all_to_grid'\n\nconst shortcuts: { name: string; list: Shortcut[] }[] = [\n\t{\n\t\tname: 'Help',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: HELP,\n\t\t\t\thelp: 'Show/hide this help',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{key: '?', shift: true},\n\t\t\t\t\t{key: 'F1', shift: true}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'File',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: SAVE,\n\t\t\t\thelp: 'Save',\n\t\t\t\tcombinations: [{key: 's', ctrl: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'History',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: UNDO,\n\t\t\t\thelp: 'Undo',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, key: 'z'},\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: REDO,\n\t\t\t\thelp: 'Redo',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, shift: true, key: 'z'},\n\t\t\t\t\t{ctrl: true, key: 'y'},\n\t\t\t\t]\n\t\t\t}\n\n\t\t],\n\t},\n\t{\n\t\tname: 'Relationship editing',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: ADD_VERTEX,\n\t\t\t\thelp: 'Add relationship vertex',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{alt: true, click: true},\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ADD_LABEL_VERTEX,\n\t\t\t\thelp: 'Add label anchor relationship vertex',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{alt: true, shift: true, click: true},\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DEL_VERTEX,\n\t\t\t\thelp: 'Remove relationship vertex',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{key: 'DELETE'},\n\t\t\t\t\t{key: 'BACKSPACE'}\n\t\t\t\t]\n\t\t\t},\n\t\t]\n\t},\n\t{\n\t\tname: 'Zoom',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: ZOOM_IN,\n\t\t\t\thelp: 'Zoom in',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, key: '='}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ZOOM_OUT,\n\t\t\t\thelp: 'Zoom out',\n\t\t\t\tcombinations: [\n\t\t\t\t\t{ctrl: true, key: '-'}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ZOOM_FIT,\n\t\t\t\thelp: 'Zoom - fit',\n\t\t\t\tcombinations: [{ctrl: true, key: '9'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ZOOM_100,\n\t\t\t\thelp: 'Zoom 100%',\n\t\t\t\tcombinations: [{ctrl: true, key: '0'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'wheel_zoom',\n\t\t\t\thelp: 'Zoom in/out with mouse wheel',\n\t\t\t\tcombinations: [{wheel: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Mouse Interactions',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: PAN_VIEW,\n\t\t\t\thelp: 'Pan view (drag empty space)',\n\t\t\t\tcombinations: [{click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: SELECT_ELEMENT,\n\t\t\t\thelp: 'Select element',\n\t\t\t\tcombinations: [{click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MULTI_SELECT,\n\t\t\t\thelp: 'Add/remove from selection',\n\t\t\t\tcombinations: [{shift: true, click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: BOX_SELECT,\n\t\t\t\thelp: 'Box selection (drag empty space)',\n\t\t\t\tcombinations: [{shift: true, click: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_ELEMENTS,\n\t\t\t\thelp: 'Move selected elements',\n\t\t\t\tcombinations: [{click: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Select',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: SELECT_ALL,\n\t\t\t\thelp: 'Select All',\n\t\t\t\tcombinations: [{ctrl: true, key: 'a'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DESELECT,\n\t\t\t\thelp: 'Deselect',\n\t\t\t\tcombinations: [{key: 'ESC'}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Move',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: MOVE_UP,\n\t\t\t\thelp: 'Move up (grid increment)',\n\t\t\t\tcombinations: [{key: 'UP'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_UP_FINE,\n\t\t\t\thelp: 'Move up (1 pixel)',\n\t\t\t\tcombinations: [{key: 'UP', shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_RIGHT,\n\t\t\t\thelp: 'Move right (grid increment)',\n\t\t\t\tcombinations: [{key: 'RIGHT'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_RIGHT_FINE,\n\t\t\t\thelp: 'Move right (1 pixel)',\n\t\t\t\tcombinations: [{key: 'RIGHT', shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_DOWN,\n\t\t\t\thelp: 'Move down (grid increment)',\n\t\t\t\tcombinations: [{key: 'DOWN'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_DOWN_FINE,\n\t\t\t\thelp: 'Move down (1 pixel)',\n\t\t\t\tcombinations: [{key: 'DOWN', shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_LEFT,\n\t\t\t\thelp: 'Move left (grid increment)',\n\t\t\t\tcombinations: [{key: 'LEFT'}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: MOVE_LEFT_FINE,\n\t\t\t\thelp: 'Move left (1 pixel)',\n\t\t\t\tcombinations: [{key: 'LEFT', shift: true}]\n\t\t\t},\n\t\t]\n\t},\n\t\t\t{\n\t\t\tname: 'View',\n\t\t\tlist: [\n\t\t\t\t{\n\t\t\t\t\tid: TOGGLE_DRAG_MODE,\n\t\t\t\t\thelp: 'Toggle between pan and select mode',\n\t\t\t\t\tcombinations: [{key: 't'}]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: RESET_POSITION,\n\t\t\t\t\thelp: 'Reset position and view',\n\t\t\t\t\tcombinations: [{key: 'Home', ctrl: true}]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t{\n\t\tname: 'Alignment',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: ALIGN_HORIZONTAL,\n\t\t\t\thelp: 'Align selected elements horizontally',\n\t\t\t\tcombinations: [{key: 'h', ctrl: true, shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: ALIGN_VERTICAL,\n\t\t\t\thelp: 'Align selected elements vertically',\n\t\t\t\tcombinations: [{key: 'a', ctrl: true, shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DISTRIBUTE_HORIZONTAL,\n\t\t\t\thelp: 'Distribute selected elements horizontally',\n\t\t\t\tcombinations: [{key: 'h', ctrl: true, alt: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: DISTRIBUTE_VERTICAL,\n\t\t\t\thelp: 'Distribute selected elements vertically',\n\t\t\t\tcombinations: [{key: 'v', ctrl: true, alt: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Layout',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: AUTO_LAYOUT,\n\t\t\t\thelp: 'Auto layout all elements',\n\t\t\t\tcombinations: [{key: 'l', ctrl: true}]\n\t\t\t}\n\t\t]\n\t},\n\t{\n\t\tname: 'Grid',\n\t\tlist: [\n\t\t\t{\n\t\t\t\tid: TOGGLE_GRID,\n\t\t\t\thelp: 'Toggle grid visibility',\n\t\t\t\tcombinations: [{key: 'g', ctrl: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: TOGGLE_SNAP_TO_GRID,\n\t\t\t\thelp: 'Toggle snap to grid',\n\t\t\t\tcombinations: [{key: 'g', ctrl: true, shift: true}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: SNAP_ALL_TO_GRID,\n\t\t\t\thelp: 'Snap all elements to grid',\n\t\t\t\tcombinations: [{key: 'g', ctrl: true, alt: true}]\n\t\t\t}\n\t\t]\n\t}\n]\n\nconst shortcutMap = shortcuts\n\t.reduce((lst, s) => lst.concat(s.list), [] as Shortcut[])\n\t.reduce<{ [k: string]: Shortcut }>((map, s) => {\n\t\tmap[s.id] = s;\n\t\treturn map\n\t}, {})\n\nconst checkKey = (e: KeyboardEvent | MouseEvent, shortcut: Shortcut, click: boolean, wheel: boolean) => {\n\treturn shortcut.combinations.some(c => {\n\t\tif (Boolean(c.shift) != e.shiftKey) return false\n\t\t// Use platform-appropriate modifier key\n\t\tif (c.ctrl) {\n\t\t\tconst modifierKey = getModifierKeyProperty(e as KeyboardEvent);\n\t\t\tif (!modifierKey) return false;\n\t\t}\n\t\tif (Boolean(c.alt) != e.altKey) return false\n\t\tif (click) return c.click\n\t\tif (wheel) return c.wheel\n\t\tif (c.key) {\n\t\t\tconst ke = e as KeyboardEvent\n\t\t\tif (c.key == 'DELETE') return ke.key == 'Delete'\n\t\t\tif (c.key == 'BACKSPACE') return ke.key == 'Backspace'\n\t\t\tif (c.key == 'ESC') return ke.key == 'Escape'\n\t\t\tif (c.key == 'UP') return ke.key == 'ArrowUp'\n\t\t\tif (c.key == 'DOWN') return ke.key == 'ArrowDown'\n\t\t\tif (c.key == 'LEFT') return ke.key == 'ArrowLeft'\n\t\t\tif (c.key == 'RIGHT') return ke.key == 'ArrowRight'\n\t\t\treturn c.key && ke.key && c.key.toLowerCase() == ke.key.toLowerCase()\n\t\t}\n\t\treturn false\n\t})\n}\n\nexport const findShortcut = (e: KeyboardEvent | MouseEvent, click = false, wheel = false) => {\n\t// Find all matching shortcuts\n\tconst matches = Object.keys(shortcutMap).filter(k => checkKey(e, shortcutMap[k], click, wheel))\n\t\n\tif (matches.length === 0) return undefined\n\tif (matches.length === 1) return matches[0]\n\t\n\t// If multiple matches, prefer the one with more specific modifiers\n\t// Sort by number of modifiers (shift, ctrl, alt) in descending order\n\tconst sortedMatches = matches.sort((a, b) => {\n\t\tconst aShortcut = shortcutMap[a]\n\t\tconst bShortcut = shortcutMap[b]\n\t\t\n\t\tconst aModifiers = aShortcut.combinations[0]\n\t\tconst bModifiers = bShortcut.combinations[0]\n\t\t\n\t\tconst aCount = (aModifiers.shift ? 1 : 0) + (aModifiers.ctrl ? 1 : 0) + (aModifiers.alt ? 1 : 0)\n\t\tconst bCount = (bModifiers.shift ? 1 : 0) + (bModifiers.ctrl ? 1 : 0) + (bModifiers.alt ? 1 : 0)\n\t\t\n\t\treturn bCount - aCount // Descending order (more modifiers first)\n\t})\n\t\n\treturn sortedMatches[0]\n}\n\nconst comboText = (c: Combination) => {\n\treturn [\n\t\tc.ctrl && getModifierKeyName().toUpperCase(),\n\t\tc.shift && 'SHIFT',\n\t\tc.alt && 'ALT',\n\t\tc.key && (c.key.length > 1 ? c.key : `\"${c.key.toUpperCase()}\"`),\n\t\tc.click && 'CLICK',\n\t\tc.wheel && 'WHEEL'\n\t].filter(Boolean).join(' + ')\n}\n\nexport const Help: FC = () => {\n\treturn
\n\t\t

Shortcuts

\n\t\t\n\t\t\t\n\t\t\t{\n\t\t\t\tshortcuts.map(section => <>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{section.list.map(item => \n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t)\n\t\t\t}\n\t\t\t\n\t\t
{section.name}
{item.combinations.map(comboText).join(', ')}{item.help}
\n\t
\n}\n","/**\n * Robust platform detection utilities\n */\n\n/**\n * Detects if the user is on a Mac platform using multiple detection methods\n * for maximum compatibility across browsers and future-proofing.\n */\nexport const isMac = (): boolean => {\n if (typeof navigator === 'undefined') return false;\n \n // Method 1: Check userAgentData (modern browsers, most reliable)\n if ('userAgentData' in navigator && (navigator as any).userAgentData) {\n const platform = (navigator as any).userAgentData.platform;\n if (platform && platform.toLowerCase().includes('mac')) {\n return true;\n }\n }\n \n // Method 2: Check userAgent string (widely supported)\n const userAgent = navigator.userAgent.toLowerCase();\n if (userAgent.includes('mac os') || userAgent.includes('macintosh')) {\n return true;\n }\n \n // Method 3: Check platform (fallback, deprecated but still widely supported)\n if (navigator.platform) {\n const platform = navigator.platform.toLowerCase();\n if (platform.includes('mac') || platform.includes('darwin')) {\n return true;\n }\n }\n \n // Method 4: Check for Mac-specific features as additional validation\n try {\n const testEvent = new KeyboardEvent('keydown', { metaKey: true });\n if (testEvent.metaKey !== undefined) {\n // Additional heuristic: Mac typically has different key layouts\n return /mac|darwin|os x/i.test(navigator.userAgent);\n }\n } catch (e) {\n // Ignore errors in older browsers\n }\n \n return false;\n};\n\n/**\n * Gets the appropriate modifier key name for the current platform\n */\nexport const getModifierKeyName = (): string => {\n return isMac() ? 'Cmd' : 'Ctrl';\n};\n\n/**\n * Gets the appropriate modifier key property for keyboard events\n */\nexport const getModifierKeyProperty = (event: KeyboardEvent): boolean => {\n return isMac() ? event.metaKey : event.ctrlKey;\n};\n\n/**\n * Gets the appropriate Alt key name for the current platform\n */\nexport const getAltKeyName = (): string => {\n return isMac() ? 'Option' : 'Alt';\n}; ","// Imports\nimport ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from \"../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/runtime/sourceMaps.js\";\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/.pnpm/css-loader@7.1.4_webpack@5.109.2/node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `html, body, #root {\n height: 100%;\n}\n\nbody {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #666;\n margin: 0;\n}\n\n#root {\n display: flex;\n flex-direction: column;\n}\n#root > div.graph {\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 10px;\n background-color: #f0f0f0;\n border-bottom: 1px solid #cccccc;\n}\n\n.toolbar > div {\n display: flex;\n align-items: center;\n}\n\n.toolbar button {\n padding: 5px 8px;\n margin: 0 2px;\n cursor: pointer;\n}\n\n.toolbar button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* Drag mode toggle button styles */\n.toolbar button.mode-toggle {\n position: relative;\n border: 1px solid #8f9fc9;\n width: 40px;\n min-height: 28px;\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\n border-color: #8f9fc9;\n color: white;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n}\n\n.toolbar button.mode-toggle:hover {\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\n border-color: #abb8db;\n}\n\n.toolbar button.mode-toggle.select-mode:active {\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\n}\n\n/* Pan mode and active toggle - darker blue */\n.toolbar button.mode-toggle.pan-mode,\n.toolbar button.active-toggle {\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n.toolbar button.mode-toggle.pan-mode:hover,\n.toolbar button.active-toggle:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\n.toolbar button.mode-toggle.pan-mode:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\n}\n\n/* Toggle buttons when inactive - gray styling */\n.toolbar button.inactive-toggle {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n.toolbar button.inactive-toggle:hover {\n background: #b1b1b1;\n}\n\n.toolbar button.inactive-toggle:active {\n background: #a1a1a1;\n}\n\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\n background: #c1c1c1;\n color: #999;\n border-color: #c1c1c1;\n box-shadow: none;\n}\n\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\n.toolbar button .fas {\n font-size: 1em;\n vertical-align: middle;\n}\n\n/* Zoom percentage display */\n.toolbar button.zoom-display {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\n padding: 6px 8px;\n text-align: center;\n}\n\n.toolbar-group {\n display: flex;\n align-items: center;\n margin-right: 25px; /* Large space between groups */\n}\n\n.toolbar-group:last-child {\n margin-right: 0; /* Remove right margin from the last group (help button) */\n}\n\nbutton {\n border: none;\n background: linear-gradient(to bottom, #4a90e2, #357abd);\n border-color: #2968a3;\n border-radius: 3px;\n padding: 6px 10px;\n color: #fff;\n outline: none;\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\n margin-right: 5px;\n min-width: 32px;\n min-height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n line-height: 1;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n position: relative;\n}\n\nbutton:hover {\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\n}\n\nbutton:active {\n background: linear-gradient(to bottom, #357abd, #2968a3);\n}\n\nbutton:last-child {\n margin-right: 0;\n}\n\n/* Save button when no changes - gray */\nbutton.action {\n background: #c1c1c1;\n color: #999;\n}\n\nbutton.action:active {\n background: #b1b1b1;\n}\n\n/* Save button when there are changes - orange */\nbutton.grp {\n background: linear-gradient(to bottom, #ee9564, #de7d48);\n margin-right: 0;\n}\n\nbutton.grp:active {\n background: #d67540;\n}\n\n/* Auto-arrange button with AI purple to blue gradient */\nbutton.auto-arrange {\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\n border-color: #4a4c93;\n}\n\nbutton.auto-arrange:hover {\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\n border-color: #5a5ca3;\n}\n\nbutton.auto-arrange:active {\n background: linear-gradient(135deg, #593B83, #357abd);\n border-color: #3a3c83;\n}\n\nselect {\n border: 1px solid #ccc;\n background: white;\n border-radius: 3px;\n padding: 3px 7px;\n color: #666;\n outline: none;\n margin-right: 5px;\n font-size: 12px;\n}\n\nselect:disabled {\n background: #f5f5f5;\n color: #999;\n}\n\nbutton:disabled {\n background: #c1c1c1;\n color: #999;\n}\n\n#root > div > svg {\n position: absolute;\n user-select: none;\n}\n\n\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\n stroke: #29c229;\n}\n.edge .v-dot {\n fill: transparent;\n stroke: transparent;\n stroke-width: 3px;\n cursor: pointer;\n transition: stroke 0.15s ease;\n}\n.edge .v-dot:hover {\n stroke: #999;\n fill: rgba(153, 153, 153, 0.1);\n}\n.edge .v-dot.selected {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.1);\n}\n.edge .v-dot.selected:hover {\n stroke: #29c229;\n fill: rgba(41, 194, 41, 0.2);\n}\n.edge .v-dot.auto.selected {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.1);\n}\n.edge .v-dot.auto.selected:hover {\n stroke: #777;\n fill: rgba(119, 119, 119, 0.2);\n}\ncircle#prj {\n fill: none;\n stroke: #777;\n}\n\n.nodeShadow {\n fill: none;\n stroke-width: 4px;\n stroke: rgba(0, 0, 0, 0.13);\n}\n\ng.node {\n user-select: none;\n cursor: default;\n}\n\ng.node.linked {\n cursor: pointer;\n}\n\ng.node text {\n pointer-events: none;\n}\n\n.icon {\n fill: #aaa;\n stroke: #fff;\n}\n#icon-cube {\n fill: #aaa;\n}\n\n/* Ensure all button icons are uncolored */\nbutton .icon,\nbutton svg,\nbutton path {\n fill: currentColor !important;\n stroke: none !important;\n}\n\n/* Font Awesome icon styling in buttons */\nbutton i {\n font-size: 12px;\n color: inherit;\n}\n\nrect.elastic {\n pointer-events: none;\n stroke: none;\n fill: #3bd8281f;\n display: none;\n}\nrect.elastic.on {\n display: block;\n}\n\n.popover {\n position: absolute;\n top: 50px;\n bottom: 10px;\n overflow: auto;\n right: 10px;\n background: ghostwhite;\n padding: 30px;\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\n border: solid 1px #eee;\n}\n\n.popover th {\n text-align: left;\n padding: 20px 0px 10px;\n}\n.popover td {\n padding-right: 20px;\n font-size: 14px;\n}\n\n/* Simple tooltip system with smart positioning */\n[data-tooltip] {\n position: relative;\n}\n\n[data-tooltip]:hover::after {\n content: attr(data-tooltip);\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.9);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px !important;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\n font-weight: normal !important;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n margin-top: 5px;\n animation: tooltip-appear 0.1s ease-out;\n min-width: 120px;\n max-width: calc(100vw - 20px);\n box-sizing: border-box;\n}\n\n[data-tooltip]:hover::before {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-bottom-color: rgba(0, 0, 0, 0.9);\n z-index: 1000;\n pointer-events: none;\n margin-top: 1px;\n animation: tooltip-appear 0.1s ease-out;\n}\n\n/* Special handling for rightmost elements that might overflow */\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 0;\n transform: none;\n}\n\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\n /* Apply to last 2 toolbar groups (save and help buttons) */\n left: auto;\n right: 16px;\n transform: none;\n}\n\n@keyframes tooltip-appear {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(-5px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\nselect {\n position: relative;\n}\n\n/* Robot shape internal elements - inherit stroke from parent node */\n.node .robot-eye-socket {\n fill: none;\n stroke: inherit;\n stroke-width: 2;\n}\n\n.node .robot-eye {\n fill: currentColor;\n stroke: none;\n}\n\n.node .robot-mouth {\n stroke: inherit;\n}\n\n.node .robot-antenna {\n stroke: inherit;\n}\n\n.node .robot-antenna-ball {\n fill: currentColor;\n stroke: inherit;\n stroke-width: 1.5;\n}\n\n.node .robot-panel {\n stroke: inherit;\n}\n\n.node .robot-indicator {\n fill: currentColor;\n stroke: none;\n}`, \"\",{\"version\":3,\"sources\":[\"webpack://./src/style.css\"],\"names\":[],\"mappings\":\"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\",\"sourcesContent\":[\"html, body, #root {\\n height: 100%;\\n}\\n\\nbody {\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\\n color: #666;\\n margin: 0;\\n}\\n\\n#root {\\n display: flex;\\n flex-direction: column;\\n}\\n#root > div.graph {\\n flex: 1;\\n overflow: auto;\\n position: relative;\\n}\\n\\n.toolbar {\\n display: flex;\\n justify-content: space-between;\\n align-items: center;\\n padding: 4px 10px;\\n background-color: #f0f0f0;\\n border-bottom: 1px solid #cccccc;\\n}\\n\\n.toolbar > div {\\n display: flex;\\n align-items: center;\\n}\\n\\n.toolbar button {\\n padding: 5px 8px;\\n margin: 0 2px;\\n cursor: pointer;\\n}\\n\\n.toolbar button:disabled {\\n opacity: 0.5;\\n cursor: not-allowed;\\n}\\n\\n/* Drag mode toggle button styles */\\n.toolbar button.mode-toggle {\\n position: relative;\\n border: 1px solid #8f9fc9;\\n width: 40px;\\n min-height: 28px;\\n background: linear-gradient(to bottom, #abb8db, #8f9fc9);\\n border-color: #8f9fc9;\\n color: white;\\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\\n}\\n\\n.toolbar button.mode-toggle:hover {\\n background: linear-gradient(to bottom, #bcc7e0, #abb8db);\\n border-color: #abb8db;\\n}\\n\\n.toolbar button.mode-toggle.select-mode:active {\\n background: linear-gradient(to bottom, #8f9fc9, #7a8bb5);\\n}\\n\\n/* Pan mode and active toggle - darker blue */\\n.toolbar button.mode-toggle.pan-mode,\\n.toolbar button.active-toggle {\\n background: linear-gradient(to bottom, #4a90e2, #357abd);\\n border-color: #2968a3;\\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\\n}\\n\\n.toolbar button.mode-toggle.pan-mode:hover,\\n.toolbar button.active-toggle:hover {\\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\\n}\\n\\n.toolbar button.mode-toggle.pan-mode:active {\\n background: linear-gradient(to bottom, #357abd, #2968a3);\\n box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);\\n}\\n\\n/* Toggle buttons when inactive - gray styling */\\n.toolbar button.inactive-toggle {\\n background: #c1c1c1;\\n color: #999;\\n border-color: #c1c1c1;\\n box-shadow: none;\\n}\\n\\n.toolbar button.inactive-toggle:hover {\\n background: #b1b1b1;\\n}\\n\\n.toolbar button.inactive-toggle:active {\\n background: #a1a1a1;\\n}\\n\\n/* Toggle buttons when disabled and not active - gray like other disabled buttons */\\n.toolbar button.mode-toggle:disabled:not(.pan-mode):not(.active-toggle),\\n.toolbar button.active-toggle:disabled:not(.active-toggle) {\\n background: #c1c1c1;\\n color: #999;\\n border-color: #c1c1c1;\\n box-shadow: none;\\n}\\n\\n/* Ensure Font Awesome icons are sized appropriately if not already handled */\\n.toolbar button .fas {\\n font-size: 1em;\\n vertical-align: middle;\\n}\\n\\n/* Zoom percentage display */\\n.toolbar button.zoom-display {\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\\n font-size: 11px;\\n font-weight: 600;\\n font-variant-numeric: tabular-nums;\\n min-width: 50px; /* Wider to prevent size change between 99% and 100% */\\n padding: 6px 8px;\\n text-align: center;\\n}\\n\\n.toolbar-group {\\n display: flex;\\n align-items: center;\\n margin-right: 25px; /* Large space between groups */\\n}\\n\\n.toolbar-group:last-child {\\n margin-right: 0; /* Remove right margin from the last group (help button) */\\n}\\n\\nbutton {\\n border: none;\\n background: linear-gradient(to bottom, #4a90e2, #357abd);\\n border-color: #2968a3;\\n border-radius: 3px;\\n padding: 6px 10px;\\n color: #fff;\\n outline: none;\\n box-shadow: 2px 2px 2px rgba(0, 0, 0, .2);\\n margin-right: 5px;\\n min-width: 32px;\\n min-height: 28px;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n font-size: 14px;\\n line-height: 1;\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\\n position: relative;\\n}\\n\\nbutton:hover {\\n background: linear-gradient(to bottom, #5ba0f2, #4585c7);\\n}\\n\\nbutton:active {\\n background: linear-gradient(to bottom, #357abd, #2968a3);\\n}\\n\\nbutton:last-child {\\n margin-right: 0;\\n}\\n\\n/* Save button when no changes - gray */\\nbutton.action {\\n background: #c1c1c1;\\n color: #999;\\n}\\n\\nbutton.action:active {\\n background: #b1b1b1;\\n}\\n\\n/* Save button when there are changes - orange */\\nbutton.grp {\\n background: linear-gradient(to bottom, #ee9564, #de7d48);\\n margin-right: 0;\\n}\\n\\nbutton.grp:active {\\n background: #d67540;\\n}\\n\\n/* Auto-arrange button with AI purple to blue gradient */\\nbutton.auto-arrange {\\n background: linear-gradient(135deg, #6A4C93, #4a90e2);\\n border-color: #4a4c93;\\n}\\n\\nbutton.auto-arrange:hover {\\n background: linear-gradient(135deg, #7B5DAD, #5ba0f2);\\n border-color: #5a5ca3;\\n}\\n\\nbutton.auto-arrange:active {\\n background: linear-gradient(135deg, #593B83, #357abd);\\n border-color: #3a3c83;\\n}\\n\\nselect {\\n border: 1px solid #ccc;\\n background: white;\\n border-radius: 3px;\\n padding: 3px 7px;\\n color: #666;\\n outline: none;\\n margin-right: 5px;\\n font-size: 12px;\\n}\\n\\nselect:disabled {\\n background: #f5f5f5;\\n color: #999;\\n}\\n\\nbutton:disabled {\\n background: #c1c1c1;\\n color: #999;\\n}\\n\\n#root > div > svg {\\n position: absolute;\\n user-select: none;\\n}\\n\\n\\n.node.selected .nodeBorder, .edge.selected path, .edge.selected rect {\\n stroke: #29c229;\\n}\\n.edge .v-dot {\\n fill: transparent;\\n stroke: transparent;\\n stroke-width: 3px;\\n cursor: pointer;\\n transition: stroke 0.15s ease;\\n}\\n.edge .v-dot:hover {\\n stroke: #999;\\n fill: rgba(153, 153, 153, 0.1);\\n}\\n.edge .v-dot.selected {\\n stroke: #29c229;\\n fill: rgba(41, 194, 41, 0.1);\\n}\\n.edge .v-dot.selected:hover {\\n stroke: #29c229;\\n fill: rgba(41, 194, 41, 0.2);\\n}\\n.edge .v-dot.auto.selected {\\n stroke: #777;\\n fill: rgba(119, 119, 119, 0.1);\\n}\\n.edge .v-dot.auto.selected:hover {\\n stroke: #777;\\n fill: rgba(119, 119, 119, 0.2);\\n}\\ncircle#prj {\\n fill: none;\\n stroke: #777;\\n}\\n\\n.nodeShadow {\\n fill: none;\\n stroke-width: 4px;\\n stroke: rgba(0, 0, 0, 0.13);\\n}\\n\\ng.node {\\n user-select: none;\\n cursor: default;\\n}\\n\\ng.node.linked {\\n cursor: pointer;\\n}\\n\\ng.node text {\\n pointer-events: none;\\n}\\n\\n.icon {\\n fill: #aaa;\\n stroke: #fff;\\n}\\n#icon-cube {\\n fill: #aaa;\\n}\\n\\n/* Ensure all button icons are uncolored */\\nbutton .icon,\\nbutton svg,\\nbutton path {\\n fill: currentColor !important;\\n stroke: none !important;\\n}\\n\\n/* Font Awesome icon styling in buttons */\\nbutton i {\\n font-size: 12px;\\n color: inherit;\\n}\\n\\nrect.elastic {\\n pointer-events: none;\\n stroke: none;\\n fill: #3bd8281f;\\n display: none;\\n}\\nrect.elastic.on {\\n display: block;\\n}\\n\\n.popover {\\n position: absolute;\\n top: 50px;\\n bottom: 10px;\\n overflow: auto;\\n right: 10px;\\n background: ghostwhite;\\n padding: 30px;\\n box-shadow: 3px 3px 5px rgba(0,0,0, .2);\\n border: solid 1px #eee;\\n}\\n\\n.popover th {\\n text-align: left;\\n padding: 20px 0px 10px;\\n}\\n.popover td {\\n padding-right: 20px;\\n font-size: 14px;\\n}\\n\\n/* Simple tooltip system with smart positioning */\\n[data-tooltip] {\\n position: relative;\\n}\\n\\n[data-tooltip]:hover::after {\\n content: attr(data-tooltip);\\n position: absolute;\\n top: 100%;\\n left: 50%;\\n transform: translateX(-50%);\\n background: rgba(0, 0, 0, 0.9);\\n color: white;\\n padding: 6px 12px;\\n border-radius: 4px;\\n font-size: 12px !important;\\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;\\n font-weight: normal !important;\\n white-space: nowrap;\\n z-index: 1000;\\n pointer-events: none;\\n margin-top: 5px;\\n animation: tooltip-appear 0.1s ease-out;\\n min-width: 120px;\\n max-width: calc(100vw - 20px);\\n box-sizing: border-box;\\n}\\n\\n[data-tooltip]:hover::before {\\n content: '';\\n position: absolute;\\n top: 100%;\\n left: 50%;\\n transform: translateX(-50%);\\n border: 4px solid transparent;\\n border-bottom-color: rgba(0, 0, 0, 0.9);\\n z-index: 1000;\\n pointer-events: none;\\n margin-top: 1px;\\n animation: tooltip-appear 0.1s ease-out;\\n}\\n\\n/* Special handling for rightmost elements that might overflow */\\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::after {\\n /* Apply to last 2 toolbar groups (save and help buttons) */\\n left: auto;\\n right: 0;\\n transform: none;\\n}\\n\\n.toolbar-group:nth-last-child(-n+2) button[data-tooltip]:hover::before {\\n /* Apply to last 2 toolbar groups (save and help buttons) */\\n left: auto;\\n right: 16px;\\n transform: none;\\n}\\n\\n@keyframes tooltip-appear {\\n from {\\n opacity: 0;\\n transform: translateX(-50%) translateY(-5px);\\n }\\n to {\\n opacity: 1;\\n transform: translateX(-50%) translateY(0);\\n }\\n}\\n\\nselect {\\n position: relative;\\n}\\n\\n/* Robot shape internal elements - inherit stroke from parent node */\\n.node .robot-eye-socket {\\n fill: none;\\n stroke: inherit;\\n stroke-width: 2;\\n}\\n\\n.node .robot-eye {\\n fill: currentColor;\\n stroke: none;\\n}\\n\\n.node .robot-mouth {\\n stroke: inherit;\\n}\\n\\n.node .robot-antenna {\\n stroke: inherit;\\n}\\n\\n.node .robot-antenna-ball {\\n fill: currentColor;\\n stroke: inherit;\\n stroke-width: 1.5;\\n}\\n\\n.node .robot-panel {\\n stroke: inherit;\\n}\\n\\n.node .robot-indicator {\\n fill: currentColor;\\n stroke: none;\\n}\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n"],"names":["layoutDirections","TopBottom","BottomTop","LeftRight","RightLeft","listViews","model","viewsList","Object","keys","views","filter","section","endsWith","forEach","s","v","push","key","title","graphs","camelToWords","camel","split","replace","charAt","toUpperCase","slice","Toolbar","currentID","onViewChange","graph","onAutoLayout","onSave","onToggleHelp","saving","layouting","dragMode","setDragMode","jsx_runtime","jsxs","className","children","jsx","ViewSelector","ToolbarActions","length","onChange","e","target","value","disabled","hidden","map","view","style","marginLeft","fontWeight","display","alignItems","DragModeButton","UndoRedoButtons","AlignmentButtons","LayoutControls","GridControls","ZoomControls","SaveButton","HelpButton","onClick","modKey","platform","sy","Fragment","undo","redo","alignSelectionH","alignSelectionV","transform","distributeSelectionH","distributeSelectionV","gridVisible","setGridVisible","react","useState","isGridVisible","snapToGrid","setSnapToGrid","isSnapToGrid","useEffect","updateGridState","window","addEventListener","removeEventListener","toggleGrid","toggleSnapToGrid","snapAllToGrid","ZoomDisplay","zoom","setZoomState","updateZoom","currentZoom","Math","round","graph_view_graph","IX","interval","setInterval","clearInterval","a_","max","min","fitToView","hasChanges","setHasChanges","checkChanges","changed","Help","lazy","Promise","resolve","then","__webpack_require__","bind","module","default","Graph","setAutomationStatus","status","error","root","document","documentElement","dataset","mdlAutomationStatus","mdlAutomationError","reportInteractiveError","action","console","alert","Root","layout","chunk_62JRHF6Z","Kd","BV","qh","path","element","ModelPane","layouts","refreshGraph","URLSearchParams","location","search","get","searchParams","setSearchParams","ok","decodeURI","helpVisible","setHelpVisible","readyGraphID","setReadyGraphID","automationKey","useRef","automationRun","viewKey","elements","Map","relations","collectRels","el","Array","isArray","relationships","rel","set","id","people","softwareSystems","containers","el1","parent","components","el2","deploymentNodes","containerInstances","item","containerId","recAddNodes","infrastructureNodes","some","getView","jg","rankDirection","automaticLayout","layoutDirection","undefined","metadata","name","description","version","groupingIDs","ref","softwareSystemId","find","p","enterprise","styles","cssClassName","tag","toLowerCase","varPrefix","background","colorToVarMap","color","stroke","elementViewKey","containerViews","candidate","lookupContainerViewKey","sub","tags","technology","addNode","encodedViewKey","encodeURIComponent","href","exportHref","url","nodeLink","properties","nodesMap","has","sourceId","warn","destinationId","routing","addEdge","vertices","level","i","sort","a","b","groupMembers","addGroup","init","parseView","useGraph","handleAutoLayout","setLayouting","useCallback","async","options","direction","opts","autoLayout","useAutoLayout","handleSave","setSaving","response","fetch","method","body","exportSVG","detail","text","trim","Error","setSaved","useSave","ViewRedirect","handleToggleHelp","handleGraphReady","handleInteractiveAutoLayout","catch","handleInteractiveSave","params","fromEntries","entries","auto","save","current","toString","run","compact","layoutOpts","includes","compactLayout","message","String","errorMessage","toggleHelp","saveLayout","handleKeyDown","shortcut","shortcuts","Yp","preventDefault","aX","t9","Jk","DE","Vy","Hd","_t","resetView","Op","hZ","OE","Gg","moveSelected","getGridSize","J8","b3","iD","uK","l8","rB","mt","useKeyboardShortcuts","handleViewChange","handleSelect","m","log","obj","JSON","parse","stringify","Suspense","fallback","data","onSelect","onReady","svgTextWrap","width","attrs","svg","createElementNS","appendChild","measure","node","setAttribute","attr","createTextNode","height","getBBox","removeChild","clean","textMeasure","maxW","ret","words","lines","currentLine","word","join","brokenParts","maxWidth","parts","currentPart","testPart","breakLongWord","newLine","size","reduce","concat","create","type","k","classList","add","use","this","setAttributeNS","d","t","textContent","textArea","fontSize","bold","x","y","anchor","txt","line","span","dy","append","rect","r","rx","ry","icon","expand","expanded","g","setPosition","insideBox","centeredBox","uncenterBox","intersectRectFull","p1","p2","box","w","h","q","q1","q2","denominator","numerator1","numerator2","result","onLine1","onLine2","segmentIntersection","intersectRect","intersectEllipse","ellCenter","nodeCenter","point","si","c","radicand_sqrt","sqrt","pos","project","atob","len","dot","cabDistance","abs","cylinderRadiusY","shapeLabelOffsetY","shape","D3Element","_el","constructor","insert","insertBefore","querySelector","bbox","rounded","shapeSvg","intersect","_ellipse","mobiledeviceportrait","shapes","roundedbox","component","dx","cylinder","cy","person","circle","ellipse","hexagon","sz","n","folder","mobiledevicelandscape","mobiledevice","pipe","robot","headSize","headR","antennaH","antennaR","eyeR","eyeSpacing","earW","earH","bodyW","bodyTop","bodyH","headTop","eyeY","mouthY","mouthW","webbrowser","Undo","versions","lastSavedPos","exportDoc","importDoc","change","tmpPreviousState","func","timeout","context","clearTimeout","setTimeout","apply","debounce","saveNow","beforeChange","deepClone","currentState","splice","doc","structuredClone","SVG_STYLES","applyStyle","setProperty","calculateDistance","textBlock","gapAfter","field","wrapped","lineHeight","defaultEdgeStyle","thickness","opacity","dashed","defaultNodeStyle","GraphData","edges","edgeVertices","groupsMap","_undo","_gridVisible","_snapToGrid","_gridSize","_skipAutoFit","exportLayout","lo","importLayout","label","link","nodeStyle","minimumHeight","contentLayout","subtitle","nodeWidth","textWidth","HORIZONTAL_PADDING","blocks","textHeight","block","VERTICAL_PADDING","layoutNodeContent","requiredHeight","nodes","from","values","fromNode","toNode","edge","to","initVertex","edgeID","input","charCodeAt","imul","fnv1a36","stableVertexID","userDeletedVertices","nodesOrGroups","group","Boolean","setNodeSelected","selected","remove","updateEdgesSel","moveNode","disableSnap","skipUndo","snapped","redrawEdges","redrawGroups","moveEdgeVertex","redrawEdge","insertEdgeVertex","isLabel","deleteEdgeVertex","index","indexOf","delete","alignTopLeft","contentBounds","calculateContentBounds","offsetX","offsetY","vertex","resetPanTransform","getZoom","zoomGroup","graphData","bb","parentElement","clientWidth","clientHeight","updatePanningOptimized","clearViewState","shouldSkipAutoFit","updatePanning","buildEdge","buildGroup","originalSvg","exportSvg","cloneNode","querySelectorAll","getAttribute","removeAttribute","exportElastic","exportWidth","padding","exportHeight","exportZoomGroup","convertStylesToCustomProperties","script","createElement","firstChild","outerHTML","fill","minX","Infinity","minY","maxX","maxY","left","right","top","bottom","centerX","centerY","approxLabelSize","full","lst","rerender","coordinates","startsWith","normalizedPoint","assign","edgeId","an","ae","labelVertex","insertPos","labelPos","fullPath","minDistance","bestSegmentIndex","distance","distanceToSegment","findOptimalLabelPosition","projectedPos","segmentStart","segmentEnd","A","B","C","D","lenSq","param","projectPointOntoSegment","projectLabelOntoSegment","selectedNodes","selectedVertices","allElements","spacing","newX","newY","setEdgeSelected","viewportWidth","viewportHeight","zoomX","zoomY","optimalZoom","finalZoom","translateX","translateY","saveViewState","saveLayoutState","restoreLayoutState","state","updateGridDisplay","dispatchEvent","CustomEvent","snappedX","snappedY","existingGrid","existingGridRect","defs","pattern","clickListener","selectListener","dragging","buildGraph","onNodeSelect","innerHTML","__data","_buildGraph","elasticEl","addCursorInteraction","setZoom","zoomG","nodesG","edgesG","groupsG","gdata","content","shapeType","nodeBorder","setBorderStyle","border","tg","buildNodeContent","Number","buildNode","labelBounds","n1","n2","position","sameEdges","spreadPos","spreadX","spreadY","unshift","firstRoutingVertex","lastRoutingVertex","calculateNodeIntersection","targetPoint","nodeShape","halfHeight","angle","atan2","cos","sin","rectX","rectY","topCurveY","bottomCurveY","ellipseY","discriminant","sqrt_d","x1","x2","radius","halfWidth","startIntersection","endIntersection","calculateEdgeVertices","labelPlacement","segment","labelIndex","findIndex","movable","adjacentSegments","longest","targetLength","sum","traversed","segmentPosition","horizontalDistance","verticalDistance","orientation","calculateLabelPlacement","bg","placement","anchors","fraction","existing","occupied","expandBox","other","flatMap","side","edgeLabelCandidate","best","score","bounds","edgeText","edgeRect","buildEdgeLabel","segments","reverse","s2","intersectPolylineBox","createEdgeSegments","cx","overlap","total","first","second","p0","topExtension","bottomExtension","hexHeight","pad","groupRect","groupText","findClosestSegment","fnd","dst","POSITIVE_INFINITY","prj","pts","mouseToDrawing","getBoundingClientRect","z","currentTransform","getCurrentTransform","clientX","clientY","addCustomCursorInteraction","conn","ini","elastic","isPanning","panStartX","panStartY","initialTransform","pendingSelectionChange","pendingNavigation","hasDragged","suppressLinkClick","eventListeners","Element","closest","stopPropagation","md","convertEvent","changedTouches","onMouseMoveHandler","getSelection","setSelection","setTransform","drawingDx","drawingDy","setDragging","update","onMouseMove","ex","ey","onMouseUpHandler","navigation","end","boxSelection","shiftKey","onMouseUp","onMouseDownHandler","nodeFromEvent","effectiveMode","isSelected","translateMatch","match","parseFloat","getCurrentTransformLocal","startDrawingX","startDrawingY","pt","currentPt","currentDrawingX","currentDrawingY","createElastic","onMouseDown","event","handler","addDnd","existingCleanup","__cursorInteractionCleanup","getData","gd","beforeUnloadHandler","returnValue","setDotSelected","dotEl","mouseMoveHandler","altKey","removePrjDot","keyUpHandler","Zj","_s","clickHandler","wheelHandler","delta","sign","deltaY","newZoom","setZoomCentered","keyDownHandler","bl","Ur","hU","i1","mD","F","Gn","customInteractionCleanup","handles","handle","b1","b2","scaleMatch","oldZoom","container","newTranslateX","newTranslateY","nodeText","cursor","viewStateCache","graphId","restoreViewState","xx","yy","getEffectiveSpacing","userOptions","isGroup","effectiveConfig","nodeSpacing","layerSpacing","componentSpacing","groupMultiplier","getELKOptions","baseOptions","elk","elkGraph","layoutOptions","nodeMap","elkNodes","nodeHeight","arrowPadding","nodeParentGroup","groupParent","childGroupIDs","Set","member","elkGroups","buildELKGroup","elkGroup","groupAncestors","groupID","ancestors","labelWidth","elkEdge","sources","targets","labels","sourceID","destinationID","sourceAncestors","destinationAncestors","lowestCommonGroup","layoutedGraph","extractNodes","child","processEdgesFromELK","sections","startPoint","bendPoints","bp","endPoint","originalEdge","elkLabel","midIndex","floor","v1","v2","maxCols","ceil","col","createFallbackLayout","styleTagTransform","styleTagTransform_default","setAttributes","setAttributesWithoutAttributes_default","insertBySelector_default","domAPI","styleDomAPI_default","insertStyleElement","insertStyleElement_default","injectStylesIntoStyleTag_default","locals","Timer","callback","running","timeoutId","start","stop","isRunning","RefreshConnector","static","minDelay","maxDelay","handshakeTimeout","uri","fileChangeHandler","socket","nextDelay","connectionDesired","disconnectionReason","reconnectTimer","DEFAULT_OPTIONS","handleHandshakeTimeout","attemptReconnection","connect","isSocketConnected","prepareForConnection","createWebSocket","disconnect","close","readyState","WebSocket","OPEN","onopen","handleOpen","onclose","handleClose","onmessage","handleMessage","onerror","handleError","startHandshake","scheduleReconnection","processMessage","command","handleHelloMessage","handleReloadMessage","helloMessage","protocols","LIVERELOAD_PROTOCOLS","ver","sendCommand","send","src_Root","App","setState","loading","loadData","prev","modelResponse","layoutResponse","all","statusText","json","handleFileChange","S","LoadingScreen","ErrorScreen","onRetry","justifyContent","fontFamily","whiteSpace","flexDirection","backgroundColor","borderRadius","getElementById","client","createRoot","render","ADD_VERTEX","ADD_LABEL_VERTEX","DEL_VERTEX","ZOOM_IN","ZOOM_OUT","ZOOM_FIT","ZOOM_100","SELECT_ALL","DESELECT","MOVE_LEFT","MOVE_RIGHT","MOVE_UP","MOVE_DOWN","MOVE_LEFT_FINE","MOVE_RIGHT_FINE","MOVE_UP_FINE","MOVE_DOWN_FINE","TOGGLE_DRAG_MODE","ALIGN_HORIZONTAL","ALIGN_VERTICAL","DISTRIBUTE_HORIZONTAL","DISTRIBUTE_VERTICAL","AUTO_LAYOUT","RESET_POSITION","TOGGLE_GRID","TOGGLE_SNAP_TO_GRID","SNAP_ALL_TO_GRID","list","help","combinations","shift","ctrl","alt","click","wheel","shortcutMap","comboText","_utils_platform__WEBPACK_IMPORTED_MODULE_1__","react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__","colSpan","matches","SA","ke","checkKey","aShortcut","bShortcut","aModifiers","bModifiers","aCount","isMac","navigator","userAgentData","userAgent","KeyboardEvent","metaKey","test","ctrlKey","___CSS_LOADER_EXPORT___","_node_modules_pnpm_css_loader_7_1_4_webpack_5_109_2_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default","_node_modules_pnpm_css_loader_7_1_4_webpack_5_109_2_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default","names","mappings","sourcesContent","sourceRoot","__WEBPACK_DEFAULT_EXPORT__"],"sourceRoot":""} \ No newline at end of file diff --git a/cmd/mdl/webapp/src/Root.tsx b/cmd/mdl/webapp/src/Root.tsx index da123645..c4c9380b 100644 --- a/cmd/mdl/webapp/src/Root.tsx +++ b/cmd/mdl/webapp/src/Root.tsx @@ -1,5 +1,6 @@ -import React, { FC, useState, useCallback, useEffect, Suspense, lazy } from "react"; -import { GraphData } from "./graph-view/graph"; +import React, { FC, useState, useCallback, useEffect, useRef, Suspense, lazy } from "react"; +import { GraphData, LayoutDirection } from "./graph-view/graph"; +import { LayoutOptions } from "./graph-view/layout"; import { BrowserRouter as Router, Routes, Route, useSearchParams } from 'react-router-dom'; import { listViews } from "./parseModel"; import { useGraph, useAutoLayout, useSave, useKeyboardShortcuts, clearGraphCache } from "./hooks"; @@ -15,6 +16,31 @@ interface ModelData { layout: any; } +type AutomationStatus = 'running' | 'complete' | 'error'; + +const setAutomationStatus = (status: AutomationStatus | null, error?: string) => { + const root = document.documentElement; + if (!status) { + delete root.dataset.mdlAutomationStatus; + delete root.dataset.mdlAutomationError; + return; + } + root.dataset.mdlAutomationStatus = status; + if (error) { + root.dataset.mdlAutomationError = error; + } else { + delete root.dataset.mdlAutomationError; + } +}; + +const errorMessage = (error: unknown): string => + error instanceof Error ? error.message : String(error); + +const reportInteractiveError = (action: string, error: unknown) => { + console.error(`${action} failed:`, error); + alert(`${action} failed. See console for details.`); +}; + export const Root: FC = ({ model, layout }) => ( @@ -35,6 +61,9 @@ const ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => { // UI State const [helpVisible, setHelpVisible] = useState(false); const [dragMode, setDragMode] = useState<'pan' | 'select'>('pan'); + const [readyGraphID, setReadyGraphID] = useState(null); + const automationKey = useRef(null); + const automationRun = useRef(0); // Get or create graph for current view const graph = useGraph(model, layouts, currentID); @@ -51,6 +80,18 @@ const ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => { setHelpVisible(!helpVisible); }, [helpVisible]); + const handleGraphReady = useCallback(() => { + setReadyGraphID(currentID); + }, [currentID]); + + const handleInteractiveAutoLayout = useCallback(() => { + void handleAutoLayout().catch(error => reportInteractiveError('Layout', error)); + }, [handleAutoLayout]); + + const handleInteractiveSave = useCallback(() => { + void handleSave().catch(error => reportInteractiveError('Save', error)); + }, [handleSave]); + // Update document title when view changes useEffect(() => { if (graph && graph.name) { @@ -60,23 +101,39 @@ const ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => { // Headless automation: support query params to auto-layout and save useEffect(() => { - // Only run when graph changes to avoid duplicate actions const params = Object.fromEntries(searchParams.entries()); const auto = params['auto'] === '1' || params['auto'] === 'true'; const save = params['save'] === '1' || params['save'] === 'true'; + if (!auto && !save) { + automationKey.current = null; + automationRun.current++; + setAutomationStatus(null); + return; + } + if (readyGraphID !== currentID) { + return; + } + + const key = `${currentID}:${searchParams.toString()}`; + if (automationKey.current === key) { + return; + } + automationKey.current = key; + const run = ++automationRun.current; + setAutomationStatus('running'); + const direction = (params['direction'] || '').toUpperCase(); const compact = params['compact'] === '1' || params['compact'] === 'true'; - const validDirections = ['UP', 'DOWN', 'LEFT', 'RIGHT']; - const layoutOpts: any = {}; - if (validDirections.includes(direction)) { - layoutOpts.direction = direction as any; + const validDirections: LayoutDirection[] = ['UP', 'DOWN', 'LEFT', 'RIGHT']; + const layoutOpts: LayoutOptions = {}; + if (validDirections.includes(direction as LayoutDirection)) { + layoutOpts.direction = direction as LayoutDirection; } if (compact) { layoutOpts.compactLayout = true; } - let cancelled = false; (async () => { try { if (auto) { @@ -85,16 +142,28 @@ const ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => { if (save) { await handleSave(); } - } catch (e) { - console.error('automation error', e); + if (automationRun.current === run) { + setAutomationStatus('complete'); + } + } catch (error) { + const message = errorMessage(error); + console.error('Automation failed:', error); + if (automationRun.current === run) { + setAutomationStatus('error', message); + } } })(); - - return () => { cancelled = true; }; - }, [graph, handleAutoLayout, handleSave, searchParams]); + }, [currentID, graph, handleAutoLayout, handleSave, readyGraphID, searchParams]); // Setup keyboard shortcuts - useKeyboardShortcuts(handleToggleHelp, handleSave, graph, dragMode, setDragMode, handleAutoLayout); + useKeyboardShortcuts( + handleToggleHelp, + handleInteractiveSave, + graph, + dragMode, + setDragMode, + handleInteractiveAutoLayout, + ); const handleViewChange = useCallback((id: string) => { setSearchParams({ id: encodeURIComponent(id) }); @@ -114,8 +183,8 @@ const ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => { currentID={currentID} onViewChange={handleViewChange} graph={graph} - onAutoLayout={handleAutoLayout} - onSave={handleSave} + onAutoLayout={handleInteractiveAutoLayout} + onSave={handleInteractiveSave} onToggleHelp={handleToggleHelp} saving={saving} layouting={layouting} @@ -127,6 +196,7 @@ const ModelPane: FC<{ model: any; layouts: any }> = ({ model, layouts }) => { key={currentID} data={graph} onSelect={handleSelect} + onReady={handleGraphReady} dragMode={dragMode} /> diff --git a/cmd/mdl/webapp/src/graph-view/edge-utils.ts b/cmd/mdl/webapp/src/graph-view/edge-utils.ts index 3b190863..c301cafb 100644 --- a/cmd/mdl/webapp/src/graph-view/edge-utils.ts +++ b/cmd/mdl/webapp/src/graph-view/edge-utils.ts @@ -64,6 +64,8 @@ interface GraphData { export interface EdgeLabelPlacement extends Point { orientation: 'horizontal' | 'vertical'; + segment?: Segment; + movable: boolean; } /** @@ -275,9 +277,12 @@ export function calculateLabelPlacement( let point = {x: fallback.x, y: fallback.y}; let segment: Segment | undefined; const labelIndex = vertices.findIndex(vertex => (vertex as EdgeVertex).label); + let movable = true; if (labelIndex >= 0) { - point = vertices[labelIndex]; + const labelVertex = vertices[labelIndex] as EdgeVertex; + point = labelVertex; + movable = labelVertex.auto === true; const adjacentSegments: Segment[] = []; if (labelIndex > 0) { adjacentSegments.push({p: vertices[labelIndex - 1], q: point}); @@ -322,6 +327,8 @@ export function calculateLabelPlacement( return { ...point, orientation: verticalDistance > horizontalDistance ? 'vertical' : 'horizontal', + segment, + movable, }; } diff --git a/cmd/mdl/webapp/src/graph-view/graph-react.tsx b/cmd/mdl/webapp/src/graph-view/graph-react.tsx index abbb1ef5..92927e19 100644 --- a/cmd/mdl/webapp/src/graph-view/graph-react.tsx +++ b/cmd/mdl/webapp/src/graph-view/graph-react.tsx @@ -4,10 +4,11 @@ import {buildGraph, GraphData, Node, addCursorInteraction, restoreViewState, sav interface Props { data: GraphData; onSelect: (nodeName: string | null) => void; + onReady: () => void; dragMode: 'pan' | 'select'; } -export const Graph: FC = ({data, onSelect, dragMode}) => { +export const Graph: FC = ({data, onSelect, onReady, dragMode}) => { const [graphState, setGraphState] = useState(null); const ref = useRef(null); @@ -28,6 +29,7 @@ export const Graph: FC = ({data, onSelect, dragMode}) => { if (!restoreViewState(data.id) && !data.shouldSkipAutoFit()) { data.fitToView(); } + onReady(); // Save view state before page unload const handleBeforeUnload = () => { @@ -50,7 +52,7 @@ export const Graph: FC = ({data, onSelect, dragMode}) => { ref.current.innerHTML = ''; } }; - }, [data, onSelect]); + }, [data, onSelect, onReady]); // Effect for updating drag mode on existing graph useEffect(() => { diff --git a/cmd/mdl/webapp/src/graph-view/graph.ts b/cmd/mdl/webapp/src/graph-view/graph.ts index 53414855..3e9b44e4 100644 --- a/cmd/mdl/webapp/src/graph-view/graph.ts +++ b/cmd/mdl/webapp/src/graph-view/graph.ts @@ -12,7 +12,7 @@ import { Segment, uncenterBox } from "./intersect"; -import {autoLayout} from "./layout"; +import {autoLayout, LayoutOptions} from "./layout"; import {Undo} from "./undo"; import { ADD_LABEL_VERTEX, @@ -69,6 +69,8 @@ export interface NodeLink { exportHref: string; } +export type LayoutDirection = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'; + export interface Node extends BBox { id: string; title: string; @@ -106,6 +108,7 @@ interface Edge { initVertex: (p: Point) => EdgeVertex; userDeletedVertices?: boolean; // Track if user explicitly deleted vertices labelVertex?: EdgeVertex; // ELK-calculated label position (separate from routing vertices) + labelBounds?: BBox; } interface EdgeVertex extends Point { @@ -129,6 +132,7 @@ export class GraphData { edgeVertices: Map groupsMap: Map; metadata: any; + layoutDirection?: LayoutDirection; colorToVarMap: Map = new Map(); // For CSS custom properties theming private _undo: Undo; private _gridVisible: boolean = false; @@ -758,7 +762,7 @@ export class GraphData { } } - async autoLayout(options?: import('./layout').LayoutOptions) { + async autoLayout(options?: LayoutOptions) { try { const auto = await autoLayout(this, options) @@ -1172,6 +1176,9 @@ const _buildGraph = (data: GraphData) => { nodesG.append(n.ref) }) + data.edges.forEach(edge => { + edge.labelBounds = undefined + }) data.edges.forEach(e => { buildEdge(data, e) edgesG.append(e.ref) @@ -1200,7 +1207,7 @@ function buildEdge(data: GraphData, edge: Edge) { const labelPlacement = calculateLabelPlacement(vertices, position, n1) - const {bg, txt, bbox} = buildEdgeLabel(labelPlacement, edge) + const {bg, txt, bbox} = buildEdgeLabel(labelPlacement, edge, data) g.append(bg, txt) // Create edge segments and path using utility function @@ -1241,19 +1248,64 @@ function buildEdge(data: GraphData, edge: Edge) { return g } -function buildEdgeLabel(placement: EdgeLabelPlacement, edge: Edge) { +function buildEdgeLabel(placement: EdgeLabelPlacement, edge: Edge, data: GraphData) { const labelGap = 12; + const collisionPadding = 8; const fontSize = edge.style.fontSize let {txt, dy, maxW} = create.textArea(edge.label, 200, fontSize, false, placement.x, placement.y, 'middle') dy -= fontSize / 2 maxW += fontSize - const centerX = placement.orientation === 'vertical' - ? placement.x + maxW / 2 + labelGap - : placement.x - const centerY = placement.orientation === 'vertical' - ? placement.y - : placement.y - dy / 2 - labelGap + const anchors: Point[] = [{x: placement.x, y: placement.y}] + if (placement.movable && placement.segment) { + for (const fraction of [0.5, 0.35, 0.65, 0.2, 0.8]) { + const anchor = { + x: placement.segment.p.x + + (placement.segment.q.x - placement.segment.p.x) * fraction, + y: placement.segment.p.y + + (placement.segment.q.y - placement.segment.p.y) * fraction, + } + if (!anchors.some(existing => + Math.abs(existing.x - anchor.x) < 0.1 && + Math.abs(existing.y - anchor.y) < 0.1 + )) { + anchors.push(anchor) + } + } + } + + const occupied = [ + ...data.nodes().map(node => expandBox(uncenterBox(node), collisionPadding)), + ...data.edges + .filter(other => other !== edge && other.labelBounds) + .map(other => expandBox(other.labelBounds, collisionPadding)), + ] + const candidates = anchors.flatMap(anchor => { + if (placement.orientation === 'vertical') { + return [1, -1].map(side => edgeLabelCandidate( + anchor.x + side * (maxW / 2 + labelGap), + anchor.y, + maxW, + dy, + anchor, + placement, + occupied, + )) + } + return [-1, 1].map(side => edgeLabelCandidate( + anchor.x, + anchor.y + side * (dy / 2 + labelGap), + maxW, + dy, + anchor, + placement, + occupied, + )) + }) + const selected = candidates.reduce((best, candidate) => + candidate.score < best.score ? candidate : best + ) + const {centerX, centerY, bounds} = selected txt.querySelectorAll('tspan').forEach((span: SVGTSpanElement) => { span.setAttribute('x', String(centerX)) }) @@ -1264,16 +1316,67 @@ function buildEdgeLabel(placement: EdgeLabelPlacement, edge: Edge) { txt.setAttribute('font-size', String(edge.style.fontSize)) txt.setAttribute('fill', edge.style.color) - const bbox = {x: centerX - maxW / 2, y: centerY - dy / 2, width: maxW, height: dy} + const bbox = {...bounds} const bg = create.rect(bbox.width, bbox.height, bbox.x, bbox.y) applyStyle(bg, styles.edgeRect) txt.setAttribute('data-field', 'label') + edge.labelBounds = bounds bbox.x += bbox.width / 2 bbox.y += bbox.height / 2 return {bg, txt, bbox} } +function edgeLabelCandidate( + centerX: number, + centerY: number, + width: number, + height: number, + anchor: Point, + placement: EdgeLabelPlacement, + occupied: BBox[], +) { + const bounds = { + x: centerX - width / 2, + y: centerY - height / 2, + width, + height, + } + const overlap = occupied.reduce( + (total, box) => total + boxOverlapArea(bounds, box), + 0, + ) + return { + centerX, + centerY, + bounds, + score: overlap * 1000 + calculateDistance(anchor, placement), + } +} + +function expandBox(box: BBox, padding: number): BBox { + return { + x: box.x - padding, + y: box.y - padding, + width: box.width + padding * 2, + height: box.height + padding * 2, + } +} + +function boxOverlapArea(first: BBox, second: BBox): number { + const width = Math.max( + 0, + Math.min(first.x + first.width, second.x + second.width) - + Math.max(first.x, second.x), + ) + const height = Math.max( + 0, + Math.min(first.y + first.height, second.y + second.height) - + Math.max(first.y, second.y), + ) + return width * height +} + function buildNode(n: Node, data: GraphData) { // @ts-ignore diff --git a/cmd/mdl/webapp/src/graph-view/layout.ts b/cmd/mdl/webapp/src/graph-view/layout.ts index ead0747e..376a65a2 100644 --- a/cmd/mdl/webapp/src/graph-view/layout.ts +++ b/cmd/mdl/webapp/src/graph-view/layout.ts @@ -1,7 +1,7 @@ -import {GraphData, Node, Group} from "./graph"; +import {GraphData, Node, Group, LayoutDirection} from "./graph"; export interface LayoutOptions { - direction?: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'; + direction?: LayoutDirection; nodeSpacing?: number; layerSpacing?: number; compactLayout?: boolean; diff --git a/cmd/mdl/webapp/src/hooks.ts b/cmd/mdl/webapp/src/hooks.ts index 197b9da0..948c34c5 100644 --- a/cmd/mdl/webapp/src/hooks.ts +++ b/cmd/mdl/webapp/src/hooks.ts @@ -51,13 +51,10 @@ export const useAutoLayout = (graph: GraphData) => { setLayouting(true); try { const options: LayoutOptions = { - direction: 'DOWN', + direction: graph.layoutDirection || 'DOWN', ...(opts || {}) }; await graph.autoLayout(options); - } catch (error) { - console.error('Layout failed:', error); - alert('Layout failed. See console for details.'); } finally { setLayouting(false); } @@ -80,13 +77,10 @@ export const useSave = (graph: GraphData, currentID: string) => { }); if (response.status !== 202) { - alert('Error saving\nSee terminal output.'); - } else { - graph.setSaved(); + const detail = (await response.text()).trim(); + throw new Error(detail || `save failed with HTTP ${response.status}`); } - } catch (error) { - console.error('Save failed:', error); - alert('Save failed. See console for details.'); + graph.setSaved(); } finally { setSaving(false); } diff --git a/cmd/mdl/webapp/src/parseModel.ts b/cmd/mdl/webapp/src/parseModel.ts index f2dba94b..a608b8e0 100644 --- a/cmd/mdl/webapp/src/parseModel.ts +++ b/cmd/mdl/webapp/src/parseModel.ts @@ -1,4 +1,4 @@ -import {GraphData, NodeLink} from "./graph-view/graph"; +import {GraphData, LayoutDirection, NodeLink} from "./graph-view/graph"; interface Model { @@ -63,10 +63,15 @@ interface Relation { interactionStyle: string; } +type RankDirection = 'TopBottom' | 'BottomTop' | 'LeftRight' | 'RightLeft'; + interface View { key: string; title: string; description: string + automaticLayout?: { + rankDirection?: RankDirection; + }; elements: { id: string }[]; @@ -78,6 +83,13 @@ interface View { softwareSystemId: string; } +const layoutDirections: Record = { + TopBottom: 'DOWN', + BottomTop: 'UP', + LeftRight: 'RIGHT', + RightLeft: 'LEFT', +}; + interface Metadata { name: string description: string @@ -171,6 +183,8 @@ export const parseView = (model: Model, layouts: Layouts, viewKey: string) => { if (!view) return null const graph = new GraphData(view.key, view.title || view.key) + const rankDirection = view.automaticLayout?.rankDirection + graph.layoutDirection = rankDirection ? layoutDirections[rankDirection] : undefined const metadata: Metadata = {name: graph.name, description: view.description, version: model.version, elements: []} graph.metadata = metadata diff --git a/examples/label_collision/model/model.go b/examples/label_collision/model/model.go new file mode 100644 index 00000000..713222af --- /dev/null +++ b/examples/label_collision/model/model.go @@ -0,0 +1,82 @@ +// Package model defines a dense service view used to verify that automatic +// relationship labels do not obscure nodes or one another. +package model + +import ( + . "goa.design/model/dsl" + "goa.design/model/expr" +) + +var _ = Design("Label Collision", "Exercises collision-aware relationship labels.", func() { + var pulse *expr.SoftwareSystem + pulse = SoftwareSystem("Event Bus", "Platform event transport.", func() { + Tag("External") + }) + + var provider *expr.Container + SoftwareSystem("Automation", "Agentic automation platform.", func() { + provider = Container("Workflow Provider", "Bridges automation routines to workflows.", "Go and Goa", func() { + Uses("Workflows/Definitions Service", "Manages routine-backed workflow definitions", "gRPC", Synchronous) + Uses("Workflows/Runs Service", "Starts runs and reads run status", "gRPC", Synchronous) + }) + }) + + workflows := SoftwareSystem("Workflows", "Turns platform signals into typed, durable workflows.", func() { + Container( + "Signals Service", + "Stores signal contracts, accepts events, publishes them to the event bus, and serves subscriptions.", + "Go and Goa", + func() { + Uses(pulse, "Publishes accepted signal events to", "gRPC", Asynchronous) + }, + ) + Container( + "Definitions Service", + "Owns workflow templates, provider contracts, compilation, persistence, and runtime projections.", + "Go and Goa", + ) + Container( + "Designer Agent", + "Builds workflow drafts from deployment context and the signal catalog.", + "Go and Goa", + func() { + Uses("Workflows/Definitions Service", "Compiles draft workflows through", "gRPC", Synchronous) + Uses("Workflows/Inference Engine", "Generates workflow drafts using", "gRPC", Synchronous) + }, + ) + Container( + "Inference Engine", + "Provides model-independent inference for workflow drafting.", + "Go and Goa", + ) + Container( + "Runs Service", + "Creates runs, validates compiled definitions, persists lifecycle state, and serves run history.", + "Go and Goa", + func() { + Uses( + "Workflows/Definitions Service", + "Validates runs against compiled definitions and consumes activations from", + "gRPC", + Synchronous, + ) + Uses( + "Workflows/Signals Service", + "Subscribes to signal events that trigger runs", + "gRPC", + Asynchronous, + ) + }, + ) + }) + + Views(func() { + ContainerView(workflows, "Label Collision", "Labels must remain clear of nodes and peer labels.", func() { + AddAll() + Add(provider) + Add(pulse) + SystemBoundariesVisible() + AutoLayout(RankTopBottom) + }) + }) +}) From 36fcb9c6fdc8da057b40cfd5ef6752930ef42178 Mon Sep 17 00:00:00 2001 From: "Raphael (manual office deploy after cloud-state fix)" Date: Thu, 20 Aug 2026 11:16:22 -0700 Subject: [PATCH 2/2] Stabilize cold Chrome handshake test Give CI the same browser startup budget as the SVG regression tests so the synthetic error contract is measured after Chrome is ready. --- cmd/mdl/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/mdl/main_test.go b/cmd/mdl/main_test.go index 0da03acf..2a2b428b 100644 --- a/cmd/mdl/main_test.go +++ b/cmd/mdl/main_test.go @@ -105,7 +105,7 @@ func TestChromedpExecReportsBrowserAutomationError(t *testing.T) { defer server.Close() output := filepath.Join(t.TempDir(), "missing.svg") - timeout := 15 * time.Second + timeout := 30 * time.Second err := withChromedp(timeout, false, func(exec navigateExec) error { return exec(server.URL, output, timeout) })