anywidget-graph 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (48) hide show
  1. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/CHANGELOG.md +11 -0
  2. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/PKG-INFO +1 -1
  3. anywidget_graph-0.3.1/assets/demo.png +0 -0
  4. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/pyproject.toml +1 -1
  5. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/icons.js +1 -0
  6. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/index.js +150 -57
  7. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/styles.css +1 -4
  8. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/uv.lock +1 -1
  9. anywidget_graph-0.3.0/assets/demo.png +0 -0
  10. anywidget_graph-0.3.0/assets/widget.png +0 -0
  11. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.github/workflows/ci.yml +0 -0
  12. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.github/workflows/pypi.yml +0 -0
  13. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.gitignore +0 -0
  14. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.python-version +0 -0
  15. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/LICENSE +0 -0
  16. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/README.md +0 -0
  17. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/examples/demo.py +0 -0
  18. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/examples/test_hierarchy.py +0 -0
  19. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/examples/test_large_graph.py +0 -0
  20. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/__init__.py +0 -0
  21. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/__init__.py +0 -0
  22. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/arango.py +0 -0
  23. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/cosmosdb.py +0 -0
  24. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/grafeo.py +0 -0
  25. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/ladybug.py +0 -0
  26. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/neo4j.py +0 -0
  27. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/__init__.py +0 -0
  28. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/base.py +0 -0
  29. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/common.py +0 -0
  30. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/cypher.py +0 -0
  31. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/gql.py +0 -0
  32. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/graphql.py +0 -0
  33. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/gremlin.py +0 -0
  34. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/sparql.py +0 -0
  35. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/demo.py +0 -0
  36. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/py.typed +0 -0
  37. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/__init__.py +0 -0
  38. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/grafeo-embed.js +0 -0
  39. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/grafeo.js +0 -0
  40. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/neo4j.js +0 -0
  41. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/properties.js +0 -0
  42. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/results.js +0 -0
  43. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/schema.js +0 -0
  44. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/settings.js +0 -0
  45. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/toolbar.js +0 -0
  46. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/widget.py +0 -0
  47. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/tests/__init__.py +0 -0
  48. {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/tests/test_graph.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 2026-03-16
4
+
5
+ ### New Features
6
+
7
+ - **Lasso selection**: Freeform lasso tool for selecting nodes by drawing around them
8
+
9
+ ### Bug Fixes
10
+
11
+ - Fixed missing ICONS import causing zoom and lasso buttons to fail
12
+ - Fixed `lassoSvg` referenced before declaration, crashing widget on load
13
+
3
14
  ## 0.3.0
4
15
 
5
16
  ### New Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anywidget-graph
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Interactive graph visualization for Python notebooks using anywidget
5
5
  Project-URL: Homepage, https://grafeo.dev/
6
6
  Project-URL: Repository, https://github.com/GrafeoDB/anywidget-graph
Binary file
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "anywidget-graph"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Interactive graph visualization for Python notebooks using anywidget"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -18,4 +18,5 @@ export const ICONS = {
18
18
  search: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
19
19
  chevronUp: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 15l-6-6-6 6"/></svg>`,
20
20
  table: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18"/></svg>`,
21
+ lasso: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="10" rx="9" ry="7"/><path d="M12 17v3M12 20l-2 2"/></svg>`,
21
22
  };
@@ -6,6 +6,7 @@ import Graph from "https://esm.sh/graphology@0.25.4";
6
6
  import Sigma from "https://esm.sh/sigma@3.0.0";
7
7
  import * as d3Force from "https://esm.sh/d3-force@3.0.0";
8
8
 
9
+ import { ICONS } from "./icons.js";
9
10
  import { createToolbar } from "./toolbar.js";
10
11
  import { createSchemaPanel } from "./schema.js";
11
12
  import { createSettingsPanel } from "./settings.js";
@@ -85,6 +86,18 @@ function getCategoricalColor(value) {
85
86
  return CATEGORICAL_COLORS[Math.abs(hash) % CATEGORICAL_COLORS.length];
86
87
  }
87
88
 
89
+ function pointInPolygon(x, y, polygon) {
90
+ let inside = false;
91
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
92
+ const [xi, yi] = polygon[i];
93
+ const [xj, yj] = polygon[j];
94
+ if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
95
+ inside = !inside;
96
+ }
97
+ }
98
+ return inside;
99
+ }
100
+
88
101
  function computeNodeColor(node, colorField, colorScale, colorDomain) {
89
102
  if (node.color) return node.color;
90
103
  if (!colorField || node[colorField] === undefined) {
@@ -283,11 +296,16 @@ function render({ model, el }) {
283
296
  wrapper.style.height = model.get("height") + "px";
284
297
  wrapper.style.maxHeight = model.get("height") + "px";
285
298
 
286
- // Auto-detect host theme (marimo uses Tailwind class="dark" on <html>)
299
+ // Renderer reference (set after Sigma init, used by theme/filter callbacks)
300
+ let rendererRef = null;
301
+
302
+ // Auto-detect host theme (Tailwind dark/dark-theme class, data-theme, or prefers-color-scheme)
287
303
  function detectHostDark() {
288
304
  const html = document.documentElement;
289
- if (html.classList.contains("dark")) return true;
305
+ if (html.classList.contains("dark") || html.classList.contains("dark-theme")) return true;
290
306
  if (html.dataset.theme === "dark") return true;
307
+ if (document.body?.classList.contains("dark") || document.body?.classList.contains("dark-theme")) return true;
308
+ if (document.body?.dataset.theme === "dark") return true;
291
309
  if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) return true;
292
310
  return false;
293
311
  }
@@ -299,14 +317,28 @@ function render({ model, el }) {
299
317
  rendererRef?.refresh();
300
318
  }
301
319
 
302
- // Detect if inside a themed host (marimo, etc.)
303
- const hasHostTheme = !!el.getRootNode()?.host?.tagName?.startsWith("MARIMO-");
304
- if (hasHostTheme) {
305
- wrapper.classList.add("awg-auto-theme");
306
- model.set("dark_mode", detectHostDark());
307
- model.save_changes();
308
- updateTheme();
309
- const themeObserver = new MutationObserver(() => {
320
+ // Always auto-detect and observe theme changes
321
+ let autoTheme = true;
322
+ model.set("dark_mode", detectHostDark());
323
+ model.save_changes();
324
+ updateTheme();
325
+
326
+ const themeObserver = new MutationObserver(() => {
327
+ if (!autoTheme) return;
328
+ const dark = detectHostDark();
329
+ if (model.get("dark_mode") !== dark) {
330
+ model.set("dark_mode", dark);
331
+ model.save_changes();
332
+ updateTheme();
333
+ }
334
+ });
335
+ themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme", "style"] });
336
+ if (document.body) {
337
+ themeObserver.observe(document.body, { attributes: true, attributeFilter: ["class", "data-theme", "style"] });
338
+ }
339
+ if (window.matchMedia) {
340
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
341
+ if (!autoTheme) return;
310
342
  const dark = detectHostDark();
311
343
  if (model.get("dark_mode") !== dark) {
312
344
  model.set("dark_mode", dark);
@@ -314,13 +346,11 @@ function render({ model, el }) {
314
346
  updateTheme();
315
347
  }
316
348
  });
317
- themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme"] });
318
- } else {
319
- updateTheme();
320
349
  }
321
350
 
322
- // Manual toggle from settings panel still works
351
+ // Manual toggle from settings panel disables auto-theme
323
352
  model.on("change:dark_mode", () => {
353
+ autoTheme = false;
324
354
  updateTheme();
325
355
  });
326
356
 
@@ -331,7 +361,6 @@ function render({ model, el }) {
331
361
  let hiddenNodeTypes = new Set();
332
362
  let hiddenEdgeTypes = new Set();
333
363
  let typeColorOverrides = new Map(); // custom colors from filter panel
334
- let rendererRef = null; // set after Sigma init
335
364
 
336
365
  function onFilterChange(nodeTypes, edgeTypes) {
337
366
  hiddenNodeTypes = nodeTypes;
@@ -855,7 +884,8 @@ function render({ model, el }) {
855
884
  let justDragged = false;
856
885
 
857
886
  renderer.on("downNode", ({ node }) => {
858
- if (model.get("selection_mode") === "box") return;
887
+ const selMode = model.get("selection_mode");
888
+ if (selMode === "box" || selMode === "lasso") return;
859
889
  draggedNode = node;
860
890
  isDragging = false;
861
891
  renderer.getCamera().disable();
@@ -945,10 +975,16 @@ function render({ model, el }) {
945
975
  boxModeBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="1" stroke-dasharray="4 2"/></svg>`;
946
976
  boxModeBtn.title = "Box select";
947
977
 
978
+ const lassoModeBtn = document.createElement("button");
979
+ lassoModeBtn.className = "awg-zoom-btn awg-mode-btn";
980
+ lassoModeBtn.innerHTML = ICONS.lasso;
981
+ lassoModeBtn.title = "Lasso select";
982
+
948
983
  function updateModeButtons() {
949
984
  const mode = model.get("selection_mode");
950
- clickModeBtn.classList.toggle("awg-mode-active", mode !== "box");
985
+ clickModeBtn.classList.toggle("awg-mode-active", mode === "click");
951
986
  boxModeBtn.classList.toggle("awg-mode-active", mode === "box");
987
+ lassoModeBtn.classList.toggle("awg-mode-active", mode === "lasso");
952
988
  }
953
989
  updateModeButtons();
954
990
 
@@ -960,10 +996,15 @@ function render({ model, el }) {
960
996
  model.set("selection_mode", "box");
961
997
  model.save_changes();
962
998
  });
