anywidget-graph 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Properties panel component for displaying selected node/edge details.
3
+ */
4
+ import { ICONS } from "./icons.js";
5
+
6
+ /**
7
+ * Create the properties panel.
8
+ */
9
+ export function createPropertiesPanel(model) {
10
+ const panel = document.createElement("div");
11
+ panel.className = "awg-properties-panel";
12
+
13
+ // Header
14
+ const header = document.createElement("div");
15
+ header.className = "awg-panel-header";
16
+ header.innerHTML = "<span>Properties</span>";
17
+ panel.appendChild(header);
18
+
19
+ // Content
20
+ const content = document.createElement("div");
21
+ content.className = "awg-properties-content";
22
+
23
+ function renderProperties() {
24
+ content.innerHTML = "";
25
+
26
+ const selectedNode = model.get("selected_node");
27
+ const selectedEdge = model.get("selected_edge");
28
+
29
+ if (!selectedNode && !selectedEdge) {
30
+ const empty = document.createElement("div");
31
+ empty.className = "awg-properties-empty";
32
+ empty.textContent = "Click a node or edge to view properties";
33
+ content.appendChild(empty);
34
+ return;
35
+ }
36
+
37
+ if (selectedNode) {
38
+ renderNodeProperties(content, selectedNode);
39
+ }
40
+
41
+ if (selectedEdge) {
42
+ renderEdgeProperties(content, selectedEdge);
43
+ }
44
+ }
45
+
46
+ model.on("change:selected_node", renderProperties);
47
+ model.on("change:selected_edge", renderProperties);
48
+ renderProperties();
49
+
50
+ panel.appendChild(content);
51
+ return panel;
52
+ }
53
+
54
+ /**
55
+ * Render node properties.
56
+ */
57
+ function renderNodeProperties(content, node) {
58
+ // Node header
59
+ const nodeHeader = document.createElement("div");
60
+ nodeHeader.className = "awg-properties-header";
61
+ nodeHeader.innerHTML = `${ICONS.node} <span class="awg-properties-type">Node</span>`;
62
+ content.appendChild(nodeHeader);
63
+
64
+ // Node ID
65
+ const idItem = document.createElement("div");
66
+ idItem.className = "awg-property-item";
67
+ idItem.innerHTML = `<span class="awg-property-key">id</span><span class="awg-property-value">${node.id || "N/A"}</span>`;
68
+ content.appendChild(idItem);
69
+
70
+ // Labels
71
+ if (node.labels && node.labels.length > 0) {
72
+ const labelsItem = document.createElement("div");
73
+ labelsItem.className = "awg-property-item";
74
+ labelsItem.innerHTML = `<span class="awg-property-key">labels</span><span class="awg-property-value awg-property-labels">${node.labels.map((l) => `<span class="awg-label-tag">${l}</span>`).join("")}</span>`;
75
+ content.appendChild(labelsItem);
76
+ }
77
+
78
+ // Other properties
79
+ Object.entries(node).forEach(([key, value]) => {
80
+ if (["id", "labels", "label", "x", "y", "size", "color"].includes(key)) return;
81
+ const item = document.createElement("div");
82
+ item.className = "awg-property-item";
83
+ const displayValue = typeof value === "object" ? JSON.stringify(value) : String(value);
84
+ item.innerHTML = `<span class="awg-property-key">${key}</span><span class="awg-property-value">${displayValue}</span>`;
85
+ content.appendChild(item);
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Render edge properties.
91
+ */
92
+ function renderEdgeProperties(content, edge) {
93
+ // Edge header
94
+ const edgeHeader = document.createElement("div");
95
+ edgeHeader.className = "awg-properties-header";
96
+ edgeHeader.innerHTML = `${ICONS.edge} <span class="awg-properties-type">Relationship</span>`;
97
+ content.appendChild(edgeHeader);
98
+
99
+ // Edge type/label
100
+ if (edge.label) {
101
+ const typeItem = document.createElement("div");
102
+ typeItem.className = "awg-property-item";
103
+ typeItem.innerHTML = `<span class="awg-property-key">type</span><span class="awg-property-value"><span class="awg-edge-tag">${edge.label}</span></span>`;
104
+ content.appendChild(typeItem);
105
+ }
106
+
107
+ // Source/Target
108
+ const sourceItem = document.createElement("div");
109
+ sourceItem.className = "awg-property-item";
110
+ sourceItem.innerHTML = `<span class="awg-property-key">source</span><span class="awg-property-value awg-property-truncate">${edge.source}</span>`;
111
+ content.appendChild(sourceItem);
112
+
113
+ const targetItem = document.createElement("div");
114
+ targetItem.className = "awg-property-item";
115
+ targetItem.innerHTML = `<span class="awg-property-key">target</span><span class="awg-property-value awg-property-truncate">${edge.target}</span>`;
116
+ content.appendChild(targetItem);
117
+
118
+ // Other properties
119
+ Object.entries(edge).forEach(([key, value]) => {
120
+ if (["source", "target", "label", "size", "color"].includes(key)) return;
121
+ const item = document.createElement("div");
122
+ item.className = "awg-property-item";
123
+ const displayValue = typeof value === "object" ? JSON.stringify(value) : String(value);
124
+ item.innerHTML = `<span class="awg-property-key">${key}</span><span class="awg-property-value">${displayValue}</span>`;
125
+ content.appendChild(item);
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Toggle properties panel visibility.
131
+ */
132
+ export function togglePropertiesPanel(wrapper) {
133
+ const panel = wrapper.querySelector(".awg-properties-panel");
134
+ if (panel) {
135
+ panel.classList.toggle("awg-panel-open");
136
+ }
137
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Schema browser sidebar component.
3
+ */
4
+ import { ICONS } from "./icons.js";
5
+ import { fetchSchema } from "./neo4j.js";
6
+
7
+ /**
8
+ * Create the schema sidebar panel.
9
+ */
10
+ export function createSchemaPanel(model, onExecuteQuery) {
11
+ const panel = document.createElement("div");
12
+ panel.className = "awg-schema-panel";
13
+
14
+ // Header
15
+ const header = document.createElement("div");
16
+ header.className = "awg-panel-header";
17
+ header.innerHTML = `<span>Schema</span>`;
18
+
19
+ const refreshBtn = document.createElement("button");
20
+ refreshBtn.className = "awg-btn awg-btn-icon awg-btn-sm";
21
+ refreshBtn.innerHTML = ICONS.refresh;
22
+ refreshBtn.title = "Refresh schema";
23
+ refreshBtn.addEventListener("click", () => fetchSchema(model));
24
+ header.appendChild(refreshBtn);
25
+
26
+ panel.appendChild(header);
27
+
28
+ // Content
29
+ const content = document.createElement("div");
30
+ content.className = "awg-schema-content";
31
+
32
+ function renderSchema() {
33
+ content.innerHTML = "";
34
+
35
+ const nodeTypes = model.get("schema_node_types") || [];
36
+ const edgeTypes = model.get("schema_edge_types") || [];
37
+
38
+ if (nodeTypes.length === 0 && edgeTypes.length === 0) {
39
+ const empty = document.createElement("div");
40
+ empty.className = "awg-schema-empty";
41
+ empty.textContent = "Connect to load schema";
42
+ content.appendChild(empty);
43
+ return;
44
+ }
45
+
46
+ // Node types section
47
+ if (nodeTypes.length > 0) {
48
+ const nodeSection = document.createElement("div");
49
+ nodeSection.className = "awg-schema-section";
50
+
51
+ const nodeHeader = document.createElement("div");
52
+ nodeHeader.className = "awg-schema-section-header";
53
+ nodeHeader.innerHTML = `${ICONS.node} <span>Node Labels</span>`;
54
+ nodeSection.appendChild(nodeHeader);
55
+
56
+ nodeTypes.forEach(({ label, properties }) => {
57
+ const item = createSchemaItem(label, properties, "node", model, onExecuteQuery);
58
+ nodeSection.appendChild(item);
59
+ });
60
+
61
+ content.appendChild(nodeSection);
62
+ }
63
+
64
+ // Edge types section
65
+ if (edgeTypes.length > 0) {
66
+ const edgeSection = document.createElement("div");
67
+ edgeSection.className = "awg-schema-section";
68
+
69
+ const edgeHeader = document.createElement("div");
70
+ edgeHeader.className = "awg-schema-section-header";
71
+ edgeHeader.innerHTML = `${ICONS.edge} <span>Relationships</span>`;
72
+ edgeSection.appendChild(edgeHeader);
73
+
74
+ edgeTypes.forEach(({ type, properties }) => {
75
+ const item = createSchemaItem(type, properties, "edge", model, onExecuteQuery);
76
+ edgeSection.appendChild(item);
77
+ });
78
+
79
+ content.appendChild(edgeSection);
80
+ }
81
+ }
82
+
83
+ model.on("change:schema_node_types", renderSchema);
84
+ model.on("change:schema_edge_types", renderSchema);
85
+ renderSchema();
86
+
87
+ panel.appendChild(content);
88
+ return panel;
89
+ }
90
+
91
+ /**
92
+ * Create a schema item (node label or relationship type).
93
+ */
94
+ function createSchemaItem(name, properties, type, model, onExecuteQuery) {
95
+ const item = document.createElement("div");
96
+ item.className = "awg-schema-item";
97
+
98
+ const itemHeader = document.createElement("div");
99
+ itemHeader.className = "awg-schema-item-header";
100
+
101
+ const expandBtn = document.createElement("span");
102
+ expandBtn.className = "awg-schema-expand";
103
+ expandBtn.innerHTML = ICONS.chevronRight;
104
+
105
+ const nameSpan = document.createElement("span");
106
+ nameSpan.className = "awg-schema-name";
107
+ nameSpan.textContent = name;
108
+
109
+ itemHeader.appendChild(expandBtn);
110
+ itemHeader.appendChild(nameSpan);
111
+
112
+ // Click on name to query
113
+ nameSpan.addEventListener("click", (e) => {
114
+ e.stopPropagation();
115
+ let query;
116
+ if (type === "node") {
117
+ query = `MATCH (n:\`${name}\`) RETURN n LIMIT 25`;
118
+ } else {
119
+ query = `MATCH (a)-[r:\`${name}\`]->(b) RETURN a, r, b LIMIT 25`;
120
+ }
121
+ model.set("query", query);
122
+ model.save_changes();
123
+ onExecuteQuery();
124
+ });
125
+
126
+ // Properties list (collapsed by default)
127
+ const propsList = document.createElement("div");
128
+ propsList.className = "awg-schema-props";
129
+
130
+ if (properties && properties.length > 0) {
131
+ properties.forEach((prop) => {
132
+ const propItem = document.createElement("div");
133
+ propItem.className = "awg-schema-prop";
134
+ propItem.innerHTML = `${ICONS.property} <span>${prop}</span>`;
135
+
136
+ // Click on property to query with that property
137
+ propItem.addEventListener("click", (e) => {
138
+ e.stopPropagation();
139
+ let query;
140
+ if (type === "node") {
141
+ query = `MATCH (n:\`${name}\`) RETURN n.\`${prop}\` AS ${prop}, n LIMIT 25`;
142
+ } else {
143
+ query = `MATCH (a)-[r:\`${name}\`]->(b) RETURN r.\`${prop}\` AS ${prop}, a, r, b LIMIT 25`;
144
+ }
145
+ model.set("query", query);
146
+ model.save_changes();
147
+ onExecuteQuery();
148
+ });
149
+
150
+ propsList.appendChild(propItem);
151
+ });
152
+ } else {
153
+ const noProp = document.createElement("div");
154
+ noProp.className = "awg-schema-prop awg-schema-prop-empty";
155
+ noProp.textContent = "No properties";
156
+ propsList.appendChild(noProp);
157
+ }
158
+
159
+ item.appendChild(itemHeader);
160
+ item.appendChild(propsList);
161
+
162
+ // Toggle expand/collapse
163
+ itemHeader.addEventListener("click", () => {
164
+ item.classList.toggle("awg-schema-item-expanded");
165
+ });
166
+
167
+ return item;
168
+ }
169
+
170
+ /**
171
+ * Toggle schema panel visibility.
172
+ */
173
+ export function toggleSchemaPanel(wrapper) {
174
+ const panel = wrapper.querySelector(".awg-schema-panel");
175
+ if (panel) {
176
+ panel.classList.toggle("awg-panel-open");
177
+ }
178
+ }
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Settings panel component.
3
+ */
4
+ import { ICONS } from "./icons.js";
5
+ import * as neo4jBackend from "./neo4j.js";
6
+
7
+ // Configuration constants
8
+ const BACKENDS = [
9
+ { id: "neo4j", name: "Neo4j" },
10
+ { id: "grafeo", name: "Grafeo" },
11
+ ];
12
+
13
+ const QUERY_LANGUAGES = [
14
+ { id: "cypher", name: "Cypher", enabled: true },
15
+ { id: "gql", name: "GQL", enabled: false },
16
+ { id: "sparql", name: "SPARQL", enabled: false },
17
+ { id: "gremlin", name: "Gremlin", enabled: false },
18
+ { id: "graphql", name: "GraphQL", enabled: false },
19
+ ];
20
+
21
+ /**
22
+ * Create the settings panel.
23
+ */
24
+ export function createSettingsPanel(model) {
25
+ const panel = document.createElement("div");
26
+ panel.className = "awg-settings-panel";
27
+
28
+ // Header
29
+ const header = document.createElement("div");
30
+ header.className = "awg-panel-header";
31
+ header.innerHTML = "<span>Settings</span>";
32
+ panel.appendChild(header);
33
+
34
+ // Form
35
+ const form = document.createElement("div");
36
+ form.className = "awg-panel-form";
37
+
38
+ // Dark mode toggle
39
+ form.appendChild(
40
+ createFormGroup("Theme", () => {
41
+ const toggle = document.createElement("label");
42
+ toggle.className = "awg-toggle";
43
+ const checkbox = document.createElement("input");
44
+ checkbox.type = "checkbox";
45
+ checkbox.checked = model.get("dark_mode");
46
+ checkbox.addEventListener("change", (e) => {
47
+ model.set("dark_mode", e.target.checked);
48
+ model.save_changes();
49
+ });
50
+ model.on("change:dark_mode", () => {
51
+ checkbox.checked = model.get("dark_mode");
52
+ });
53
+ const slider = document.createElement("span");
54
+ slider.className = "awg-toggle-slider";
55
+ const label = document.createElement("span");
56
+ label.className = "awg-toggle-label";
57
+ label.textContent = "Dark mode";
58
+ toggle.appendChild(checkbox);
59
+ toggle.appendChild(slider);
60
+ toggle.appendChild(label);
61
+ return toggle;
62
+ })
63
+ );
64
+
65
+ // Backend selector
66
+ form.appendChild(
67
+ createFormGroup("Backend", () => {
68
+ const select = document.createElement("select");
69
+ select.className = "awg-select";
70
+ BACKENDS.forEach((b) => {
71
+ const opt = document.createElement("option");
72
+ opt.value = b.id;
73
+ opt.textContent = b.name;
74
+ opt.selected = model.get("database_backend") === b.id;
75
+ select.appendChild(opt);
76
+ });
77
+ select.addEventListener("change", (e) => {
78
+ model.set("database_backend", e.target.value);
79
+ model.save_changes();
80
+ updateFormVisibility();
81
+ });
82
+ return select;
83
+ })
84
+ );
85
+
86
+ // Neo4j fields container
87
+ const neo4jFields = document.createElement("div");
88
+ neo4jFields.className = "awg-neo4j-fields";
89
+
90
+ // URI
91
+ neo4jFields.appendChild(
92
+ createFormGroup("URI", () => {
93
+ const input = document.createElement("input");
94
+ input.type = "text";
95
+ input.className = "awg-input";
96
+ input.placeholder = "neo4j+s://localhost:7687";
97
+ input.value = model.get("connection_uri") || "";
98
+ input.addEventListener("input", (e) => {
99
+ model.set("connection_uri", e.target.value);
100
+ model.save_changes();
101
+ });
102
+ return input;
103
+ })
104
+ );
105
+
106
+ // Username
107
+ neo4jFields.appendChild(
108
+ createFormGroup("Username", () => {
109
+ const input = document.createElement("input");
110
+ input.type = "text";
111
+ input.className = "awg-input";
112
+ input.placeholder = "neo4j";
113
+ input.value = model.get("connection_username") || "";
114
+ input.addEventListener("input", (e) => {
115
+ model.set("connection_username", e.target.value);
116
+ model.save_changes();
117
+ });
118
+ return input;
119
+ })
120
+ );
121
+
122
+ // Password
123
+ neo4jFields.appendChild(
124
+ createFormGroup("Password", () => {
125
+ const input = document.createElement("input");
126
+ input.type = "password";
127
+ input.className = "awg-input";
128
+ input.value = model.get("connection_password") || "";
129
+ input.addEventListener("input", (e) => {
130
+ model.set("connection_password", e.target.value);
131
+ model.save_changes();
132
+ });
133
+ return input;
134
+ })
135
+ );
136
+
137
+ form.appendChild(neo4jFields);
138
+
139
+ // Database name
140
+ form.appendChild(
141
+ createFormGroup("Database", () => {
142
+ const input = document.createElement("input");
143
+ input.type = "text";
144
+ input.className = "awg-input";
145
+ input.placeholder = "neo4j";
146
+ input.value = model.get("connection_database") || "neo4j";
147
+ input.addEventListener("input", (e) => {
148
+ model.set("connection_database", e.target.value);
149
+ model.save_changes();
150
+ });
151
+ return input;
152
+ })
153
+ );
154
+
155
+ // Query language
156
+ form.appendChild(
157
+ createFormGroup("Language", () => {
158
+ const select = document.createElement("select");
159
+ select.className = "awg-select";
160
+ QUERY_LANGUAGES.forEach((lang) => {
161
+ const opt = document.createElement("option");
162
+ opt.value = lang.id;
163
+ opt.textContent = lang.name;
164
+ opt.disabled = !lang.enabled;
165
+ opt.selected = model.get("query_language") === lang.id;
166
+ select.appendChild(opt);
167
+ });
168
+ select.addEventListener("change", (e) => {
169
+ model.set("query_language", e.target.value);
170
+ model.save_changes();
171
+ });
172
+ return select;
173
+ })
174
+ );
175
+
176
+ panel.appendChild(form);
177
+
178
+ // Connect button
179
+ const actions = document.createElement("div");
180
+ actions.className = "awg-panel-actions";
181
+
182
+ const connectBtn = document.createElement("button");
183
+ connectBtn.className = "awg-btn awg-btn-primary awg-btn-full";
184
+
185
+ const statusIndicator = document.createElement("div");
186
+ statusIndicator.className = "awg-connection-status";
187
+
188
+ function updateConnectButton() {
189
+ const status = model.get("connection_status");
190
+ const backend = model.get("database_backend");
191
+
192
+ if (backend === "grafeo") {
193
+ connectBtn.textContent = "Python Backend";
194
+ connectBtn.disabled = true;
195
+ statusIndicator.innerHTML =
196
+ '<span class="awg-status-dot-inline awg-status-connected"></span> Grafeo';
197
+ } else if (status === "connected") {
198
+ connectBtn.textContent = "Disconnect";
199
+ connectBtn.disabled = false;
200
+ statusIndicator.innerHTML =
201
+ '<span class="awg-status-dot-inline awg-status-connected"></span> Connected';
202
+ } else if (status === "connecting") {
203
+ connectBtn.textContent = "Connecting...";
204
+ connectBtn.disabled = true;
205
+ statusIndicator.innerHTML =
206
+ '<span class="awg-status-dot-inline awg-status-connecting"></span> Connecting';
207
+ } else if (status === "error") {
208
+ connectBtn.textContent = "Retry";
209
+ connectBtn.disabled = false;
210
+ statusIndicator.innerHTML =
211
+ '<span class="awg-status-dot-inline awg-status-error"></span> Error';
212
+ } else {
213
+ connectBtn.textContent = "Connect";
214
+ connectBtn.disabled = false;
215
+ statusIndicator.innerHTML =
216
+ '<span class="awg-status-dot-inline awg-status-disconnected"></span> Disconnected';
217
+ }
218
+ }
219
+
220
+ function updateFormVisibility() {
221
+ const backend = model.get("database_backend");
222
+ neo4jFields.style.display = backend === "neo4j" ? "block" : "none";
223
+ updateConnectButton();
224
+ }
225
+
226
+ connectBtn.addEventListener("click", async () => {
227
+ const backend = model.get("database_backend");
228
+ if (backend !== "neo4j") return;
229
+
230
+ const status = model.get("connection_status");
231
+ if (status === "connected") {
232
+ await neo4jBackend.disconnect(model);
233
+ } else {
234
+ await neo4jBackend.connect(
235
+ model.get("connection_uri"),
236
+ model.get("connection_username"),
237
+ model.get("connection_password"),
238
+ model
239
+ );
240
+ }
241
+ });
242
+
243
+ model.on("change:connection_status", updateConnectButton);
244
+ model.on("change:database_backend", updateFormVisibility);
245
+
246
+ actions.appendChild(statusIndicator);
247
+ actions.appendChild(connectBtn);
248
+ panel.appendChild(actions);
249
+
250
+ // Error display
251
+ const errorDiv = document.createElement("div");
252
+ errorDiv.className = "awg-panel-error";
253
+ function updateError() {
254
+ const err = model.get("query_error");
255
+ errorDiv.textContent = err || "";
256
+ errorDiv.style.display = err ? "block" : "none";
257
+ }
258
+ model.on("change:query_error", updateError);
259
+ updateError();
260
+ panel.appendChild(errorDiv);
261
+
262
+ // Initialize
263
+ updateFormVisibility();
264
+
265
+ return panel;
266
+ }
267
+
268
+ /**
269
+ * Create a form group with label and input.
270
+ */
271
+ function createFormGroup(label, inputFn) {
272
+ const group = document.createElement("div");
273
+ group.className = "awg-form-group";
274
+
275
+ const labelEl = document.createElement("label");
276
+ labelEl.className = "awg-label";
277
+ labelEl.textContent = label;
278
+ group.appendChild(labelEl);
279
+
280
+ group.appendChild(inputFn());
281
+ return group;
282
+ }
283
+
284
+ /**
285
+ * Toggle settings panel visibility.
286
+ */
287
+ export function toggleSettingsPanel(wrapper) {
288
+ const panel = wrapper.querySelector(".awg-settings-panel");
289
+ if (panel) {
290
+ panel.classList.toggle("awg-panel-open");
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Update the status dot appearance.
296
+ */
297
+ export function updateStatusDot(dot, status) {
298
+ dot.className = "awg-status-dot awg-status-" + status;
299
+ }