sqlite-hub 0.9.3 → 0.9.6
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/.github/workflows/ci.yml +36 -0
- package/README.md +2 -2
- package/bin/sqlite-hub.js +1 -1
- package/frontend/index.html +2 -158
- package/frontend/js/app.js +41 -22
- package/frontend/js/components/connectionCard.js +62 -87
- package/frontend/js/components/emptyState.js +20 -23
- package/frontend/js/components/modal.js +145 -195
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryEditor.js +16 -30
- package/frontend/js/components/queryHistoryDetail.js +93 -164
- package/frontend/js/components/queryHistoryPanel.js +81 -99
- package/frontend/js/components/queryResults.js +3 -1
- package/frontend/js/components/rowEditorPanel.js +28 -31
- package/frontend/js/components/structureGraph.js +10 -9
- package/frontend/js/components/tableDesignerEditor.js +91 -116
- package/frontend/js/store.js +39 -3
- package/frontend/js/utils/dom.js +28 -0
- package/frontend/js/utils/tableDesigner.js +8 -2
- package/frontend/js/views/charts.js +23 -43
- package/frontend/js/views/data.js +116 -132
- package/frontend/js/views/mediaTagging.js +131 -164
- package/frontend/js/views/structure.js +52 -48
- package/frontend/styles/tailwind.css +80 -0
- package/frontend/styles/tailwind.generated.css +2 -0
- package/frontend/styles/tokens.css +3 -3
- package/package.json +19 -5
- package/server/routes/mediaTagging.js +2 -10
- package/server/routes/sql.js +35 -10
- package/server/server.js +24 -0
- package/server/services/sqlite/dataBrowserService.js +25 -5
- package/server/services/sqlite/exportService.js +4 -2
- package/server/services/sqlite/introspection.js +2 -2
- package/server/services/sqlite/mediaTaggingService.js +166 -53
- package/server/services/sqlite/structureService.js +2 -2
- package/server/services/sqlite/tableDesigner/sql.js +19 -3
- package/server/services/storage/appStateStore.js +227 -87
- package/server/utils/appPaths.js +55 -19
- package/server/utils/fileValidation.js +94 -8
- package/tailwind.config.cjs +73 -0
- package/tests/security-paths.test.js +84 -0
- package/tests/sql-identifier-safety.test.js +66 -0
- package/.npmingnore +0 -4
- package/changelog.md +0 -84
- package/docs/DESIGN_GUIDELINES.md +0 -36
- package/scripts/publish_brew.sh +0 -466
- package/scripts/publish_npm.sh +0 -241
- package/shortkeys.md +0 -5
|
@@ -241,38 +241,34 @@ function renderTableSearchBar(table, state, activeColumn, filteredRowCount) {
|
|
|
241
241
|
if (!table || !columns.length) {
|
|
242
242
|
return '';
|
|
243
243
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
class="control-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
${escapeHtml(formatNumber(filteredRowCount))} match${filteredRowCount === 1 ? '' : 'es'} on this page
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
`;
|
|
244
|
+
const columnOptions = columns
|
|
245
|
+
.map(columnName => [
|
|
246
|
+
'<option value="',
|
|
247
|
+
escapeHtml(columnName),
|
|
248
|
+
'" ',
|
|
249
|
+
columnName === activeColumn ? 'selected' : '',
|
|
250
|
+
'>',
|
|
251
|
+
escapeHtml(columnName),
|
|
252
|
+
'</option>',
|
|
253
|
+
].join(''))
|
|
254
|
+
.join('');
|
|
255
|
+
|
|
256
|
+
return [
|
|
257
|
+
'<div class="flex flex-wrap items-center gap-3 border-b border-outline-variant/10 bg-surface-container-low px-6 py-4">',
|
|
258
|
+
'<label class="control-shell flex min-w-[18rem] flex-1 items-center gap-3 border border-outline-variant/20 bg-surface-container-lowest px-3">',
|
|
259
|
+
'<span class="material-symbols-outlined text-base text-on-surface-variant/55">search</span>',
|
|
260
|
+
'<input class="control-input control-input--ghost min-w-0 flex-1 text-sm text-on-surface outline-none placeholder:text-on-surface-variant/40" data-bind="data-search-query" placeholder="Filter current page..." type="search" value="',
|
|
261
|
+
escapeHtml(state.dataBrowser.searchQuery ?? ''),
|
|
262
|
+
'" /></label>',
|
|
263
|
+
'<select class="control-select min-w-[14rem] border border-outline-variant/20 bg-surface-container-lowest font-mono text-xs tracking-[0.04em] text-on-surface outline-none" data-bind="data-search-column">',
|
|
264
|
+
columnOptions,
|
|
265
|
+
'</select>',
|
|
266
|
+
'<div class="text-[10px] font-mono tracking-[0.14em] text-on-surface-variant/55">',
|
|
267
|
+
escapeHtml(formatNumber(filteredRowCount)),
|
|
268
|
+
' match',
|
|
269
|
+
filteredRowCount === 1 ? '' : 'es',
|
|
270
|
+
' on this page</div></div>',
|
|
271
|
+
].join('');
|
|
276
272
|
}
|
|
277
273
|
|
|
278
274
|
function renderTableSurface(state) {
|
|
@@ -331,107 +327,95 @@ function renderTableSurface(state) {
|
|
|
331
327
|
const pageSizes = [25, 50, 100];
|
|
332
328
|
const filteredRowCount = filteredRows.length;
|
|
333
329
|
const hasActiveSearch = Boolean(searchQuery);
|
|
330
|
+
const gridMarkup = renderDataGrid({
|
|
331
|
+
columns,
|
|
332
|
+
rows: filteredRows.map(({ row }) => row),
|
|
333
|
+
tableClass: 'min-w-full border-collapse text-left font-mono text-xs',
|
|
334
|
+
theadClass: 'sticky top-0 z-10 bg-surface-container-highest',
|
|
335
|
+
tbodyClass: 'divide-y divide-outline-variant/5',
|
|
336
|
+
getRowClass: (_, filteredIndex) => {
|
|
337
|
+
const rowIndex = filteredRows[filteredIndex]?.index ?? filteredIndex;
|
|
338
|
+
|
|
339
|
+
return [
|
|
340
|
+
'data-browser-row',
|
|
341
|
+
filteredIndex % 2 === 0 ? 'data-browser-row--even' : 'data-browser-row--odd',
|
|
342
|
+
state.dataBrowser.selectedRowIndex === rowIndex ? 'is-selected' : '',
|
|
343
|
+
'cursor-pointer transition-colors',
|
|
344
|
+
].filter(Boolean).join(' ');
|
|
345
|
+
},
|
|
346
|
+
getRowAttrs: (_, filteredIndex) => {
|
|
347
|
+
const rowIndex = filteredRows[filteredIndex]?.index ?? filteredIndex;
|
|
334
348
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
</
|
|
384
|
-
<div class="flex
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
.join('')}
|
|
406
|
-
</div>
|
|
407
|
-
</div>
|
|
408
|
-
<div class="flex items-center gap-2">
|
|
409
|
-
<button
|
|
410
|
-
class="standard-button"
|
|
411
|
-
data-action="set-data-page"
|
|
412
|
-
data-page="${page - 1}"
|
|
413
|
-
type="button"
|
|
414
|
-
${page <= 1 ? 'disabled' : ''}
|
|
415
|
-
>
|
|
416
|
-
Prev
|
|
417
|
-
</button>
|
|
418
|
-
<div class="min-w-[7rem] text-center text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
419
|
-
page ${escapeHtml(formatNumber(page))} / ${escapeHtml(formatNumber(pageCount))}
|
|
420
|
-
</div>
|
|
421
|
-
<button
|
|
422
|
-
class="standard-button"
|
|
423
|
-
data-action="set-data-page"
|
|
424
|
-
data-page="${page + 1}"
|
|
425
|
-
type="button"
|
|
426
|
-
${page >= pageCount ? 'disabled' : ''}
|
|
427
|
-
>
|
|
428
|
-
Next
|
|
429
|
-
</button>
|
|
430
|
-
</div>
|
|
431
|
-
</div>
|
|
432
|
-
</footer>
|
|
433
|
-
</div>
|
|
434
|
-
`;
|
|
349
|
+
return ['data-action="select-data-row" data-row-index="', rowIndex, '"'].join('');
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
const emptyMarkup = !table.rows?.length
|
|
353
|
+
? '<div class="flex min-h-[180px] items-center justify-center border-t border-outline-variant/10"><p class="font-mono text-[10px] uppercase tracking-[0.22em] text-on-surface-variant/40">TABLE_IS_EMPTY</p></div>'
|
|
354
|
+
: !filteredRowCount
|
|
355
|
+
? [
|
|
356
|
+
'<div class="flex min-h-[180px] items-center justify-center border-t border-outline-variant/10">',
|
|
357
|
+
'<p class="font-mono text-[10px] tracking-[0.18em] text-on-surface-variant/40">',
|
|
358
|
+
hasActiveSearch ? 'No matching rows on this page.' : 'No rows available.',
|
|
359
|
+
'</p></div>',
|
|
360
|
+
].join('')
|
|
361
|
+
: '';
|
|
362
|
+
const visibleRowsText = hasActiveSearch
|
|
363
|
+
? [' // ', escapeHtml(formatNumber(filteredRowCount)), ' visible on this page'].join('')
|
|
364
|
+
: '';
|
|
365
|
+
const pageSizeButtons = pageSizes
|
|
366
|
+
.map(pageSize =>
|
|
367
|
+
[
|
|
368
|
+
'<button class="standard-button ',
|
|
369
|
+
pageSize === (table.limit ?? state.dataBrowser.pageSize) ? 'is-active' : '',
|
|
370
|
+
'" data-action="set-data-page-size" data-page-size="',
|
|
371
|
+
pageSize,
|
|
372
|
+
'" type="button">',
|
|
373
|
+
pageSize,
|
|
374
|
+
'</button>',
|
|
375
|
+
].join(''),
|
|
376
|
+
)
|
|
377
|
+
.join('');
|
|
378
|
+
|
|
379
|
+
return [
|
|
380
|
+
'<div class="flex flex-1 min-h-0 flex-col bg-surface-container-lowest">',
|
|
381
|
+
renderTableSearchBar(table, state, activeColumn, filteredRowCount),
|
|
382
|
+
'<div class="custom-scrollbar flex-1 overflow-auto">',
|
|
383
|
+
gridMarkup,
|
|
384
|
+
emptyMarkup,
|
|
385
|
+
'</div>',
|
|
386
|
+
'<footer class="flex flex-wrap items-center justify-between gap-4 border-t border-outline-variant/10 bg-surface-container px-6 py-4">',
|
|
387
|
+
'<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">showing ',
|
|
388
|
+
escapeHtml(formatNumber(fromRow)),
|
|
389
|
+
'-',
|
|
390
|
+
escapeHtml(formatNumber(toRow)),
|
|
391
|
+
' of ',
|
|
392
|
+
escapeHtml(formatNumber(totalRows)),
|
|
393
|
+
' rows',
|
|
394
|
+
visibleRowsText,
|
|
395
|
+
'</div>',
|
|
396
|
+
'<div class="flex flex-wrap items-center gap-4"><div class="flex items-center gap-2">',
|
|
397
|
+
'<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">rows</span>',
|
|
398
|
+
'<div class="flex items-center gap-2">',
|
|
399
|
+
pageSizeButtons,
|
|
400
|
+
'</div></div>',
|
|
401
|
+
'<div class="flex items-center gap-2">',
|
|
402
|
+
'<button class="standard-button" data-action="set-data-page" data-page="',
|
|
403
|
+
page - 1,
|
|
404
|
+
'" type="button" ',
|
|
405
|
+
page <= 1 ? 'disabled' : '',
|
|
406
|
+
'>Prev</button>',
|
|
407
|
+
'<div class="min-w-[7rem] text-center text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">page ',
|
|
408
|
+
escapeHtml(formatNumber(page)),
|
|
409
|
+
' / ',
|
|
410
|
+
escapeHtml(formatNumber(pageCount)),
|
|
411
|
+
'</div>',
|
|
412
|
+
'<button class="standard-button" data-action="set-data-page" data-page="',
|
|
413
|
+
page + 1,
|
|
414
|
+
'" type="button" ',
|
|
415
|
+
page >= pageCount ? 'disabled' : '',
|
|
416
|
+
'>Next</button>',
|
|
417
|
+
'</div></div></footer></div>',
|
|
418
|
+
].join('');
|
|
435
419
|
}
|
|
436
420
|
|
|
437
421
|
export function renderDataRowEditorPanel(state) {
|
|
@@ -180,52 +180,39 @@ function renderParentTagFields({
|
|
|
180
180
|
|
|
181
181
|
const isCreatingParentTag = Boolean(parentToggleColumn ? tagFormValues[parentToggleColumn.name] : false);
|
|
182
182
|
const selectedParentTagId = parentSelectColumn ? String(tagFormValues[parentSelectColumn.name] ?? '') : '';
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
data-field="${escapeHtml(parentToggleColumn.name)}"
|
|
217
|
-
type="checkbox"
|
|
218
|
-
${isCreatingParentTag ? 'checked' : ''}
|
|
219
|
-
/>
|
|
220
|
-
<span class="media-tagging-field__meta">
|
|
221
|
-
<span class="media-tagging-field__label">Create Parent Tag</span>
|
|
222
|
-
</span>
|
|
223
|
-
</label>
|
|
224
|
-
</div>
|
|
225
|
-
`
|
|
226
|
-
: ''
|
|
227
|
-
}
|
|
228
|
-
`;
|
|
183
|
+
const parentSelectMarkup = parentSelectColumn
|
|
184
|
+
? [
|
|
185
|
+
'<label class="media-tagging-field"><span class="media-tagging-field__label">Parent Tag</span>',
|
|
186
|
+
'<select class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container" data-bind="media-tagging-tag-form-field" data-field="',
|
|
187
|
+
escapeHtml(parentSelectColumn.name),
|
|
188
|
+
'" ',
|
|
189
|
+
isCreatingParentTag ? 'disabled' : '',
|
|
190
|
+
'>',
|
|
191
|
+
renderOptionList(
|
|
192
|
+
parentTagOptions.map(tag => ({
|
|
193
|
+
value: String(tag.identityValue ?? ''),
|
|
194
|
+
label: tag.label,
|
|
195
|
+
})),
|
|
196
|
+
selectedParentTagId,
|
|
197
|
+
parentTagOptions.length ? 'Select a parent tag' : 'No parent tags available',
|
|
198
|
+
),
|
|
199
|
+
'</select></label>',
|
|
200
|
+
].join('')
|
|
201
|
+
: '';
|
|
202
|
+
const parentToggleMarkup = parentToggleColumn
|
|
203
|
+
? [
|
|
204
|
+
'<div class="media-tagging-field"><label class="standard-checkbox table-designer-check table-designer-checkbox-override">',
|
|
205
|
+
'<input data-bind="media-tagging-tag-form-field" data-field="',
|
|
206
|
+
escapeHtml(parentToggleColumn.name),
|
|
207
|
+
'" type="checkbox" ',
|
|
208
|
+
isCreatingParentTag ? 'checked' : '',
|
|
209
|
+
'/>',
|
|
210
|
+
'<span class="media-tagging-field__meta"><span class="media-tagging-field__label">Create Parent Tag</span></span>',
|
|
211
|
+
'</label></div>',
|
|
212
|
+
].join('')
|
|
213
|
+
: '';
|
|
214
|
+
|
|
215
|
+
return [parentSelectMarkup, parentToggleMarkup].join('');
|
|
229
216
|
}
|
|
230
217
|
|
|
231
218
|
function renderTagList(tags = [], selectedTagKeys = []) {
|
|
@@ -285,42 +272,40 @@ function renderCreatedTagList(tags = [], { canRemove = false, removingTagKey = n
|
|
|
285
272
|
return `
|
|
286
273
|
<div class="media-tagging-created-tags custom-scrollbar">
|
|
287
274
|
${tags
|
|
288
|
-
.map(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
`,
|
|
323
|
-
)
|
|
275
|
+
.map(tag => {
|
|
276
|
+
const parentMetaMarkup =
|
|
277
|
+
tag.isParentTag || tag.parentTagLabel
|
|
278
|
+
? [
|
|
279
|
+
'<div class="media-tagging-created-tag__meta">',
|
|
280
|
+
tag.isParentTag ? '<span class="media-tagging-created-tag__badge">Parent</span>' : '',
|
|
281
|
+
tag.parentTagLabel
|
|
282
|
+
? [
|
|
283
|
+
'<span class="media-tagging-created-tag__badge">',
|
|
284
|
+
escapeHtml(tag.parentTagLabel),
|
|
285
|
+
'</span>',
|
|
286
|
+
].join('')
|
|
287
|
+
: '',
|
|
288
|
+
'</div>',
|
|
289
|
+
].join('')
|
|
290
|
+
: '';
|
|
291
|
+
|
|
292
|
+
return [
|
|
293
|
+
'<article class="media-tagging-created-tag">',
|
|
294
|
+
'<div class="media-tagging-created-tag__content">',
|
|
295
|
+
'<div class="media-tagging-created-tag__label">',
|
|
296
|
+
escapeHtml(tag.label),
|
|
297
|
+
'</div>',
|
|
298
|
+
parentMetaMarkup,
|
|
299
|
+
'</div>',
|
|
300
|
+
'<button aria-label="Remove tag" class="delete-button media-tagging-created-tag__remove" data-action="remove-media-tag" data-tag-key="',
|
|
301
|
+
escapeHtml(tag.key),
|
|
302
|
+
'" type="button" title="Remove tag" ',
|
|
303
|
+
canRemove && removingTagKey !== tag.key ? '' : 'disabled',
|
|
304
|
+
'><span class="material-symbols-outlined text-[18px]">',
|
|
305
|
+
removingTagKey === tag.key ? 'hourglass_top' : 'delete',
|
|
306
|
+
'</span></button></article>',
|
|
307
|
+
].join('');
|
|
308
|
+
})
|
|
324
309
|
.join('')}
|
|
325
310
|
</div>
|
|
326
311
|
`;
|
|
@@ -633,89 +618,71 @@ function renderTaggingSection(state) {
|
|
|
633
618
|
const mediaTableColumns = state.mediaTagging.mediaTableColumns ?? [];
|
|
634
619
|
const pathCandidates = state.mediaTagging.pathCandidates ?? [];
|
|
635
620
|
const booleanCandidates = state.mediaTagging.booleanCandidates ?? [];
|
|
621
|
+
const tableOptions = renderOptionList(
|
|
622
|
+
tables
|
|
623
|
+
.map(table => ({ value: table.name, label: table.name }))
|
|
624
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
625
|
+
draft.mediaTable,
|
|
626
|
+
'Select a media table',
|
|
627
|
+
);
|
|
628
|
+
const pathOptions = renderOptionList(
|
|
629
|
+
mediaTableColumns
|
|
630
|
+
.map(column => ({
|
|
631
|
+
value: column.name,
|
|
632
|
+
label: pathCandidates.includes(column.name)
|
|
633
|
+
? [column.name, ' // suggested'].join('')
|
|
634
|
+
: column.name,
|
|
635
|
+
}))
|
|
636
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
637
|
+
draft.pathColumn,
|
|
638
|
+
'Select the media path column',
|
|
639
|
+
);
|
|
640
|
+
const taggedOptions = renderOptionList(
|
|
641
|
+
mediaTableColumns
|
|
642
|
+
.map(column => ({
|
|
643
|
+
value: column.name,
|
|
644
|
+
label: booleanCandidates.includes(column.name)
|
|
645
|
+
? [column.name, ' // suggested'].join('')
|
|
646
|
+
: column.name,
|
|
647
|
+
}))
|
|
648
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
649
|
+
draft.taggedColumn,
|
|
650
|
+
mediaTableColumns.length ? 'Select the tagged flag column' : 'No columns available',
|
|
651
|
+
);
|
|
636
652
|
|
|
637
|
-
return
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
<div>
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
draft.pathColumn,
|
|
671
|
-
'Select the media path column',
|
|
672
|
-
)}
|
|
673
|
-
</select>
|
|
674
|
-
</label>
|
|
675
|
-
<label class="media-tagging-field">
|
|
676
|
-
<span class="media-tagging-field__label">Tagged Boolean Column</span>
|
|
677
|
-
<select class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container" data-bind="media-tagging-field" data-field="taggedColumn">
|
|
678
|
-
${renderOptionList(
|
|
679
|
-
mediaTableColumns
|
|
680
|
-
.map(column => ({
|
|
681
|
-
value: column.name,
|
|
682
|
-
label: booleanCandidates.includes(column.name) ? `${column.name} // suggested` : column.name,
|
|
683
|
-
}))
|
|
684
|
-
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
685
|
-
draft.taggedColumn,
|
|
686
|
-
mediaTableColumns.length ? 'Select the tagged flag column' : 'No columns available',
|
|
687
|
-
)}
|
|
688
|
-
</select>
|
|
689
|
-
</label>
|
|
690
|
-
</div>
|
|
691
|
-
|
|
692
|
-
<div class="media-tagging-query-grid">
|
|
693
|
-
${renderSqlTextarea({
|
|
694
|
-
label: 'Untagged Query',
|
|
695
|
-
value: draft.untaggedQuery,
|
|
696
|
-
dataBind: 'media-tagging-field',
|
|
697
|
-
dataField: 'untaggedQuery',
|
|
698
|
-
})}
|
|
699
|
-
${renderSqlTextarea({
|
|
700
|
-
label: 'Tagged Query',
|
|
701
|
-
value: draft.taggedQuery,
|
|
702
|
-
dataBind: 'media-tagging-field',
|
|
703
|
-
dataField: 'taggedQuery',
|
|
704
|
-
})}
|
|
705
|
-
</div>
|
|
706
|
-
|
|
707
|
-
<div class="flex flex-wrap items-center gap-3">
|
|
708
|
-
<button
|
|
709
|
-
class="standard-button"
|
|
710
|
-
data-action="reset-media-tagging-queries"
|
|
711
|
-
type="button"
|
|
712
|
-
>
|
|
713
|
-
Reset Queries to Defaults
|
|
714
|
-
</button>
|
|
715
|
-
</div>
|
|
716
|
-
</div>
|
|
717
|
-
</section>
|
|
718
|
-
`;
|
|
653
|
+
return [
|
|
654
|
+
'<section class="media-tagging-card media-tagging-card--tagging shell-section">',
|
|
655
|
+
'<div class="media-tagging-card__header"><div><div class="media-tagging-card__eyebrow">2. Tagging</div><h2 class="media-tagging-card__title">Media Source</h2></div></div>',
|
|
656
|
+
'<div class="media-tagging-card__body"><div class="media-tagging-form-grid">',
|
|
657
|
+
'<label class="media-tagging-field"><span class="media-tagging-field__label">Media Table</span>',
|
|
658
|
+
'<select class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container" data-bind="media-tagging-field" data-field="mediaTable">',
|
|
659
|
+
tableOptions,
|
|
660
|
+
'</select></label><div></div>',
|
|
661
|
+
'<label class="media-tagging-field"><span class="media-tagging-field__label">Path Column</span>',
|
|
662
|
+
'<select class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container" data-bind="media-tagging-field" data-field="pathColumn">',
|
|
663
|
+
pathOptions,
|
|
664
|
+
'</select></label>',
|
|
665
|
+
'<label class="media-tagging-field"><span class="media-tagging-field__label">Tagged Boolean Column</span>',
|
|
666
|
+
'<select class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container" data-bind="media-tagging-field" data-field="taggedColumn">',
|
|
667
|
+
taggedOptions,
|
|
668
|
+
'</select></label></div>',
|
|
669
|
+
'<div class="media-tagging-query-grid">',
|
|
670
|
+
renderSqlTextarea({
|
|
671
|
+
label: 'Untagged Query',
|
|
672
|
+
value: draft.untaggedQuery,
|
|
673
|
+
dataBind: 'media-tagging-field',
|
|
674
|
+
dataField: 'untaggedQuery',
|
|
675
|
+
}),
|
|
676
|
+
renderSqlTextarea({
|
|
677
|
+
label: 'Tagged Query',
|
|
678
|
+
value: draft.taggedQuery,
|
|
679
|
+
dataBind: 'media-tagging-field',
|
|
680
|
+
dataField: 'taggedQuery',
|
|
681
|
+
}),
|
|
682
|
+
'</div>',
|
|
683
|
+
'<div class="flex flex-wrap items-center gap-3"><button class="standard-button" data-action="reset-media-tagging-queries" type="button">Reset Queries to Defaults</button></div>',
|
|
684
|
+
'</div></section>',
|
|
685
|
+
].join('');
|
|
719
686
|
}
|
|
720
687
|
|
|
721
688
|
function renderMappingSection(state) {
|