@matdata/yasgui 5.10.0 → 5.11.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 (133) hide show
  1. package/README.md +6 -2
  2. package/build/ts/src/PersistentConfig.d.ts +10 -0
  3. package/build/ts/src/PersistentConfig.js +40 -0
  4. package/build/ts/src/PersistentConfig.js.map +1 -1
  5. package/build/ts/src/Tab.d.ts +17 -0
  6. package/build/ts/src/Tab.js +372 -15
  7. package/build/ts/src/Tab.js.map +1 -1
  8. package/build/ts/src/TabContextMenu.d.ts +1 -0
  9. package/build/ts/src/TabContextMenu.js +17 -0
  10. package/build/ts/src/TabContextMenu.js.map +1 -1
  11. package/build/ts/src/TabElements.d.ts +3 -0
  12. package/build/ts/src/TabElements.js +97 -28
  13. package/build/ts/src/TabElements.js.map +1 -1
  14. package/build/ts/src/TabSettingsModal.d.ts +2 -0
  15. package/build/ts/src/TabSettingsModal.js +44 -19
  16. package/build/ts/src/TabSettingsModal.js.map +1 -1
  17. package/build/ts/src/index.d.ts +3 -0
  18. package/build/ts/src/index.js +4 -0
  19. package/build/ts/src/index.js.map +1 -1
  20. package/build/ts/src/queryManagement/QueryBrowser.d.ts +64 -0
  21. package/build/ts/src/queryManagement/QueryBrowser.js +914 -0
  22. package/build/ts/src/queryManagement/QueryBrowser.js.map +1 -0
  23. package/build/ts/src/queryManagement/SaveManagedQueryModal.d.ts +55 -0
  24. package/build/ts/src/queryManagement/SaveManagedQueryModal.js +451 -0
  25. package/build/ts/src/queryManagement/SaveManagedQueryModal.js.map +1 -0
  26. package/build/ts/src/queryManagement/WorkspaceSettingsForm.d.ts +16 -0
  27. package/build/ts/src/queryManagement/WorkspaceSettingsForm.js +452 -0
  28. package/build/ts/src/queryManagement/WorkspaceSettingsForm.js.map +1 -0
  29. package/build/ts/src/queryManagement/backends/BaseGitProviderClient.d.ts +19 -0
  30. package/build/ts/src/queryManagement/backends/BaseGitProviderClient.js +77 -0
  31. package/build/ts/src/queryManagement/backends/BaseGitProviderClient.js.map +1 -0
  32. package/build/ts/src/queryManagement/backends/BitbucketProviderClient.d.ts +16 -0
  33. package/build/ts/src/queryManagement/backends/BitbucketProviderClient.js +269 -0
  34. package/build/ts/src/queryManagement/backends/BitbucketProviderClient.js.map +1 -0
  35. package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.d.ts +26 -0
  36. package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js +93 -0
  37. package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js.map +1 -0
  38. package/build/ts/src/queryManagement/backends/GiteaProviderClient.d.ts +14 -0
  39. package/build/ts/src/queryManagement/backends/GiteaProviderClient.js +244 -0
  40. package/build/ts/src/queryManagement/backends/GiteaProviderClient.js.map +1 -0
  41. package/build/ts/src/queryManagement/backends/GithubProviderClient.d.ts +14 -0
  42. package/build/ts/src/queryManagement/backends/GithubProviderClient.js +252 -0
  43. package/build/ts/src/queryManagement/backends/GithubProviderClient.js.map +1 -0
  44. package/build/ts/src/queryManagement/backends/GitlabProviderClient.d.ts +16 -0
  45. package/build/ts/src/queryManagement/backends/GitlabProviderClient.js +246 -0
  46. package/build/ts/src/queryManagement/backends/GitlabProviderClient.js.map +1 -0
  47. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.d.ts +21 -0
  48. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.js +175 -0
  49. package/build/ts/src/queryManagement/backends/InMemoryWorkspaceBackend.js.map +1 -0
  50. package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.d.ts +28 -0
  51. package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js +687 -0
  52. package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js.map +1 -0
  53. package/build/ts/src/queryManagement/backends/WorkspaceBackend.d.ts +15 -0
  54. package/build/ts/src/queryManagement/backends/WorkspaceBackend.js +2 -0
  55. package/build/ts/src/queryManagement/backends/WorkspaceBackend.js.map +1 -0
  56. package/build/ts/src/queryManagement/backends/errors.d.ts +7 -0
  57. package/build/ts/src/queryManagement/backends/errors.js +18 -0
  58. package/build/ts/src/queryManagement/backends/errors.js.map +1 -0
  59. package/build/ts/src/queryManagement/backends/getWorkspaceBackend.d.ts +8 -0
  60. package/build/ts/src/queryManagement/backends/getWorkspaceBackend.js +114 -0
  61. package/build/ts/src/queryManagement/backends/getWorkspaceBackend.js.map +1 -0
  62. package/build/ts/src/queryManagement/backends/gitRemote.d.ts +5 -0
  63. package/build/ts/src/queryManagement/backends/gitRemote.js +40 -0
  64. package/build/ts/src/queryManagement/backends/gitRemote.js.map +1 -0
  65. package/build/ts/src/queryManagement/browserFilter.d.ts +2 -0
  66. package/build/ts/src/queryManagement/browserFilter.js +7 -0
  67. package/build/ts/src/queryManagement/browserFilter.js.map +1 -0
  68. package/build/ts/src/queryManagement/index.d.ts +13 -0
  69. package/build/ts/src/queryManagement/index.js +14 -0
  70. package/build/ts/src/queryManagement/index.js.map +1 -0
  71. package/build/ts/src/queryManagement/normalizeQueryFilename.d.ts +1 -0
  72. package/build/ts/src/queryManagement/normalizeQueryFilename.js +10 -0
  73. package/build/ts/src/queryManagement/normalizeQueryFilename.js.map +1 -0
  74. package/build/ts/src/queryManagement/openManagedQuery.d.ts +15 -0
  75. package/build/ts/src/queryManagement/openManagedQuery.js +27 -0
  76. package/build/ts/src/queryManagement/openManagedQuery.js.map +1 -0
  77. package/build/ts/src/queryManagement/saveManagedQuery.d.ts +20 -0
  78. package/build/ts/src/queryManagement/saveManagedQuery.js +109 -0
  79. package/build/ts/src/queryManagement/saveManagedQuery.js.map +1 -0
  80. package/build/ts/src/queryManagement/textHash.d.ts +2 -0
  81. package/build/ts/src/queryManagement/textHash.js +13 -0
  82. package/build/ts/src/queryManagement/textHash.js.map +1 -0
  83. package/build/ts/src/queryManagement/types.d.ts +76 -0
  84. package/build/ts/src/queryManagement/types.js +2 -0
  85. package/build/ts/src/queryManagement/types.js.map +1 -0
  86. package/build/ts/src/queryManagement/validateWorkspaceConfig.d.ts +6 -0
  87. package/build/ts/src/queryManagement/validateWorkspaceConfig.js +33 -0
  88. package/build/ts/src/queryManagement/validateWorkspaceConfig.js.map +1 -0
  89. package/build/ts/src/version.d.ts +1 -1
  90. package/build/ts/src/version.js +1 -1
  91. package/build/yasgui.min.css +10 -1
  92. package/build/yasgui.min.css.map +3 -3
  93. package/build/yasgui.min.js +398 -172
  94. package/build/yasgui.min.js.map +4 -4
  95. package/package.json +1 -1
  96. package/src/PersistentConfig.ts +61 -0
  97. package/src/Tab.ts +431 -20
  98. package/src/TabContextMenu.ts +10 -0
  99. package/src/TabElements.scss +46 -7
  100. package/src/TabElements.ts +95 -27
  101. package/src/TabSettingsModal.scss +102 -5
  102. package/src/TabSettingsModal.ts +48 -25
  103. package/src/endpointSelect.scss +2 -3
  104. package/src/index.scss +4 -0
  105. package/src/index.ts +7 -0
  106. package/src/queryManagement/QueryBrowser.scss +418 -0
  107. package/src/queryManagement/QueryBrowser.ts +1079 -0
  108. package/src/queryManagement/SaveManagedQueryModal.scss +245 -0
  109. package/src/queryManagement/SaveManagedQueryModal.ts +554 -0
  110. package/src/queryManagement/WorkspaceSettingsForm.ts +546 -0
  111. package/src/queryManagement/backends/BaseGitProviderClient.ts +124 -0
  112. package/src/queryManagement/backends/BitbucketProviderClient.ts +403 -0
  113. package/src/queryManagement/backends/GitWorkspaceBackend.ts +96 -0
  114. package/src/queryManagement/backends/GiteaProviderClient.ts +316 -0
  115. package/src/queryManagement/backends/GithubProviderClient.ts +319 -0
  116. package/src/queryManagement/backends/GitlabProviderClient.ts +327 -0
  117. package/src/queryManagement/backends/InMemoryWorkspaceBackend.ts +175 -0
  118. package/src/queryManagement/backends/SparqlWorkspaceBackend.ts +729 -0
  119. package/src/queryManagement/backends/WorkspaceBackend.ts +41 -0
  120. package/src/queryManagement/backends/errors.ts +28 -0
  121. package/src/queryManagement/backends/getWorkspaceBackend.ts +132 -0
  122. package/src/queryManagement/backends/gitRemote.ts +54 -0
  123. package/src/queryManagement/browserFilter.ts +8 -0
  124. package/src/queryManagement/index.ts +15 -0
  125. package/src/queryManagement/normalizeQueryFilename.ts +8 -0
  126. package/src/queryManagement/openManagedQuery.ts +31 -0
  127. package/src/queryManagement/saveManagedQuery.ts +135 -0
  128. package/src/queryManagement/textHash.ts +15 -0
  129. package/src/queryManagement/types.ts +85 -0
  130. package/src/queryManagement/validateWorkspaceConfig.ts +40 -0
  131. package/src/tab.scss +4 -14
  132. package/src/themes.scss +14 -23
  133. package/src/version.ts +1 -1
