@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.
- package/README.md +128 -96
- package/build/ts/src/ConfigExportImport.d.ts +16 -0
- package/build/ts/src/ConfigExportImport.js +304 -0
- package/build/ts/src/ConfigExportImport.js.map +1 -0
- package/build/ts/src/PersistentConfig.d.ts +8 -1
- package/build/ts/src/PersistentConfig.js +15 -0
- package/build/ts/src/PersistentConfig.js.map +1 -1
- package/build/ts/src/Tab.d.ts +9 -0
- package/build/ts/src/Tab.js +83 -1
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +6 -0
- package/build/ts/src/TabSettingsModal.js +405 -1
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/defaults.js +2 -0
- package/build/ts/src/defaults.js.map +1 -1
- package/build/ts/src/index.d.ts +6 -0
- package/build/ts/src/index.js.map +1 -1
- package/build/yasgui.min.css +1 -1
- package/build/yasgui.min.css.map +3 -3
- package/build/yasgui.min.js +157 -105
- package/build/yasgui.min.js.map +4 -4
- package/package.json +1 -1
- package/src/ConfigExportImport.ts +391 -0
- package/src/PersistentConfig.ts +27 -1
- package/src/Tab.ts +123 -2
- package/src/TabSettingsModal.scss +180 -0
- package/src/TabSettingsModal.ts +499 -3
- package/src/defaults.ts +2 -0
- package/src/endpointSelect.scss +34 -0
- package/src/index.scss +2 -2
- package/src/index.ts +11 -0
- package/src/sortablejs.d.ts +36 -0
- package/src/tab.scss +50 -2
- package/src/themes.scss +23 -0
package/src/TabSettingsModal.ts
CHANGED
|
@@ -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 (
|
|
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
package/src/endpointSelect.scss
CHANGED
|
@@ -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
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];
|