@matdata/yasgui 5.15.0 → 5.17.0

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 (52) hide show
  1. package/build/ts/src/Tab.d.ts +2 -0
  2. package/build/ts/src/Tab.js +51 -2
  3. package/build/ts/src/Tab.js.map +1 -1
  4. package/build/ts/src/TabContextMenu.d.ts +2 -1
  5. package/build/ts/src/TabContextMenu.js +11 -3
  6. package/build/ts/src/TabContextMenu.js.map +1 -1
  7. package/build/ts/src/TabElements.js +13 -3
  8. package/build/ts/src/TabElements.js.map +1 -1
  9. package/build/ts/src/TabSettingsModal.d.ts +8 -0
  10. package/build/ts/src/TabSettingsModal.js +89 -3
  11. package/build/ts/src/TabSettingsModal.js.map +1 -1
  12. package/build/ts/src/endpointSelect.js +1 -1
  13. package/build/ts/src/queryManagement/QueryBrowser.d.ts +1 -0
  14. package/build/ts/src/queryManagement/QueryBrowser.js +62 -0
  15. package/build/ts/src/queryManagement/QueryBrowser.js.map +1 -1
  16. package/build/ts/src/queryManagement/SaveManagedQueryModal.d.ts +6 -0
  17. package/build/ts/src/queryManagement/SaveManagedQueryModal.js +67 -3
  18. package/build/ts/src/queryManagement/SaveManagedQueryModal.js.map +1 -1
  19. package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.d.ts +1 -0
  20. package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js +18 -0
  21. package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js.map +1 -1
  22. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.d.ts +1 -0
  23. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.js +17 -0
  24. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.js.map +1 -1
  25. package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.d.ts +1 -0
  26. package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js +61 -0
  27. package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js.map +1 -1
  28. package/build/ts/src/queryManagement/backends/WorkspaceBackend.d.ts +1 -0
  29. package/build/ts/src/version.d.ts +1 -1
  30. package/build/ts/src/version.js +1 -1
  31. package/build/yasgui.min.css +1 -1
  32. package/build/yasgui.min.css.map +3 -3
  33. package/build/yasgui.min.js +254 -216
  34. package/build/yasgui.min.js.map +4 -4
  35. package/package.json +3 -3
  36. package/src/Tab.ts +64 -2
  37. package/src/TabContextMenu.ts +15 -5
  38. package/src/TabElements.scss +17 -0
  39. package/src/TabElements.ts +17 -3
  40. package/src/TabSettingsModal.scss +73 -0
  41. package/src/TabSettingsModal.ts +117 -3
  42. package/src/endpointSelect.scss +0 -22
  43. package/src/endpointSelect.ts +1 -1
  44. package/src/queryManagement/QueryBrowser.ts +72 -0
  45. package/src/queryManagement/SaveManagedQueryModal.ts +82 -3
  46. package/src/queryManagement/backends/GitWorkspaceBackend.ts +19 -0
  47. package/src/queryManagement/backends/InMemoryWorkspaceBackend.ts +17 -0
  48. package/src/queryManagement/backends/SparqlWorkspaceBackend.ts +69 -0
  49. package/src/queryManagement/backends/WorkspaceBackend.ts +6 -0
  50. package/src/tab.scss +1 -0
  51. package/src/themes.scss +0 -10
  52. package/src/version.ts +1 -1
