@matdata/yasgui 5.2.0 → 5.4.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,8 @@
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";
5
+ import { VERSION } from "./version";
4
6
 
5
7
  // Theme toggle icons
6
8
  const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
@@ -126,14 +128,38 @@ export default class TabSettingsModal {
126
128
  addClass(prefixTab, "modalTabButton");
127
129
  prefixTab.onclick = () => this.switchTab("prefix");
128
130
 
131
+ const editorTab = document.createElement("button");
132
+ editorTab.textContent = "Editor";
133
+ addClass(editorTab, "modalTabButton");
134
+ editorTab.onclick = () => this.switchTab("editor");
135
+
129
136
  const endpointsTab = document.createElement("button");
130
137
  endpointsTab.textContent = "Endpoint Buttons";
131
138
  addClass(endpointsTab, "modalTabButton");
132
139
  endpointsTab.onclick = () => this.switchTab("endpoints");
133
140
 
141
+ const importExportTab = document.createElement("button");
142
+ importExportTab.textContent = "Import/Export";
143
+ addClass(importExportTab, "modalTabButton");
144
+ importExportTab.onclick = () => this.switchTab("importexport");
145
+
146
+ const shortcutsTab = document.createElement("button");
147
+ shortcutsTab.textContent = "Keyboard Shortcuts";
148
+ addClass(shortcutsTab, "modalTabButton");
149
+ shortcutsTab.onclick = () => this.switchTab("shortcuts");
150
+
151
+ const aboutTab = document.createElement("button");
152
+ aboutTab.textContent = "About";
153
+ addClass(aboutTab, "modalTabButton");
154
+ aboutTab.onclick = () => this.switchTab("about");
155
+
134
156
  tabsContainer.appendChild(requestTab);
135
157
  tabsContainer.appendChild(prefixTab);
158
+ tabsContainer.appendChild(editorTab);
136
159
  tabsContainer.appendChild(endpointsTab);
160
+ tabsContainer.appendChild(importExportTab);
161
+ tabsContainer.appendChild(shortcutsTab);
162
+ tabsContainer.appendChild(aboutTab);
137
163
  body.appendChild(tabsContainer);
138
164
 
139
165
  // Tab content containers
@@ -147,14 +173,38 @@ export default class TabSettingsModal {
147
173
  prefixContent.id = "prefix-content";
148
174
  this.drawPrefixSettings(prefixContent);
149
175
 
176
+ const editorContent = document.createElement("div");
177
+ addClass(editorContent, "modalTabContent");
178
+ editorContent.id = "editor-content";
179
+ this.drawEditorSettings(editorContent);
180
+
150
181
  const endpointsContent = document.createElement("div");
151
182
  addClass(endpointsContent, "modalTabContent");
152
183
  endpointsContent.id = "endpoints-content";
153
184
  this.drawEndpointButtonsSettings(endpointsContent);
154
185
 
186
+ const importExportContent = document.createElement("div");
187
+ addClass(importExportContent, "modalTabContent");
188
+ importExportContent.id = "importexport-content";
189
+ this.drawImportExportSettings(importExportContent);
190
+
191
+ const shortcutsContent = document.createElement("div");
192
+ addClass(shortcutsContent, "modalTabContent");
193
+ shortcutsContent.id = "shortcuts-content";
194
+ this.drawKeyboardShortcuts(shortcutsContent);
195
+
196
+ const aboutContent = document.createElement("div");
197
+ addClass(aboutContent, "modalTabContent");
198
+ aboutContent.id = "about-content";
199
+ this.drawAboutSettings(aboutContent);
200
+
155
201
  body.appendChild(requestContent);
156
202
  body.appendChild(prefixContent);
203
+ body.appendChild(editorContent);
157
204
  body.appendChild(endpointsContent);
205
+ body.appendChild(importExportContent);
206
+ body.appendChild(shortcutsContent);
207
+ body.appendChild(aboutContent);
158
208
 
159
209
  this.modalContent.appendChild(body);
160
210
 
@@ -189,7 +239,11 @@ export default class TabSettingsModal {
189
239
  if (
190
240
  (tabName === "request" && index === 0) ||
191
241
  (tabName === "prefix" && index === 1) ||
192
- (tabName === "endpoints" && index === 2)
242
+ (tabName === "editor" && index === 2) ||
243
+ (tabName === "endpoints" && index === 3) ||
244
+ (tabName === "importexport" && index === 4) ||
245
+ (tabName === "shortcuts" && index === 5) ||
246
+ (tabName === "about" && index === 6)
193
247
  ) {
194
248
  addClass(btn as HTMLElement, "active");
195
249
  } else {
@@ -200,6 +254,11 @@ export default class TabSettingsModal {
200
254
  contents.forEach((content) => {
201
255
  if (content.id === `${tabName}-content`) {
202
256
  addClass(content as HTMLElement, "active");
257
+ // Reload editor settings if switching to editor tab and it hasn't been loaded yet
258
+ if (tabName === "editor" && content.innerHTML.indexOf("not yet initialized") > -1) {
259
+ content.innerHTML = "";
260
+ this.drawEditorSettings(content as HTMLElement);
261
+ }
203
262
  } else {
204
263
  removeClass(content as HTMLElement, "active");
205
264
  }
@@ -246,6 +305,137 @@ export default class TabSettingsModal {
246
305
  container.appendChild(section);
247
306
  }
248
307
 
308
+ private drawEditorSettings(container: HTMLElement) {
309
+ const yasqe = this.tab.getYasqe();
310
+ if (!yasqe) {
311
+ const notice = document.createElement("div");
312
+ notice.textContent = "Query editor is not yet initialized.";
313
+ addClass(notice, "settingsHelp");
314
+ container.appendChild(notice);
315
+ return;
316
+ }
317
+
318
+ // Formatter Type Section
319
+ const formatterSection = document.createElement("div");
320
+ addClass(formatterSection, "settingsSection");
321
+
322
+ const formatterLabel = document.createElement("label");
323
+ formatterLabel.textContent = "Query Formatter";
324
+ addClass(formatterLabel, "settingsLabel");
325
+
326
+ const formatterHelp = document.createElement("div");
327
+ formatterHelp.textContent = "Choose which formatter to use when formatting SPARQL queries (Shift+Ctrl+F).";
328
+ addClass(formatterHelp, "settingsHelp");
329
+
330
+ const formatterSelect = document.createElement("select");
331
+ formatterSelect.id = "formatterTypeSelect";
332
+ addClass(formatterSelect, "settingsSelect");
333
+
334
+ const sparqlFormatterOption = document.createElement("option");
335
+ sparqlFormatterOption.value = "sparql-formatter";
336
+ sparqlFormatterOption.textContent = "SPARQL Formatter (external library)";
337
+ formatterSelect.appendChild(sparqlFormatterOption);
338
+
339
+ const legacyOption = document.createElement("option");
340
+ legacyOption.value = "legacy";
341
+ legacyOption.textContent = "Legacy Formatter (built-in)";
342
+ formatterSelect.appendChild(legacyOption);
343
+
344
+ const currentFormatter = yasqe.persistentConfig?.formatterType || "sparql-formatter";
345
+ formatterSelect.value = currentFormatter;
346
+
347
+ formatterSection.appendChild(formatterLabel);
348
+ formatterSection.appendChild(formatterHelp);
349
+ formatterSection.appendChild(formatterSelect);
350
+ container.appendChild(formatterSection);
351
+
352
+ // Auto-format on Query Section
353
+ const autoformatSection = document.createElement("div");
354
+ addClass(autoformatSection, "settingsSection");
355
+
356
+ const autoformatCheckboxContainer = document.createElement("div");
357
+ addClass(autoformatCheckboxContainer, "checkboxContainer");
358
+
359
+ const autoformatCheckbox = document.createElement("input");
360
+ autoformatCheckbox.type = "checkbox";
361
+ autoformatCheckbox.id = "autoformatOnQuery";
362
+ autoformatCheckbox.checked = yasqe.persistentConfig?.autoformatOnQuery || true;
363
+
364
+ const autoformatLabel = document.createElement("label");
365
+ autoformatLabel.htmlFor = "autoformatOnQuery";
366
+ autoformatLabel.textContent = "Auto-format query before execution";
367
+
368
+ const autoformatHelp = document.createElement("div");
369
+ autoformatHelp.textContent = "Automatically format the query using the selected formatter before executing it.";
370
+ addClass(autoformatHelp, "settingsHelp");
371
+ autoformatHelp.style.marginTop = "5px";
372
+
373
+ autoformatCheckboxContainer.appendChild(autoformatCheckbox);
374
+ autoformatCheckboxContainer.appendChild(autoformatLabel);
375
+
376
+ autoformatSection.appendChild(autoformatCheckboxContainer);
377
+ autoformatSection.appendChild(autoformatHelp);
378
+ container.appendChild(autoformatSection);
379
+
380
+ // CONSTRUCT Variable Validation Section
381
+ const constructValidationSection = document.createElement("div");
382
+ addClass(constructValidationSection, "settingsSection");
383
+
384
+ const constructValidationCheckboxContainer = document.createElement("div");
385
+ addClass(constructValidationCheckboxContainer, "checkboxContainer");
386
+
387
+ const constructValidationCheckbox = document.createElement("input");
388
+ constructValidationCheckbox.type = "checkbox";
389
+ constructValidationCheckbox.id = "checkConstructVariables";
390
+ constructValidationCheckbox.checked = yasqe.config.checkConstructVariables ?? true;
391
+
392
+ const constructValidationLabel = document.createElement("label");
393
+ constructValidationLabel.htmlFor = "checkConstructVariables";
394
+ constructValidationLabel.textContent = "Validate CONSTRUCT query variables";
395
+
396
+ const constructValidationHelp = document.createElement("div");
397
+ constructValidationHelp.textContent =
398
+ "Show warnings for variables used in CONSTRUCT template but not defined in WHERE clause.";
399
+ addClass(constructValidationHelp, "settingsHelp");
400
+ constructValidationHelp.style.marginTop = "5px";
401
+
402
+ constructValidationCheckboxContainer.appendChild(constructValidationCheckbox);
403
+ constructValidationCheckboxContainer.appendChild(constructValidationLabel);
404
+
405
+ constructValidationSection.appendChild(constructValidationCheckboxContainer);
406
+ constructValidationSection.appendChild(constructValidationHelp);
407
+ container.appendChild(constructValidationSection);
408
+
409
+ // Code Snippets Bar Visibility Section
410
+ const snippetsBarSection = document.createElement("div");
411
+ addClass(snippetsBarSection, "settingsSection");
412
+
413
+ const snippetsBarCheckboxContainer = document.createElement("div");
414
+ addClass(snippetsBarCheckboxContainer, "checkboxContainer");
415
+
416
+ const snippetsBarCheckbox = document.createElement("input");
417
+ snippetsBarCheckbox.type = "checkbox";
418
+ snippetsBarCheckbox.id = "showSnippetsBar";
419
+ snippetsBarCheckbox.checked = yasqe.getSnippetsBarVisible();
420
+
421
+ const snippetsBarLabel = document.createElement("label");
422
+ snippetsBarLabel.htmlFor = "showSnippetsBar";
423
+ snippetsBarLabel.textContent = "Show code snippets bar";
424
+
425
+ const snippetsBarHelp = document.createElement("div");
426
+ snippetsBarHelp.textContent =
427
+ "Display the code snippets bar above the editor for quick insertion of common SPARQL patterns.";
428
+ addClass(snippetsBarHelp, "settingsHelp");
429
+ snippetsBarHelp.style.marginTop = "5px";
430
+
431
+ snippetsBarCheckboxContainer.appendChild(snippetsBarCheckbox);
432
+ snippetsBarCheckboxContainer.appendChild(snippetsBarLabel);
433
+
434
+ snippetsBarSection.appendChild(snippetsBarCheckboxContainer);
435
+ snippetsBarSection.appendChild(snippetsBarHelp);
436
+ container.appendChild(snippetsBarSection);
437
+ }
438
+
249
439
  private drawRequestSettings(container: HTMLElement) {
250
440
  // This is a simplified version - you can expand based on TabPanel.ts
251
441
  const reqConfig = this.tab.getRequestConfig();
@@ -312,6 +502,11 @@ export default class TabSettingsModal {
312
502
 
313
503
  public open() {
314
504
  this.loadSettings();
505
+ // Reload editor settings in case yasqe wasn't available during init
506
+ const editorContent = this.modalContent.querySelector("#editor-content");
507
+ if (editorContent && editorContent.innerHTML === "") {
508
+ this.drawEditorSettings(editorContent as HTMLElement);
509
+ }
315
510
  addClass(this.modalOverlay, "open");
316
511
  }
317
512
 
@@ -344,6 +539,29 @@ export default class TabSettingsModal {
344
539
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
345
540
  this.tab.yasgui.persistentConfig.setAutoCaptureEnabled(autoCapture);
346
541
 
542
+ // Save editor settings
543
+ const yasqe = this.tab.getYasqe();
544
+ if (yasqe && yasqe.persistentConfig) {
545
+ const formatterSelect = document.getElementById("formatterTypeSelect") as HTMLSelectElement;
546
+ const autoformatCheckbox = document.getElementById("autoformatOnQuery") as HTMLInputElement;
547
+ const constructValidationCheckbox = document.getElementById("checkConstructVariables") as HTMLInputElement;
548
+
549
+ if (formatterSelect) {
550
+ yasqe.persistentConfig.formatterType = formatterSelect.value as "sparql-formatter" | "legacy";
551
+ }
552
+ if (autoformatCheckbox) {
553
+ yasqe.persistentConfig.autoformatOnQuery = autoformatCheckbox.checked;
554
+ }
555
+ if (constructValidationCheckbox) {
556
+ yasqe.setCheckConstructVariables(constructValidationCheckbox.checked);
557
+ }
558
+ const snippetsBarCheckbox = document.getElementById("showSnippetsBar") as HTMLInputElement;
559
+ if (snippetsBarCheckbox) {
560
+ yasqe.setSnippetsBarVisible(snippetsBarCheckbox.checked);
561
+ }
562
+ yasqe.saveQuery();
563
+ }
564
+
347
565
  // Save request settings
348
566
  const requestContent = this.modalContent.querySelector("#request-content");
349
567
  if (requestContent) {
@@ -414,10 +632,10 @@ export default class TabSettingsModal {
414
632
  // If we didn't find a non-PREFIX line, all lines are PREFIX or empty
415
633
  if (firstNonPrefixLine === 0 && lines.length > 0) {
416
634
  // Check if there's any content at all
417
- const hasContent = lines.some((line) => line.trim().length > 0);
635
+ const hasContent = lines.some((line: string) => line.trim().length > 0);
418
636
  if (
419
637
  !hasContent ||
420
- lines.every((line) => line.trim().length === 0 || line.trim().toUpperCase().startsWith("PREFIX"))
638
+ lines.every((line: string) => line.trim().length === 0 || line.trim().toUpperCase().startsWith("PREFIX"))
421
639
  ) {
422
640
  firstNonPrefixLine = lines.length;
423
641
  }
@@ -574,6 +792,422 @@ export default class TabSettingsModal {
574
792
  return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
575
793
  }
576
794
 
795
+ private drawImportExportSettings(container: HTMLElement) {
796
+ // Export Section
797
+ const exportSection = document.createElement("div");
798
+ addClass(exportSection, "settingsSection");
799
+
800
+ const exportLabel = document.createElement("label");
801
+ exportLabel.textContent = "Export Configuration";
802
+ addClass(exportLabel, "settingsLabel");
803
+
804
+ const exportHelp = document.createElement("div");
805
+ exportHelp.textContent =
806
+ "Export your YASGUI configuration in RDF Turtle format. This includes tabs, queries, endpoints, and preferences.";
807
+ addClass(exportHelp, "settingsHelp");
808
+
809
+ const exportButtonsContainer = document.createElement("div");
810
+ addClass(exportButtonsContainer, "exportButtons");
811
+
812
+ const copyButton = document.createElement("button");
813
+ copyButton.textContent = "📋 Copy to Clipboard";
814
+ copyButton.type = "button";
815
+ addClass(copyButton, "secondaryButton");
816
+ copyButton.onclick = async () => {
817
+ try {
818
+ const config = this.tab.yasgui.persistentConfig.getPersistedConfig();
819
+ await ConfigExportImport.copyConfigToClipboard(config);
820
+ this.showNotification("Configuration copied to clipboard!", "success");
821
+ } catch (error) {
822
+ this.showNotification("Failed to copy to clipboard: " + (error as Error).message, "error");
823
+ }
824
+ };
825
+
826
+ const downloadButton = document.createElement("button");
827
+ downloadButton.textContent = "💾 Download as File";
828
+ downloadButton.type = "button";
829
+ addClass(downloadButton, "primaryButton");
830
+ downloadButton.onclick = () => {
831
+ try {
832
+ const config = this.tab.yasgui.persistentConfig.getPersistedConfig();
833
+ ConfigExportImport.downloadConfigAsFile(config);
834
+ this.showNotification("Configuration downloaded!", "success");
835
+ } catch (error) {
836
+ this.showNotification("Failed to download: " + (error as Error).message, "error");
837
+ }
838
+ };
839
+
840
+ exportButtonsContainer.appendChild(copyButton);
841
+ exportButtonsContainer.appendChild(downloadButton);
842
+
843
+ exportSection.appendChild(exportLabel);
844
+ exportSection.appendChild(exportHelp);
845
+ exportSection.appendChild(exportButtonsContainer);
846
+ container.appendChild(exportSection);
847
+
848
+ // Import Section
849
+ const importSection = document.createElement("div");
850
+ addClass(importSection, "settingsSection");
851
+
852
+ const importLabel = document.createElement("label");
853
+ importLabel.textContent = "Import Configuration";
854
+ addClass(importLabel, "settingsLabel");
855
+
856
+ const importHelp = document.createElement("div");
857
+ importHelp.textContent = "Import a previously exported configuration in RDF Turtle format.";
858
+ addClass(importHelp, "settingsHelp");
859
+
860
+ // Drag and drop area
861
+ const dropZone = document.createElement("div");
862
+ addClass(dropZone, "dropZone");
863
+ dropZone.innerHTML = `
864
+ <div class="dropZoneContent">
865
+ <div class="dropZoneIcon">📁</div>
866
+ <div class="dropZoneText">Drag & drop a .ttl file here</div>
867
+ <div class="dropZoneOr">or</div>
868
+ </div>
869
+ `;
870
+
871
+ const fileInput = document.createElement("input");
872
+ fileInput.type = "file";
873
+ fileInput.accept = ".ttl,.turtle,text/turtle";
874
+ fileInput.style.display = "none";
875
+ fileInput.id = "config-file-input";
876
+
877
+ const browseButton = document.createElement("button");
878
+ browseButton.textContent = "📂 Browse Files";
879
+ browseButton.type = "button";
880
+ addClass(browseButton, "secondaryButton");
881
+ browseButton.onclick = () => fileInput.click();
882
+
883
+ const pasteButton = document.createElement("button");
884
+ pasteButton.textContent = "📋 Paste from Clipboard";
885
+ pasteButton.type = "button";
886
+ addClass(pasteButton, "secondaryButton");
887
+ pasteButton.onclick = async () => {
888
+ try {
889
+ const content = await ConfigExportImport.readConfigFromClipboard();
890
+ await this.importConfiguration(content);
891
+ } catch (error) {
892
+ this.showNotification("Failed to read from clipboard: " + (error as Error).message, "error");
893
+ }
894
+ };
895
+
896
+ // File input change handler
897
+ fileInput.onchange = async (e) => {
898
+ const file = (e.target as HTMLInputElement).files?.[0];
899
+ if (file) {
900
+ try {
901
+ const content = await ConfigExportImport.readConfigFromFile(file);
902
+ await this.importConfiguration(content);
903
+ } catch (error) {
904
+ this.showNotification("Failed to read file: " + (error as Error).message, "error");
905
+ }
906
+ }
907
+ };
908
+
909
+ // Drag and drop handlers
910
+ dropZone.ondragover = (e) => {
911
+ e.preventDefault();
912
+ addClass(dropZone, "dragover");
913
+ };
914
+
915
+ dropZone.ondragleave = () => {
916
+ removeClass(dropZone, "dragover");
917
+ };
918
+
919
+ dropZone.ondrop = async (e) => {
920
+ e.preventDefault();
921
+ removeClass(dropZone, "dragover");
922
+
923
+ const file = e.dataTransfer?.files?.[0];
924
+ if (file) {
925
+ try {
926
+ const content = await ConfigExportImport.readConfigFromFile(file);
927
+ await this.importConfiguration(content);
928
+ } catch (error) {
929
+ this.showNotification("Failed to read file: " + (error as Error).message, "error");
930
+ }
931
+ }
932
+ };
933
+
934
+ const importButtonsContainer = document.createElement("div");
935
+ addClass(importButtonsContainer, "importButtons");
936
+ importButtonsContainer.appendChild(browseButton);
937
+ importButtonsContainer.appendChild(pasteButton);
938
+
939
+ dropZone.appendChild(importButtonsContainer);
940
+
941
+ importSection.appendChild(importLabel);
942
+ importSection.appendChild(importHelp);
943
+ importSection.appendChild(dropZone);
944
+ importSection.appendChild(fileInput);
945
+ container.appendChild(importSection);
946
+ }
947
+
948
+ private drawKeyboardShortcuts(container: HTMLElement) {
949
+ const shortcutsData = [
950
+ {
951
+ category: "Query Editor (YASQE)",
952
+ shortcuts: [
953
+ { keys: ["Ctrl+Enter", "Cmd+Enter"], description: "Execute query" },
954
+ { keys: ["Ctrl+Space", "Cmd+Space"], description: "Trigger autocomplete" },
955
+ { keys: ["Ctrl+S", "Cmd+S"], description: "Save query to local storage" },
956
+ { keys: ["Ctrl+Shift+F", "Cmd+Shift+F"], description: "Format query" },
957
+ { keys: ["Ctrl+/", "Cmd+/"], description: "Toggle comment on selected lines" },
958
+ { keys: ["Ctrl+Shift+D", "Cmd+Shift+D"], description: "Duplicate current line" },
959
+ { keys: ["Ctrl+Shift+K", "Cmd+Shift+K"], description: "Delete current line" },
960
+ { keys: ["Esc"], description: "Remove focus from editor" },
961
+ { keys: ["Ctrl+Click"], description: "Explore URI connections (on URI)" },
962
+ ],
963
+ },
964
+ {
965
+ category: "Fullscreen",
966
+ shortcuts: [
967
+ { keys: ["F11"], description: "Toggle YASQE (editor) fullscreen" },
968
+ { keys: ["F10"], description: "Toggle YASR (results) fullscreen" },
969
+ { keys: ["F9"], description: "Switch between YASQE and YASR fullscreen" },
970
+ { keys: ["Esc"], description: "Exit fullscreen mode" },
971
+ ],
972
+ },
973
+ ];
974
+
975
+ shortcutsData.forEach((section) => {
976
+ const sectionEl = document.createElement("div");
977
+ addClass(sectionEl, "shortcutsSection");
978
+
979
+ const categoryLabel = document.createElement("h3");
980
+ categoryLabel.textContent = section.category;
981
+ addClass(categoryLabel, "shortcutsCategory");
982
+ sectionEl.appendChild(categoryLabel);
983
+
984
+ const table = document.createElement("table");
985
+ addClass(table, "shortcutsTable");
986
+ table.setAttribute("role", "table");
987
+ table.setAttribute("aria-label", `${section.category} keyboard shortcuts`);
988
+
989
+ // Add table caption for screen readers
990
+ const caption = document.createElement("caption");
991
+ caption.textContent = `${section.category} keyboard shortcuts`;
992
+ caption.style.position = "absolute";
993
+ caption.style.left = "-10000px";
994
+ caption.style.width = "1px";
995
+ caption.style.height = "1px";
996
+ caption.style.overflow = "hidden";
997
+ table.appendChild(caption);
998
+
999
+ // Add thead with proper headers
1000
+ const thead = document.createElement("thead");
1001
+ const headerRow = document.createElement("tr");
1002
+
1003
+ const keysHeader = document.createElement("th");
1004
+ keysHeader.textContent = "Keys";
1005
+ keysHeader.setAttribute("scope", "col");
1006
+ addClass(keysHeader, "shortcutsKeysHeader");
1007
+ headerRow.appendChild(keysHeader);
1008
+
1009
+ const descHeader = document.createElement("th");
1010
+ descHeader.textContent = "Description";
1011
+ descHeader.setAttribute("scope", "col");
1012
+ addClass(descHeader, "shortcutsDescHeader");
1013
+ headerRow.appendChild(descHeader);
1014
+
1015
+ thead.appendChild(headerRow);
1016
+ table.appendChild(thead);
1017
+
1018
+ // Add tbody
1019
+ const tbody = document.createElement("tbody");
1020
+
1021
+ section.shortcuts.forEach((shortcut) => {
1022
+ const row = document.createElement("tr");
1023
+
1024
+ const keysCell = document.createElement("td");
1025
+ addClass(keysCell, "shortcutsKeys");
1026
+ shortcut.keys.forEach((key, index) => {
1027
+ if (index > 0) {
1028
+ const separator = document.createElement("span");
1029
+ separator.textContent = " / ";
1030
+ addClass(separator, "shortcutsSeparator");
1031
+ keysCell.appendChild(separator);
1032
+ }
1033
+ const kbd = document.createElement("kbd");
1034
+ kbd.textContent = key;
1035
+ keysCell.appendChild(kbd);
1036
+ });
1037
+ row.appendChild(keysCell);
1038
+
1039
+ const descCell = document.createElement("td");
1040
+ addClass(descCell, "shortcutsDescription");
1041
+ descCell.textContent = shortcut.description;
1042
+ row.appendChild(descCell);
1043
+
1044
+ tbody.appendChild(row);
1045
+ });
1046
+
1047
+ table.appendChild(tbody);
1048
+
1049
+ sectionEl.appendChild(table);
1050
+ container.appendChild(sectionEl);
1051
+ });
1052
+ }
1053
+
1054
+ private async importConfiguration(turtleContent: string) {
1055
+ try {
1056
+ const parsedConfig = ConfigExportImport.parseFromTurtle(turtleContent);
1057
+
1058
+ // Confirm with user before importing
1059
+ const confirmMsg = `This will replace your current configuration with ${parsedConfig.tabs?.length || 0} tab(s). Continue?`;
1060
+ if (!confirm(confirmMsg)) {
1061
+ return;
1062
+ }
1063
+
1064
+ // Close the modal first
1065
+ this.close();
1066
+
1067
+ // Apply theme if provided
1068
+ if (parsedConfig.theme) {
1069
+ this.tab.yasgui.themeManager.setTheme(parsedConfig.theme);
1070
+ }
1071
+
1072
+ // Apply global orientation if provided
1073
+ if (parsedConfig.orientation) {
1074
+ this.tab.yasgui.config.orientation = parsedConfig.orientation;
1075
+ }
1076
+
1077
+ // Close all existing tabs
1078
+ const existingTabIds = [...this.tab.yasgui.persistentConfig.getTabs()];
1079
+ for (const tabId of existingTabIds) {
1080
+ const tab = this.tab.yasgui.getTab(tabId);
1081
+ if (tab) {
1082
+ tab.close();
1083
+ }
1084
+ }
1085
+
1086
+ // Update the configuration storage
1087
+ this.tab.yasgui.persistentConfig.updatePersistedConfig(parsedConfig);
1088
+
1089
+ window.location.reload();
1090
+ } catch (error) {
1091
+ this.showNotification("Failed to import configuration: " + (error as Error).message, "error");
1092
+ }
1093
+ }
1094
+
1095
+ private showNotification(message: string, type: "success" | "error") {
1096
+ const notification = document.createElement("div");
1097
+ addClass(notification, "importExportNotification", type);
1098
+ notification.textContent = message;
1099
+
1100
+ this.modalContent.appendChild(notification);
1101
+
1102
+ setTimeout(() => {
1103
+ if (notification.parentNode) {
1104
+ notification.parentNode.removeChild(notification);
1105
+ }
1106
+ }, 5000);
1107
+ }
1108
+
1109
+ private drawAboutSettings(container: HTMLElement) {
1110
+ // About Section
1111
+ const aboutSection = document.createElement("div");
1112
+ addClass(aboutSection, "settingsSection", "aboutSection");
1113
+
1114
+ // YASGUI Title and Version
1115
+ const titleContainer = document.createElement("div");
1116
+ addClass(titleContainer, "aboutTitle");
1117
+
1118
+ const title = document.createElement("h3");
1119
+ title.textContent = "YASGUI";
1120
+ addClass(title, "aboutMainTitle");
1121
+
1122
+ const versionBadge = document.createElement("span");
1123
+ versionBadge.textContent = `v${VERSION}`;
1124
+ addClass(versionBadge, "versionBadge");
1125
+
1126
+ titleContainer.appendChild(title);
1127
+ titleContainer.appendChild(versionBadge);
1128
+ aboutSection.appendChild(titleContainer);
1129
+
1130
+ // Subtitle
1131
+ const subtitle = document.createElement("p");
1132
+ subtitle.textContent = "Yet Another SPARQL GUI";
1133
+ addClass(subtitle, "aboutSubtitle");
1134
+ aboutSection.appendChild(subtitle);
1135
+
1136
+ // Links Section
1137
+ const linksSection = document.createElement("div");
1138
+ addClass(linksSection, "aboutLinks");
1139
+
1140
+ // Documentation Link
1141
+ const docsLink = this.createAboutLink(
1142
+ "📚 Documentation",
1143
+ "https://yasgui-doc.matdata.eu/docs/",
1144
+ "View the complete documentation and guides",
1145
+ );
1146
+ linksSection.appendChild(docsLink);
1147
+
1148
+ // Release Notes Link
1149
+ const releasesLink = this.createAboutLink(
1150
+ "📝 Release Notes",
1151
+ "https://github.com/Matdata-eu/Yasgui/releases",
1152
+ "See what's new in the latest releases",
1153
+ );
1154
+ linksSection.appendChild(releasesLink);
1155
+
1156
+ // Issues/Support Link
1157
+ const issuesLink = this.createAboutLink(
1158
+ "🐛 Report Issues & Get Support",
1159
+ "https://github.com/Matdata-eu/Yasgui/issues",
1160
+ "Report bugs, request features, or ask for help",
1161
+ );
1162
+ linksSection.appendChild(issuesLink);
1163
+
1164
+ aboutSection.appendChild(linksSection);
1165
+
1166
+ // Footer info
1167
+ const footerInfo = document.createElement("div");
1168
+ addClass(footerInfo, "aboutFooter");
1169
+
1170
+ const paragraph1 = document.createElement("p");
1171
+ paragraph1.textContent = "YASGUI is an open-source project maintained by ";
1172
+ const matdataLink = document.createElement("a");
1173
+ matdataLink.href = "https://matdata.eu";
1174
+ matdataLink.target = "_blank";
1175
+ matdataLink.rel = "noopener noreferrer";
1176
+ matdataLink.textContent = "Matdata";
1177
+ paragraph1.appendChild(matdataLink);
1178
+ paragraph1.appendChild(document.createTextNode("."));
1179
+
1180
+ const paragraph2 = document.createElement("p");
1181
+ paragraph2.textContent = "Licensed under the MIT License.";
1182
+
1183
+ footerInfo.appendChild(paragraph1);
1184
+ footerInfo.appendChild(paragraph2);
1185
+ aboutSection.appendChild(footerInfo);
1186
+
1187
+ container.appendChild(aboutSection);
1188
+ }
1189
+
1190
+ private createAboutLink(label: string, url: string, description: string): HTMLElement {
1191
+ const linkContainer = document.createElement("div");
1192
+ addClass(linkContainer, "aboutLinkItem");
1193
+
1194
+ const link = document.createElement("a");
1195
+ link.href = url;
1196
+ link.target = "_blank";
1197
+ link.rel = "noopener noreferrer";
1198
+ link.textContent = label;
1199
+ addClass(link, "aboutLink");
1200
+
1201
+ const desc = document.createElement("p");
1202
+ desc.textContent = description;
1203
+ addClass(desc, "aboutLinkDescription");
1204
+
1205
+ linkContainer.appendChild(link);
1206
+ linkContainer.appendChild(desc);
1207
+
1208
+ return linkContainer;
1209
+ }
1210
+
577
1211
  public destroy() {
578
1212
  if (this.modalOverlay && this.modalOverlay.parentNode) {
579
1213
  this.modalOverlay.parentNode.removeChild(this.modalOverlay);