999
+ lassoModeBtn.addEventListener("click", () => {
1000
+ model.set("selection_mode", "lasso");
1001
+ model.save_changes();
1002
+ });
963
1003
  model.on("change:selection_mode", updateModeButtons);
964
1004
 
965
1005
  modeGroup.appendChild(clickModeBtn);
966
1006
  modeGroup.appendChild(boxModeBtn);
1007
+ modeGroup.appendChild(lassoModeBtn);
967
1008
 
968
1009
  zoomControls.appendChild(zoomFitBtn);
969
1010
  zoomControls.appendChild(zoomInBtn);
@@ -982,64 +1023,116 @@ function render({ model, el }) {
982
1023
  selectionRect.className = "awg-selection-rect";
983
1024
  container.appendChild(selectionRect);
984
1025
 
1026
+ // Lasso SVG overlay for drawing the freeform path
1027
+ const lassoSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1028
+ lassoSvg.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;z-index:16;display:none;pointer-events:none;";
1029
+ container.appendChild(lassoSvg);
1030
+
1031
+ const lassoPathEl = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
1032
+ lassoPathEl.setAttribute("fill", "rgba(99, 102, 241, 0.1)");
1033
+ lassoPathEl.setAttribute("stroke", "#6366f1");
1034
+ lassoPathEl.setAttribute("stroke-width", "2");
1035
+ lassoPathEl.setAttribute("stroke-dasharray", "4 2");
1036
+ lassoSvg.appendChild(lassoPathEl);
1037
+
985
1038
  function updateSelectionMode() {
986
1039
  const mode = model.get("selection_mode");
987
- selectionOverlay.style.pointerEvents = mode === "box" ? "auto" : "none";
988
- selectionOverlay.style.cursor = mode === "box" ? "crosshair" : "default";
1040
+ const active = mode === "box" || mode === "lasso";
1041
+ selectionOverlay.style.pointerEvents = active ? "auto" : "none";
1042
+ selectionOverlay.style.cursor = active ? "crosshair" : "default";
1043
+ lassoSvg.style.display = "none";
989
1044
  }
990
1045
  updateSelectionMode();
991
1046
  model.on("change:selection_mode", updateSelectionMode);
992
1047
 
993
1048
  let boxStart = null;
1049
+ let isLassoing = false;
1050
+ let lassoCoords = [];
994
1051
 
995
1052
  selectionOverlay.addEventListener("mousedown", (e) => {
996
- if (model.get("selection_mode") !== "box") return;
1053
+ const mode = model.get("selection_mode");
997
1054
  const rect = container.getBoundingClientRect();
998
- boxStart = { x: e.clientX - rect.left, y: e.clientY - rect.top };
999
- selectionRect.style.display = "block";
1000
- selectionRect.style.left = boxStart.x + "px";
1001
- selectionRect.style.top = boxStart.y + "px";
1002
- selectionRect.style.width = "0px";
1003
- selectionRect.style.height = "0px";
1055
+
1056
+ if (mode === "box") {
1057
+ boxStart = { x: e.clientX - rect.left, y: e.clientY - rect.top };
1058
+ selectionRect.style.display = "block";
1059
+ selectionRect.style.left = boxStart.x + "px";
1060
+ selectionRect.style.top = boxStart.y + "px";
1061
+ selectionRect.style.width = "0px";
1062
+ selectionRect.style.height = "0px";
1063
+ } else if (mode === "lasso") {
1064
+ e.preventDefault();
1065
+ isLassoing = true;
1066
+ lassoCoords = [[e.clientX - rect.left, e.clientY - rect.top]];
1067
+ lassoSvg.style.display = "block";
1068
+ lassoPathEl.setAttribute("points", "");
1069
+ }
1004
1070
  });
1005
1071
 
1006
1072
  selectionOverlay.addEventListener("mousemove", (e) => {
1007
- if (!boxStart) return;
1008
1073
  const rect = container.getBoundingClientRect();
1009
- const cx = e.clientX - rect.left;
1010
- const cy = e.clientY - rect.top;
1011
- const x = Math.min(boxStart.x, cx);
1012
- const y = Math.min(boxStart.y, cy);
1013
- const w = Math.abs(cx - boxStart.x);
1014
- const h = Math.abs(cy - boxStart.y);
1015
- selectionRect.style.left = x + "px";
1016
- selectionRect.style.top = y + "px";
1017
- selectionRect.style.width = w + "px";
1018
- selectionRect.style.height = h + "px";
1074
+
1075
+ if (boxStart) {
1076
+ const cx = e.clientX - rect.left;
1077
+ const cy = e.clientY - rect.top;
1078
+ const x = Math.min(boxStart.x, cx);
1079
+ const y = Math.min(boxStart.y, cy);
1080
+ const w = Math.abs(cx - boxStart.x);
1081
+ const h = Math.abs(cy - boxStart.y);
1082
+ selectionRect.style.left = x + "px";
1083
+ selectionRect.style.top = y + "px";
1084
+ selectionRect.style.width = w + "px";
1085
+ selectionRect.style.height = h + "px";
1086
+ } else if (isLassoing) {
1087
+ lassoCoords.push([e.clientX - rect.left, e.clientY - rect.top]);
1088
+ lassoPathEl.setAttribute("points", lassoCoords.map(([x, y]) => `${x},${y}`).join(" "));
1089
+ }
1019
1090
  });
1020
1091
 
1021
1092
  selectionOverlay.addEventListener("mouseup", (e) => {
1022
- if (!boxStart) return;
1023
1093
  const rect = container.getBoundingClientRect();
1024
- const cx = e.clientX - rect.left;
1025
- const cy = e.clientY - rect.top;
1026
- const x1 = Math.min(boxStart.x, cx);
1027
- const y1 = Math.min(boxStart.y, cy);
1028
- const x2 = Math.max(boxStart.x, cx);
1029
- const y2 = Math.max(boxStart.y, cy);
1030
- boxStart = null;
1031
- selectionRect.style.display = "none";
1032
-
1033
- // Find nodes inside the box
1034
- const selected = [];
1035
- graph.forEachNode((id, attrs) => {
1036
- const pos = renderer.graphToViewport({ x: attrs.x, y: attrs.y });
1037
- if (pos.x >= x1 && pos.x <= x2 && pos.y >= y1 && pos.y <= y2) {
1038
- selected.push(id);
1039
- }
1040
- });
1041
- model.set("selected_nodes", selected);
1042
- model.save_changes();
1094
+
1095
+ if (boxStart) {
1096
+ const cx = e.clientX - rect.left;
1097
+ const cy = e.clientY - rect.top;
1098
+ const x1 = Math.min(boxStart.x, cx);
1099
+ const y1 = Math.min(boxStart.y, cy);
1100
+ const x2 = Math.max(boxStart.x, cx);
1101
+ const y2 = Math.max(boxStart.y, cy);
1102
+ boxStart = null;
1103
+ selectionRect.style.display = "none";
1104
+
1105
+ // Find nodes inside the box
1106
+ const selected = [];
1107
+ graph.forEachNode((id, attrs) => {
1108
+ const pos = renderer.graphToViewport({ x: attrs.x, y: attrs.y });
1109
+ if (pos.x >= x1 && pos.x <= x2 && pos.y >= y1 && pos.y <= y2) {
1110
+ selected.push(id);
1111
+ }
1112
+ });
1113
+ model.set("selected_nodes", selected);
1114
+ model.save_changes();
1115
+ } else if (isLassoing) {
1116
+ isLassoing = false;
1117
+ lassoSvg.style.display = "none";
1118
+
1119
+ if (lassoCoords.length < 3) { lassoCoords = []; return; }
1120
+
1121
+ // Close the polygon
1122
+ lassoCoords.push(lassoCoords[0]);
1123
+
1124
+ // Find nodes inside the lasso
1125
+ const selected = [];
1126
+ graph.forEachNode((id, attrs) => {
1127
+ const pos = renderer.graphToViewport({ x: attrs.x, y: attrs.y });
1128
+ if (pointInPolygon(pos.x, pos.y, lassoCoords)) {
1129
+ selected.push(id);
1130
+ }
1131
+ });
1132
+ model.set("selected_nodes", selected);
1133
+ model.save_changes();
1134
+ lassoCoords = [];
1135
+ }
1043
1136
  });
1044
1137
 
1045
1138
  // Tooltip element
@@ -515,10 +515,7 @@
515
515
  color: var(--awg-text);
516
516
  }
517
517
 
518
- /* Hide dark mode toggle when host provides theme */
519
- .awg-wrapper.awg-auto-theme .awg-theme-toggle {
520
- display: none;
521
- }
518
+ /* Dark mode toggle always visible — manual toggle disables auto-theme */
522
519
 
523
520
  /* === SCHEMA PANEL (LEFT) === */
524
521
  .awg-schema-panel {
@@ -154,7 +154,7 @@ wheels = [
154
154
 
155
155
  [[package]]
156
156
  name = "anywidget-graph"
157
- version = "0.3.0"
157
+ version = "0.3.1"
158
158
  source = { editable = "." }
159
159
  dependencies = [
160
160
  { name = "anywidget" },
Binary file
Binary file
File without changes