@matdata/yasgui 5.0.1 → 5.2.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 +196 -111
- package/build/ts/src/PersistentConfig.d.ts +4 -1
- package/build/ts/src/PersistentConfig.js +8 -0
- package/build/ts/src/PersistentConfig.js.map +1 -1
- package/build/ts/src/Tab.d.ts +8 -0
- package/build/ts/src/Tab.js +85 -1
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabElements.js +6 -0
- package/build/ts/src/TabElements.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +4 -0
- package/build/ts/src/TabSettingsModal.js +128 -9
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/ThemeManager.d.ts +17 -0
- package/build/ts/src/ThemeManager.js +69 -0
- package/build/ts/src/ThemeManager.js.map +1 -0
- package/build/ts/src/defaults.js +4 -0
- package/build/ts/src/defaults.js.map +1 -1
- package/build/ts/src/index.d.ts +16 -0
- package/build/ts/src/index.js +17 -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 +114 -98
- package/build/yasgui.min.js.map +4 -4
- package/package.json +1 -1
- package/src/PersistentConfig.ts +10 -1
- package/src/Tab.ts +125 -0
- package/src/TabElements.scss +33 -6
- package/src/TabElements.ts +9 -0
- package/src/TabSettingsModal.scss +154 -20
- package/src/TabSettingsModal.ts +171 -13
- package/src/ThemeManager.ts +120 -0
- package/src/defaults.ts +4 -0
- package/src/endpointSelect.scss +34 -0
- package/src/index.scss +2 -2
- package/src/index.ts +43 -0
- package/src/tab.scss +60 -8
- package/src/themes.scss +543 -0
package/src/TabSettingsModal.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import { addClass, removeClass
|
|
1
|
+
import { addClass, removeClass } from "@matdata/yasgui-utils";
|
|
2
2
|
import "./TabSettingsModal.scss";
|
|
3
3
|
import Tab from "./Tab";
|
|
4
4
|
|
|
5
|
+
// Theme toggle icons
|
|
6
|
+
const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
7
|
+
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
|
|
8
|
+
</svg>`;
|
|
9
|
+
|
|
10
|
+
const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
11
|
+
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
|
|
12
|
+
</svg>`;
|
|
13
|
+
|
|
14
|
+
const SETTINGS_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
15
|
+
<path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-7.43 2.52c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
|
|
16
|
+
</svg>`;
|
|
17
|
+
|
|
5
18
|
const AcceptOptionsMap: { key: string; value: string }[] = [
|
|
6
19
|
{ key: "JSON", value: "application/sparql-results+json" },
|
|
7
20
|
{ key: "XML", value: "application/sparql-results+xml" },
|
|
@@ -24,6 +37,7 @@ export default class TabSettingsModal {
|
|
|
24
37
|
private modalOverlay!: HTMLElement;
|
|
25
38
|
private modalContent!: HTMLElement;
|
|
26
39
|
private settingsButton!: HTMLButtonElement;
|
|
40
|
+
private themeToggleButton!: HTMLButtonElement;
|
|
27
41
|
private prefixButton!: HTMLButtonElement;
|
|
28
42
|
private prefixTextarea!: HTMLTextAreaElement;
|
|
29
43
|
private autoCaptureCheckbox!: HTMLInputElement;
|
|
@@ -36,19 +50,26 @@ export default class TabSettingsModal {
|
|
|
36
50
|
private init(controlBarEl: HTMLElement) {
|
|
37
51
|
// Settings button
|
|
38
52
|
this.settingsButton = document.createElement("button");
|
|
53
|
+
addClass(this.settingsButton, "tabContextButton");
|
|
39
54
|
this.settingsButton.setAttribute("aria-label", "Settings");
|
|
40
55
|
this.settingsButton.title = "Settings";
|
|
41
|
-
this.settingsButton.
|
|
42
|
-
drawSvgStringAsElement(
|
|
43
|
-
`<svg width="100.06" height="100.05" data-name="Layer 1" version="1.1" viewBox="0 0 100.06 100.05" xmlns="http://www.w3.org/2000/svg">
|
|
44
|
-
<title>Settings</title>
|
|
45
|
-
<path d="m95.868 58.018-3-3.24a42.5 42.5 0 0 0 0-9.43l3-3.22c1.79-1.91 5-4.44 4-6.85l-4.11-10c-1-2.41-5.08-1.91-7.69-2l-4.43-0.16a43.24 43.24 0 0 0-6.64-6.66l-0.14-4.43c-0.08-2.6 0.43-6.69-2-7.69l-10-4.15c-2.4-1-4.95 2.25-6.85 4l-3.23 3a42.49 42.49 0 0 0-9.44 0l-3.21-3c-1.9-1.78-4.44-5-6.85-4l-10 4.11c-2.41 1-1.9 5.09-2 7.69l-0.16 4.42a43.24 43.24 0 0 0-6.67 6.65l-4.42 0.14c-2.6 0.08-6.69-0.43-7.69 2l-4.15 10c-1 2.4 2.25 4.94 4 6.84l3 3.23a42.49 42.49 0 0 0 0 9.44l-3 3.22c-1.78 1.9-5 4.43-4 6.84l4.11 10c1 2.41 5.09 1.91 7.7 2l4.41 0.15a43.24 43.24 0 0 0 6.66 6.68l0.13 4.41c0.08 2.6-0.43 6.7 2 7.7l10 4.15c2.4 1 4.94-2.25 6.84-4l3.24-3a42.5 42.5 0 0 0 9.42 0l3.22 3c1.91 1.79 4.43 5 6.84 4l10-4.11c2.41-1 1.91-5.08 2-7.7l0.15-4.42a43.24 43.24 0 0 0 6.68-6.65l4.42-0.14c2.6-0.08 6.7 0.43 7.7-2l4.15-10c1.04-2.36-2.22-4.9-3.99-6.82zm-45.74 15.7c-12.66 0-22.91-10.61-22.91-23.7s10.25-23.7 22.91-23.7 22.91 10.61 22.91 23.7-10.25 23.7-22.91 23.7z"/>
|
|
46
|
-
</svg>`,
|
|
47
|
-
),
|
|
48
|
-
);
|
|
49
|
-
addClass(this.settingsButton, "tabContextButton");
|
|
50
|
-
controlBarEl.appendChild(this.settingsButton);
|
|
56
|
+
this.settingsButton.innerHTML = SETTINGS_ICON;
|
|
51
57
|
this.settingsButton.onclick = () => this.open();
|
|
58
|
+
controlBarEl.appendChild(this.settingsButton);
|
|
59
|
+
|
|
60
|
+
// Theme toggle button (if enabled)
|
|
61
|
+
if (this.tab.yasgui.config.showThemeToggle) {
|
|
62
|
+
this.themeToggleButton = document.createElement("button");
|
|
63
|
+
addClass(this.themeToggleButton, "themeToggle");
|
|
64
|
+
this.themeToggleButton.setAttribute("aria-label", "Toggle between light and dark theme");
|
|
65
|
+
this.themeToggleButton.title = "Toggle theme";
|
|
66
|
+
this.themeToggleButton.innerHTML = this.getThemeToggleIcon();
|
|
67
|
+
this.themeToggleButton.addEventListener("click", () => {
|
|
68
|
+
this.tab.yasgui.toggleTheme();
|
|
69
|
+
this.themeToggleButton.innerHTML = this.getThemeToggleIcon();
|
|
70
|
+
});
|
|
71
|
+
controlBarEl.appendChild(this.themeToggleButton);
|
|
72
|
+
}
|
|
52
73
|
|
|
53
74
|
// Prefix button
|
|
54
75
|
this.prefixButton = document.createElement("button");
|
|
@@ -77,7 +98,7 @@ export default class TabSettingsModal {
|
|
|
77
98
|
const header = document.createElement("div");
|
|
78
99
|
addClass(header, "modalHeader");
|
|
79
100
|
const title = document.createElement("h2");
|
|
80
|
-
title.textContent = "
|
|
101
|
+
title.textContent = "Settings";
|
|
81
102
|
header.appendChild(title);
|
|
82
103
|
|
|
83
104
|
const closeBtn = document.createElement("button");
|
|
@@ -105,8 +126,14 @@ export default class TabSettingsModal {
|
|
|
105
126
|
addClass(prefixTab, "modalTabButton");
|
|
106
127
|
prefixTab.onclick = () => this.switchTab("prefix");
|
|
107
128
|
|
|
129
|
+
const endpointsTab = document.createElement("button");
|
|
130
|
+
endpointsTab.textContent = "Endpoint Buttons";
|
|
131
|
+
addClass(endpointsTab, "modalTabButton");
|
|
132
|
+
endpointsTab.onclick = () => this.switchTab("endpoints");
|
|
133
|
+
|
|
108
134
|
tabsContainer.appendChild(requestTab);
|
|
109
135
|
tabsContainer.appendChild(prefixTab);
|
|
136
|
+
tabsContainer.appendChild(endpointsTab);
|
|
110
137
|
body.appendChild(tabsContainer);
|
|
111
138
|
|
|
112
139
|
// Tab content containers
|
|
@@ -120,8 +147,14 @@ export default class TabSettingsModal {
|
|
|
120
147
|
prefixContent.id = "prefix-content";
|
|
121
148
|
this.drawPrefixSettings(prefixContent);
|
|
122
149
|
|
|
150
|
+
const endpointsContent = document.createElement("div");
|
|
151
|
+
addClass(endpointsContent, "modalTabContent");
|
|
152
|
+
endpointsContent.id = "endpoints-content";
|
|
153
|
+
this.drawEndpointButtonsSettings(endpointsContent);
|
|
154
|
+
|
|
123
155
|
body.appendChild(requestContent);
|
|
124
156
|
body.appendChild(prefixContent);
|
|
157
|
+
body.appendChild(endpointsContent);
|
|
125
158
|
|
|
126
159
|
this.modalContent.appendChild(body);
|
|
127
160
|
|
|
@@ -153,7 +186,11 @@ export default class TabSettingsModal {
|
|
|
153
186
|
const contents = this.modalContent.querySelectorAll(".modalTabContent");
|
|
154
187
|
|
|
155
188
|
buttons.forEach((btn, index) => {
|
|
156
|
-
if (
|
|
189
|
+
if (
|
|
190
|
+
(tabName === "request" && index === 0) ||
|
|
191
|
+
(tabName === "prefix" && index === 1) ||
|
|
192
|
+
(tabName === "endpoints" && index === 2)
|
|
193
|
+
) {
|
|
157
194
|
addClass(btn as HTMLElement, "active");
|
|
158
195
|
} else {
|
|
159
196
|
removeClass(btn as HTMLElement, "active");
|
|
@@ -321,6 +358,9 @@ export default class TabSettingsModal {
|
|
|
321
358
|
this.tab.setRequestConfig(updates);
|
|
322
359
|
}
|
|
323
360
|
|
|
361
|
+
// Refresh endpoint buttons to show any changes
|
|
362
|
+
this.tab.refreshEndpointButtons();
|
|
363
|
+
|
|
324
364
|
this.close();
|
|
325
365
|
}
|
|
326
366
|
|
|
@@ -416,6 +456,124 @@ export default class TabSettingsModal {
|
|
|
416
456
|
this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
|
|
417
457
|
}
|
|
418
458
|
|
|
459
|
+
private drawEndpointButtonsSettings(container: HTMLElement) {
|
|
460
|
+
const section = document.createElement("div");
|
|
461
|
+
addClass(section, "settingsSection");
|
|
462
|
+
|
|
463
|
+
const label = document.createElement("label");
|
|
464
|
+
label.textContent = "Custom Endpoint Buttons";
|
|
465
|
+
addClass(label, "settingsLabel");
|
|
466
|
+
|
|
467
|
+
const help = document.createElement("div");
|
|
468
|
+
help.textContent = "Add custom endpoint buttons that will appear next to the endpoint textbox.";
|
|
469
|
+
addClass(help, "settingsHelp");
|
|
470
|
+
|
|
471
|
+
section.appendChild(label);
|
|
472
|
+
section.appendChild(help);
|
|
473
|
+
|
|
474
|
+
// List of existing buttons
|
|
475
|
+
const buttonsList = document.createElement("div");
|
|
476
|
+
addClass(buttonsList, "endpointButtonsList");
|
|
477
|
+
this.renderEndpointButtonsList(buttonsList);
|
|
478
|
+
section.appendChild(buttonsList);
|
|
479
|
+
|
|
480
|
+
// Form to add new button
|
|
481
|
+
const addForm = document.createElement("div");
|
|
482
|
+
addClass(addForm, "addEndpointButtonForm");
|
|
483
|
+
|
|
484
|
+
const labelInput = document.createElement("input");
|
|
485
|
+
labelInput.type = "text";
|
|
486
|
+
labelInput.placeholder = "Button label (e.g., DBpedia)";
|
|
487
|
+
addClass(labelInput, "endpointButtonLabelInput");
|
|
488
|
+
|
|
489
|
+
const endpointInput = document.createElement("input");
|
|
490
|
+
endpointInput.type = "url";
|
|
491
|
+
endpointInput.placeholder = "Endpoint URL (e.g., https://dbpedia.org/sparql)";
|
|
492
|
+
addClass(endpointInput, "endpointButtonEndpointInput");
|
|
493
|
+
|
|
494
|
+
const addButton = document.createElement("button");
|
|
495
|
+
addButton.textContent = "+ Add Button";
|
|
496
|
+
addClass(addButton, "addEndpointButton");
|
|
497
|
+
addButton.type = "button";
|
|
498
|
+
addButton.onclick = () => {
|
|
499
|
+
const labelValue = labelInput.value.trim();
|
|
500
|
+
const endpointValue = endpointInput.value.trim();
|
|
501
|
+
|
|
502
|
+
if (!labelValue || !endpointValue) {
|
|
503
|
+
alert("Please enter both a label and an endpoint URL.");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Add to persistent config
|
|
508
|
+
const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
|
|
509
|
+
currentButtons.push({ label: labelValue, endpoint: endpointValue });
|
|
510
|
+
this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
|
|
511
|
+
|
|
512
|
+
// Clear inputs
|
|
513
|
+
labelInput.value = "";
|
|
514
|
+
endpointInput.value = "";
|
|
515
|
+
|
|
516
|
+
// Refresh list
|
|
517
|
+
this.renderEndpointButtonsList(buttonsList);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
addForm.appendChild(labelInput);
|
|
521
|
+
addForm.appendChild(endpointInput);
|
|
522
|
+
addForm.appendChild(addButton);
|
|
523
|
+
section.appendChild(addForm);
|
|
524
|
+
|
|
525
|
+
container.appendChild(section);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private renderEndpointButtonsList(container: HTMLElement) {
|
|
529
|
+
container.innerHTML = "";
|
|
530
|
+
const customButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
|
|
531
|
+
|
|
532
|
+
if (customButtons.length === 0) {
|
|
533
|
+
const emptyMsg = document.createElement("div");
|
|
534
|
+
emptyMsg.textContent = "No custom buttons yet. Add one below.";
|
|
535
|
+
addClass(emptyMsg, "emptyMessage");
|
|
536
|
+
container.appendChild(emptyMsg);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
customButtons.forEach((btn, index) => {
|
|
541
|
+
const item = document.createElement("div");
|
|
542
|
+
addClass(item, "endpointButtonItem");
|
|
543
|
+
|
|
544
|
+
const labelSpan = document.createElement("span");
|
|
545
|
+
labelSpan.textContent = `${btn.label}`;
|
|
546
|
+
addClass(labelSpan, "buttonLabel");
|
|
547
|
+
|
|
548
|
+
const endpointSpan = document.createElement("span");
|
|
549
|
+
endpointSpan.textContent = btn.endpoint;
|
|
550
|
+
addClass(endpointSpan, "buttonEndpoint");
|
|
551
|
+
|
|
552
|
+
const removeBtn = document.createElement("button");
|
|
553
|
+
removeBtn.textContent = "×";
|
|
554
|
+
addClass(removeBtn, "removeButton");
|
|
555
|
+
removeBtn.type = "button";
|
|
556
|
+
removeBtn.onclick = () => {
|
|
557
|
+
const currentButtons = this.tab.yasgui.persistentConfig.getCustomEndpointButtons();
|
|
558
|
+
currentButtons.splice(index, 1);
|
|
559
|
+
this.tab.yasgui.persistentConfig.setCustomEndpointButtons(currentButtons);
|
|
560
|
+
this.renderEndpointButtonsList(container);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
item.appendChild(labelSpan);
|
|
564
|
+
item.appendChild(endpointSpan);
|
|
565
|
+
item.appendChild(removeBtn);
|
|
566
|
+
container.appendChild(item);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private getThemeToggleIcon(): string {
|
|
571
|
+
const currentTheme = this.tab.yasgui.getTheme();
|
|
572
|
+
// In dark mode, show moon icon (clicking will switch to light)
|
|
573
|
+
// In light mode, show sun icon (clicking will switch to dark)
|
|
574
|
+
return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
|
|
575
|
+
}
|
|
576
|
+
|
|
419
577
|
public destroy() {
|
|
420
578
|
if (this.modalOverlay && this.modalOverlay.parentNode) {
|
|
421
579
|
this.modalOverlay.parentNode.removeChild(this.modalOverlay);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Storage as YStorage } from "@matdata/yasgui-utils";
|
|
2
|
+
|
|
3
|
+
export type Theme = "light" | "dark";
|
|
4
|
+
|
|
5
|
+
export class ThemeManager {
|
|
6
|
+
private static STORAGE_KEY = "yasgui_theme";
|
|
7
|
+
private storage: YStorage;
|
|
8
|
+
private currentTheme: Theme;
|
|
9
|
+
private rootElement: HTMLElement;
|
|
10
|
+
|
|
11
|
+
constructor(rootElement: HTMLElement) {
|
|
12
|
+
this.storage = new YStorage("yasgui");
|
|
13
|
+
this.rootElement = rootElement;
|
|
14
|
+
this.currentTheme = this.getStoredTheme() || this.getSystemTheme();
|
|
15
|
+
this.applyTheme(this.currentTheme);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the currently active theme
|
|
20
|
+
*/
|
|
21
|
+
public getTheme(): Theme {
|
|
22
|
+
return this.currentTheme;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Set and apply a new theme
|
|
27
|
+
*/
|
|
28
|
+
public setTheme(theme: Theme): void {
|
|
29
|
+
this.currentTheme = theme;
|
|
30
|
+
this.applyTheme(theme);
|
|
31
|
+
// Store theme preference with 1 year expiration
|
|
32
|
+
this.storage.set(ThemeManager.STORAGE_KEY, theme, 60 * 60 * 24 * 365, () => {
|
|
33
|
+
console.warn("Failed to store theme preference due to quota exceeded");
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Toggle between light and dark themes
|
|
39
|
+
*/
|
|
40
|
+
public toggleTheme(): Theme {
|
|
41
|
+
const newTheme = this.currentTheme === "light" ? "dark" : "light";
|
|
42
|
+
this.setTheme(newTheme);
|
|
43
|
+
return newTheme;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Apply theme to the DOM
|
|
48
|
+
*/
|
|
49
|
+
private applyTheme(theme: Theme): void {
|
|
50
|
+
// Set data-theme attribute on root element
|
|
51
|
+
this.rootElement.setAttribute("data-theme", theme);
|
|
52
|
+
|
|
53
|
+
// Also set on document root for global styles
|
|
54
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
55
|
+
|
|
56
|
+
// Update Yasqe CodeMirror theme
|
|
57
|
+
this.updateCodeMirrorTheme(theme);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Update CodeMirror theme for all Yasqe editors
|
|
62
|
+
*/
|
|
63
|
+
private updateCodeMirrorTheme(theme: Theme): void {
|
|
64
|
+
const cmTheme = theme === "dark" ? "material-palenight" : "default";
|
|
65
|
+
|
|
66
|
+
// Find all CodeMirror instances within the root element
|
|
67
|
+
const cmElements = this.rootElement.querySelectorAll(".CodeMirror");
|
|
68
|
+
cmElements.forEach((element) => {
|
|
69
|
+
const cm = (element as any).CodeMirror;
|
|
70
|
+
if (cm && cm.setOption) {
|
|
71
|
+
cm.setOption("theme", cmTheme);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get theme from localStorage
|
|
78
|
+
*/
|
|
79
|
+
private getStoredTheme(): Theme | null {
|
|
80
|
+
const stored = this.storage.get<Theme>(ThemeManager.STORAGE_KEY);
|
|
81
|
+
if (stored === "light" || stored === "dark") {
|
|
82
|
+
return stored;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect system theme preference
|
|
89
|
+
*/
|
|
90
|
+
private getSystemTheme(): Theme {
|
|
91
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
92
|
+
return "dark";
|
|
93
|
+
}
|
|
94
|
+
return "light";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Listen for system theme changes
|
|
99
|
+
*/
|
|
100
|
+
public listenToSystemTheme(): void {
|
|
101
|
+
if (window.matchMedia) {
|
|
102
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
103
|
+
mediaQuery.addEventListener("change", (e) => {
|
|
104
|
+
// Only update if user hasn't manually set a theme
|
|
105
|
+
if (!this.storage.get(ThemeManager.STORAGE_KEY)) {
|
|
106
|
+
const newTheme = e.matches ? "dark" : "light";
|
|
107
|
+
this.currentTheme = newTheme;
|
|
108
|
+
this.applyTheme(newTheme);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Refresh theme application (useful after new content is loaded)
|
|
116
|
+
*/
|
|
117
|
+
public refresh(): void {
|
|
118
|
+
this.applyTheme(this.currentTheme);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/defaults.ts
CHANGED
|
@@ -28,6 +28,10 @@ export default function initialize(): Config<CatalogueItem> {
|
|
|
28
28
|
persistenceLabelConfig: "config",
|
|
29
29
|
yasqe: Yasqe.defaults,
|
|
30
30
|
yasr: Yasr.defaults,
|
|
31
|
+
theme: undefined,
|
|
32
|
+
showThemeToggle: true,
|
|
33
|
+
orientation: "vertical",
|
|
34
|
+
endpointButtons: undefined,
|
|
31
35
|
endpointCatalogueOptions: {
|
|
32
36
|
getData: () => {
|
|
33
37
|
return [
|
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
|
@@ -12,8 +12,11 @@ import { default as Yasr, Config as YasrConfig } from "@matdata/yasr";
|
|
|
12
12
|
import { addClass, removeClass } from "@matdata/yasgui-utils";
|
|
13
13
|
import GeoPlugin from "yasgui-geo-tg";
|
|
14
14
|
import GraphPlugin from "@matdata/yasgui-graph-plugin";
|
|
15
|
+
import { ThemeManager, Theme } from "./ThemeManager";
|
|
15
16
|
import "./index.scss";
|
|
17
|
+
import "./themes.scss";
|
|
16
18
|
import "../../yasr/src/scss/global.scss";
|
|
19
|
+
import "codemirror/theme/material-palenight.css";
|
|
17
20
|
|
|
18
21
|
// Register plugins to Yasr
|
|
19
22
|
Yasr.registerPlugin("Geo", GeoPlugin);
|
|
@@ -28,6 +31,10 @@ if (window) {
|
|
|
28
31
|
export type YasguiRequestConfig = Omit<RequestConfig<Yasgui>, "adjustQueryBeforeRequest"> & {
|
|
29
32
|
adjustQueryBeforeRequest: RequestConfig<Yasqe>["adjustQueryBeforeRequest"];
|
|
30
33
|
};
|
|
34
|
+
export interface EndpointButton {
|
|
35
|
+
endpoint: string;
|
|
36
|
+
label: string;
|
|
37
|
+
}
|
|
31
38
|
export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
|
|
32
39
|
/**
|
|
33
40
|
* Autofocus yasqe on load or tab switch
|
|
@@ -38,6 +45,7 @@ export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
|
|
|
38
45
|
tabName: string;
|
|
39
46
|
corsProxy: string | undefined;
|
|
40
47
|
endpointCatalogueOptions: EndpointSelectConfig<EndpointObject>;
|
|
48
|
+
endpointButtons?: EndpointButton[];
|
|
41
49
|
//The function allows us to modify the config before we pass it on to a tab
|
|
42
50
|
populateFromUrl: boolean | ((configFromUrl: PersistedTabJson) => PersistedTabJson);
|
|
43
51
|
autoAddOnInit: boolean;
|
|
@@ -50,6 +58,14 @@ export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
|
|
|
50
58
|
requestConfig: YasguiRequestConfig;
|
|
51
59
|
contextMenuContainer: HTMLElement | undefined;
|
|
52
60
|
nonSslDomain?: string;
|
|
61
|
+
theme?: Theme;
|
|
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";
|
|
53
69
|
}
|
|
54
70
|
export type PartialConfig = {
|
|
55
71
|
[P in keyof Config]?: Config[P] extends object ? Partial<Config[P]> : Config[P];
|
|
@@ -96,6 +112,7 @@ export class Yasgui extends EventEmitter {
|
|
|
96
112
|
public tabPanelsEl: HTMLDivElement;
|
|
97
113
|
public config: Config;
|
|
98
114
|
public persistentConfig: PersistentConfig;
|
|
115
|
+
public themeManager: ThemeManager;
|
|
99
116
|
public static Tab = Tab;
|
|
100
117
|
constructor(parent: HTMLElement, config: PartialConfig) {
|
|
101
118
|
super();
|
|
@@ -104,6 +121,13 @@ export class Yasgui extends EventEmitter {
|
|
|
104
121
|
parent.appendChild(this.rootEl);
|
|
105
122
|
|
|
106
123
|
this.config = merge({}, Yasgui.defaults, config);
|
|
124
|
+
|
|
125
|
+
// Initialize theme manager
|
|
126
|
+
this.themeManager = new ThemeManager(this.rootEl);
|
|
127
|
+
if (this.config.theme) {
|
|
128
|
+
this.themeManager.setTheme(this.config.theme);
|
|
129
|
+
}
|
|
130
|
+
this.themeManager.listenToSystemTheme();
|
|
107
131
|
this.persistentConfig = new PersistentConfig(this);
|
|
108
132
|
|
|
109
133
|
this.tabElements = new TabElements(this);
|
|
@@ -348,6 +372,24 @@ export class Yasgui extends EventEmitter {
|
|
|
348
372
|
this.addTab(true, config.tab, { atIndex: config.index });
|
|
349
373
|
}
|
|
350
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Get the current theme
|
|
377
|
+
*/
|
|
378
|
+
public getTheme(): Theme {
|
|
379
|
+
return this.themeManager.getTheme();
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Set the theme
|
|
383
|
+
*/
|
|
384
|
+
public setTheme(theme: Theme): void {
|
|
385
|
+
this.themeManager.setTheme(theme);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Toggle between light and dark themes
|
|
389
|
+
*/
|
|
390
|
+
public toggleTheme(): Theme {
|
|
391
|
+
return this.themeManager.toggleTheme();
|
|
392
|
+
}
|
|
351
393
|
public destroy() {
|
|
352
394
|
this.removeAllListeners();
|
|
353
395
|
this.tabElements.destroy();
|
|
@@ -370,4 +412,5 @@ export function getRandomId() {
|
|
|
370
412
|
return Math.random().toString(36).substring(7);
|
|
371
413
|
}
|
|
372
414
|
|
|
415
|
+
export type { Theme } from "./ThemeManager";
|
|
373
416
|
export default Yasgui;
|
package/src/tab.scss
CHANGED
|
@@ -18,9 +18,57 @@
|
|
|
18
18
|
left: 0;
|
|
19
19
|
right: 0;
|
|
20
20
|
z-index: 9999;
|
|
21
|
-
background:
|
|
22
|
-
border-bottom: 1px solid
|
|
21
|
+
background: var(--yasgui-bg-primary);
|
|
22
|
+
border-bottom: 1px solid var(--yasgui-border-color);
|
|
23
23
|
padding: 5px;
|
|
24
|
+
|
|
25
|
+
// Hide the layout orientation toggle button in fullscreen mode
|
|
26
|
+
.orientationToggle {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Horizontal layout (side-by-side)
|
|
32
|
+
&.orientation-horizontal {
|
|
33
|
+
&.active {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: row;
|
|
36
|
+
gap: 10px;
|
|
37
|
+
// Allow flexible height - can use 100vh or be constrained by parent
|
|
38
|
+
min-height: var(--yasgui-min-height);
|
|
39
|
+
height: calc(100vh - var(--yasgui-header-height));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.editorwrapper {
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Make YASQE fill the vertical space in horizontal mode
|
|
51
|
+
.yasqe {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
flex: 1;
|
|
55
|
+
min-height: 0;
|
|
56
|
+
|
|
57
|
+
.CodeMirror {
|
|
58
|
+
height: 100% !important;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.yasrWrapperEl {
|
|
63
|
+
flex: 1;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.yasr {
|
|
69
|
+
margin-top: 0;
|
|
70
|
+
height: 100%;
|
|
71
|
+
}
|
|
24
72
|
}
|
|
25
73
|
}
|
|
26
74
|
.yasr {
|
|
@@ -32,9 +80,13 @@
|
|
|
32
80
|
background: none;
|
|
33
81
|
align-self: center;
|
|
34
82
|
padding-left: 10px;
|
|
83
|
+
padding-right: 10px;
|
|
35
84
|
cursor: pointer;
|
|
36
|
-
color: #505050;
|
|
37
|
-
fill: #505050;
|
|
85
|
+
color: var(--yasgui-button-text, #505050);
|
|
86
|
+
fill: var(--yasgui-button-text, #505050);
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
38
90
|
|
|
39
91
|
.svgImg {
|
|
40
92
|
width: 15px;
|
|
@@ -43,13 +95,13 @@
|
|
|
43
95
|
}
|
|
44
96
|
// IE11 Needs this specified otherwise it will not resize the svg
|
|
45
97
|
svg {
|
|
46
|
-
|
|
47
|
-
|
|
98
|
+
width: 20px;
|
|
99
|
+
height: 20px;
|
|
48
100
|
}
|
|
49
101
|
|
|
50
102
|
&:hover {
|
|
51
|
-
color: black;
|
|
52
|
-
fill: black;
|
|
103
|
+
color: var(--yasgui-button-hover, black);
|
|
104
|
+
fill: var(--yasgui-button-hover, black);
|
|
53
105
|
}
|
|
54
106
|
}
|
|
55
107
|
|