@matdata/yasgui 5.16.0 → 5.18.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/build/ts/src/Tab.js +12 -2
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabContextMenu.d.ts +1 -0
- package/build/ts/src/TabContextMenu.js +42 -0
- package/build/ts/src/TabContextMenu.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +0 -1
- package/build/ts/src/TabSettingsModal.js +22 -25
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/index.d.ts +6 -0
- package/build/ts/src/index.js +42 -0
- package/build/ts/src/index.js.map +1 -1
- package/build/ts/src/prefixUtils.d.ts +1 -0
- package/build/ts/src/prefixUtils.js +23 -0
- package/build/ts/src/prefixUtils.js.map +1 -0
- package/build/ts/src/queryManagement/QueryBrowser.d.ts +8 -0
- package/build/ts/src/queryManagement/QueryBrowser.js +283 -116
- package/build/ts/src/queryManagement/QueryBrowser.js.map +1 -1
- package/build/ts/src/queryManagement/QueryHistoryModal.d.ts +35 -0
- package/build/ts/src/queryManagement/QueryHistoryModal.js +266 -0
- package/build/ts/src/queryManagement/QueryHistoryModal.js.map +1 -0
- package/build/ts/src/queryManagement/WorkspaceSettingsForm.js +8 -5
- package/build/ts/src/queryManagement/WorkspaceSettingsForm.js.map +1 -1
- package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.d.ts +1 -0
- package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js +33 -0
- package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js.map +1 -1
- package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.d.ts +1 -0
- package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js +3 -0
- package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js.map +1 -1
- package/build/ts/src/queryManagement/backends/WorkspaceBackend.d.ts +1 -0
- package/build/ts/src/queryManagement/countManagedQueries.d.ts +2 -0
- package/build/ts/src/queryManagement/countManagedQueries.js +35 -0
- package/build/ts/src/queryManagement/countManagedQueries.js.map +1 -0
- package/build/ts/src/tabNavigationHistory.d.ts +3 -0
- package/build/ts/src/tabNavigationHistory.js +13 -0
- package/build/ts/src/tabNavigationHistory.js.map +1 -0
- package/build/ts/src/version.d.ts +1 -1
- package/build/ts/src/version.js +1 -1
- package/build/yasgui.min.css +1 -1
- package/build/yasgui.min.css.map +3 -3
- package/build/yasgui.min.js +244 -232
- package/build/yasgui.min.js.map +4 -4
- package/package.json +3 -3
- package/src/Tab.ts +22 -3
- package/src/TabContextMenu.ts +42 -0
- package/src/TabSettingsModal.ts +22 -29
- package/src/index.ts +50 -0
- package/src/prefixUtils.ts +26 -0
- package/src/queryManagement/QueryBrowser.scss +85 -0
- package/src/queryManagement/QueryBrowser.ts +429 -213
- package/src/queryManagement/QueryHistoryModal.scss +239 -0
- package/src/queryManagement/QueryHistoryModal.ts +346 -0
- package/src/queryManagement/WorkspaceSettingsForm.ts +8 -5
- package/src/queryManagement/backends/GitWorkspaceBackend.ts +39 -0
- package/src/queryManagement/backends/SparqlWorkspaceBackend.ts +5 -0
- package/src/queryManagement/backends/WorkspaceBackend.ts +8 -0
- package/src/queryManagement/countManagedQueries.ts +28 -0
- package/src/tabNavigationHistory.ts +17 -0
- package/src/themes.scss +2 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
.queryHistoryModalOverlay {
|
|
2
|
+
display: none;
|
|
3
|
+
position: fixed;
|
|
4
|
+
top: 0;
|
|
5
|
+
left: 0;
|
|
6
|
+
right: 0;
|
|
7
|
+
bottom: 0;
|
|
8
|
+
background: var(--yasgui-overlay-bg, rgba(0, 0, 0, 0.5));
|
|
9
|
+
z-index: 10000;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
|
|
13
|
+
&.open {
|
|
14
|
+
display: flex;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.queryHistoryModal {
|
|
19
|
+
background: var(--yasgui-bg-primary, white);
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
width: 860px;
|
|
22
|
+
max-width: 95vw;
|
|
23
|
+
max-height: 90vh;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.queryHistoryModalHeader {
|
|
31
|
+
display: flex;
|
|
32
|
+
justify-content: space-between;
|
|
33
|
+
align-items: center;
|
|
34
|
+
padding: 16px;
|
|
35
|
+
border-bottom: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
36
|
+
flex-shrink: 0;
|
|
37
|
+
|
|
38
|
+
h2 {
|
|
39
|
+
margin: 0;
|
|
40
|
+
font-size: 18px;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
color: var(--yasgui-text-primary, #000);
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.closeButton {
|
|
49
|
+
background: none;
|
|
50
|
+
border: none;
|
|
51
|
+
font-size: 28px;
|
|
52
|
+
line-height: 1;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
color: var(--yasgui-text-secondary, #666);
|
|
55
|
+
padding: 0;
|
|
56
|
+
width: 32px;
|
|
57
|
+
height: 32px;
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
color: var(--yasgui-text-primary, #333);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.queryHistoryModalBody {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex: 1;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
min-height: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.queryHistoryVersionsPanel {
|
|
74
|
+
width: 280px;
|
|
75
|
+
min-width: 200px;
|
|
76
|
+
flex-shrink: 0;
|
|
77
|
+
border-right: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.queryHistoryPreviewPanel {
|
|
84
|
+
flex: 1;
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.queryHistoryPanelTitle {
|
|
92
|
+
padding: 10px 12px 8px;
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
text-transform: uppercase;
|
|
96
|
+
letter-spacing: 0.05em;
|
|
97
|
+
color: var(--yasgui-text-secondary, #666);
|
|
98
|
+
border-bottom: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.queryHistoryLoading,
|
|
103
|
+
.queryHistoryError,
|
|
104
|
+
.queryHistoryEmpty {
|
|
105
|
+
padding: 12px;
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
color: var(--yasgui-text-secondary, #666);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.queryHistoryError {
|
|
111
|
+
color: var(--yasgui-error-color, #c00);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.queryHistoryVersionsList {
|
|
115
|
+
flex: 1;
|
|
116
|
+
overflow-y: auto;
|
|
117
|
+
padding: 6px 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.queryHistoryVersionItem {
|
|
121
|
+
padding: 10px 12px;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
border-bottom: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
124
|
+
outline: none;
|
|
125
|
+
|
|
126
|
+
&:last-child {
|
|
127
|
+
border-bottom: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&:hover {
|
|
131
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&:focus-visible {
|
|
135
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
136
|
+
box-shadow: inset 0 0 0 2px var(--yasgui-accent-color, #337ab7);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&--selected {
|
|
140
|
+
background: var(--yasgui-accent-bg, #e8f0fe);
|
|
141
|
+
border-left: 3px solid var(--yasgui-accent-color, #337ab7);
|
|
142
|
+
padding-left: 9px;
|
|
143
|
+
|
|
144
|
+
&:hover {
|
|
145
|
+
background: var(--yasgui-accent-bg, #e8f0fe);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.queryHistoryVersionDate {
|
|
151
|
+
font-size: 13px;
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
color: var(--yasgui-text-primary, #000);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.queryHistoryVersionMeta {
|
|
157
|
+
font-size: 12px;
|
|
158
|
+
color: var(--yasgui-text-secondary, #666);
|
|
159
|
+
margin-top: 2px;
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
text-overflow: ellipsis;
|
|
162
|
+
white-space: nowrap;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.queryHistoryVersionMessage {
|
|
166
|
+
font-size: 12px;
|
|
167
|
+
color: var(--yasgui-text-secondary, #555);
|
|
168
|
+
margin-top: 2px;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
text-overflow: ellipsis;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
font-style: italic;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.queryHistoryPreviewEmpty {
|
|
176
|
+
padding: 24px 16px;
|
|
177
|
+
font-size: 13px;
|
|
178
|
+
color: var(--yasgui-text-secondary, #999);
|
|
179
|
+
text-align: center;
|
|
180
|
+
flex: 1;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.queryHistoryPreviewContent {
|
|
187
|
+
flex: 1;
|
|
188
|
+
margin: 0;
|
|
189
|
+
padding: 12px 16px;
|
|
190
|
+
overflow: auto;
|
|
191
|
+
font-family: "SFMono-Regular", "Consolas", "Liberation Mono", monospace;
|
|
192
|
+
font-size: 12px;
|
|
193
|
+
line-height: 1.5;
|
|
194
|
+
color: var(--yasgui-text-primary, #000);
|
|
195
|
+
background: var(--yasgui-bg-secondary, #f7f7f7);
|
|
196
|
+
white-space: pre-wrap;
|
|
197
|
+
word-break: break-word;
|
|
198
|
+
border-top: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.queryHistoryModalFooter {
|
|
202
|
+
display: flex;
|
|
203
|
+
justify-content: flex-end;
|
|
204
|
+
gap: 8px;
|
|
205
|
+
padding: 12px 16px;
|
|
206
|
+
border-top: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
207
|
+
flex-shrink: 0;
|
|
208
|
+
|
|
209
|
+
button {
|
|
210
|
+
padding: 8px 16px;
|
|
211
|
+
border-radius: 4px;
|
|
212
|
+
border: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
213
|
+
background: var(--yasgui-bg-primary, white);
|
|
214
|
+
color: var(--yasgui-text-primary, #000);
|
|
215
|
+
font-size: 14px;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
|
|
218
|
+
&:hover:not(:disabled) {
|
|
219
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
&:disabled {
|
|
223
|
+
opacity: 0.5;
|
|
224
|
+
cursor: not-allowed;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
&.primary {
|
|
228
|
+
background: var(--yasgui-accent-color, #337ab7);
|
|
229
|
+
color: #fff;
|
|
230
|
+
border-color: var(--yasgui-accent-color, #337ab7);
|
|
231
|
+
font-weight: 600;
|
|
232
|
+
|
|
233
|
+
&:hover:not(:disabled) {
|
|
234
|
+
background: var(--yasgui-accent-color-hover, #2a6496);
|
|
235
|
+
border-color: var(--yasgui-accent-color-hover, #2a6496);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import type Yasgui from "../index";
|
|
2
|
+
import { addClass, removeClass } from "@matdata/yasgui-utils";
|
|
3
|
+
import { getWorkspaceBackend } from "./backends/getWorkspaceBackend";
|
|
4
|
+
import type { BackendType, ManagedTabMetadata, ReadResult, VersionInfo, VersionRef } from "./types";
|
|
5
|
+
import { hashQueryText } from "./textHash";
|
|
6
|
+
import { getEndpointToAutoSwitch } from "./openManagedQuery";
|
|
7
|
+
import { asWorkspaceBackendError } from "./backends/errors";
|
|
8
|
+
|
|
9
|
+
import "./QueryHistoryModal.scss";
|
|
10
|
+
|
|
11
|
+
export default class QueryHistoryModal {
|
|
12
|
+
private yasgui: Yasgui;
|
|
13
|
+
|
|
14
|
+
private overlayEl: HTMLDivElement;
|
|
15
|
+
private modalEl: HTMLDivElement;
|
|
16
|
+
private titleEl: HTMLHeadingElement;
|
|
17
|
+
private versionsListEl: HTMLDivElement;
|
|
18
|
+
private loadingEl: HTMLDivElement;
|
|
19
|
+
private errorEl: HTMLDivElement;
|
|
20
|
+
private previewEmptyEl: HTMLDivElement;
|
|
21
|
+
private previewContentEl: HTMLPreElement;
|
|
22
|
+
private openTabBtn: HTMLButtonElement;
|
|
23
|
+
|
|
24
|
+
private workspaceId?: string;
|
|
25
|
+
private backend?: ReturnType<typeof getWorkspaceBackend>;
|
|
26
|
+
private backendType?: BackendType;
|
|
27
|
+
private queryId?: string;
|
|
28
|
+
private queryLabel?: string;
|
|
29
|
+
|
|
30
|
+
private selectedVersionId?: string;
|
|
31
|
+
private selectedVersionRead?: ReadResult;
|
|
32
|
+
|
|
33
|
+
private mouseDownOnOverlay = false;
|
|
34
|
+
private resolve?: () => void;
|
|
35
|
+
|
|
36
|
+
constructor(yasgui: Yasgui) {
|
|
37
|
+
this.yasgui = yasgui;
|
|
38
|
+
|
|
39
|
+
this.overlayEl = document.createElement("div");
|
|
40
|
+
addClass(this.overlayEl, "queryHistoryModalOverlay");
|
|
41
|
+
|
|
42
|
+
this.modalEl = document.createElement("div");
|
|
43
|
+
addClass(this.modalEl, "queryHistoryModal");
|
|
44
|
+
this.modalEl.setAttribute("role", "dialog");
|
|
45
|
+
this.modalEl.setAttribute("aria-modal", "true");
|
|
46
|
+
this.modalEl.addEventListener("click", (e) => e.stopPropagation());
|
|
47
|
+
|
|
48
|
+
// ── Header ───────────────────────────────────────────────────────────────
|
|
49
|
+
const headerEl = document.createElement("div");
|
|
50
|
+
addClass(headerEl, "queryHistoryModalHeader");
|
|
51
|
+
|
|
52
|
+
this.titleEl = document.createElement("h2");
|
|
53
|
+
this.titleEl.textContent = "Query History";
|
|
54
|
+
|
|
55
|
+
const closeBtn = document.createElement("button");
|
|
56
|
+
closeBtn.type = "button";
|
|
57
|
+
closeBtn.className = "closeButton";
|
|
58
|
+
closeBtn.setAttribute("aria-label", "Close");
|
|
59
|
+
closeBtn.innerHTML = "×";
|
|
60
|
+
closeBtn.addEventListener("click", () => this.close());
|
|
61
|
+
|
|
62
|
+
headerEl.appendChild(this.titleEl);
|
|
63
|
+
headerEl.appendChild(closeBtn);
|
|
64
|
+
|
|
65
|
+
// ── Body ─────────────────────────────────────────────────────────────────
|
|
66
|
+
const bodyEl = document.createElement("div");
|
|
67
|
+
addClass(bodyEl, "queryHistoryModalBody");
|
|
68
|
+
|
|
69
|
+
// Left panel – version list
|
|
70
|
+
const versionsPanel = document.createElement("div");
|
|
71
|
+
addClass(versionsPanel, "queryHistoryVersionsPanel");
|
|
72
|
+
|
|
73
|
+
const versionsPanelTitle = document.createElement("div");
|
|
74
|
+
addClass(versionsPanelTitle, "queryHistoryPanelTitle");
|
|
75
|
+
versionsPanelTitle.textContent = "Versions";
|
|
76
|
+
|
|
77
|
+
this.loadingEl = document.createElement("div");
|
|
78
|
+
addClass(this.loadingEl, "queryHistoryLoading");
|
|
79
|
+
this.loadingEl.textContent = "Loading versions…";
|
|
80
|
+
|
|
81
|
+
this.errorEl = document.createElement("div");
|
|
82
|
+
addClass(this.errorEl, "queryHistoryError");
|
|
83
|
+
|
|
84
|
+
this.versionsListEl = document.createElement("div");
|
|
85
|
+
addClass(this.versionsListEl, "queryHistoryVersionsList");
|
|
86
|
+
|
|
87
|
+
versionsPanel.appendChild(versionsPanelTitle);
|
|
88
|
+
versionsPanel.appendChild(this.loadingEl);
|
|
89
|
+
versionsPanel.appendChild(this.errorEl);
|
|
90
|
+
versionsPanel.appendChild(this.versionsListEl);
|
|
91
|
+
|
|
92
|
+
// Right panel – preview
|
|
93
|
+
const previewPanel = document.createElement("div");
|
|
94
|
+
addClass(previewPanel, "queryHistoryPreviewPanel");
|
|
95
|
+
|
|
96
|
+
const previewPanelTitle = document.createElement("div");
|
|
97
|
+
addClass(previewPanelTitle, "queryHistoryPanelTitle");
|
|
98
|
+
previewPanelTitle.textContent = "Preview";
|
|
99
|
+
|
|
100
|
+
this.previewEmptyEl = document.createElement("div");
|
|
101
|
+
addClass(this.previewEmptyEl, "queryHistoryPreviewEmpty");
|
|
102
|
+
this.previewEmptyEl.textContent = "Select a version to preview";
|
|
103
|
+
|
|
104
|
+
this.previewContentEl = document.createElement("pre");
|
|
105
|
+
addClass(this.previewContentEl, "queryHistoryPreviewContent");
|
|
106
|
+
|
|
107
|
+
previewPanel.appendChild(previewPanelTitle);
|
|
108
|
+
previewPanel.appendChild(this.previewEmptyEl);
|
|
109
|
+
previewPanel.appendChild(this.previewContentEl);
|
|
110
|
+
|
|
111
|
+
bodyEl.appendChild(versionsPanel);
|
|
112
|
+
bodyEl.appendChild(previewPanel);
|
|
113
|
+
|
|
114
|
+
// ── Footer ────────────────────────────────────────────────────────────────
|
|
115
|
+
const footerEl = document.createElement("div");
|
|
116
|
+
addClass(footerEl, "queryHistoryModalFooter");
|
|
117
|
+
|
|
118
|
+
const cancelBtn = document.createElement("button");
|
|
119
|
+
cancelBtn.type = "button";
|
|
120
|
+
cancelBtn.textContent = "Close";
|
|
121
|
+
cancelBtn.addEventListener("click", () => this.close());
|
|
122
|
+
|
|
123
|
+
this.openTabBtn = document.createElement("button");
|
|
124
|
+
this.openTabBtn.type = "button";
|
|
125
|
+
this.openTabBtn.textContent = "Open in tab";
|
|
126
|
+
addClass(this.openTabBtn, "primary");
|
|
127
|
+
this.openTabBtn.disabled = true;
|
|
128
|
+
this.openTabBtn.addEventListener("click", () => void this.openSelectedVersionInTab());
|
|
129
|
+
|
|
130
|
+
footerEl.appendChild(cancelBtn);
|
|
131
|
+
footerEl.appendChild(this.openTabBtn);
|
|
132
|
+
|
|
133
|
+
// ── Assemble ──────────────────────────────────────────────────────────────
|
|
134
|
+
this.modalEl.appendChild(headerEl);
|
|
135
|
+
this.modalEl.appendChild(bodyEl);
|
|
136
|
+
this.modalEl.appendChild(footerEl);
|
|
137
|
+
|
|
138
|
+
this.overlayEl.appendChild(this.modalEl);
|
|
139
|
+
|
|
140
|
+
this.overlayEl.addEventListener("mousedown", (e) => {
|
|
141
|
+
if (e.target === this.overlayEl) this.mouseDownOnOverlay = true;
|
|
142
|
+
});
|
|
143
|
+
this.overlayEl.addEventListener("mouseup", (e) => {
|
|
144
|
+
if (e.target === this.overlayEl && this.mouseDownOnOverlay) this.close();
|
|
145
|
+
this.mouseDownOnOverlay = false;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Bound keydown handler stored so it can be removed on close. */
|
|
150
|
+
private readonly onKeyDown = (e: KeyboardEvent) => {
|
|
151
|
+
if (e.key === "Escape") {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
this.close();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Show the history modal for the given managed query.
|
|
159
|
+
* Returns a promise that resolves when the modal is closed.
|
|
160
|
+
*/
|
|
161
|
+
public show(
|
|
162
|
+
workspaceId: string,
|
|
163
|
+
backend: ReturnType<typeof getWorkspaceBackend>,
|
|
164
|
+
backendType: BackendType,
|
|
165
|
+
queryId: string,
|
|
166
|
+
queryLabel: string,
|
|
167
|
+
): Promise<void> {
|
|
168
|
+
this.workspaceId = workspaceId;
|
|
169
|
+
this.backend = backend;
|
|
170
|
+
this.backendType = backendType;
|
|
171
|
+
this.queryId = queryId;
|
|
172
|
+
this.queryLabel = queryLabel;
|
|
173
|
+
|
|
174
|
+
// Reset state
|
|
175
|
+
this.selectedVersionId = undefined;
|
|
176
|
+
this.selectedVersionRead = undefined;
|
|
177
|
+
this.versionsListEl.innerHTML = "";
|
|
178
|
+
this.previewContentEl.textContent = "";
|
|
179
|
+
this.previewContentEl.style.display = "none";
|
|
180
|
+
this.previewEmptyEl.style.display = "block";
|
|
181
|
+
this.loadingEl.style.display = "block";
|
|
182
|
+
this.errorEl.textContent = "";
|
|
183
|
+
this.errorEl.style.display = "none";
|
|
184
|
+
this.openTabBtn.disabled = true;
|
|
185
|
+
this.titleEl.textContent = `History: ${queryLabel}`;
|
|
186
|
+
this.mouseDownOnOverlay = false;
|
|
187
|
+
|
|
188
|
+
document.body.appendChild(this.overlayEl);
|
|
189
|
+
addClass(this.overlayEl, "open");
|
|
190
|
+
document.addEventListener("keydown", this.onKeyDown);
|
|
191
|
+
|
|
192
|
+
void this.loadVersions();
|
|
193
|
+
|
|
194
|
+
return new Promise<void>((resolve) => {
|
|
195
|
+
this.resolve = resolve;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private close() {
|
|
200
|
+
document.removeEventListener("keydown", this.onKeyDown);
|
|
201
|
+
removeClass(this.overlayEl, "open");
|
|
202
|
+
this.overlayEl.remove();
|
|
203
|
+
const resolve = this.resolve;
|
|
204
|
+
this.resolve = undefined;
|
|
205
|
+
resolve?.();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async loadVersions() {
|
|
209
|
+
if (!this.backend || !this.queryId) return;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const versions = await this.backend.listVersions(this.queryId);
|
|
213
|
+
this.loadingEl.style.display = "none";
|
|
214
|
+
this.renderVersions(versions);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
this.loadingEl.style.display = "none";
|
|
217
|
+
this.errorEl.textContent = `Failed to load versions: ${asWorkspaceBackendError(err).message}`;
|
|
218
|
+
this.errorEl.style.display = "block";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private renderVersions(versions: VersionInfo[]) {
|
|
223
|
+
this.versionsListEl.innerHTML = "";
|
|
224
|
+
|
|
225
|
+
if (versions.length === 0) {
|
|
226
|
+
const emptyEl = document.createElement("div");
|
|
227
|
+
addClass(emptyEl, "queryHistoryEmpty");
|
|
228
|
+
emptyEl.textContent = "No version history found.";
|
|
229
|
+
this.versionsListEl.appendChild(emptyEl);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const version of versions) {
|
|
234
|
+
const item = document.createElement("div");
|
|
235
|
+
addClass(item, "queryHistoryVersionItem");
|
|
236
|
+
item.setAttribute("role", "button");
|
|
237
|
+
item.setAttribute("tabindex", "0");
|
|
238
|
+
|
|
239
|
+
const dateEl = document.createElement("div");
|
|
240
|
+
addClass(dateEl, "queryHistoryVersionDate");
|
|
241
|
+
dateEl.textContent = this.formatDate(version.createdAt);
|
|
242
|
+
item.appendChild(dateEl);
|
|
243
|
+
|
|
244
|
+
if (version.author) {
|
|
245
|
+
const authorEl = document.createElement("div");
|
|
246
|
+
addClass(authorEl, "queryHistoryVersionMeta");
|
|
247
|
+
authorEl.textContent = version.author;
|
|
248
|
+
item.appendChild(authorEl);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (version.message) {
|
|
252
|
+
const msgEl = document.createElement("div");
|
|
253
|
+
addClass(msgEl, "queryHistoryVersionMessage");
|
|
254
|
+
msgEl.textContent = version.message;
|
|
255
|
+
item.appendChild(msgEl);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const selectFn = () => void this.selectVersion(version.id, item);
|
|
259
|
+
item.addEventListener("click", selectFn);
|
|
260
|
+
item.addEventListener("keydown", (e) => {
|
|
261
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
selectFn();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
this.versionsListEl.appendChild(item);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private async selectVersion(versionId: string, itemEl: HTMLElement) {
|
|
272
|
+
// Highlight the selected item
|
|
273
|
+
this.versionsListEl.querySelectorAll(".queryHistoryVersionItem").forEach((el) => {
|
|
274
|
+
removeClass(el as HTMLElement, "queryHistoryVersionItem--selected");
|
|
275
|
+
});
|
|
276
|
+
addClass(itemEl, "queryHistoryVersionItem--selected");
|
|
277
|
+
|
|
278
|
+
// Reset selection state and show loading in preview
|
|
279
|
+
this.selectedVersionId = undefined;
|
|
280
|
+
this.selectedVersionRead = undefined;
|
|
281
|
+
this.openTabBtn.disabled = true;
|
|
282
|
+
this.previewEmptyEl.style.display = "none";
|
|
283
|
+
this.previewContentEl.style.display = "block";
|
|
284
|
+
this.previewContentEl.textContent = "Loading…";
|
|
285
|
+
|
|
286
|
+
if (!this.backend || !this.queryId) return;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const read = await this.backend.readVersion(this.queryId, versionId);
|
|
290
|
+
this.selectedVersionId = versionId;
|
|
291
|
+
this.selectedVersionRead = read;
|
|
292
|
+
this.previewContentEl.textContent = read.queryText;
|
|
293
|
+
this.openTabBtn.disabled = false;
|
|
294
|
+
} catch (err) {
|
|
295
|
+
this.previewContentEl.textContent = `Error: ${asWorkspaceBackendError(err).message}`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private async openSelectedVersionInTab() {
|
|
300
|
+
if (
|
|
301
|
+
!this.selectedVersionId ||
|
|
302
|
+
!this.selectedVersionRead ||
|
|
303
|
+
!this.workspaceId ||
|
|
304
|
+
!this.backendType ||
|
|
305
|
+
!this.queryId
|
|
306
|
+
) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const { workspaceId, backendType, queryId, queryLabel } = this;
|
|
311
|
+
const read = this.selectedVersionRead;
|
|
312
|
+
|
|
313
|
+
const tab = this.yasgui.addTab(true);
|
|
314
|
+
if (queryLabel) tab.setName(queryLabel);
|
|
315
|
+
|
|
316
|
+
const endpoint = getEndpointToAutoSwitch(backendType, read);
|
|
317
|
+
if (endpoint) tab.setEndpoint(endpoint);
|
|
318
|
+
|
|
319
|
+
tab.setQuery(read.queryText);
|
|
320
|
+
|
|
321
|
+
const versionRef: VersionRef | undefined = this.versionRefFromVersionTag(backendType, read.versionTag);
|
|
322
|
+
|
|
323
|
+
const managedMetadata: ManagedTabMetadata = {
|
|
324
|
+
workspaceId,
|
|
325
|
+
backendType,
|
|
326
|
+
queryRef: backendType === "git" ? { path: queryId } : { managedQueryIri: queryId },
|
|
327
|
+
lastSavedVersionRef: versionRef,
|
|
328
|
+
lastSavedTextHash: hashQueryText(read.queryText),
|
|
329
|
+
};
|
|
330
|
+
tab.setManagedQueryMetadata(managedMetadata);
|
|
331
|
+
|
|
332
|
+
this.close();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private versionRefFromVersionTag(backendType: BackendType, versionTag: string | undefined): VersionRef | undefined {
|
|
336
|
+
if (!versionTag) return undefined;
|
|
337
|
+
if (backendType === "git") return { commitSha: versionTag };
|
|
338
|
+
return { managedQueryVersionIri: versionTag };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private formatDate(isoDate: string): string {
|
|
342
|
+
const d = new Date(isoDate);
|
|
343
|
+
if (isNaN(d.getTime())) return "Unknown date";
|
|
344
|
+
return d.toLocaleString();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -4,6 +4,7 @@ import { addClass } from "@matdata/yasgui-utils";
|
|
|
4
4
|
import { validateWorkspaceConfig } from "./validateWorkspaceConfig";
|
|
5
5
|
import { getWorkspaceBackend } from "./backends/getWorkspaceBackend";
|
|
6
6
|
import { asWorkspaceBackendError } from "./backends/errors";
|
|
7
|
+
import { countManagedQueries } from "./countManagedQueries";
|
|
7
8
|
|
|
8
9
|
export interface WorkspaceSettingsFormOptions {
|
|
9
10
|
persistentConfig: PersistentConfig;
|
|
@@ -202,7 +203,9 @@ ORDER BY ?workspace`;
|
|
|
202
203
|
status.textContent = "Validating…";
|
|
203
204
|
const backend = getWorkspaceBackend(workspace, { persistentConfig: this.options.persistentConfig });
|
|
204
205
|
await backend.validateAccess();
|
|
205
|
-
|
|
206
|
+
const queryCount = await countManagedQueries(backend);
|
|
207
|
+
const queryLabel = queryCount === 1 ? "query" : "queries";
|
|
208
|
+
status.textContent = `✓ Access OK (${queryCount} saved managed ${queryLabel})`;
|
|
206
209
|
} catch (e) {
|
|
207
210
|
const err = asWorkspaceBackendError(e);
|
|
208
211
|
status.textContent = err.message;
|
|
@@ -359,14 +362,14 @@ ORDER BY ?workspace`;
|
|
|
359
362
|
const typeSelect = document.createElement("select");
|
|
360
363
|
typeSelect.setAttribute("aria-label", "Workspace type");
|
|
361
364
|
addClass(typeSelect, "settingsSelect");
|
|
362
|
-
const optGit = document.createElement("option");
|
|
363
|
-
optGit.value = "git";
|
|
364
|
-
optGit.textContent = "Git";
|
|
365
365
|
const optSparql = document.createElement("option");
|
|
366
366
|
optSparql.value = "sparql";
|
|
367
367
|
optSparql.textContent = "SPARQL";
|
|
368
|
-
|
|
368
|
+
const optGit = document.createElement("option");
|
|
369
|
+
optGit.value = "git";
|
|
370
|
+
optGit.textContent = "Git";
|
|
369
371
|
typeSelect.appendChild(optSparql);
|
|
372
|
+
typeSelect.appendChild(optGit);
|
|
370
373
|
|
|
371
374
|
if (existing) {
|
|
372
375
|
typeSelect.value = existing.type;
|
|
@@ -2,6 +2,7 @@ import type { GitWorkspaceConfig, FolderEntry, ReadResult, VersionInfo, WriteQue
|
|
|
2
2
|
import type { WorkspaceBackend } from "./WorkspaceBackend";
|
|
3
3
|
import { WorkspaceBackendError } from "./errors";
|
|
4
4
|
import { normalizeQueryFilename } from "../normalizeQueryFilename";
|
|
5
|
+
import { parseGitRemote } from "./gitRemote";
|
|
5
6
|
|
|
6
7
|
export interface GitProviderClient {
|
|
7
8
|
validateAccess(config: GitWorkspaceConfig): Promise<void>;
|
|
@@ -112,4 +113,42 @@ export default class GitWorkspaceBackend implements WorkspaceBackend {
|
|
|
112
113
|
|
|
113
114
|
await this.client.deleteQuery(this.config, queryId);
|
|
114
115
|
}
|
|
116
|
+
|
|
117
|
+
getQueryUri(queryId: string): string | undefined {
|
|
118
|
+
try {
|
|
119
|
+
const { host, repoPath } = parseGitRemote(this.config.remoteUrl);
|
|
120
|
+
const parts = repoPath.split("/").filter(Boolean);
|
|
121
|
+
const [owner, repoName] = parts;
|
|
122
|
+
if (!owner || !repoName) return undefined;
|
|
123
|
+
|
|
124
|
+
const filePath = [this.config.rootPath, queryId].filter(Boolean).join("/");
|
|
125
|
+
const branch = this.config.branch?.trim() || "HEAD";
|
|
126
|
+
const provider = this.config.provider;
|
|
127
|
+
|
|
128
|
+
const isGithub =
|
|
129
|
+
provider === "github" ||
|
|
130
|
+
((!provider || provider === "auto") &&
|
|
131
|
+
(host === "github.com" || (host.includes("github") && !host.includes("gitlab"))));
|
|
132
|
+
if (isGithub) {
|
|
133
|
+
return `https://${host}/${owner}/${repoName}/blob/${encodeURIComponent(branch)}/${filePath}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const isGitlab =
|
|
137
|
+
provider === "gitlab" ||
|
|
138
|
+
((!provider || provider === "auto") && (host === "gitlab.com" || host.includes("gitlab")));
|
|
139
|
+
if (isGitlab) {
|
|
140
|
+
return `https://${host}/${repoPath}/-/blob/${encodeURIComponent(branch)}/${filePath}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const isBitbucket = provider === "bitbucket" || ((!provider || provider === "auto") && host === "bitbucket.org");
|
|
144
|
+
if (isBitbucket) {
|
|
145
|
+
return `https://bitbucket.org/${owner}/${repoName}/src/${encodeURIComponent(branch)}/${filePath}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Gitea (and other self-hosted providers)
|
|
149
|
+
return `https://${host}/${owner}/${repoName}/src/branch/${encodeURIComponent(branch)}/${filePath}`;
|
|
150
|
+
} catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
115
154
|
}
|
|
@@ -44,4 +44,12 @@ export interface WorkspaceBackend {
|
|
|
44
44
|
* Optional: Delete a folder and everything inside it (recursive).
|
|
45
45
|
*/
|
|
46
46
|
deleteFolder?(folderId: string): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional: Return a URI for the given query.
|
|
50
|
+
* - For SPARQL workspaces this is the managed-query IRI stored in the triplestore.
|
|
51
|
+
* - For git workspaces this is the browser-accessible URL to the file in the repository.
|
|
52
|
+
* Returns `undefined` when no meaningful URI can be determined.
|
|
53
|
+
*/
|
|
54
|
+
getQueryUri?(queryId: string): string | undefined;
|
|
47
55
|
}
|