@@ -8,6 +8,31 @@ $minTabHeight: 35px;
8
8
  display: flex;
9
9
  flex-wrap: wrap;
10
10
 
11
+ .queryBrowserToggle {
12
+ cursor: pointer;
13
+ min-height: $minTabHeight;
14
+ padding: 0 10px;
15
+ background: transparent;
16
+ border: none;
17
+ color: var(--yasgui-button-text, #505050);
18
+ fill: var(--yasgui-button-text, #505050);
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ border-bottom: 2px solid transparent;
23
+ box-sizing: border-box;
24
+
25
+ &:hover,
26
+ &:focus-visible {
27
+ color: var(--yasgui-button-hover, #000);
28
+ background: var(--yasgui-bg-secondary, #f7f7f7);
29
+ }
30
+
31
+ i {
32
+ font-size: 20px;
33
+ }
34
+ }
35
+
11
36
  .sortable-placeholder {
12
37
  min-width: 100px;
13
38
  min-height: $minTabHeight;
@@ -33,7 +58,6 @@ $minTabHeight: 35px;
33
58
  background: transparent;
34
59
  border: none;
35
60
  color: var(--yasgui-button-text, #505050);
36
- fill: var(--yasgui-button-text, #505050);
37
61
  display: flex;
38
62
  align-items: center;
39
63
  justify-content: center;
@@ -41,14 +65,8 @@ $minTabHeight: 35px;
41
65
 
42
66
  &:hover {
43
67
  color: var(--yasgui-button-hover, #000);
44
- fill: var(--yasgui-button-hover, #000);
45
68
  background: var(--yasgui-bg-secondary, #f7f7f7);
46
69
  }
47
-
48
- svg {
49
- width: 20px;
50
- height: 20px;
51
- }
52
70
  }
53
71
 
54
72
  .addTab {
@@ -77,6 +95,18 @@ $minTabHeight: 35px;
77
95
  $activeColor: #337ab7;
78
96
  $hoverColor: color.adjust($activeColor, $lightness: 30%);
79
97
 
98
+ // Distinguish managed-query tabs from legacy tabs via background.
99
+ // Legacy tabs keep their current styling.
100
+ &.managed a {
101
+ background: var(--yasgui-tab-managed, #a4cbed);
102
+ }
103
+
104
+ // Unsaved changes indicator for managed queries.
105
+ // The dot indicates the user still needs to save.
106
+ &.managedDirty a span::before {
107
+ content: "• ";
108
+ }
109
+
80
110
  .loader {
81
111
  display: none;
82
112
  background-color: color.adjust(#555, $lightness: 50%);
@@ -118,6 +148,15 @@ $minTabHeight: 35px;
118
148
  display: block;
119
149
  }
120
150
 
151
+ &.renamingInProgress .loader {
152
+ display: block;
153
+ }
154
+
155
+ &.renamingInProgress input {
156
+ opacity: 0.6;
157
+ cursor: not-allowed;
158
+ }
159
+
121
160
  &.active a {
122
161
  border-bottom-color: $activeColor;
123
162
  color: var(--yasgui-text-primary, #555);
@@ -4,14 +4,6 @@ import { hasClass, addClass, removeClass } from "@matdata/yasgui-utils";
4
4
  import sortablejs from "sortablejs";
5
5
  import "./TabElements.scss";
6
6
 
7
- // Theme toggle icons
8
- const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
9
- <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"/>
10
- </svg>`;
11
-
12
- const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
13
- <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"/>
14
- </svg>`;
15
7
  export interface TabList {}
16
8
  export class TabListEl {
17
9
  private tabList: TabList;
@@ -20,6 +12,7 @@ export class TabListEl {
20
12
  private renameEl?: HTMLInputElement;
21
13
  private nameEl?: HTMLSpanElement;
22
14
  public tabEl?: HTMLDivElement;
15
+ private renameSubmitted = false;
23
16
  constructor(yasgui: Yasgui, tabList: TabList, tabId: string) {
24
17
  this.tabList = tabList;
25
18
  this.yasgui = yasgui;
@@ -35,6 +28,7 @@ export class TabListEl {
35
28
  if (this.renameEl) {
36
29
  const tab = this.yasgui.getTab(this.tabId);
37
30
  if (tab) {
31
+ this.renameSubmitted = false;
38
32
  this.renameEl.value = tab.name();
39
33
  addClass(this.tabEl, "renaming");
40
34
  this.renameEl.focus();
@@ -67,6 +61,15 @@ export class TabListEl {
67
61
  removeClass(this.tabEl, "querying");
68
62
  }
69
63
  }
64
+ public setAsRenaming(renaming: boolean) {
65
+ if (renaming) {
66
+ addClass(this.tabEl, "renamingInProgress");
67
+ if (this.renameEl) this.renameEl.disabled = true;
68
+ } else {
69
+ removeClass(this.tabEl, "renamingInProgress");
70
+ if (this.renameEl) this.renameEl.disabled = false;
71
+ }
72
+ }
70
73
  public draw(name: string) {
71
74
  this.tabEl = document.createElement("div");
72
75
  this.tabEl.setAttribute("role", "presentation");
@@ -74,8 +77,21 @@ export class TabListEl {
74
77
  this.startRename();
75
78
  };
76
79
  addClass(this.tabEl, "tab");
80
+
81
+ const tabConf = this.yasgui.persistentConfig.getTab(this.tabId) as any;
82
+ if (tabConf?.managedQuery) {
83
+ addClass(this.tabEl, "managed");
84
+ }
85
+
86
+ // Set initial dirty state for managed queries (e.g., after reload).
87
+ const initialTab = this.yasgui.getTab(this.tabId);
88
+ if (initialTab?.hasUnsavedManagedChanges?.()) {
89
+ addClass(this.tabEl, "managedDirty");
90
+ }
91
+
77
92
  this.tabEl.addEventListener("keydown", (e: KeyboardEvent) => {
78
93
  // Don't handle Delete key if we're renaming the tab (input field is active)
94
+ if (!this.tabEl) return;
79
95
  if (e.code === "Delete" && !this.tabEl.classList.contains("renaming")) {
80
96
  handleDeleteTab();
81
97
  }
@@ -103,9 +119,10 @@ export class TabListEl {
103
119
  tabLinkEl.addEventListener("focus", () => {
104
120
  if (!this.tabEl) return;
105
121
  if (this.tabEl.classList.contains("active")) {
106
- const allTabs = Object.keys(this.tabList._tabs);
107
- const currentTabIndex = allTabs.indexOf(this.tabId);
108
- this.tabList.tabEntryIndex = currentTabIndex;
122
+ // Keep arrow-key navigation in sync with actual DOM order
123
+ const listEl = this.tabList._tabsListEl;
124
+ if (!listEl) return;
125
+ this.tabList.tabEntryIndex = Array.prototype.indexOf.call(listEl.children, this.tabEl);
109
126
  }
110
127
  });
111
128
  // if (this.yasgui.persistentConfig.tabIsActive(this.tabId)) {
@@ -134,15 +151,19 @@ export class TabListEl {
134
151
  const renameEl = (this.renameEl = document.createElement("input"));
135
152
  renameEl.type = "text";
136
153
  renameEl.value = name;
154
+ const submitRename = () => {
155
+ if (this.renameSubmitted) return;
156
+ this.renameSubmitted = true;
157
+ void this.yasgui.getTab(this.tabId)?.renameTab(renameEl.value);
158
+ removeClass(this.tabEl, "renaming");
159
+ };
137
160
  renameEl.onkeyup = (event) => {
138
161
  if (event.key === "Enter") {
139
- this.yasgui.getTab(this.tabId)?.setName(renameEl.value);
140
- removeClass(this.tabEl, "renaming");
162
+ submitRename();
141
163
  }
142
164
  };
143
165
  renameEl.onblur = () => {
144
- this.yasgui.getTab(this.tabId)?.setName(renameEl.value);
145
- removeClass(this.tabEl, "renaming");
166
+ submitRename();
146
167
  };
147
168
  // Prevent sortablejs from detecting drag events on the input field
148
169
  renameEl.addEventListener("mousedown", (e) => {
@@ -185,6 +206,7 @@ export class TabList {
185
206
  public _tabsListEl?: HTMLDivElement;
186
207
  public tabContextMenu?: TabContextMenu;
187
208
  public tabEntryIndex: number | undefined;
209
+ private _queryBrowserToggleEl?: HTMLDivElement;
188
210
 
189
211
  constructor(yasgui: Yasgui) {
190
212
  this.yasgui = yasgui;
@@ -214,6 +236,24 @@ export class TabList {
214
236
  this._tabs[id].setAsQuerying(false);
215
237
  }
216
238
  });
239
+
240
+ this.yasgui.on("tabChange", (_yasgui, tab) => {
241
+ const id = tab.getId();
242
+ const tabEl = this._tabs[id]?.tabEl;
243
+ if (!tabEl) return;
244
+
245
+ if (tab.isManagedQueryTab()) {
246
+ addClass(tabEl, "managed");
247
+ } else {
248
+ removeClass(tabEl, "managed");
249
+ }
250
+
251
+ if (tab.hasUnsavedManagedChanges()) {
252
+ addClass(tabEl, "managedDirty");
253
+ } else {
254
+ removeClass(tabEl, "managedDirty");
255
+ }
256
+ });
217
257
  }
218
258
  private getActiveIndex() {
219
259
  if (!this._selectedTab) return;
@@ -228,26 +268,37 @@ export class TabList {
228
268
  if (!this._tabsListEl) return;
229
269
  const numOfChildren = this._tabsListEl.childElementCount;
230
270
  if (typeof this.tabEntryIndex !== "number") return;
271
+
272
+ const isQueryBrowserToggleEntry = (el: Element) => !!(el as HTMLElement).querySelector?.(".queryBrowserToggle");
273
+ const advance = (direction: -1 | 1) => {
274
+ let nextIndex = this.tabEntryIndex as number;
275
+ for (let i = 0; i < numOfChildren; i++) {
276
+ nextIndex += direction;
277
+ if (nextIndex < 0) nextIndex = numOfChildren - 1;
278
+ if (nextIndex >= numOfChildren) nextIndex = 0;
279
+
280
+ const candidate = this._tabsListEl!.children[nextIndex];
281
+ if (isQueryBrowserToggleEntry(candidate)) continue;
282
+ if (!candidate.children[0]) continue;
283
+ return nextIndex;
284
+ }
285
+ return this.tabEntryIndex as number;
286
+ };
287
+
231
288
  const tabEntryDiv = this._tabsListEl.children[this.tabEntryIndex];
232
289
  // If the current tab does not have active set its tabindex to -1
233
290
  if (!tabEntryDiv.classList.contains("active")) {
234
- tabEntryDiv.children[0].setAttribute("tabindex", "-1"); // cur tab removed from tab index
291
+ tabEntryDiv.children[0]?.setAttribute("tabindex", "-1"); // cur tab removed from tab index
235
292
  }
236
293
  if (e.code === "ArrowLeft") {
237
- this.tabEntryIndex--;
238
- if (this.tabEntryIndex < 0) {
239
- this.tabEntryIndex = numOfChildren - 1;
240
- }
294
+ this.tabEntryIndex = advance(-1);
241
295
  }
242
296
  if (e.code === "ArrowRight") {
243
- this.tabEntryIndex++;
244
- if (this.tabEntryIndex >= numOfChildren) {
245
- this.tabEntryIndex = 0;
246
- }
297
+ this.tabEntryIndex = advance(1);
247
298
  }
248
299
  const newTabEntryDiv = this._tabsListEl.children[this.tabEntryIndex];
249
- newTabEntryDiv.children[0].setAttribute("tabindex", "0");
250
- (newTabEntryDiv.children[0] as HTMLElement).focus(); // focus on the a tag inside the div for click event
300
+ newTabEntryDiv.children[0]?.setAttribute("tabindex", "0");
301
+ (newTabEntryDiv.children[0] as HTMLElement | undefined)?.focus(); // focus on the a tag inside the div for click event
251
302
  }
252
303
  };
253
304
  drawTabsList() {
@@ -256,6 +307,22 @@ export class TabList {
256
307
  this._tabsListEl.setAttribute("role", "tablist");
257
308
  this._tabsListEl.addEventListener("keydown", this.handleKeydownArrowKeys);
258
309
 
310
+ this._queryBrowserToggleEl = document.createElement("div");
311
+ this._queryBrowserToggleEl.setAttribute("role", "presentation");
312
+
313
+ const queryBrowserButton = document.createElement("button");
314
+ queryBrowserButton.type = "button";
315
+ queryBrowserButton.className = "queryBrowserToggle";
316
+ queryBrowserButton.setAttribute("aria-label", "Open query browser");
317
+ queryBrowserButton.title = "Open query browser";
318
+ queryBrowserButton.innerHTML = '<i class="fas fa-bars"></i>';
319
+ queryBrowserButton.addEventListener("click", () => {
320
+ this.yasgui.queryBrowser.toggle(queryBrowserButton);
321
+ });
322
+
323
+ this._queryBrowserToggleEl.appendChild(queryBrowserButton);
324
+ this._tabsListEl.appendChild(this._queryBrowserToggleEl);
325
+
259
326
  sortablejs.create(this._tabsListEl, {
260
327
  group: "tabList",
261
328
  animation: 100,
@@ -264,7 +331,7 @@ export class TabList {
264
331
  this.yasgui.emit("tabOrderChanged", this.yasgui, tabs);
265
332
  this.yasgui.persistentConfig.setTabOrder(tabs);
266
333
  },
267
- filter: ".addTab, input, .renaming",
334
+ filter: ".queryBrowserToggle, .addTab, input, .renaming",
268
335
  preventOnFilter: false,
269
336
  onMove: (ev: any, _origEv: any) => {
270
337
  return hasClass(ev.related, "tab");
@@ -353,6 +420,7 @@ export class TabList {
353
420
  this.tabContextMenu?.destroy();
354
421
  this._tabsListEl?.remove();
355
422
  this._tabsListEl = undefined;
423
+ this._queryBrowserToggleEl = undefined;
356
424
  }
357
425
  }
358
426
 
@@ -9,11 +9,6 @@
9
9
  align-items: center;
10
10
  justify-content: center;
11
11
 
12
- svg {
13
- width: 20px;
14
- height: 20px;
15
- }
16
-
17
12
  &:hover {
18
13
  opacity: 0.7;
19
14
  }
@@ -165,6 +160,108 @@
165
160
  font-style: italic;
166
161
  }
167
162
 
163
+ .settingsField {
164
+ margin-bottom: 10px;
165
+ }
166
+
167
+ .settingsFieldLabel {
168
+ font-size: 12px;
169
+ color: var(--yasgui-text-secondary, #666);
170
+ margin-bottom: 6px;
171
+ }
172
+
173
+ .settingsActionsRow {
174
+ display: flex;
175
+ gap: 10px;
176
+ align-items: center;
177
+ margin-top: 10px;
178
+ }
179
+
180
+ .workspaceList {
181
+ display: flex;
182
+ flex-direction: column;
183
+ gap: 10px;
184
+ }
185
+
186
+ .workspaceListRow {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 10px;
190
+ padding: 10px;
191
+ border: 1px solid var(--yasgui-border-color, #e0e0e0);
192
+ border-radius: 6px;
193
+ background: var(--yasgui-bg-secondary, white);
194
+ }
195
+
196
+ .workspaceListLabel {
197
+ flex: 1;
198
+ font-size: 14px;
199
+ font-weight: 600;
200
+ color: var(--yasgui-text-primary, #000);
201
+ min-width: 0;
202
+ overflow: hidden;
203
+ text-overflow: ellipsis;
204
+ white-space: nowrap;
205
+ }
206
+
207
+ .workspaceListActions {
208
+ display: flex;
209
+ gap: 8px;
210
+ align-items: center;
211
+
212
+ .dangerButton {
213
+ margin-top: 0;
214
+ }
215
+ }
216
+
217
+ .workspaceListStatus {
218
+ flex: 1;
219
+ font-size: 12px;
220
+ color: var(--yasgui-text-secondary, #666);
221
+ min-width: 0;
222
+ }
223
+
224
+ .workspaceConfigModalOverlay {
225
+ z-index: 10001;
226
+ }
227
+
228
+ .workspaceConfigModal {
229
+ background: var(--yasgui-bg-primary, white);
230
+ border-radius: 8px;
231
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
232
+ width: 700px;
233
+ max-width: 90vw;
234
+ max-height: 90vh;
235
+ display: flex;
236
+ flex-direction: column;
237
+ overflow: hidden;
238
+ }
239
+
240
+ .workspaceConfigModalBody {
241
+ padding: 20px;
242
+ overflow: auto;
243
+ }
244
+
245
+ .workspaceConfigModalFooter {
246
+ display: flex;
247
+ justify-content: flex-end;
248
+ gap: 10px;
249
+ padding: 16px 20px;
250
+ border-top: 1px solid var(--yasgui-border-color, #e0e0e0);
251
+ }
252
+
253
+ .settingsActionsRow {
254
+ .dangerButton {
255
+ margin-top: 0;
256
+ }
257
+ }
258
+
259
+ .settingsStatus {
260
+ flex: 1;
261
+ font-size: 12px;
262
+ color: var(--yasgui-text-secondary, #666);
263
+ }
264
+
168
265
  .settingsSelect {
169
266
  width: 100%;
170
267
  padding: 8px;
@@ -5,19 +5,7 @@ import * as ConfigExportImport from "./ConfigExportImport";
5
5
  import { VERSION } from "./version";
6
6
  import * as OAuth2Utils from "./OAuth2Utils";
7
7
  import PersistentConfig from "./PersistentConfig";
8
-
9
- // Theme toggle icons
10
- const MOON_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
11
- <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"/>
12
- </svg>`;
13
-
14
- const SUN_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
15
- <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"/>
16
- </svg>`;
17
-
18
- const SETTINGS_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
19
- <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"/>
20
- </svg>`;
8
+ import { WorkspaceSettingsForm } from "./queryManagement/WorkspaceSettingsForm";
21
9
 
22
10
  const AcceptOptionsMap: { key: string; value: string }[] = [
23
11
  { key: "JSON", value: "application/sparql-results+json" },
@@ -48,6 +36,12 @@ export default class TabSettingsModal {
48
36
  private prefixButton!: HTMLButtonElement;
49
37
  private prefixTextarea!: HTMLTextAreaElement;
50
38
  private autoCaptureCheckbox!: HTMLInputElement;
39
+ private handleKeyDown = (e: KeyboardEvent) => {
40
+ if (e.key !== "Escape") return;
41
+ if (!this.modalOverlay.classList.contains("open")) return;
42
+ e.preventDefault();
43
+ this.close();
44
+ };
51
45
 
52
46
  constructor(tab: Tab, controlBarEl: HTMLElement) {
53
47
  this.tab = tab;
@@ -60,7 +54,7 @@ export default class TabSettingsModal {
60
54
  addClass(this.settingsButton, "tabContextButton");
61
55
  this.settingsButton.setAttribute("aria-label", "Settings");
62
56
  this.settingsButton.title = "Settings";
63
- this.settingsButton.innerHTML = SETTINGS_ICON;
57
+ this.settingsButton.innerHTML = '<i class="fas fa-cog"></i>';
64
58
  this.settingsButton.onclick = () => this.open();
65
59
  controlBarEl.appendChild(this.settingsButton);
66
60
 
@@ -82,9 +76,7 @@ export default class TabSettingsModal {
82
76
  this.prefixButton = document.createElement("button");
83
77
  this.prefixButton.setAttribute("aria-label", "Insert Prefixes");
84
78
  this.prefixButton.title = "Insert saved prefixes into query";
85
- this.prefixButton.innerHTML = `<svg viewBox="0 0 24 24" fill="currentColor">
86
- <path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/>
87
- </svg>`;
79
+ this.prefixButton.innerHTML = '<i class="fas fa-arrow-up-right-from-square"></i>';
88
80
  addClass(this.prefixButton, "tabContextButton", "prefixButton");
89
81
  controlBarEl.appendChild(this.prefixButton);
90
82
  this.prefixButton.onclick = () => this.insertPrefixesIntoQuery();
@@ -137,6 +129,11 @@ export default class TabSettingsModal {
137
129
  addClass(endpointsTab, "modalNavButton");
138
130
  endpointsTab.onclick = () => this.switchTab("endpoints");
139
131
 
132
+ const workspacesTab = document.createElement("button");
133
+ workspacesTab.textContent = "Workspaces";
134
+ addClass(workspacesTab, "modalNavButton");
135
+ workspacesTab.onclick = () => this.switchTab("workspaces");
136
+
140
137
  const prefixTab = document.createElement("button");
141
138
  prefixTab.textContent = "Prefixes";
142
139
  addClass(prefixTab, "modalNavButton");
@@ -164,6 +161,7 @@ export default class TabSettingsModal {
164
161
 
165
162
  sidebar.appendChild(requestTab);
166
163
  sidebar.appendChild(endpointsTab);
164
+ sidebar.appendChild(workspacesTab);
167
165
  sidebar.appendChild(prefixTab);
168
166
  sidebar.appendChild(editorTab);
169
167
  sidebar.appendChild(importExportTab);
@@ -188,6 +186,11 @@ export default class TabSettingsModal {
188
186
  endpointsContent.id = "endpoints-content";
189
187
  this.drawEndpointsSettings(endpointsContent);
190
188
 
189
+ const workspacesContent = document.createElement("div");
190
+ addClass(workspacesContent, "modalTabContent");
191
+ workspacesContent.id = "workspaces-content";
192
+ this.drawWorkspacesSettings(workspacesContent);
193
+
191
194
  const prefixContent = document.createElement("div");
192
195
  addClass(prefixContent, "modalTabContent");
193
196
  prefixContent.id = "prefix-content";
@@ -215,6 +218,7 @@ export default class TabSettingsModal {
215
218
 
216
219
  contentArea.appendChild(requestContent);
217
220
  contentArea.appendChild(endpointsContent);
221
+ contentArea.appendChild(workspacesContent);
218
222
  contentArea.appendChild(prefixContent);
219
223
  contentArea.appendChild(editorContent);
220
224
  contentArea.appendChild(importExportContent);
@@ -254,11 +258,12 @@ export default class TabSettingsModal {
254
258
  if (
255
259
  (tabName === "request" && index === 0) ||
256
260
  (tabName === "endpoints" && index === 1) ||
257
- (tabName === "prefix" && index === 2) ||
258
- (tabName === "editor" && index === 3) ||
259
- (tabName === "importexport" && index === 4) ||
260
- (tabName === "shortcuts" && index === 5) ||
261
- (tabName === "about" && index === 6)
261
+ (tabName === "workspaces" && index === 2) ||
262
+ (tabName === "prefix" && index === 3) ||
263
+ (tabName === "editor" && index === 4) ||
264
+ (tabName === "importexport" && index === 5) ||
265
+ (tabName === "shortcuts" && index === 6) ||
266
+ (tabName === "about" && index === 7)
262
267
  ) {
263
268
  addClass(btn as HTMLElement, "active");
264
269
  } else {
@@ -287,6 +292,22 @@ export default class TabSettingsModal {
287
292
  });
288
293
  }
289
294
 
295
+ private drawWorkspacesSettings(container: HTMLElement) {
296
+ const form = new WorkspaceSettingsForm(container, {
297
+ persistentConfig: this.tab.yasgui.persistentConfig,
298
+ onDeleteRequested: (workspaceId) => {
299
+ const w = this.tab.yasgui.persistentConfig.getWorkspace(workspaceId);
300
+ const name = w?.label || workspaceId;
301
+ if (confirm(`Delete workspace "${name}"? This only removes the local configuration.`)) {
302
+ this.tab.yasgui.persistentConfig.deleteWorkspace(workspaceId);
303
+ form.render();
304
+ }
305
+ },
306
+ });
307
+
308
+ form.render();
309
+ }
310
+
290
311
  private drawPrefixSettings(container: HTMLElement) {
291
312
  const section = document.createElement("div");
292
313
  addClass(section, "settingsSection");
@@ -1298,10 +1319,12 @@ export default class TabSettingsModal {
1298
1319
  }
1299
1320
  }
1300
1321
  addClass(this.modalOverlay, "open");
1322
+ document.addEventListener("keydown", this.handleKeyDown);
1301
1323
  }
1302
1324
 
1303
1325
  public close() {
1304
1326
  removeClass(this.modalOverlay, "open");
1327
+ document.removeEventListener("keydown", this.handleKeyDown);
1305
1328
  }
1306
1329
 
1307
1330
  private loadSettings() {
@@ -1488,9 +1511,9 @@ export default class TabSettingsModal {
1488
1511
 
1489
1512
  private getThemeToggleIcon(): string {
1490
1513
  const currentTheme = this.tab.yasgui.getTheme();
1491
- // In dark mode, show moon icon (clicking will switch to light)
1492
- // In light mode, show sun icon (clicking will switch to dark)
1493
- return currentTheme === "dark" ? MOON_ICON : SUN_ICON;
1514
+ // In dark mode, show lightbulb icon (clicking will switch to light)
1515
+ // In light mode, show moon icon (clicking will switch to dark)
1516
+ return currentTheme === "dark" ? '<i class="fas fa-lightbulb"></i>' : '<i class="fas fa-moon"></i>';
1494
1517
  }
1495
1518
 
1496
1519
  private drawImportExportSettings(container: HTMLElement) {
@@ -186,9 +186,8 @@
186
186
  flex-shrink: 0;
187
187
  transition: all 0.2s ease;
188
188
 
189
- svg {
190
- width: 16px;
191
- height: 16px;
189
+ i {
190
+ font-size: 16px;
192
191
  }
193
192
 
194
193
  &:hover {
package/src/index.scss CHANGED
@@ -1,3 +1,7 @@
1
+ // Font Awesome icons
2
+ @import "@fortawesome/fontawesome-free/css/fontawesome.css";
3
+ @import "@fortawesome/fontawesome-free/css/solid.css";
4
+
1
5
  // Main YASGUI container - fills 100% of parent element
2
6
  // Parent element (typically body) should have height: 100%; width: 100%
3
7
  .yasgui {
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ import TablePlugin from "@matdata/yasgui-table-plugin";
16
16
  import "@matdata/yasgui-graph-plugin/dist/yasgui-graph-plugin.min.css";
17
17
  import "@matdata/yasgui-table-plugin/dist/yasgui-table-plugin.min.css";
18
18
  import { ThemeManager, Theme } from "./ThemeManager";
19
+ import QueryBrowser from "./queryManagement/QueryBrowser";
19
20
  import "./index.scss";
20
21
  import "./themes.scss";
21
22
  import "../../yasr/src/scss/global.scss";
@@ -152,6 +153,7 @@ export class Yasgui extends EventEmitter {
152
153
  public config: Config;
153
154
  public persistentConfig: PersistentConfig;
154
155
  public themeManager: ThemeManager;
156
+ public queryBrowser: QueryBrowser;
155
157
  public static Tab = Tab;
156
158
  constructor(parent: HTMLElement, config: PartialConfig) {
157
159
  super();
@@ -178,8 +180,11 @@ export class Yasgui extends EventEmitter {
178
180
  this.tabElements = new TabElements(this);
179
181
  this.tabPanelsEl = document.createElement("div");
180
182
 
183
+ this.queryBrowser = new QueryBrowser(this);
184
+
181
185
  this.rootEl.appendChild(this.tabElements.drawTabsList());
182
186
  this.rootEl.appendChild(this.tabPanelsEl);
187
+ this.rootEl.appendChild(this.queryBrowser.getElement());
183
188
  let executeIdAfterInit: string | undefined;
184
189
  let optionsFromUrl: PersistedTabJson | undefined;
185
190
  if (this.config.populateFromUrl) {
@@ -456,5 +461,7 @@ export function getRandomId() {
456
461
  return Math.random().toString(36).substring(7);
457
462
  }
458
463
 
464
+ export * from "./queryManagement";
465
+
459
466
  export type { Theme } from "./ThemeManager";
460
467
  export default Yasgui;