sqlite-hub 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -9
- package/docs/MCP.md +8 -0
- package/docs/changelog.md +10 -0
- package/docs/guidelines/DESIGN.md +64 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/logs_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +11 -0
- package/frontend/js/app.js +238 -8
- package/frontend/js/components/badges.js +16 -0
- package/frontend/js/components/connectionCard.js +35 -0
- package/frontend/js/components/modal.js +116 -245
- package/frontend/js/components/queryHistoryDetail.js +25 -45
- package/frontend/js/components/rowEditorPanel.js +7 -60
- package/frontend/js/components/structureGraph.js +292 -13
- package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
- package/frontend/js/components/tableDesignerEditor.js +51 -16
- package/frontend/js/components/tableDesignerSidebar.js +9 -5
- package/frontend/js/store.js +551 -7
- package/frontend/js/utils/connectionRegistry.js +76 -0
- package/frontend/js/utils/tableDesigner.js +431 -62
- package/frontend/js/views/backups.js +2 -5
- package/frontend/js/views/charts.js +20 -10
- package/frontend/js/views/connections.js +103 -3
- package/frontend/js/views/data.js +19 -5
- package/frontend/js/views/structure.js +28 -5
- package/frontend/js/views/tableAdvisor.js +14 -6
- package/frontend/js/views/tableDesigner.js +44 -9
- package/frontend/styles/components.css +676 -79
- package/frontend/styles/structure-graph.css +49 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/package.json +1 -1
- package/server/routes/connections.js +12 -0
- package/server/routes/data.js +1 -1
- package/server/routes/tableDesigner.js +15 -1
- package/server/services/mcpToolService.js +28 -0
- package/server/services/sqlite/connectionManager.js +23 -2
- package/server/services/sqlite/dataBrowserService.js +21 -0
- package/server/services/sqlite/introspection.js +39 -1
- package/server/services/sqlite/structureService.js +66 -11
- package/server/services/sqlite/tableAdvisor.js +6 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
- package/server/services/sqlite/tableDesigner/sql.js +31 -2
- package/server/services/sqlite/tableDesigner/validation.js +69 -9
- package/server/services/sqlite/tableDesignerService.js +110 -5
- package/server/services/storage/appStateStore.js +303 -4
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { escapeHtml, formatNumber, highlightSql } from "../utils/format.js";
|
|
2
|
+
|
|
3
|
+
function normalizeColumnName(name) {
|
|
4
|
+
return String(name ?? "").trim().toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function quoteIdentifier(identifier) {
|
|
8
|
+
return `"${String(identifier ?? "").replaceAll('"', '""')}"`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeCheckExpression(expression) {
|
|
12
|
+
const normalized = String(expression ?? "").trim();
|
|
13
|
+
|
|
14
|
+
if (!normalized) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return /^CHECK\s*\(/i.test(normalized) ? normalized : `CHECK (${normalized})`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkIncludesColumn(constraint, column) {
|
|
22
|
+
if (constraint.columnId && column?.id) {
|
|
23
|
+
return constraint.columnId === column.id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const normalizedColumn = normalizeColumnName(column?.name);
|
|
27
|
+
|
|
28
|
+
if (!normalizedColumn) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (constraint.columnId) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (constraint.columns ?? []).some(
|
|
37
|
+
(column) => normalizeColumnName(column.name) === normalizedColumn
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getVisibleChecks(draft, column) {
|
|
42
|
+
const checks = (draft?.checkConstraints ?? []).filter((constraint) => !constraint.deleted);
|
|
43
|
+
|
|
44
|
+
if (!column) {
|
|
45
|
+
return checks;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return checks.filter((constraint) => {
|
|
49
|
+
if (!(constraint.columns ?? []).length && !constraint.columnId) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return checkIncludesColumn(constraint, column);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isTextColumn(column) {
|
|
58
|
+
return ["TEXT", "DATE", "DATETIME"].includes(String(column?.type ?? "").trim().toUpperCase());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isNumericColumn(column) {
|
|
62
|
+
return ["INTEGER", "REAL", "NUMERIC"].includes(String(column?.type ?? "").trim().toUpperCase());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isBooleanLikeColumn(column) {
|
|
66
|
+
const type = String(column?.type ?? "").trim().toUpperCase();
|
|
67
|
+
const name = String(column?.name ?? "").trim().toLowerCase();
|
|
68
|
+
|
|
69
|
+
return type === "BOOLEAN" || (type === "INTEGER" && /^(is_|has_|can_|in_stock$)/.test(name));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderPresetButton(id, label, activePresetId) {
|
|
73
|
+
return `
|
|
74
|
+
<button
|
|
75
|
+
class="standard-button table-designer-check-drawer__preset-button ${activePresetId === id ? "is-active" : ""}"
|
|
76
|
+
data-action="apply-table-designer-check-preset"
|
|
77
|
+
data-preset-id="${escapeHtml(id)}"
|
|
78
|
+
title="${escapeHtml(label)}"
|
|
79
|
+
type="button"
|
|
80
|
+
>
|
|
81
|
+
<span>${escapeHtml(label)}</span>
|
|
82
|
+
</button>
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function renderPresetParameters(editor) {
|
|
87
|
+
const presetId = editor?.presetId ?? "";
|
|
88
|
+
const fields = editor?.presetFields ?? {};
|
|
89
|
+
|
|
90
|
+
if (presetId === "text-min-length") {
|
|
91
|
+
return renderNumberField("MIN LENGTH", "minLength", fields.minLength ?? "3");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (presetId === "text-max-length") {
|
|
95
|
+
return renderNumberField("MAX LENGTH", "maxLength", fields.maxLength ?? "255");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (presetId === "text-length-range") {
|
|
99
|
+
return `
|
|
100
|
+
<div class="grid grid-cols-2 gap-3">
|
|
101
|
+
${renderNumberField("MIN", "minLength", fields.minLength ?? "3")}
|
|
102
|
+
${renderNumberField("MAX", "maxLength", fields.maxLength ?? "255")}
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (presetId === "text-allowed-values") {
|
|
108
|
+
return `
|
|
109
|
+
<label class="table-designer-check-drawer__field">
|
|
110
|
+
<span>Allowed Values</span>
|
|
111
|
+
<textarea
|
|
112
|
+
class="table-designer-field table-designer-check-drawer__textarea custom-scrollbar"
|
|
113
|
+
data-bind="table-designer-check-editor-field"
|
|
114
|
+
data-field="allowedValues"
|
|
115
|
+
spellcheck="false"
|
|
116
|
+
>${escapeHtml(fields.allowedValues ?? "draft\npublished\narchived")}</textarea>
|
|
117
|
+
</label>
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (presetId === "numeric-min-value") {
|
|
122
|
+
return renderNumberField("MIN VALUE", "minValue", fields.minValue ?? "0");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (presetId === "numeric-max-value") {
|
|
126
|
+
return renderNumberField("MAX VALUE", "maxValue", fields.maxValue ?? "100");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (presetId === "numeric-range") {
|
|
130
|
+
return `
|
|
131
|
+
<div class="grid grid-cols-2 gap-3">
|
|
132
|
+
${renderNumberField("MIN", "minValue", fields.minValue ?? "0")}
|
|
133
|
+
${renderNumberField("MAX", "maxValue", fields.maxValue ?? "100")}
|
|
134
|
+
</div>
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renderNumberField(label, field, value) {
|
|
142
|
+
return `
|
|
143
|
+
<label class="table-designer-check-drawer__field">
|
|
144
|
+
<span>${escapeHtml(label)}</span>
|
|
145
|
+
<input
|
|
146
|
+
class="table-designer-field"
|
|
147
|
+
data-bind="table-designer-check-editor-field"
|
|
148
|
+
data-field="${escapeHtml(field)}"
|
|
149
|
+
inputmode="numeric"
|
|
150
|
+
spellcheck="false"
|
|
151
|
+
type="text"
|
|
152
|
+
value="${escapeHtml(value)}"
|
|
153
|
+
/>
|
|
154
|
+
</label>
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function renderPresetButtons(column, activePresetId) {
|
|
159
|
+
if (!column) {
|
|
160
|
+
return "";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const buttons = [];
|
|
164
|
+
|
|
165
|
+
if (isTextColumn(column)) {
|
|
166
|
+
buttons.push(
|
|
167
|
+
renderPresetButton("text-non-empty", "Non Empty", activePresetId),
|
|
168
|
+
renderPresetButton("text-min-length", "Min Length", activePresetId),
|
|
169
|
+
renderPresetButton("text-max-length", "Max Length", activePresetId),
|
|
170
|
+
renderPresetButton("text-length-range", "Length Range", activePresetId),
|
|
171
|
+
renderPresetButton("text-allowed-values", "Allowed Values", activePresetId)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (isNumericColumn(column)) {
|
|
176
|
+
buttons.push(
|
|
177
|
+
renderPresetButton("numeric-positive", "Positive", activePresetId),
|
|
178
|
+
renderPresetButton("numeric-non-negative", "Non Negative", activePresetId),
|
|
179
|
+
renderPresetButton("numeric-min-value", "Min Value", activePresetId),
|
|
180
|
+
renderPresetButton("numeric-max-value", "Max Value", activePresetId),
|
|
181
|
+
renderPresetButton("numeric-range", "Range", activePresetId),
|
|
182
|
+
renderPresetButton("numeric-non-zero", "Non Zero", activePresetId)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (isBooleanLikeColumn(column)) {
|
|
187
|
+
buttons.push(renderPresetButton("boolean-integer", "Boolean 0 / 1", activePresetId));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!buttons.length) {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return `
|
|
195
|
+
<div class="table-designer-check-drawer__quick">
|
|
196
|
+
<div class="table-designer-check-drawer__subheader">
|
|
197
|
+
<span>Quick Checks</span>
|
|
198
|
+
<span class="status-badge status-badge--muted">${escapeHtml(column.type || "TEXT")}</span>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="table-designer-check-drawer__preset-grid">
|
|
201
|
+
${buttons.join("")}
|
|
202
|
+
</div>
|
|
203
|
+
<p class="table-designer-check-drawer__note">
|
|
204
|
+
CHECK constraints do not replace NOT NULL. Use NOT NULL separately to reject NULL.
|
|
205
|
+
</p>
|
|
206
|
+
</div>
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function renderCheckItem(constraint, draft) {
|
|
211
|
+
const expression = normalizeCheckExpression(constraint.expression);
|
|
212
|
+
const canEdit = draft.mode === "create";
|
|
213
|
+
|
|
214
|
+
return `
|
|
215
|
+
<article class="table-designer-check-drawer__item">
|
|
216
|
+
<div class="table-designer-check-drawer__item-header">
|
|
217
|
+
<div>
|
|
218
|
+
<div class="table-designer-check-drawer__item-title">${escapeHtml(
|
|
219
|
+
constraint.name || "CHECK"
|
|
220
|
+
)}</div>
|
|
221
|
+
${
|
|
222
|
+
constraint.source === "preset"
|
|
223
|
+
? '<div class="table-designer-check-drawer__item-meta">Preset</div>'
|
|
224
|
+
: ""
|
|
225
|
+
}
|
|
226
|
+
</div>
|
|
227
|
+
${constraint.originalExpression ? '<span class="status-badge status-badge--muted">Detected</span>' : ""}
|
|
228
|
+
</div>
|
|
229
|
+
<pre class="table-designer-check-drawer__sql custom-scrollbar"><code>${highlightSql(expression)}</code></pre>
|
|
230
|
+
${
|
|
231
|
+
canEdit
|
|
232
|
+
? `
|
|
233
|
+
<div class="table-designer-check-drawer__item-actions">
|
|
234
|
+
<button
|
|
235
|
+
class="standard-button"
|
|
236
|
+
data-action="edit-table-designer-check"
|
|
237
|
+
data-constraint-id="${escapeHtml(constraint.id)}"
|
|
238
|
+
type="button"
|
|
239
|
+
>
|
|
240
|
+
Edit
|
|
241
|
+
</button>
|
|
242
|
+
<button
|
|
243
|
+
class="delete-button"
|
|
244
|
+
data-action="remove-table-designer-check"
|
|
245
|
+
data-constraint-id="${escapeHtml(constraint.id)}"
|
|
246
|
+
type="button"
|
|
247
|
+
>
|
|
248
|
+
<span class="material-symbols-outlined text-base">delete</span>
|
|
249
|
+
Remove
|
|
250
|
+
</button>
|
|
251
|
+
</div>
|
|
252
|
+
`
|
|
253
|
+
: ""
|
|
254
|
+
}
|
|
255
|
+
</article>
|
|
256
|
+
`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function renderCheckList(draft, column, checks) {
|
|
260
|
+
return `
|
|
261
|
+
<section class="table-designer-check-drawer__section">
|
|
262
|
+
<div class="table-designer-check-drawer__section-header">
|
|
263
|
+
<div class="table-designer-check-drawer__section-title">
|
|
264
|
+
<span>Check Constraints</span>
|
|
265
|
+
<span class="status-badge status-badge--muted">${formatNumber(checks.length)}</span>
|
|
266
|
+
</div>
|
|
267
|
+
${
|
|
268
|
+
draft.mode === "create"
|
|
269
|
+
? `
|
|
270
|
+
<button
|
|
271
|
+
class="standard-button table-designer-check-drawer__header-action"
|
|
272
|
+
data-action="start-table-designer-check-editor"
|
|
273
|
+
type="button"
|
|
274
|
+
>
|
|
275
|
+
<span class="material-symbols-outlined text-base">add</span>
|
|
276
|
+
Add Check
|
|
277
|
+
</button>
|
|
278
|
+
`
|
|
279
|
+
: ""
|
|
280
|
+
}
|
|
281
|
+
</div>
|
|
282
|
+
${
|
|
283
|
+
checks.length
|
|
284
|
+
? `<div class="table-designer-check-drawer__list">${checks
|
|
285
|
+
.map((constraint) => renderCheckItem(constraint, draft))
|
|
286
|
+
.join("")}</div>`
|
|
287
|
+
: `
|
|
288
|
+
<div class="table-designer-check-drawer__empty">
|
|
289
|
+
<div>NO CHECK CONSTRAINTS</div>
|
|
290
|
+
<p>CHECK constraints reject rows that do not satisfy a SQL expression.</p>
|
|
291
|
+
</div>
|
|
292
|
+
`
|
|
293
|
+
}
|
|
294
|
+
</section>
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderEditor(drawer, column) {
|
|
299
|
+
const editor = drawer.editor;
|
|
300
|
+
|
|
301
|
+
if (!editor) {
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const expression = String(editor.expression ?? "");
|
|
306
|
+
const generatedSql = normalizeCheckExpression(expression);
|
|
307
|
+
|
|
308
|
+
return `
|
|
309
|
+
<section class="table-designer-check-drawer__section">
|
|
310
|
+
<div class="table-designer-check-drawer__section-header">
|
|
311
|
+
<div class="table-designer-check-drawer__section-title">
|
|
312
|
+
<span>${drawer.editingConstraintId ? "Edit Check" : "New Check"}</span>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
${renderPresetButtons(column, editor.presetId)}
|
|
316
|
+
${renderPresetParameters(editor)}
|
|
317
|
+
<label class="table-designer-check-drawer__field">
|
|
318
|
+
<span>Custom Expression</span>
|
|
319
|
+
<textarea
|
|
320
|
+
aria-label="CHECK expression"
|
|
321
|
+
class="table-designer-field table-designer-check-drawer__expression custom-scrollbar"
|
|
322
|
+
data-bind="table-designer-check-editor-field"
|
|
323
|
+
data-field="expression"
|
|
324
|
+
spellcheck="false"
|
|
325
|
+
>${escapeHtml(expression)}</textarea>
|
|
326
|
+
</label>
|
|
327
|
+
${
|
|
328
|
+
editor.error
|
|
329
|
+
? `
|
|
330
|
+
<div class="table-designer-check-drawer__error" role="alert">
|
|
331
|
+
<div>${escapeHtml(editor.error.code || "INVALID_CHECK_EXPRESSION")}</div>
|
|
332
|
+
<p>${escapeHtml(editor.error.message || "Invalid CHECK expression.")}</p>
|
|
333
|
+
</div>
|
|
334
|
+
`
|
|
335
|
+
: ""
|
|
336
|
+
}
|
|
337
|
+
<div class="table-designer-check-drawer__preview">
|
|
338
|
+
<div>Generated SQL</div>
|
|
339
|
+
<pre class="custom-scrollbar"><code data-table-designer-check-preview>${highlightSql(generatedSql || "CHECK (...)")}</code></pre>
|
|
340
|
+
</div>
|
|
341
|
+
<div class="table-designer-check-drawer__actions">
|
|
342
|
+
<button class="standard-button" data-action="cancel-table-designer-check-editor" type="button">
|
|
343
|
+
Cancel
|
|
344
|
+
</button>
|
|
345
|
+
<button
|
|
346
|
+
class="signature-button"
|
|
347
|
+
data-action="save-table-designer-check"
|
|
348
|
+
type="button"
|
|
349
|
+
${editor.validating ? "disabled" : ""}
|
|
350
|
+
>
|
|
351
|
+
${editor.validating ? "Validating..." : "Save Check"}
|
|
352
|
+
</button>
|
|
353
|
+
</div>
|
|
354
|
+
</section>
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function renderTableDesignerConstraintsDrawer(state) {
|
|
359
|
+
const drawer = state.tableDesigner?.constraintsDrawer;
|
|
360
|
+
const draft = state.tableDesigner?.draft;
|
|
361
|
+
|
|
362
|
+
if (!drawer?.visible || !draft) {
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const column =
|
|
367
|
+
(draft.columns ?? []).find((candidate) => candidate.id === drawer.columnId) ?? null;
|
|
368
|
+
const columnLabel = column?.name || drawer.columnName || "Table";
|
|
369
|
+
const checks = getVisibleChecks(draft, column);
|
|
370
|
+
|
|
371
|
+
return `
|
|
372
|
+
<section class="table-designer-check-drawer flex h-full min-h-0 flex-col bg-surface-low">
|
|
373
|
+
<div class="border-b border-outline-variant/10 px-5 py-4">
|
|
374
|
+
<div class="flex items-center justify-between gap-3">
|
|
375
|
+
<div class="min-w-0">
|
|
376
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
|
|
377
|
+
Table Designer // ${escapeHtml(columnLabel)}
|
|
378
|
+
</div>
|
|
379
|
+
<h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
|
|
380
|
+
Checks
|
|
381
|
+
</h2>
|
|
382
|
+
</div>
|
|
383
|
+
<button
|
|
384
|
+
class="query-history-icon-button"
|
|
385
|
+
aria-label="Close check constraints"
|
|
386
|
+
data-action="close-table-designer-constraints"
|
|
387
|
+
title="Close check constraints"
|
|
388
|
+
type="button"
|
|
389
|
+
>
|
|
390
|
+
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
<div class="table-designer-check-drawer__body custom-scrollbar">
|
|
395
|
+
${renderCheckList(draft, column, checks)}
|
|
396
|
+
${renderEditor(drawer, column)}
|
|
397
|
+
</div>
|
|
398
|
+
</section>
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
@@ -85,7 +85,12 @@ function normalizeConstraintColumnName(name) {
|
|
|
85
85
|
return String(name ?? "").trim().toLowerCase();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function constraintIncludesColumn(constraint,
|
|
88
|
+
function constraintIncludesColumn(constraint, column) {
|
|
89
|
+
if (constraint.columnId && constraint.columnId === column.id) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const columnName = column.name;
|
|
89
94
|
const normalizedColumn = normalizeConstraintColumnName(columnName);
|
|
90
95
|
|
|
91
96
|
if (!normalizedColumn) {
|
|
@@ -98,33 +103,34 @@ function constraintIncludesColumn(constraint, columnName) {
|
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
function countColumnCheckConstraints(column, draft) {
|
|
101
|
-
if (draft.mode !== "edit") {
|
|
102
|
-
return 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
106
|
return (draft.checkConstraints ?? []).filter((constraint) =>
|
|
106
|
-
constraintIncludesColumn(constraint, column
|
|
107
|
+
!constraint.deleted && constraintIncludesColumn(constraint, column)
|
|
107
108
|
).length;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
function renderColumnCheckAction(column, draft) {
|
|
111
112
|
const checkCount = countColumnCheckConstraints(column, draft);
|
|
112
113
|
const columnName = column.name || "Unnamed column";
|
|
114
|
+
const checkCountLabel = String(checkCount || 0);
|
|
113
115
|
|
|
114
116
|
return `
|
|
115
117
|
<button
|
|
116
118
|
aria-label="Checks for ${escapeHtml(columnName)}"
|
|
117
119
|
class="standard-button table-designer-row-check-button"
|
|
118
|
-
data-action="open-
|
|
120
|
+
data-action="open-table-designer-constraints"
|
|
119
121
|
data-column-id="${escapeHtml(column.id)}"
|
|
120
122
|
data-column-name="${escapeHtml(column.name)}"
|
|
121
|
-
data-modal="table-designer-constraints"
|
|
122
123
|
title="Checks for ${escapeHtml(columnName)}"
|
|
123
124
|
type="button"
|
|
124
125
|
>
|
|
125
126
|
<span class="material-symbols-outlined text-base">fact_check</span>
|
|
126
127
|
<span>Checks</span>
|
|
127
|
-
|
|
128
|
+
<span
|
|
129
|
+
class="status-badge status-badge--muted table-designer-row-check-button__count${checkCount ? "" : " is-empty"}"
|
|
130
|
+
${checkCount ? "" : 'aria-hidden="true"'}
|
|
131
|
+
>
|
|
132
|
+
${escapeHtml(checkCountLabel)}
|
|
133
|
+
</span>
|
|
128
134
|
</button>
|
|
129
135
|
`;
|
|
130
136
|
}
|
|
@@ -241,12 +247,35 @@ function renderColumnGrid(draft, catalogTables) {
|
|
|
241
247
|
`;
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
function getDraftImportRows(draft) {
|
|
251
|
+
return Array.isArray(draft.importRows) && draft.importRows.length
|
|
252
|
+
? draft.importRows
|
|
253
|
+
: draft.importedCsvRows ?? [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function renderImportSource(draft) {
|
|
257
|
+
const importFormat = String(draft.importFormat ?? "").trim().toUpperCase();
|
|
258
|
+
const sourceFileName = String(draft.importSourceFileName ?? draft.importedCsvFileName ?? "").trim();
|
|
259
|
+
|
|
260
|
+
if (draft.mode !== "create" || !importFormat || !sourceFileName) {
|
|
261
|
+
return "";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return `
|
|
265
|
+
<div class="table-designer-import-source">
|
|
266
|
+
<span class="table-designer-import-source__label">SOURCE // ${escapeHtml(importFormat)}</span>
|
|
267
|
+
<span class="table-designer-import-source__file">${escapeHtml(sourceFileName)}</span>
|
|
268
|
+
</div>
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
244
272
|
function renderFillToggle(draft) {
|
|
245
273
|
if (draft.mode !== "create") {
|
|
246
274
|
return "";
|
|
247
275
|
}
|
|
248
276
|
|
|
249
|
-
const
|
|
277
|
+
const importRows = getDraftImportRows(draft);
|
|
278
|
+
const hasImportedRows = importRows.length > 0;
|
|
250
279
|
|
|
251
280
|
return `
|
|
252
281
|
<label class="standard-checkbox table-designer-fill-toggle table-designer-checkbox-override ${hasImportedRows ? "" : "is-disabled"}">
|
|
@@ -261,10 +290,10 @@ function renderFillToggle(draft) {
|
|
|
261
290
|
<span class="table-designer-fill-toggle__meta">
|
|
262
291
|
${
|
|
263
292
|
hasImportedRows
|
|
264
|
-
? `${escapeHtml(String(
|
|
265
|
-
|
|
293
|
+
? `${escapeHtml(String(importRows.length))} imported row${
|
|
294
|
+
importRows.length === 1 ? "" : "s"
|
|
266
295
|
}`
|
|
267
|
-
: "Available after
|
|
296
|
+
: "Available after data import"
|
|
268
297
|
}
|
|
269
298
|
</span>
|
|
270
299
|
</label>
|
|
@@ -323,11 +352,16 @@ export function renderTableDesignerEditor(state) {
|
|
|
323
352
|
}
|
|
324
353
|
|
|
325
354
|
const catalogTables = state.tableDesigner.tables ?? [];
|
|
355
|
+
const hasImportedRows = getDraftImportRows(draft).length > 0;
|
|
326
356
|
const saveLabel =
|
|
327
357
|
draft.mode === "create"
|
|
328
|
-
?
|
|
329
|
-
?
|
|
330
|
-
|
|
358
|
+
? hasImportedRows
|
|
359
|
+
? state.tableDesigner.saving
|
|
360
|
+
? "Creating & importing..."
|
|
361
|
+
: "Create & Import"
|
|
362
|
+
: state.tableDesigner.saving
|
|
363
|
+
? "Creating..."
|
|
364
|
+
: "Create Table"
|
|
331
365
|
: state.tableDesigner.saving
|
|
332
366
|
? "Saving..."
|
|
333
367
|
: "Save Changes";
|
|
@@ -355,6 +389,7 @@ export function renderTableDesignerEditor(state) {
|
|
|
355
389
|
? `<div class="status-badge status-badge--primary">CREATE</div>`
|
|
356
390
|
: ""
|
|
357
391
|
}
|
|
392
|
+
${renderImportSource(draft)}
|
|
358
393
|
</div>
|
|
359
394
|
<div class="table-designer-main__section-actions">
|
|
360
395
|
<button
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeHtml, formatNumber } from '../utils/format.js';
|
|
2
|
+
import { renderVirtualTableBadge } from './badges.js';
|
|
2
3
|
|
|
3
4
|
function getFilteredTables(tables, searchQuery) {
|
|
4
5
|
const normalizedSearch = String(searchQuery ?? '')
|
|
@@ -9,11 +10,11 @@ function getFilteredTables(tables, searchQuery) {
|
|
|
9
10
|
return tables;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
|
|
13
|
+
return tables.filter(table => !table?.isShadow && table.name.toLowerCase().includes(normalizedSearch));
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export function renderTableDesignerSidebar(state) {
|
|
16
|
-
const tables = state.tableDesigner.tables ?? [];
|
|
17
|
+
const tables = (state.tableDesigner.tables ?? []).filter(table => !table?.isShadow);
|
|
17
18
|
const filteredTables = getFilteredTables(tables, state.tableDesigner.searchQuery);
|
|
18
19
|
const isNewDraft = state.tableDesigner.draft?.mode === 'create';
|
|
19
20
|
|
|
@@ -87,9 +88,12 @@ export function renderTableDesignerSidebar(state) {
|
|
|
87
88
|
data-to="/table-designer/${encodeURIComponent(table.name)}"
|
|
88
89
|
type="button"
|
|
89
90
|
>
|
|
90
|
-
<div class="
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
92
|
+
<div class="table-designer-sidebar__item-name min-w-0 flex-1 ${
|
|
93
|
+
!isNewDraft && table.name === state.tableDesigner.selectedTableName ? 'is-active' : ''
|
|
94
|
+
}">${escapeHtml(table.name)}</div>
|
|
95
|
+
${renderVirtualTableBadge(table)}
|
|
96
|
+
</div>
|
|
93
97
|
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
94
98
|
${escapeHtml(formatNumber(table.columnCount ?? 0))} column${
|
|
95
99
|
Number(table.columnCount ?? 0) === 1 ? '' : 's'
|