@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.
Files changed (38) hide show
  1. package/README.md +196 -111
  2. package/build/ts/src/PersistentConfig.d.ts +4 -1
  3. package/build/ts/src/PersistentConfig.js +8 -0
  4. package/build/ts/src/PersistentConfig.js.map +1 -1
  5. package/build/ts/src/Tab.d.ts +8 -0
  6. package/build/ts/src/Tab.js +85 -1
  7. package/build/ts/src/Tab.js.map +1 -1
  8. package/build/ts/src/TabElements.js +6 -0
  9. package/build/ts/src/TabElements.js.map +1 -1
  10. package/build/ts/src/TabSettingsModal.d.ts +4 -0
  11. package/build/ts/src/TabSettingsModal.js +128 -9
  12. package/build/ts/src/TabSettingsModal.js.map +1 -1
  13. package/build/ts/src/ThemeManager.d.ts +17 -0
  14. package/build/ts/src/ThemeManager.js +69 -0
  15. package/build/ts/src/ThemeManager.js.map +1 -0
  16. package/build/ts/src/defaults.js +4 -0
  17. package/build/ts/src/defaults.js.map +1 -1
  18. package/build/ts/src/index.d.ts +16 -0
  19. package/build/ts/src/index.js +17 -0
  20. package/build/ts/src/index.js.map +1 -1
  21. package/build/yasgui.min.css +1 -1
  22. package/build/yasgui.min.css.map +3 -3
  23. package/build/yasgui.min.js +114 -98
  24. package/build/yasgui.min.js.map +4 -4
  25. package/package.json +1 -1
  26. package/src/PersistentConfig.ts +10 -1
  27. package/src/Tab.ts +125 -0
  28. package/src/TabElements.scss +33 -6
  29. package/src/TabElements.ts +9 -0
  30. package/src/TabSettingsModal.scss +154 -20
  31. package/src/TabSettingsModal.ts +171 -13
  32. package/src/ThemeManager.ts +120 -0
  33. package/src/defaults.ts +4 -0
  34. package/src/endpointSelect.scss +34 -0
  35. package/src/index.scss +2 -2
  36. package/src/index.ts +43 -0
  37. package/src/tab.scss +60 -8
  38. package/src/themes.scss +543 -0
@@ -1,7 +1,20 @@
1
- import { addClass, removeClass, drawSvgStringAsElement } from "@matdata/yasgui-utils";
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.appendChild(
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 = "Tab Settings";
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 ((tabName === "request" && index === 0) || (tabName === "prefix" && index === 1)) {
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 [
@@ -119,4 +119,38 @@
119
119
  opacity: 0.8;
120
120
  }
121
121
  }
122
+
123
+ .endpointButtonsContainer {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 4px;
127
+ margin-left: 4px;
128
+ }
129
+
130
+ .endpointButton {
131
+ border: 1px solid var(--yasgui-endpoint-button-border, #337ab7);
132
+ background-color: var(--yasgui-endpoint-button-bg, #337ab7);
133
+ color: var(--yasgui-endpoint-button-text, #ffffff);
134
+ border-radius: 3px;
135
+ cursor: pointer;
136
+ padding: 4px 12px;
137
+ font-size: 13px;
138
+ font-weight: 500;
139
+ white-space: nowrap;
140
+ transition: all 0.2s ease;
141
+
142
+ &:hover {
143
+ background-color: var(--yasgui-endpoint-button-hover-bg, #286090);
144
+ border-color: var(--yasgui-endpoint-button-hover-border, #286090);
145
+ }
146
+
147
+ &:active {
148
+ transform: translateY(1px);
149
+ }
150
+
151
+ &:focus {
152
+ outline: 2px solid var(--yasgui-endpoint-button-focus, #5cb3fd);
153
+ outline-offset: 2px;
154
+ }
155
+ }
122
156
  }
package/src/index.scss CHANGED
@@ -11,8 +11,8 @@
11
11
  left: 0;
12
12
  right: 0;
13
13
  z-index: 9999;
14
- background: white;
15
- border-bottom: 1px solid #ddd;
14
+ background: var(--yasgui-bg-primary);
15
+ border-bottom: 1px solid var(--yasgui-border-color);
16
16
  padding: 5px;
17
17
  margin: 0;
18
18
 
package/src/index.ts CHANGED
@@ -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: white;
22
- border-bottom: 1px solid #ddd;
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
- max-width: 15px;
47
- max-height: 15px;
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