@matdata/yasgui 5.2.0 → 5.3.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.
@@ -1,6 +1,7 @@
1
1
  import { addClass, removeClass } from "@matdata/yasgui-utils";
2
2
  import "./TabSettingsModal.scss";
3
3
  import Tab from "./Tab";
4
+ import * as ConfigExportImport from "./ConfigExportImport";
4
5
 
5
6
  // Theme toggle icons
6
7
  const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
@@ -126,14 +127,26 @@ export default class TabSettingsModal {
126
127
  addClass(prefixTab, "modalTabButton");
127
128
  prefixTab.onclick = () => this.switchTab("prefix");
128
129
 
130
+ const editorTab = document.createElement("button");
131
+ editorTab.textContent = "Editor";
132
+ addClass(editorTab, "modalTabButton");
133
+ editorTab.onclick = () => this.switchTab("editor");
134
+
129
135
  const endpointsTab = document.createElement("button");
130
136
  endpointsTab.textContent = "Endpoint Buttons";
131
137
  addClass(endpointsTab, "modalTabButton");
132
138
  endpointsTab.onclick = () => this.switchTab("endpoints");
133
139
 
140
+ const importExportTab = document.createElement("button");
141
+ importExportTab.textContent = "Import/Export";
142
+ addClass(importExportTab, "modalTabButton");
143
+ importExportTab.onclick = () => this.switchTab("importexport");
144
+
134
145
  tabsContainer.appendChild(requestTab);
135
146
  tabsContainer.appendChild(prefixTab);
147
+ tabsContainer.appendChild(editorTab);
136
148
  tabsContainer.appendChild(endpointsTab);
149
+ tabsContainer.appendChild(importExportTab);
137
150
  body.appendChild(tabsContainer);
138
151
 
139
152
  // Tab content containers
@@ -147,14 +160,26 @@ export default class TabSettingsModal {
147
160
  prefixContent.id = "prefix-content";
148
161
  this.drawPrefixSettings(prefixContent);
149
162
 
163
+ const editorContent = document.createElement("div");
164
+ addClass(editorContent, "modalTabContent");
165
+ editorContent.id = "editor-content";
166
+ this.drawEditorSettings(editorContent);
167
+
150
168
  const endpointsContent = document.createElement("div");
151
169
  addClass(endpointsContent, "modalTabContent");
152
170
  endpointsContent.id = "endpoints-content";
153
171
  this.drawEndpointButtonsSettings(endpointsContent);
154
172
 
173
+ const importExportContent = document.createElement("div");
174
+ addClass(importExportContent, "modalTabContent");
175
+ importExportContent.id = "importexport-content";
176
+ this.drawImportExportSettings(importExportContent);
177
+
155
178
  body.appendChild(requestContent);
156
179
  body.appendChild(prefixContent);
180
+ body.appendChild(editorContent);
157
181
  body.appendChild(endpointsContent);
182
+ body.appendChild(importExportContent);
158
183
 
159
184
  this.modalContent.appendChild(body);
160
185
 
@@ -189,7 +214,9 @@ export default class TabSettingsModal {
189
214
  if (
190
215
  (tabName === "request" && index === 0) ||
191
216
  (tabName === "prefix" && index === 1) ||
192
- (tabName === "endpoints" && index === 2)
217
+ (tabName === "editor" && index === 2) ||
218
+ (tabName === "endpoints" && index === 3) ||
219
+ (tabName === "importexport" && index === 4)
193
220
  ) {
194
221
  addClass(btn as HTMLElement, "active");
195
222
  } else {
@@ -200,6 +227,11 @@ export default class TabSettingsModal {
200
227
  contents.forEach((content) => {
201
228
  if (content.id === `${tabName}-content`) {
202
229
  addClass(content as HTMLElement, "active");
230
+ // Reload editor settings if switching to editor tab and it hasn't been loaded yet
231
+ if (tabName === "editor" && content.innerHTML.indexOf("not yet initialized") > -1) {
232
+ content.innerHTML = "";
233
+ this.drawEditorSettings(content as HTMLElement);
234
+ }
203
235
  } else {
204
236
  removeClass(content as HTMLElement, "active");
205
237
  }
@@ -246,6 +278,108 @@ export default class TabSettingsModal {
246
278
  container.appendChild(section);
247
279
  }
248
280
 
281
+ private drawEditorSettings(container: HTMLElement) {
282
+ const yasqe = this.tab.getYasqe();
283
+ if (!yasqe) {
284
+ const notice = document.createElement("div");
285
+ notice.textContent = "Query editor is not yet initialized.";
286
+ addClass(notice, "settingsHelp");
287
+ container.appendChild(notice);
288
+ return;
289
+ }
290
+
291
+ // Formatter Type Section
292
+ const formatterSection = document.createElement("div");
293
+ addClass(formatterSection, "settingsSection");
294
+
295
+ const formatterLabel = document.createElement("label");
296
+ formatterLabel.textContent = "Query Formatter";
297
+ addClass(formatterLabel, "settingsLabel");
298
+
299
+ const formatterHelp = document.createElement("div");
300
+ formatterHelp.textContent = "Choose which formatter to use when formatting SPARQL queries (Shift+Ctrl+F).";
301
+ addClass(formatterHelp, "settingsHelp");
302
+
303
+ const formatterSelect = document.createElement("select");
304
+ formatterSelect.id = "formatterTypeSelect";
305
+ addClass(formatterSelect, "settingsSelect");
306
+
307
+ const sparqlFormatterOption = document.createElement("option");
308
+ sparqlFormatterOption.value = "sparql-formatter";
309
+ sparqlFormatterOption.textContent = "SPARQL Formatter (external library)";
310
+ formatterSelect.appendChild(sparqlFormatterOption);
311
+
312
+ const legacyOption = document.createElement("option");
313
+ legacyOption.value = "legacy";
314
+ legacyOption.textContent = "Legacy Formatter (built-in)";
315
+ formatterSelect.appendChild(legacyOption);
316
+
317
+ const currentFormatter = yasqe.persistentConfig?.formatterType || "sparql-formatter";
318
+ formatterSelect.value = currentFormatter;
319
+
320
+ formatterSection.appendChild(formatterLabel);
321
+ formatterSection.appendChild(formatterHelp);
322
+ formatterSection.appendChild(formatterSelect);
323
+ container.appendChild(formatterSection);
324
+
325
+ // Auto-format on Query Section
326
+ const autoformatSection = document.createElement("div");
327
+ addClass(autoformatSection, "settingsSection");
328
+
329
+ const autoformatCheckboxContainer = document.createElement("div");
330
+ addClass(autoformatCheckboxContainer, "checkboxContainer");
331
+
332
+ const autoformatCheckbox = document.createElement("input");
333
+ autoformatCheckbox.type = "checkbox";
334
+ autoformatCheckbox.id = "autoformatOnQuery";
335
+ autoformatCheckbox.checked = yasqe.persistentConfig?.autoformatOnQuery || true;
336
+
337
+ const autoformatLabel = document.createElement("label");
338
+ autoformatLabel.htmlFor = "autoformatOnQuery";
339
+ autoformatLabel.textContent = "Auto-format query before execution";
340
+
341
+ const autoformatHelp = document.createElement("div");
342
+ autoformatHelp.textContent = "Automatically format the query using the selected formatter before executing it.";
343
+ addClass(autoformatHelp, "settingsHelp");
344
+ autoformatHelp.style.marginTop = "5px";
345
+
346
+ autoformatCheckboxContainer.appendChild(autoformatCheckbox);
347
+ autoformatCheckboxContainer.appendChild(autoformatLabel);
348
+
349
+ autoformatSection.appendChild(autoformatCheckboxContainer);
350
+ autoformatSection.appendChild(autoformatHelp);
351
+ container.appendChild(autoformatSection);
352
+
353
+ // CONSTRUCT Variable Validation Section
354
+ const constructValidationSection = document.createElement("div");
355
+ addClass(constructValidationSection, "settingsSection");
356
+
357
+ const constructValidationCheckboxContainer = document.createElement("div");
358
+ addClass(constructValidationCheckboxContainer, "checkboxContainer");
359
+
360
+ const constructValidationCheckbox = document.createElement("input");
361
+ constructValidationCheckbox.type = "checkbox";
362
+ constructValidationCheckbox.id = "checkConstructVariables";
363
+ constructValidationCheckbox.checked = yasqe.config.checkConstructVariables ?? true;
364
+
365
+ const constructValidationLabel = document.createElement("label");
366
+ constructValidationLabel.htmlFor = "checkConstructVariables";
367
+ constructValidationLabel.textContent = "Validate CONSTRUCT query variables";
368
+
369
+ const constructValidationHelp = document.createElement("div");
370
+ constructValidationHelp.textContent =
371
+ "Show warnings for variables used in CONSTRUCT template but not defined in WHERE clause.";
372
+ addClass(constructValidationHelp, "settingsHelp");
373
+ constructValidationHelp.style.marginTop = "5px";
374
+
375
+ constructValidationCheckboxContainer.appendChild(constructValidationCheckbox);
376
+ constructValidationCheckboxContainer.appendChild(constructValidationLabel);
377
+
378
+ constructValidationSection.appendChild(constructValidationCheckboxContainer);
379
+ constructValidationSection.appendChild(constructValidationHelp);
380
+ container.appendChild(constructValidationSection);
381
+ }
382
+
249
383
  private drawRequestSettings(container: HTMLElement) {
250
384
  // This is a simplified version - you can expand based on TabPanel.ts
251
385
  const reqConfig = this.tab.getRequestConfig();
@@ -312,6 +446,11 @@ export default class TabSettingsModal {
312
446
 
313
447
  public open() {
314
448
  this.loadSettings();
449
+ // Reload editor settings in case yasqe wasn't available during init
450
+ const editorContent = this.modalContent.querySelector("#editor-content");
451
+ if (editorContent && editorContent.innerHTML === "") {
452
+ this.drawEditorSettings(editorContent as HTMLElement);
453
+ }
315
454
  addClass(this.modalOverlay, "open");
316
455
  }
317
456
 
@@ -344,6 +483,25 @@ export default class TabSettingsModal {
344
483
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
345
484
  this.tab.yasgui.persistentConfig.setAutoCaptureEnabled(autoCapture);
346
485
 
486
+ // Save editor settings
487
+ const yasqe = this.tab.getYasqe();
488
+ if (yasqe && yasqe.persistentConfig) {
489
+ const formatterSelect = document.getElementById("formatterTypeSelect") as HTMLSelectElement;
490
+ const autoformatCheckbox = document.getElementById("autoformatOnQuery") as HTMLInputElement;
491
+ const constructValidationCheckbox = document.getElementById("checkConstructVariables") as HTMLInputElement;
492
+
493
+ if (formatterSelect) {
494
+ yasqe.persistentConfig.formatterType = formatterSelect.value as "sparql-formatter" | "legacy";
495
+ }
496
+ if (autoformatCheckbox) {
497
+ yasqe.persistentConfig.autoformatOnQuery = autoformatCheckbox.checked;
498
+ }
499
+ if (constructValidationCheckbox) {
500
+ yasqe.setCheckConstructVariables(constructValidationCheckbox.checked);
501
+ }
502
+ yasqe.saveQuery();
503
+ }
504
+
347
505
  // Save request settings
348
506
  const requestContent = this.modalContent.querySelector("#request-content");
349
507
  if (requestContent) {
@@ -414,10 +572,10 @@ export default class TabSettingsModal {
414
572
  // If we didn't find a non-PREFIX line, all lines are PREFIX or empty
415
573
  if (firstNonPrefixLine === 0 && lines.length > 0) {
416
574
  // Check if there's any content at all
417
- const hasContent = lines.some((line) => line.trim().length > 0);
575
+ const hasContent = lines.some((line: string) => line.trim().length > 0);
418
576
  if (
419
577
  !hasContent ||
420
- lines.every((line) => line.trim().length === 0 || line.trim().toUpperCase().startsWith("PREFIX"))
578
+ lines.every((line: string) => line.trim().length === 0 || line.trim().toUpperCase().startsWith("PREFIX"))
421
579
  ) {
422
580
  firstNonPrefixLine = lines.length;
423
581
  }
@@ -574,6 +732,214 @@ export default class TabSettingsModal {
574
732
  return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
575
733
  }
576
734
 
735
+ private drawImportExportSettings(container: HTMLElement) {
736
+ // Export Section
737
+ const exportSection = document.createElement("div");
738
+ addClass(exportSection, "settingsSection");
739
+
740
+ const exportLabel = document.createElement("label");
741
+ exportLabel.textContent = "Export Configuration";
742
+ addClass(exportLabel, "settingsLabel");
743
+
744
+ const exportHelp = document.createElement("div");
745
+ exportHelp.textContent =
746
+ "Export your YASGUI configuration in RDF Turtle format. This includes tabs, queries, endpoints, and preferences.";
747
+ addClass(exportHelp, "settingsHelp");
748
+
749
+ const exportButtonsContainer = document.createElement("div");
750
+ addClass(exportButtonsContainer, "exportButtons");
751
+
752
+ const copyButton = document.createElement("button");
753
+ copyButton.textContent = "📋 Copy to Clipboard";
754
+ copyButton.type = "button";
755
+ addClass(copyButton, "secondaryButton");
756
+ copyButton.onclick = async () => {
757
+ try {
758
+ const config = this.tab.yasgui.persistentConfig.getPersistedConfig();
759
+ await ConfigExportImport.copyConfigToClipboard(config);
760
+ this.showNotification("Configuration copied to clipboard!", "success");
761
+ } catch (error) {
762
+ this.showNotification("Failed to copy to clipboard: " + (error as Error).message, "error");
763
+ }
764
+ };
765
+
766
+ const downloadButton = document.createElement("button");
767
+ downloadButton.textContent = "💾 Download as File";
768
+ downloadButton.type = "button";
769
+ addClass(downloadButton, "primaryButton");
770
+ downloadButton.onclick = () => {
771
+ try {
772
+ const config = this.tab.yasgui.persistentConfig.getPersistedConfig();
773
+ ConfigExportImport.downloadConfigAsFile(config);
774
+ this.showNotification("Configuration downloaded!", "success");
775
+ } catch (error) {
776
+ this.showNotification("Failed to download: " + (error as Error).message, "error");
777
+ }
778
+ };
779
+
780
+ exportButtonsContainer.appendChild(copyButton);
781
+ exportButtonsContainer.appendChild(downloadButton);
782
+
783
+ exportSection.appendChild(exportLabel);
784
+ exportSection.appendChild(exportHelp);
785
+ exportSection.appendChild(exportButtonsContainer);
786
+ container.appendChild(exportSection);
787
+
788
+ // Import Section
789
+ const importSection = document.createElement("div");
790
+ addClass(importSection, "settingsSection");
791
+
792
+ const importLabel = document.createElement("label");
793
+ importLabel.textContent = "Import Configuration";
794
+ addClass(importLabel, "settingsLabel");
795
+
796
+ const importHelp = document.createElement("div");
797
+ importHelp.textContent = "Import a previously exported configuration in RDF Turtle format.";
798
+ addClass(importHelp, "settingsHelp");
799
+
800
+ // Drag and drop area
801
+ const dropZone = document.createElement("div");
802
+ addClass(dropZone, "dropZone");
803
+ dropZone.innerHTML = `
804
+ <div class="dropZoneContent">
805
+ <div class="dropZoneIcon">📁</div>
806
+ <div class="dropZoneText">Drag & drop a .ttl file here</div>
807
+ <div class="dropZoneOr">or</div>
808
+ </div>
809
+ `;
810
+
811
+ const fileInput = document.createElement("input");
812
+ fileInput.type = "file";
813
+ fileInput.accept = ".ttl,.turtle,text/turtle";
814
+ fileInput.style.display = "none";
815
+ fileInput.id = "config-file-input";
816
+
817
+ const browseButton = document.createElement("button");
818
+ browseButton.textContent = "📂 Browse Files";
819
+ browseButton.type = "button";
820
+ addClass(browseButton, "secondaryButton");
821
+ browseButton.onclick = () => fileInput.click();
822
+
823
+ const pasteButton = document.createElement("button");
824
+ pasteButton.textContent = "📋 Paste from Clipboard";
825
+ pasteButton.type = "button";
826
+ addClass(pasteButton, "secondaryButton");
827
+ pasteButton.onclick = async () => {
828
+ try {
829
+ const content = await ConfigExportImport.readConfigFromClipboard();
830
+ await this.importConfiguration(content);
831
+ } catch (error) {
832
+ this.showNotification("Failed to read from clipboard: " + (error as Error).message, "error");
833
+ }
834
+ };
835
+
836
+ // File input change handler
837
+ fileInput.onchange = async (e) => {
838
+ const file = (e.target as HTMLInputElement).files?.[0];
839
+ if (file) {
840
+ try {
841
+ const content = await ConfigExportImport.readConfigFromFile(file);
842
+ await this.importConfiguration(content);
843
+ } catch (error) {
844
+ this.showNotification("Failed to read file: " + (error as Error).message, "error");
845
+ }
846
+ }
847
+ };
848
+
849
+ // Drag and drop handlers
850
+ dropZone.ondragover = (e) => {
851
+ e.preventDefault();
852
+ addClass(dropZone, "dragover");
853
+ };
854
+
855
+ dropZone.ondragleave = () => {
856
+ removeClass(dropZone, "dragover");
857
+ };
858
+
859
+ dropZone.ondrop = async (e) => {
860
+ e.preventDefault();
861
+ removeClass(dropZone, "dragover");
862
+
863
+ const file = e.dataTransfer?.files?.[0];
864
+ if (file) {
865
+ try {
866
+ const content = await ConfigExportImport.readConfigFromFile(file);
867
+ await this.importConfiguration(content);
868
+ } catch (error) {
869
+ this.showNotification("Failed to read file: " + (error as Error).message, "error");
870
+ }
871
+ }
872
+ };
873
+
874
+ const importButtonsContainer = document.createElement("div");
875
+ addClass(importButtonsContainer, "importButtons");
876
+ importButtonsContainer.appendChild(browseButton);
877
+ importButtonsContainer.appendChild(pasteButton);
878
+
879
+ dropZone.appendChild(importButtonsContainer);
880
+
881
+ importSection.appendChild(importLabel);
882
+ importSection.appendChild(importHelp);
883
+ importSection.appendChild(dropZone);
884
+ importSection.appendChild(fileInput);
885
+ container.appendChild(importSection);
886
+ }
887
+
888
+ private async importConfiguration(turtleContent: string) {
889
+ try {
890
+ const parsedConfig = ConfigExportImport.parseFromTurtle(turtleContent);
891
+
892
+ // Confirm with user before importing
893
+ const confirmMsg = `This will replace your current configuration with ${parsedConfig.tabs?.length || 0} tab(s). Continue?`;
894
+ if (!confirm(confirmMsg)) {
895
+ return;
896
+ }
897
+
898
+ // Close the modal first
899
+ this.close();
900
+
901
+ // Apply theme if provided
902
+ if (parsedConfig.theme) {
903
+ this.tab.yasgui.themeManager.setTheme(parsedConfig.theme);
904
+ }
905
+
906
+ // Apply global orientation if provided
907
+ if (parsedConfig.orientation) {
908
+ this.tab.yasgui.config.orientation = parsedConfig.orientation;
909
+ }
910
+
911
+ // Close all existing tabs
912
+ const existingTabIds = [...this.tab.yasgui.persistentConfig.getTabs()];
913
+ for (const tabId of existingTabIds) {
914
+ const tab = this.tab.yasgui.getTab(tabId);
915
+ if (tab) {
916
+ tab.close();
917
+ }
918
+ }
919
+
920
+ // Update the configuration storage
921
+ this.tab.yasgui.persistentConfig.updatePersistedConfig(parsedConfig);
922
+
923
+ window.location.reload();
924
+ } catch (error) {
925
+ this.showNotification("Failed to import configuration: " + (error as Error).message, "error");
926
+ }
927
+ }
928
+
929
+ private showNotification(message: string, type: "success" | "error") {
930
+ const notification = document.createElement("div");
931
+ addClass(notification, "importExportNotification", type);
932
+ notification.textContent = message;
933
+
934
+ this.modalContent.appendChild(notification);
935
+
936
+ setTimeout(() => {
937
+ if (notification.parentNode) {
938
+ notification.parentNode.removeChild(notification);
939
+ }
940
+ }, 5000);
941
+ }
942
+
577
943
  public destroy() {
578
944
  if (this.modalOverlay && this.modalOverlay.parentNode) {
579
945
  this.modalOverlay.parentNode.removeChild(this.modalOverlay);
@@ -0,0 +1,36 @@
1
+ declare module "sortablejs" {
2
+ interface SortableOptions {
3
+ animation?: number;
4
+ handle?: string;
5
+ draggable?: string;
6
+ ghostClass?: string;
7
+ chosenClass?: string;
8
+ dragClass?: string;
9
+ filter?: string;
10
+ preventOnFilter?: boolean;
11
+ onEnd?: (evt: SortableEvent) => void;
12
+ onStart?: (evt: SortableEvent) => void;
13
+ onUpdate?: (evt: SortableEvent) => void;
14
+ onSort?: (evt: SortableEvent) => void;
15
+ onMove?: (evt: SortableEvent, originalEvent: any) => boolean | -1 | 1 | undefined;
16
+ [key: string]: any;
17
+ }
18
+
19
+ interface SortableEvent {
20
+ oldIndex?: number;
21
+ newIndex?: number;
22
+ item: HTMLElement;
23
+ to: HTMLElement;
24
+ from: HTMLElement;
25
+ [key: string]: any;
26
+ }
27
+
28
+ class Sortable {
29
+ constructor(el: HTMLElement, options?: SortableOptions);
30
+ destroy(): void;
31
+ option(name: string, value?: any): any;
32
+ static create(el: HTMLElement, options?: SortableOptions): Sortable;
33
+ }
34
+
35
+ export default Sortable;
36
+ }