@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.
- package/README.md +128 -144
- 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 +4 -0
- package/build/ts/src/PersistentConfig.js +7 -0
- package/build/ts/src/PersistentConfig.js.map +1 -1
- package/build/ts/src/Tab.d.ts +2 -1
- package/build/ts/src/Tab.js +19 -10
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +7 -0
- package/build/ts/src/TabSettingsModal.js +504 -1
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/version.d.ts +1 -0
- package/build/ts/src/version.js +2 -0
- package/build/ts/src/version.js.map +1 -0
- package/build/yasgui.min.css +1 -1
- package/build/yasgui.min.css.map +3 -3
- package/build/yasgui.min.js +166 -106
- package/build/yasgui.min.js.map +4 -4
- package/package.json +1 -1
- package/src/ConfigExportImport.ts +391 -0
- package/src/PersistentConfig.ts +17 -0
- package/src/Tab.ts +32 -17
- package/src/TabSettingsModal.scss +262 -2
- package/src/TabSettingsModal.ts +637 -3
- package/src/sortablejs.d.ts +36 -0
- package/src/tab.scss +3 -4
- package/src/themes.scss +1 -1
- package/src/version.ts +3 -0
package/src/TabSettingsModal.ts
CHANGED
|
@@ -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 === "
|
|
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);
|