backpack-viewer 0.2.16 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sidebar.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { showConfirm, showPrompt } from "./dialog";
1
2
  export function initSidebar(container, onSelectOrCallbacks) {
2
3
  const cbs = typeof onSelectOrCallbacks === "function"
3
4
  ? { onSelect: onSelectOrCallbacks }
@@ -16,12 +17,31 @@ export function initSidebar(container, onSelectOrCallbacks) {
16
17
  footer.innerHTML =
17
18
  '<a href="mailto:support@backpackontology.com">support@backpackontology.com</a>' +
18
19
  "<span>Feedback & support</span>";
19
- container.appendChild(heading);
20
+ // Collapse toggle button
21
+ const collapseBtn = document.createElement("button");
22
+ collapseBtn.className = "sidebar-collapse-btn";
23
+ collapseBtn.title = "Toggle sidebar (Tab)";
24
+ collapseBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
25
+ let collapsed = false;
26
+ function toggleSidebar() {
27
+ collapsed = !collapsed;
28
+ container.classList.toggle("sidebar-collapsed", collapsed);
29
+ collapseBtn.innerHTML = collapsed
30
+ ? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 7 18 12 13 17"/><polyline points="6 7 11 12 6 17"/></svg>'
31
+ : '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
32
+ }
33
+ collapseBtn.addEventListener("click", toggleSidebar);
34
+ const headingRow = document.createElement("div");
35
+ headingRow.className = "sidebar-heading-row";
36
+ headingRow.appendChild(heading);
37
+ headingRow.appendChild(collapseBtn);
38
+ container.appendChild(headingRow);
20
39
  container.appendChild(input);
21
40
  container.appendChild(list);
22
41
  container.appendChild(footer);
23
42
  let items = [];
24
43
  let activeName = "";
44
+ let activeBranchName = "main";
25
45
  // Filter
26
46
  input.addEventListener("input", () => {
27
47
  const query = input.value.toLowerCase();
@@ -43,8 +63,12 @@ export function initSidebar(container, onSelectOrCallbacks) {
43
63
  const statsSpan = document.createElement("span");
44
64
  statsSpan.className = "stats";
45
65
  statsSpan.textContent = `${s.nodeCount} nodes, ${s.edgeCount} edges`;
66
+ const branchSpan = document.createElement("span");
67
+ branchSpan.className = "sidebar-branch";
68
+ branchSpan.dataset.graph = s.name;
46
69
  li.appendChild(nameSpan);
47
70
  li.appendChild(statsSpan);
71
+ li.appendChild(branchSpan);
48
72
  if (cbs.onRename) {
49
73
  const editBtn = document.createElement("button");
50
74
  editBtn.className = "sidebar-edit-btn";
@@ -99,5 +123,86 @@ export function initSidebar(container, onSelectOrCallbacks) {
99
123
  item.classList.toggle("active", item.dataset.name === name);
100
124
  }
101
125
  },
126
+ setActiveBranch(graphName, branchName, allBranches) {
127
+ activeBranchName = branchName;
128
+ const spans = list.querySelectorAll(`.sidebar-branch[data-graph="${graphName}"]`);
129
+ for (const span of spans) {
130
+ span.textContent = `/ ${branchName}`;
131
+ span.title = "Click to switch branch";
132
+ span.style.cursor = "pointer";
133
+ // Remove old listener by replacing element
134
+ const fresh = span.cloneNode(true);
135
+ span.replaceWith(fresh);
136
+ fresh.addEventListener("click", (e) => {
137
+ e.stopPropagation();
138
+ showBranchPicker(graphName, fresh, allBranches ?? []);
139
+ });
140
+ }
141
+ },
142
+ toggle: toggleSidebar,
102
143
  };
144
+ function showBranchPicker(graphName, anchor, branches) {
145
+ // Remove existing picker
146
+ const old = container.querySelector(".branch-picker");
147
+ if (old)
148
+ old.remove();
149
+ const picker = document.createElement("div");
150
+ picker.className = "branch-picker";
151
+ for (const b of branches) {
152
+ const row = document.createElement("div");
153
+ row.className = "branch-picker-item";
154
+ if (b.active)
155
+ row.classList.add("branch-picker-active");
156
+ const label = document.createElement("span");
157
+ label.textContent = b.name;
158
+ row.appendChild(label);
159
+ if (!b.active && cbs.onBranchDelete) {
160
+ const del = document.createElement("button");
161
+ del.className = "branch-picker-delete";
162
+ del.textContent = "\u00d7";
163
+ del.title = `Delete ${b.name}`;
164
+ del.addEventListener("click", (e) => {
165
+ e.stopPropagation();
166
+ showConfirm("Delete branch", `Delete branch "${b.name}"?`).then((ok) => {
167
+ if (ok) {
168
+ cbs.onBranchDelete(graphName, b.name);
169
+ picker.remove();
170
+ }
171
+ });
172
+ });
173
+ row.appendChild(del);
174
+ }
175
+ if (!b.active) {
176
+ row.addEventListener("click", () => {
177
+ cbs.onBranchSwitch?.(graphName, b.name);
178
+ picker.remove();
179
+ });
180
+ }
181
+ picker.appendChild(row);
182
+ }
183
+ // Create new branch row
184
+ if (cbs.onBranchCreate) {
185
+ const createRow = document.createElement("div");
186
+ createRow.className = "branch-picker-item branch-picker-create";
187
+ createRow.textContent = "+ New branch";
188
+ createRow.addEventListener("click", () => {
189
+ showPrompt("New branch", "Branch name").then((name) => {
190
+ if (name) {
191
+ cbs.onBranchCreate(graphName, name);
192
+ picker.remove();
193
+ }
194
+ });
195
+ });
196
+ picker.appendChild(createRow);
197
+ }
198
+ anchor.after(picker);
199
+ // Close on outside click
200
+ const close = (e) => {
201
+ if (!picker.contains(e.target)) {
202
+ picker.remove();
203
+ document.removeEventListener("click", close);
204
+ }
205
+ };
206
+ setTimeout(() => document.addEventListener("click", close), 0);
207
+ }
103
208
  }
package/dist/style.css CHANGED
@@ -114,6 +114,41 @@ body {
114
114
  overflow-y: auto;
115
115
  }
116
116
 
117
+ #sidebar.sidebar-collapsed {
118
+ width: 0;
119
+ min-width: 0;
120
+ padding: 0;
121
+ overflow: hidden;
122
+ border-right: none;
123
+ }
124
+
125
+ .sidebar-heading-row {
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: space-between;
129
+ margin-bottom: 14px;
130
+ }
131
+
132
+ .sidebar-heading-row h2 {
133
+ margin-bottom: 0;
134
+ }
135
+
136
+ .sidebar-collapse-btn {
137
+ background: none;
138
+ border: 1px solid var(--border);
139
+ border-radius: 6px;
140
+ color: var(--text-dim);
141
+ cursor: pointer;
142
+ padding: 4px 6px;
143
+ line-height: 1;
144
+ transition: color 0.15s, border-color 0.15s;
145
+ }
146
+
147
+ .sidebar-collapse-btn:hover {
148
+ color: var(--text);
149
+ border-color: var(--text-muted);
150
+ }
151
+
117
152
  #sidebar h2 {
118
153
  font-size: 13px;
119
154
  font-weight: 600;
@@ -218,6 +253,74 @@ body {
218
253
  padding: 0;
219
254
  }
220
255
 
256
+ .sidebar-branch {
257
+ font-size: 10px;
258
+ color: var(--accent);
259
+ opacity: 0.7;
260
+ display: block;
261
+ margin-top: 2px;
262
+ }
263
+
264
+ .sidebar-branch:hover {
265
+ opacity: 1;
266
+ }
267
+
268
+ .branch-picker {
269
+ background: var(--bg-surface);
270
+ border: 1px solid var(--border);
271
+ border-radius: 8px;
272
+ padding: 4px;
273
+ margin-top: 4px;
274
+ box-shadow: 0 4px 16px var(--shadow);
275
+ z-index: 50;
276
+ }
277
+
278
+ .branch-picker-item {
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: space-between;
282
+ padding: 6px 8px;
283
+ font-size: 12px;
284
+ color: var(--text);
285
+ border-radius: 4px;
286
+ cursor: pointer;
287
+ }
288
+
289
+ .branch-picker-item:hover {
290
+ background: var(--bg-hover);
291
+ }
292
+
293
+ .branch-picker-active {
294
+ color: var(--accent);
295
+ font-weight: 600;
296
+ cursor: default;
297
+ }
298
+
299
+ .branch-picker-active:hover {
300
+ background: none;
301
+ }
302
+
303
+ .branch-picker-delete {
304
+ background: none;
305
+ border: none;
306
+ color: var(--text-dim);
307
+ cursor: pointer;
308
+ font-size: 14px;
309
+ padding: 0 4px;
310
+ }
311
+
312
+ .branch-picker-delete:hover {
313
+ color: var(--danger, #e55);
314
+ }
315
+
316
+ .branch-picker-create {
317
+ color: var(--accent);
318
+ font-size: 11px;
319
+ border-top: 1px solid var(--border);
320
+ margin-top: 4px;
321
+ padding-top: 8px;
322
+ }
323
+
221
324
  /* --- Sidebar Footer --- */
222
325
 
223
326
  .sidebar-footer {
@@ -269,6 +372,11 @@ body {
269
372
  gap: 4px;
270
373
  }
271
374
 
375
+ .canvas-top-left {
376
+ flex-shrink: 0;
377
+ min-width: var(--tools-width, 264px);
378
+ }
379
+
272
380
  .canvas-top-center {
273
381
  flex: 1;
274
382
  justify-content: center;
@@ -510,7 +618,8 @@ body {
510
618
  transition: background 0.1s;
511
619
  }
512
620
 
513
- .search-result-item:hover {
621
+ .search-result-item:hover,
622
+ .search-result-active {
514
623
  background: var(--bg-hover);
515
624
  }
516
625
 
@@ -626,13 +735,15 @@ body {
626
735
  position: absolute;
627
736
  top: 56px;
628
737
  right: 16px;
738
+ bottom: 16px;
629
739
  width: 360px;
630
- max-height: calc(100vh - 72px);
631
740
  background: var(--bg-surface);
632
741
  border: 1px solid var(--border);
633
742
  border-radius: 10px;
634
- overflow-y: auto;
635
- padding: 20px;
743
+ overflow: hidden;
744
+ display: flex;
745
+ flex-direction: column;
746
+ padding: 0;
636
747
  z-index: 10;
637
748
  box-shadow: 0 8px 32px var(--shadow);
638
749
  transition: top 0.25s ease, right 0.25s ease, bottom 0.25s ease,
@@ -640,6 +751,19 @@ body {
640
751
  border-radius 0.25s ease;
641
752
  }
642
753
 
754
+ .info-panel-header {
755
+ flex-shrink: 0;
756
+ padding: 20px 20px 12px;
757
+ position: relative;
758
+ }
759
+
760
+ .info-panel-body {
761
+ flex: 1;
762
+ overflow-y: auto;
763
+ min-height: 0;
764
+ padding: 0 20px 20px;
765
+ }
766
+
643
767
  .info-panel.hidden {
644
768
  display: none;
645
769
  }
@@ -837,6 +961,11 @@ body {
837
961
  flex-wrap: wrap;
838
962
  }
839
963
 
964
+ .info-connection-active {
965
+ outline: 1.5px solid var(--accent);
966
+ background: var(--bg-hover);
967
+ }
968
+
840
969
  .info-target-dot {
841
970
  width: 8px;
842
971
  height: 8px;
@@ -1069,8 +1198,11 @@ body {
1069
1198
  left: 16px;
1070
1199
  bottom: 16px;
1071
1200
  z-index: 20;
1072
- width: 200px;
1073
- overflow-y: auto;
1201
+ width: var(--tools-width, 264px);
1202
+ box-sizing: border-box;
1203
+ overflow: hidden;
1204
+ display: flex;
1205
+ flex-direction: column;
1074
1206
  background: var(--bg-surface);
1075
1207
  border: 1px solid var(--border);
1076
1208
  border-radius: 10px;
@@ -1296,6 +1428,75 @@ body {
1296
1428
  padding: 8px 0;
1297
1429
  }
1298
1430
 
1431
+ .tools-pane-tabs {
1432
+ display: flex;
1433
+ gap: 2px;
1434
+ margin-bottom: 10px;
1435
+ padding-bottom: 8px;
1436
+ border-bottom: 1px solid var(--border);
1437
+ }
1438
+
1439
+ .tools-pane-tab {
1440
+ flex: 1;
1441
+ padding: 4px 0;
1442
+ font-size: 10px;
1443
+ font-weight: 600;
1444
+ text-transform: uppercase;
1445
+ letter-spacing: 0.03em;
1446
+ background: none;
1447
+ border: 1px solid transparent;
1448
+ border-radius: 5px;
1449
+ color: var(--text-dim);
1450
+ cursor: pointer;
1451
+ transition: color 0.15s, background 0.15s, border-color 0.15s;
1452
+ }
1453
+
1454
+ .tools-pane-tab:hover {
1455
+ color: var(--text-muted);
1456
+ background: var(--bg-hover);
1457
+ }
1458
+
1459
+ .tools-pane-tab-active {
1460
+ color: var(--text);
1461
+ background: var(--bg-hover);
1462
+ border-color: var(--border);
1463
+ }
1464
+
1465
+ .tools-pane-tab-content {
1466
+ flex: 1;
1467
+ overflow-y: auto;
1468
+ overflow-x: hidden;
1469
+ min-height: 0;
1470
+ }
1471
+
1472
+ .tools-pane-search {
1473
+ width: 100%;
1474
+ padding: 4px 8px;
1475
+ font-size: 11px;
1476
+ background: var(--bg);
1477
+ border: 1px solid var(--border);
1478
+ border-radius: 6px;
1479
+ color: var(--text);
1480
+ outline: none;
1481
+ margin-bottom: 8px;
1482
+ box-sizing: border-box;
1483
+ }
1484
+
1485
+ .tools-pane-search:focus {
1486
+ border-color: var(--accent);
1487
+ }
1488
+
1489
+ .tools-pane-search::placeholder {
1490
+ color: var(--text-dim);
1491
+ }
1492
+
1493
+ .tools-pane-empty-msg {
1494
+ font-size: 11px;
1495
+ color: var(--text-dim);
1496
+ text-align: center;
1497
+ padding: 16px 0;
1498
+ }
1499
+
1299
1500
  /* --- Empty State --- */
1300
1501
 
1301
1502
  .empty-state {
@@ -1512,3 +1713,124 @@ body {
1512
1713
  opacity: 0.6;
1513
1714
  }
1514
1715
  }
1716
+
1717
+ /* --- Dialog System --- */
1718
+
1719
+ .bp-dialog-overlay {
1720
+ position: fixed;
1721
+ inset: 0;
1722
+ background: rgba(0, 0, 0, 0.5);
1723
+ display: flex;
1724
+ align-items: center;
1725
+ justify-content: center;
1726
+ z-index: 1000;
1727
+ backdrop-filter: blur(2px);
1728
+ }
1729
+
1730
+ .bp-dialog {
1731
+ background: var(--bg-surface);
1732
+ border: 1px solid var(--border);
1733
+ border-radius: 12px;
1734
+ padding: 20px;
1735
+ min-width: 280px;
1736
+ max-width: 400px;
1737
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.3);
1738
+ }
1739
+
1740
+ .bp-dialog-title {
1741
+ font-size: 14px;
1742
+ font-weight: 600;
1743
+ color: var(--text);
1744
+ margin-bottom: 12px;
1745
+ }
1746
+
1747
+ .bp-dialog-message {
1748
+ font-size: 13px;
1749
+ color: var(--text-muted);
1750
+ margin-bottom: 16px;
1751
+ line-height: 1.5;
1752
+ }
1753
+
1754
+ .bp-dialog-input {
1755
+ width: 100%;
1756
+ padding: 8px 12px;
1757
+ font-size: 13px;
1758
+ background: var(--bg);
1759
+ border: 1px solid var(--border);
1760
+ border-radius: 8px;
1761
+ color: var(--text);
1762
+ outline: none;
1763
+ margin-bottom: 16px;
1764
+ }
1765
+
1766
+ .bp-dialog-input:focus {
1767
+ border-color: var(--accent);
1768
+ }
1769
+
1770
+ .bp-dialog-buttons {
1771
+ display: flex;
1772
+ justify-content: flex-end;
1773
+ gap: 8px;
1774
+ }
1775
+
1776
+ .bp-dialog-btn {
1777
+ padding: 6px 16px;
1778
+ font-size: 12px;
1779
+ border-radius: 6px;
1780
+ border: 1px solid var(--border);
1781
+ background: var(--bg);
1782
+ color: var(--text-muted);
1783
+ cursor: pointer;
1784
+ transition: all 0.15s;
1785
+ }
1786
+
1787
+ .bp-dialog-btn:hover {
1788
+ background: var(--bg-hover);
1789
+ color: var(--text);
1790
+ }
1791
+
1792
+ .bp-dialog-btn-accent {
1793
+ background: var(--accent);
1794
+ color: #fff;
1795
+ border-color: var(--accent);
1796
+ }
1797
+
1798
+ .bp-dialog-btn-accent:hover {
1799
+ opacity: 0.9;
1800
+ color: #fff;
1801
+ background: var(--accent);
1802
+ }
1803
+
1804
+ .bp-dialog-btn-danger {
1805
+ background: #e55;
1806
+ color: #fff;
1807
+ border-color: #e55;
1808
+ }
1809
+
1810
+ .bp-dialog-btn-danger:hover {
1811
+ opacity: 0.9;
1812
+ color: #fff;
1813
+ background: #e55;
1814
+ }
1815
+
1816
+ .bp-toast {
1817
+ position: fixed;
1818
+ bottom: 24px;
1819
+ left: 50%;
1820
+ transform: translateX(-50%) translateY(20px);
1821
+ background: var(--bg-surface);
1822
+ border: 1px solid var(--border);
1823
+ color: var(--text);
1824
+ padding: 8px 20px;
1825
+ border-radius: 8px;
1826
+ font-size: 12px;
1827
+ z-index: 1001;
1828
+ opacity: 0;
1829
+ transition: opacity 0.3s, transform 0.3s;
1830
+ box-shadow: 0 4px 16px var(--shadow);
1831
+ }
1832
+
1833
+ .bp-toast-visible {
1834
+ opacity: 1;
1835
+ transform: translateX(-50%) translateY(0);
1836
+ }
@@ -9,7 +9,10 @@ interface ToolsPaneCallbacks {
9
9
  onToggleTypeHulls: (visible: boolean) => void;
10
10
  onToggleMinimap: (visible: boolean) => void;
11
11
  onLayoutChange: (param: string, value: number) => void;
12
+ onPanSpeedChange: (speed: number) => void;
12
13
  onExport: (format: "png" | "svg") => void;
14
+ onSnapshot?: (label?: string) => void;
15
+ onRollback?: (version: number) => void;
13
16
  onOpen?: () => void;
14
17
  }
15
18
  export declare function initToolsPane(container: HTMLElement, callbacks: ToolsPaneCallbacks): {
@@ -17,5 +20,12 @@ export declare function initToolsPane(container: HTMLElement, callbacks: ToolsPa
17
20
  addToFocusSet(nodeIds: string[]): void;
18
21
  clearFocusSet(): void;
19
22
  setData(newData: LearningGraphData | null): void;
23
+ setSnapshots(list: {
24
+ version: number;
25
+ timestamp: string;
26
+ nodeCount: number;
27
+ edgeCount: number;
28
+ label?: string;
29
+ }[]): void;
20
30
  };
21
31
  export {};