@matdata/yasgui 5.1.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,8 +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
+
135
+ const endpointsTab = document.createElement("button");
136
+ endpointsTab.textContent = "Endpoint Buttons";
137
+ addClass(endpointsTab, "modalTabButton");
138
+ endpointsTab.onclick = () => this.switchTab("endpoints");
139
+
140
+ const importExportTab = document.createElement("button");
141
+ importExportTab.textContent = "Import/Export";
142
+ addClass(importExportTab, "modalTabButton");
143
+ importExportTab.onclick = () => this.switchTab("importexport");
144
+
129
145
  tabsContainer.appendChild(requestTab);
130
146
  tabsContainer.appendChild(prefixTab);
147
+ tabsContainer.appendChild(editorTab);
148
+ tabsContainer.appendChild(endpointsTab);
149
+ tabsContainer.appendChild(importExportTab);
131
150
  body.appendChild(tabsContainer);
132
151
 
133
152
  // Tab content containers
@@ -141,8 +160,26 @@ export default class TabSettingsModal {
141
160
  prefixContent.id = "prefix-content";
142
161
  this.drawPrefixSettings(prefixContent);
143
162
 
163
+ const editorContent = document.createElement("div");
164
+ addClass(editorContent, "modalTabContent");
165
+ editorContent.id = "editor-content";
166
+ this.drawEditorSettings(editorContent);
167
+
168
+ const endpointsContent = document.createElement("div");
169
+ addClass(endpointsContent, "modalTabContent");
170
+ endpointsContent.id = "endpoints-content";
171
+ this.drawEndpointButtonsSettings(endpointsContent);
172
+
173
+ const importExportContent = document.createElement("div");
174
+ addClass(importExportContent, "modalTabContent");
175
+ importExportContent.id = "importexport-content";
176
+ this.drawImportExportSettings(importExportContent);
177
+
144
178
  body.appendChild(requestContent);
145
179
  body.appendChild(prefixContent);
180
+ body.appendChild(editorContent);
181
+ body.appendChild(endpointsContent);
182
+ body.appendChild(importExportContent);
146
183
 
147
184
  this.modalContent.appendChild(body);
148
185
 
@@ -174,7 +211,13 @@ export default class TabSettingsModal {
174
211
  const contents = this.modalContent.querySelectorAll(".modalTabContent");
175
212
 
176
213
  buttons.forEach((btn, index) => {
177
- if ((tabName === "request" && index === 0) || (tabName === "prefix" && index === 1)) {
214
+ if (
215
+ (tabName === "request" && index === 0) ||
216
+ (tabName === "prefix" && index === 1) ||
217
+ (tabName === "editor" && index === 2) ||
218
+ (tabName === "endpoints" && index === 3) ||
219
+ (tabName === "importexport" && index === 4)
220
+ ) {
178
221
  addClass(btn as HTMLElement, "active");
179
222
  } else {
180
223
  removeClass(btn as HTMLElement, "active");
@@ -184,6 +227,11 @@ export default class TabSettingsModal {
184
227
  contents.forEach((content) => {
185
228
  if (content.id === `${tabName}-content`) {
186
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
+ }
187
235
  } else {
188
236
  removeClass(content as HTMLElement, "active");
189
237
  }
@@ -230,6 +278,108 @@ export default class TabSettingsModal {
230
278
  container.appendChild(section);
231
279
  }
232
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
+
233
383
  private drawRequestSettings(container: HTMLElement) {
234
384
  // This is a simplified version - you can expand based on TabPanel.ts
235
385
  const reqConfig = this.tab.getRequestConfig();
@@ -296,6 +446,11 @@ export default class TabSettingsModal {
296
446
 
297
447
  public open() {
298
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
+ }
299
454
  addClass(this.modalOverlay, "open");
300
455
  }
301
456
 
@@ -328,6 +483,25 @@ export default class TabSettingsModal {
328
483
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
329
484
  this.tab.yasgui.persistentConfig.setAutoCaptureEnabled(autoCapture);
330
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
+
331
505
  // Save request settings
332
506
  const requestContent = this.modalContent.querySelector("#request-content");
333
507
  if (requestContent) {
@@ -342,6 +516,9 @@ export default class TabSettingsModal {
342
516
  this.tab.setRequestConfig(updates);
343
517
  }
344
518
 
519
+ // Refresh endpoint buttons to show any changes
520
+ this.tab.refreshEndpointButtons();
521
+
345
522
  this.close();
346
523
  }
347
524
 
@@ -395,10 +572,10 @@ export default class TabSettingsModal {
395
572
  // If we didn't find a non-PREFIX line, all lines are PREFIX or empty
396
573
  if (firstNonPrefixLine === 0 && lines.length > 0) {
397
574
  // Check if there's any content at all
398
- const hasContent = lines.some((line) => line.trim().length > 0);
575
+ const hasContent = lines.some((line: string) => line.trim().length > 0);
399
576
  if (
400
577
  !hasContent ||
401
- 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"))
402
579
  ) {
403
580
  firstNonPrefixLine = lines.length;
404
581
  }
@@ -437,6 +614,117 @@ export default class TabSettingsModal {
437
614
  this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
438
615
  }
439
616
 
617
+ private drawEndpointButtonsSettings(container: HTMLElement) {
618
+ const section = document.createElement("div");
619
+ addClass(section, "settingsSection");
620
+
621
+ const label = document.createElement("label");
622
+ label.textContent = "Custom Endpoint Buttons";
623
+ addClass(label, "settingsLabel");
624
+
625
+ const help = document.createElement("div");
626
+ help.textContent = "Add custom endpoint buttons that will appear next to the endpoint textbox.";
627
+ addClass(help, "settingsHelp");
628
+
629
+ section.appendChild(label);
630
+ section.appendChild(help);
631
+
632
+ // List of existing buttons
633
+ const buttonsList = document.createElement("div");
634
+ addClass(buttonsList, "endpointButtonsList");
635
+ this.renderEndpointButtonsList(buttonsList);
636
+ section.appendChild(buttonsList);
637
+
638
+ // Form to add new button
639
+ const addForm = document.createElement("div");
640
+ addClass(addForm, "addEndpointButtonForm");
641
+
642
+ const labelInput = document.createElement("input");
643
+ labelInput.type = "text";
644
+ labelInput.placeholder = "Button label (e.g., DBpedia)";
645
+ addClass(labelInput, "endpointButtonLabelInput");
646
+
647
+ const endpointInput = document.createElement("input");
648
+ endpointInput.type = "url";
649
+ endpointInput.placeholder = "Endpoint URL (e.g., https://dbpedia.org/sparql)";
650
+ addClass(endpointInput, "endpointButtonEndpointInput");
651
+
652
+ const addButton = document.createElement("button");
653
+ addButton.textContent = "+ Add Button";
654
+ addClass(addButton, "addEndpointButton");
655
+ addButton.type = "button";
656
+ addButton.onclick = () => {
657
+ const labelValue = labelInput.value.trim();
658
+ const endpointValue = endpointInput.value.trim();
659
+
660
+ if (!labelValue || !endpointValue) {
661
+ alert("Please enter both a label and an endpoint URL.");
662
+ return;
663
+ }
664
+
665
+ // Add to persistent config
666
+ const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
667
+ currentButtons.push({ label: labelValue, endpoint: endpointValue });
668
+ this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
669
+
670
+ // Clear inputs
671
+ labelInput.value = "";
672
+ endpointInput.value = "";
673
+
674
+ // Refresh list
675
+ this.renderEndpointButtonsList(buttonsList);
676
+ };
677
+
678
+ addForm.appendChild(labelInput);
679
+ addForm.appendChild(endpointInput);
680
+ addForm.appendChild(addButton);
681
+ section.appendChild(addForm);
682
+
683
+ container.appendChild(section);
684
+ }
685
+
686
+ private renderEndpointButtonsList(container: HTMLElement) {
687
+ container.innerHTML = "";
688
+ const customButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
689
+
690
+ if (customButtons.length === 0) {
691
+ const emptyMsg = document.createElement("div");
692
+ emptyMsg.textContent = "No custom buttons yet. Add one below.";
693
+ addClass(emptyMsg, "emptyMessage");
694
+ container.appendChild(emptyMsg);
695
+ return;
696
+ }
697
+
698
+ customButtons.forEach((btn, index) => {
699
+ const item = document.createElement("div");
700
+ addClass(item, "endpointButtonItem");
701
+
702
+ const labelSpan = document.createElement("span");
703
+ labelSpan.textContent = `${btn.label}`;
704
+ addClass(labelSpan, "buttonLabel");
705
+
706
+ const endpointSpan = document.createElement("span");
707
+ endpointSpan.textContent = btn.endpoint;
708
+ addClass(endpointSpan, "buttonEndpoint");
709
+
710
+ const removeBtn = document.createElement("button");
711
+ removeBtn.textContent = "×";
712
+ addClass(removeBtn, "removeButton");
713
+ removeBtn.type = "button";
714
+ removeBtn.onclick = () => {
715
+ const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
716
+ currentButtons.splice(index, 1);
717
+ this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
718
+ this.renderEndpointButtonsList(container);
719
+ };
720
+
721
+ item.appendChild(labelSpan);
722
+ item.appendChild(endpointSpan);
723
+ item.appendChild(removeBtn);
724
+ container.appendChild(item);
725
+ });
726
+ }
727
+
440
728
  private getThemeToggleIcon(): string {
441
729
  const currentTheme = this.tab.yasgui.getTheme();
442
730
  // In dark mode, show moon icon (clicking will switch to light)
@@ -444,6 +732,214 @@ export default class TabSettingsModal {
444
732
  return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
445
733
  }
446
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
+
447
943
  public destroy() {
448
944
  if (this.modalOverlay && this.modalOverlay.parentNode) {
449
945
  this.modalOverlay.parentNode.removeChild(this.modalOverlay);
package/src/defaults.ts CHANGED
@@ -30,6 +30,8 @@ export default function initialize(): Config<CatalogueItem> {
30
30
  yasr: Yasr.defaults,
31
31
  theme: undefined,
32
32
  showThemeToggle: true,
33
+ orientation: "vertical",
34
+ endpointButtons: undefined,
33
35
  endpointCatalogueOptions: {
34
36
  getData: () => {
35
37
  return [
@@ -119,4 +119,38 @@
119
119
  opacity: 0.8;
120
120
  }
121
121
  }
122
+
123
+ .endpointButtonsContainer {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 4px;
127
+ margin-left: 4px;
128
+ }
129
+
130
+ .endpointButton {
131
+ border: 1px solid var(--yasgui-endpoint-button-border, #337ab7);
132
+ background-color: var(--yasgui-endpoint-button-bg, #337ab7);
133
+ color: var(--yasgui-endpoint-button-text, #ffffff);
134
+ border-radius: 3px;
135
+ cursor: pointer;
136
+ padding: 4px 12px;
137
+ font-size: 13px;
138
+ font-weight: 500;
139
+ white-space: nowrap;
140
+ transition: all 0.2s ease;
141
+
142
+ &:hover {
143
+ background-color: var(--yasgui-endpoint-button-hover-bg, #286090);
144
+ border-color: var(--yasgui-endpoint-button-hover-border, #286090);
145
+ }
146
+
147
+ &:active {
148
+ transform: translateY(1px);
149
+ }
150
+
151
+ &:focus {
152
+ outline: 2px solid var(--yasgui-endpoint-button-focus, #5cb3fd);
153
+ outline-offset: 2px;
154
+ }
155
+ }
122
156
  }
package/src/index.scss CHANGED
@@ -11,8 +11,8 @@
11
11
  left: 0;
12
12
  right: 0;
13
13
  z-index: 9999;
14
- background: white;
15
- border-bottom: 1px solid #ddd;
14
+ background: var(--yasgui-bg-primary);
15
+ border-bottom: 1px solid var(--yasgui-border-color);
16
16
  padding: 5px;
17
17
  margin: 0;
18
18
 
package/src/index.ts CHANGED
@@ -31,6 +31,10 @@ if (window) {
31
31
  export type YasguiRequestConfig = Omit<RequestConfig<Yasgui>, "adjustQueryBeforeRequest"> & {
32
32
  adjustQueryBeforeRequest: RequestConfig<Yasqe>["adjustQueryBeforeRequest"];
33
33
  };
34
+ export interface EndpointButton {
35
+ endpoint: string;
36
+ label: string;
37
+ }
34
38
  export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
35
39
  /**
36
40
  * Autofocus yasqe on load or tab switch
@@ -41,6 +45,7 @@ export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
41
45
  tabName: string;
42
46
  corsProxy: string | undefined;
43
47
  endpointCatalogueOptions: EndpointSelectConfig<EndpointObject>;
48
+ endpointButtons?: EndpointButton[];
44
49
  //The function allows us to modify the config before we pass it on to a tab
45
50
  populateFromUrl: boolean | ((configFromUrl: PersistedTabJson) => PersistedTabJson);
46
51
  autoAddOnInit: boolean;
@@ -55,6 +60,12 @@ export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
55
60
  nonSslDomain?: string;
56
61
  theme?: Theme;
57
62
  showThemeToggle?: boolean;
63
+ /**
64
+ * Layout orientation: 'vertical' (default) or 'horizontal' (side-by-side)
65
+ * 'vertical': YASQE on top, YASR below
66
+ * 'horizontal': YASQE on left, YASR on right
67
+ */
68
+ orientation?: "vertical" | "horizontal";
58
69
  }
59
70
  export type PartialConfig = {
60
71
  [P in keyof Config]?: Config[P] extends object ? Partial<Config[P]> : Config[P];