@@ -41,6 +41,8 @@ export default class SaveManagedQueryModal {
41
41
  private saveBtn!: HTMLButtonElement;
42
42
  private cancelBtn!: HTMLButtonElement;
43
43
  private saveBtnOriginalText = "Save";
44
+ private titleEl!: HTMLHeadingElement;
45
+ private workspaceRowEl!: HTMLDivElement;
44
46
 
45
47
  private filenameTouched = false;
46
48
  private folderPickerOpen = false;
@@ -49,6 +51,8 @@ export default class SaveManagedQueryModal {
49
51
 
50
52
  private resolve?: (value: SaveManagedQueryModalResult) => void;
51
53
  private reject?: (reason?: unknown) => void;
54
+ private moveResolve?: (value: string | undefined) => void;
55
+ private isMoveMode = false;
52
56
 
53
57
  constructor(yasgui: Yasgui) {
54
58
  this.yasgui = yasgui;
@@ -65,6 +69,7 @@ export default class SaveManagedQueryModal {
65
69
 
66
70
  const titleEl = document.createElement("h2");
67
71
  titleEl.textContent = "Save as managed query";
72
+ this.titleEl = titleEl;
68
73
 
69
74
  const closeBtn = document.createElement("button");
70
75
  closeBtn.type = "button";
@@ -203,6 +208,7 @@ export default class SaveManagedQueryModal {
203
208
  this.messageEl.setAttribute("aria-label", "Save message");
204
209
 
205
210
  const workspaceRow = this.row("Workspace", this.workspaceSelectEl);
211
+ this.workspaceRowEl = workspaceRow;
206
212
  const folderRow = this.folderRow();
207
213
  this.nameRowEl = this.row("Name", this.nameEl);
208
214
  this.filenameRowEl = this.row("Filename", this.filenameEl);
@@ -343,9 +349,24 @@ export default class SaveManagedQueryModal {
343
349
  this.mouseDownOnOverlay = false;
344
350
  this.close();
345
351
  this.overlayEl.remove();
346
- this.reject?.(new Error("cancelled"));
347
- this.resolve = undefined;
348
- this.reject = undefined;
352
+ if (this.isMoveMode) {
353
+ this.resetMoveMode();
354
+ const resolve = this.moveResolve;
355
+ this.moveResolve = undefined;
356
+ resolve?.(undefined);
357
+ } else {
358
+ this.reject?.(new Error("cancelled"));
359
+ this.resolve = undefined;
360
+ this.reject = undefined;
361
+ }
362
+ }
363
+
364
+ private resetMoveMode() {
365
+ this.isMoveMode = false;
366
+ this.titleEl.textContent = "Save as managed query";
367
+ this.saveBtn.textContent = "Save";
368
+ this.saveBtnOriginalText = "Save";
369
+ this.workspaceRowEl.style.display = "";
349
370
  }
350
371
 
351
372
  private setLoading(isLoading: boolean) {
@@ -371,6 +392,18 @@ export default class SaveManagedQueryModal {
371
392
  }
372
393
 
373
394
  private submit() {
395
+ if (this.isMoveMode) {
396
+ const folderPath = this.folderPathEl.value.trim();
397
+ this.mouseDownOnOverlay = false;
398
+ this.close();
399
+ this.overlayEl.remove();
400
+ this.resetMoveMode();
401
+ const resolve = this.moveResolve;
402
+ this.moveResolve = undefined;
403
+ resolve?.(folderPath);
404
+ return;
405
+ }
406
+
374
407
  const workspaceId = this.workspaceSelectEl.value;
375
408
  const name = this.nameEl.value.trim();
376
409
  const filename = this.filenameEl.value.trim();
@@ -570,4 +603,50 @@ export default class SaveManagedQueryModal {
570
603
  this.newFolderNameEl.value = "";
571
604
  void this.refreshFolderPicker();
572
605
  }
606
+
607
+ /**
608
+ * Show a simplified modal containing only the folder picker, for moving an existing query.
609
+ * Resolves with the selected folder path (empty string = root), or `undefined` if cancelled.
610
+ */
611
+ public async showFolderPickerOnly(workspaceId: string, currentFolderPath: string): Promise<string | undefined> {
612
+ const workspaces = this.yasgui.persistentConfig.getWorkspaces();
613
+
614
+ this.workspaceSelectEl.innerHTML = "";
615
+ for (const w of workspaces) {
616
+ const opt = document.createElement("option");
617
+ opt.value = w.id;
618
+ opt.textContent = w.label;
619
+ this.workspaceSelectEl.appendChild(opt);
620
+ }
621
+ this.workspaceSelectEl.value = workspaceId;
622
+
623
+ // Hide rows not needed for a move operation.
624
+ this.workspaceRowEl.style.display = "none";
625
+ this.nameRowEl.style.display = "none";
626
+ this.filenameRowEl.style.display = "none";
627
+ this.messageRowEl.style.display = "none";
628
+
629
+ this.folderPathEl.value = currentFolderPath;
630
+ this.filenameTouched = false;
631
+ this.folderPickerOpen = false;
632
+ removeClass(this.folderPickerEl, "open");
633
+ this.folderBrowsePath = currentFolderPath;
634
+ this.folderPickerErrorEl.textContent = "";
635
+ this.folderPickerListEl.innerHTML = "";
636
+ this.mouseDownOnOverlay = false;
637
+
638
+ this.titleEl.textContent = "Move to folder";
639
+ this.saveBtn.textContent = "Move";
640
+ this.saveBtnOriginalText = "Move";
641
+
642
+ this.isMoveMode = true;
643
+
644
+ document.body.appendChild(this.overlayEl);
645
+ this.open();
646
+ this.folderPickerToggleEl.focus();
647
+
648
+ return new Promise<string | undefined>((resolve) => {
649
+ this.moveResolve = resolve;
650
+ });
651
+ }
573
652
  }
@@ -69,6 +69,25 @@ export default class GitWorkspaceBackend implements WorkspaceBackend {
69
69
  return this.client.deleteQuery(this.config, _queryId);
70
70
  }
71
71
 
72
+ async moveQuery(queryId: string, newFolderId: string): Promise<string> {
73
+ if (!this.client) throw this.missingClientError();
74
+
75
+ const parts = queryId.split("/").filter(Boolean);
76
+ const filename = parts[parts.length - 1] || queryId;
77
+ const folder = newFolderId.replace(/^\/+|\/+$/g, "");
78
+
79
+ const newPath = folder ? `${folder}/${filename}` : filename;
80
+ if (newPath === queryId) return queryId;
81
+
82
+ const read = await this.client.readQuery(this.config, queryId);
83
+ await this.client.writeQuery(this.config, newPath, read.queryText, {
84
+ message: `Move ${filename} to ${folder || "(root)"}`,
85
+ });
86
+ await this.client.deleteQuery(this.config, queryId);
87
+
88
+ return newPath;
89
+ }
90
+
72
91
  async renameQuery(queryId: string, newLabel: string): Promise<void> {
73
92
  if (!this.client) throw this.missingClientError();
74
93
 
@@ -152,6 +152,23 @@ export default class InMemoryWorkspaceBackend implements WorkspaceBackend {
152
152
  return { queryText: found.queryText, versionTag: found.id };
153
153
  }
154
154
 
155
+ async moveQuery(queryId: string, newFolderId: string): Promise<string> {
156
+ const versions = this.versionsByQueryId.get(queryId);
157
+ if (!versions || versions.length === 0) throw new WorkspaceBackendError("NOT_FOUND", "Query not found");
158
+
159
+ const filename = basename(queryId);
160
+ const folder = normalizeFolderId(newFolderId);
161
+ const newId = folder ? `${folder}/${filename}` : filename;
162
+ if (newId === queryId) return queryId;
163
+
164
+ if (this.versionsByQueryId.has(newId))
165
+ throw new WorkspaceBackendError("CONFLICT", "A query already exists at this path");
166
+
167
+ this.versionsByQueryId.delete(queryId);
168
+ this.versionsByQueryId.set(newId, versions);
169
+ return newId;
170
+ }
171
+
155
172
  async renameQuery(queryId: string, newLabel: string): Promise<void> {
156
173
  const trimmed = newLabel.trim();
157
174
  if (!trimmed) throw new WorkspaceBackendError("UNKNOWN", "New name is required");
@@ -422,6 +422,75 @@ WHERE { OPTIONAL { ${iri(mqIri)} rdfs:label ?oldLabel . } }
422
422
  await this.sparqlUpdate(update);
423
423
  }
424
424
 
425
+ async moveQuery(queryId: string, newFolderId: string): Promise<string> {
426
+ const mqIriValue = this.resolveManagedQueryIri(queryId);
427
+ const workspaceIri = this.config.workspaceIri;
428
+ const folder = (newFolderId || "").replace(/^\/+|\/+$/g, "");
429
+
430
+ const newContainerIriValue = folder ? mintFolderIri(workspaceIri, folder) : workspaceIri;
431
+
432
+ // Fetch the current container to detect no-ops.
433
+ const currentContainerIriValue = await (async () => {
434
+ const q = `
435
+ PREFIX yasgui: <https://matdata.eu/ns/yasgui#>
436
+ PREFIX dcterms: <http://purl.org/dc/terms/>
437
+
438
+ SELECT ?container WHERE {
439
+ ${iri(mqIriValue)} a yasgui:ManagedQuery ;
440
+ dcterms:isPartOf ?container .
441
+ }
442
+ LIMIT 1`;
443
+ const res = await this.sparqlQuery<SparqlJsonResults>(q);
444
+ const row = this.getBindings(res)[0];
445
+ const container = row?.container?.value;
446
+ if (!container) throw new WorkspaceBackendError("NOT_FOUND", "Query not found");
447
+ return container;
448
+ })();
449
+
450
+ if (currentContainerIriValue === newContainerIriValue) return queryId;
451
+
452
+ // Ensure each ancestor folder exists in the store.
453
+ if (folder) {
454
+ const parts = splitPath(folder);
455
+ const folderTriples: string[] = [];
456
+ for (let i = 0; i < parts.length; i++) {
457
+ const subPath = parts.slice(0, i + 1).join("/");
458
+ const folderIriValue = mintFolderIri(workspaceIri, subPath);
459
+ const folderLabel = parts[i];
460
+ folderTriples.push(`${iri(folderIriValue)} a <https://matdata.eu/ns/yasgui#WorkspaceFolder> ;`);
461
+ folderTriples.push(` <http://www.w3.org/2004/02/skos/core#inScheme> ${iri(workspaceIri)} ;`);
462
+ folderTriples.push(` <http://www.w3.org/2000/01/rdf-schema#label> ${sparqlStringLiteral(folderLabel)} .`);
463
+
464
+ if (i > 0) {
465
+ const parentPath = parts.slice(0, i).join("/");
466
+ const parentIri = mintFolderIri(workspaceIri, parentPath);
467
+ folderTriples.push(
468
+ `${iri(folderIriValue)} <http://www.w3.org/2004/02/skos/core#broader> ${iri(parentIri)} .`,
469
+ );
470
+ }
471
+ }
472
+ await this.sparqlUpdate(`
473
+ PREFIX yasgui: <https://matdata.eu/ns/yasgui#>
474
+
475
+ INSERT DATA {
476
+ ${iri(workspaceIri)} a yasgui:Workspace .
477
+ ${folderTriples.join("\n ")}
478
+ }
479
+ `);
480
+ }
481
+
482
+ // Update dcterms:isPartOf to point to the new container.
483
+ await this.sparqlUpdate(`
484
+ PREFIX dcterms: <http://purl.org/dc/terms/>
485
+
486
+ DELETE { ${iri(mqIriValue)} dcterms:isPartOf ${iri(currentContainerIriValue)} . }
487
+ INSERT { ${iri(mqIriValue)} dcterms:isPartOf ${iri(newContainerIriValue)} . }
488
+ WHERE { ${iri(mqIriValue)} dcterms:isPartOf ${iri(currentContainerIriValue)} . }
489
+ `);
490
+
491
+ return queryId;
492
+ }
493
+
425
494
  async deleteQuery(queryId: string): Promise<void> {
426
495
  const mqIri = this.resolveManagedQueryIri(queryId);
427
496
  const update = `
@@ -23,6 +23,12 @@ export interface WorkspaceBackend {
23
23
  */
24
24
  renameQuery?(queryId: string, newLabel: string): Promise<void>;
25
25
 
26
+ /**
27
+ * Optional: Move a query to a different folder.
28
+ * Returns the new query ID (may differ from the original for Git backends where the ID encodes the path).
29
+ */
30
+ moveQuery?(queryId: string, newFolderId: string): Promise<string>;
31
+
26
32
  /**
27
33
  * Optional: Delete a query and its version history.
28
34
  * Implementations may not support this (e.g., some Git provider clients).
package/src/tab.scss CHANGED
@@ -169,5 +169,6 @@
169
169
  flex-shrink: 0;
170
170
  max-height: 35px;
171
171
  gap: 2px;
172
+ position: relative; // For hamburger dropdown positioning
172
173
  }
173
174
  }
package/src/themes.scss CHANGED
@@ -232,16 +232,6 @@
232
232
  color: var(--yasgui-text-muted);
233
233
  }
234
234
  }
235
-
236
- .clearEndpointBtn {
237
- background-color: var(--yasgui-bg-secondary) !important;
238
- border-color: var(--yasgui-border-color) !important;
239
- color: var(--yasgui-button-text) !important;
240
-
241
- &:hover {
242
- background-color: var(--yasgui-bg-tertiary) !important;
243
- }
244
- }
245
235
  }
246
236
 
247
237
  // Yasqe (editor) theming
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Version information for YASGUI
2
2
  // This file is auto-generated during build - do not edit manually
3
- export const VERSION = "5.15.0";
3
+ export const VERSION = "5.17.0";