sqlite-hub 1.2.0 → 1.4.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 +106 -73
- package/bin/sqlite-hub.js +176 -2
- package/docs/API.md +122 -0
- package/docs/CLI.md +227 -0
- package/docs/DESIGN_GUIDELINES.md +45 -0
- package/docs/changelog.md +141 -0
- package/docs/shortkeys.md +27 -0
- package/docs/todo.md +16 -0
- package/examples/api/generate-types.js +52 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- 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/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_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/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 +18 -0
- package/frontend/js/app.js +118 -8
- package/frontend/js/components/connectionCard.js +1 -1
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/generateTypesDropdown.js +33 -0
- package/frontend/js/components/metricCard.js +1 -1
- package/frontend/js/components/modal.js +211 -18
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryHistoryDetail.js +3 -3
- package/frontend/js/components/queryHistoryHeader.js +1 -1
- package/frontend/js/components/queryHistoryPanel.js +1 -1
- package/frontend/js/components/rowEditorPanel.js +13 -3
- package/frontend/js/router.js +28 -6
- package/frontend/js/store.js +422 -8
- package/frontend/js/views/backups.js +529 -37
- package/frontend/js/views/charts.js +9 -9
- package/frontend/js/views/connections.js +2 -2
- package/frontend/js/views/data.js +1 -1
- package/frontend/js/views/documents.js +3 -3
- package/frontend/js/views/editor.js +6 -6
- package/frontend/js/views/mediaTagging.js +7 -5
- package/frontend/js/views/overview.js +3 -3
- package/frontend/js/views/settings.js +3 -3
- package/frontend/js/views/structure.js +6 -1
- package/frontend/styles/base.css +1 -40
- package/frontend/styles/components.css +109 -232
- package/frontend/styles/structure-graph.css +32 -68
- package/frontend/styles/tailwind.css +8 -4
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +28 -21
- package/frontend/styles/views.css +94 -261
- package/package.json +18 -2
- package/server/routes/backups.js +18 -2
- package/server/routes/externalApi.js +36 -0
- package/server/routes/structure.js +22 -0
- package/server/services/databaseCommandService.js +14 -0
- package/server/services/sqlite/backupDiff.js +914 -0
- package/server/services/sqlite/backupService.js +77 -1
- package/server/services/sqlite/structureService.js +7 -0
- package/server/services/typeGenerationService.js +663 -0
- package/tailwind.config.cjs +0 -1
- package/.github/funding.yml +0 -2
- package/.github/workflows/ci.yml +0 -36
- package/database.sqlite +0 -0
- package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
- package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
- package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
- package/frontend/assets/mockups/connections_1200.webp +0 -0
- package/frontend/assets/mockups/data_1_1200.webp +0 -0
- package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
- package/frontend/assets/mockups/documents_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
- package/frontend/assets/mockups/overview_1200.webp +0 -0
- package/frontend/assets/mockups/settings_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
- package/frontend/assets/mockups/structure_1_1200.webp +0 -0
- package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
- package/tests/api-token-auth.test.js +0 -279
- package/tests/backup-manager.test.js +0 -140
- package/tests/backups-view.test.js +0 -70
- package/tests/charts-height-preset-storage.test.js +0 -60
- package/tests/charts-route-state.test.js +0 -144
- package/tests/check-constraint-options.test.js +0 -90
- package/tests/cli-args.test.js +0 -113
- package/tests/cli-service-delegation.test.js +0 -140
- package/tests/connection-removal.test.js +0 -52
- package/tests/connections-file-dialog-route.test.js +0 -89
- package/tests/copy-column-modal.test.js +0 -131
- package/tests/database-command-service.test.js +0 -174
- package/tests/database-documents.test.js +0 -85
- package/tests/documents-view.test.js +0 -132
- package/tests/dropdown-button.test.js +0 -101
- package/tests/email-preview.test.js +0 -89
- package/tests/export-blob.test.js +0 -99
- package/tests/export-filenames.test.js +0 -38
- package/tests/file-path-preview.test.js +0 -165
- package/tests/form-controls.test.js +0 -34
- package/tests/json-preview.test.js +0 -49
- package/tests/local-request-security.test.js +0 -85
- package/tests/markdown-documents.test.js +0 -103
- package/tests/native-file-dialog.test.js +0 -105
- package/tests/query-editor.test.js +0 -28
- package/tests/query-history-detail.test.js +0 -37
- package/tests/query-history-header.test.js +0 -30
- package/tests/query-results-truncation.test.js +0 -20
- package/tests/risky-sql.test.js +0 -30
- package/tests/row-editor-json.test.js +0 -82
- package/tests/row-editor-null-values.test.js +0 -184
- package/tests/row-editor-timestamp-preview.test.js +0 -192
- package/tests/security-paths.test.js +0 -84
- package/tests/settings-api-tokens-route.test.js +0 -97
- package/tests/settings-metadata.test.js +0 -114
- package/tests/settings-view.test.js +0 -108
- package/tests/sql-formatter.test.js +0 -173
- package/tests/sql-highlight.test.js +0 -38
- package/tests/sql-identifier-safety.test.js +0 -171
- package/tests/sql-result-limit.test.js +0 -66
- package/tests/structure-view.test.js +0 -60
- package/tests/table-designer-v2-unique-constraints.test.js +0 -78
- package/tests/table-scroll-state.test.js +0 -70
- package/tests/text-cell-stats.test.js +0 -38
|
@@ -126,7 +126,7 @@ function renderError(error) {
|
|
|
126
126
|
|
|
127
127
|
return `
|
|
128
128
|
<div class="border border-error/20 bg-error-container/20 px-4 py-3 text-sm text-error">
|
|
129
|
-
<div class="font-
|
|
129
|
+
<div class="font-body text-xs font-bold uppercase tracking-[0.18em]">${escapeHtml(
|
|
130
130
|
error.code || 'Request failed',
|
|
131
131
|
)}</div>
|
|
132
132
|
<div class="mt-1 text-on-surface">${escapeHtml(error.message)}</div>
|
|
@@ -449,15 +449,25 @@ function renderDeleteBackupForm(modal) {
|
|
|
449
449
|
].join('');
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
function
|
|
452
|
+
function renderEditBackupForm(modal) {
|
|
453
453
|
return `
|
|
454
|
-
<form class="space-y-5" data-form="edit-backup
|
|
454
|
+
<form class="space-y-5" data-form="edit-backup">
|
|
455
455
|
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3">
|
|
456
|
-
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
457
|
-
<div class="mt-2 font-
|
|
458
|
-
${escapeHtml(modal.
|
|
456
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-on-surface-variant/55">Primary Key</div>
|
|
457
|
+
<div class="mt-2 font-mono text-[10px] uppercase tracking-[0.12em] text-on-surface/80">
|
|
458
|
+
${escapeHtml(modal.backupId ?? 'n/a')}
|
|
459
459
|
</div>
|
|
460
460
|
</div>
|
|
461
|
+
<label class="block space-y-2">
|
|
462
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">Name</span>
|
|
463
|
+
<input
|
|
464
|
+
class="control-input w-full border border-outline-variant/20 bg-surface-container-lowest px-3 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
465
|
+
name="name"
|
|
466
|
+
placeholder="Backup name"
|
|
467
|
+
required
|
|
468
|
+
value="${escapeHtml(modal.backupName ?? 'Backup')}"
|
|
469
|
+
/>
|
|
470
|
+
</label>
|
|
461
471
|
<label class="block space-y-2">
|
|
462
472
|
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">Notes</span>
|
|
463
473
|
<textarea
|
|
@@ -471,7 +481,7 @@ function renderEditBackupNotesForm(modal) {
|
|
|
471
481
|
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
472
482
|
<button class="signature-button" type="submit" ${modal.submitting ? 'disabled' : ''}>
|
|
473
483
|
<span class="material-symbols-outlined text-sm">save</span>
|
|
474
|
-
${modal.submitting ? 'Saving...' : 'Save
|
|
484
|
+
${modal.submitting ? 'Saving...' : 'Save backup'}
|
|
475
485
|
</button>
|
|
476
486
|
</div>
|
|
477
487
|
</form>
|
|
@@ -490,7 +500,7 @@ function renderBackupSafetyForm(modal) {
|
|
|
490
500
|
</p>
|
|
491
501
|
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3">
|
|
492
502
|
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-on-surface-variant/55">Safety Backup</div>
|
|
493
|
-
<div class="mt-2 font-
|
|
503
|
+
<div class="mt-2 font-body text-sm font-black uppercase text-on-surface">${escapeHtml(
|
|
494
504
|
modal.backupNameBeforeOperation ?? modal.backupName ?? 'Before operation',
|
|
495
505
|
)}</div>
|
|
496
506
|
</div>
|
|
@@ -512,6 +522,180 @@ function renderBackupSafetyForm(modal) {
|
|
|
512
522
|
`;
|
|
513
523
|
}
|
|
514
524
|
|
|
525
|
+
function renderTypeGenerationSelect({ label, field, value, options = [] }) {
|
|
526
|
+
return renderSelectField({
|
|
527
|
+
label,
|
|
528
|
+
name: field,
|
|
529
|
+
value,
|
|
530
|
+
bind: 'type-generation-field',
|
|
531
|
+
options,
|
|
532
|
+
}).replace('<select ', `<select data-type-generation-field="${escapeHtml(field)}" `);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function renderTypeGenerationCheckbox({ label, field, checked }) {
|
|
536
|
+
return `
|
|
537
|
+
<label class="standard-checkbox">
|
|
538
|
+
<input
|
|
539
|
+
${checked ? 'checked' : ''}
|
|
540
|
+
data-bind="type-generation-field"
|
|
541
|
+
data-type-generation-field="${escapeHtml(field)}"
|
|
542
|
+
type="checkbox"
|
|
543
|
+
/>
|
|
544
|
+
<span>${escapeHtml(label)}</span>
|
|
545
|
+
</label>
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function renderTypeGenerationCodePreview(code) {
|
|
550
|
+
const text = String(code ?? '');
|
|
551
|
+
const normalized = text.includes('\n') || text.includes('\\n')
|
|
552
|
+
? text.replace(/\\n/g, '\n')
|
|
553
|
+
: text
|
|
554
|
+
.replace(/\{\s*/g, '{\n ')
|
|
555
|
+
.replace(/;\s*/g, ';\n ')
|
|
556
|
+
.replace(/\s*\}\s*$/g, '\n}');
|
|
557
|
+
|
|
558
|
+
return normalized
|
|
559
|
+
.replace(/\r\n/g, '\n')
|
|
560
|
+
.split('\n')
|
|
561
|
+
.map(line => `<span class="block whitespace-pre">${line ? escapeHtml(line) : ' '}</span>`)
|
|
562
|
+
.join('');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function renderGenerateTypesForm(modal) {
|
|
566
|
+
const options = modal.options ?? {};
|
|
567
|
+
const result = modal.result ?? {};
|
|
568
|
+
const target = modal.target ?? 'typescript';
|
|
569
|
+
const isAllTables = modal.scope === 'all';
|
|
570
|
+
const fileCount = result.files?.length ?? modal.tableNames?.length ?? 0;
|
|
571
|
+
const subjectLabel = isAllTables
|
|
572
|
+
? `all ${fileCount || ''} tables`.replace(/\s+/g, ' ').trim()
|
|
573
|
+
: `the schema of "${modal.tableName ?? ''}"`;
|
|
574
|
+
const downloadLabel = result.files?.length ? 'Download Files' : 'Download';
|
|
575
|
+
|
|
576
|
+
return `
|
|
577
|
+
<div class="space-y-5">
|
|
578
|
+
<p class="text-sm leading-6 text-on-surface-variant/70">
|
|
579
|
+
Generate application types from ${escapeHtml(subjectLabel)}.
|
|
580
|
+
</p>
|
|
581
|
+
<div class="grid gap-5 xl:grid-cols-[24rem_minmax(0,1fr)]">
|
|
582
|
+
<div class="min-w-0 space-y-4">
|
|
583
|
+
<div class="space-y-4">
|
|
584
|
+
${renderTypeGenerationSelect({
|
|
585
|
+
label: 'Target',
|
|
586
|
+
field: 'target',
|
|
587
|
+
value: target,
|
|
588
|
+
options: [
|
|
589
|
+
{ value: 'typescript', label: 'TypeScript' },
|
|
590
|
+
{ value: 'rust', label: 'Rust' },
|
|
591
|
+
{ value: 'kotlin', label: 'Kotlin' },
|
|
592
|
+
{ value: 'swift', label: 'Swift' },
|
|
593
|
+
],
|
|
594
|
+
})}
|
|
595
|
+
${renderTypeGenerationSelect({
|
|
596
|
+
label: 'Property naming',
|
|
597
|
+
field: 'propertyNaming',
|
|
598
|
+
value: options.propertyNaming ?? 'camel',
|
|
599
|
+
options: [
|
|
600
|
+
{ value: 'preserve', label: 'Preserve' },
|
|
601
|
+
{ value: 'camel', label: 'camelCase' },
|
|
602
|
+
{ value: 'pascal', label: 'PascalCase' },
|
|
603
|
+
{ value: 'snake', label: 'snake_case' },
|
|
604
|
+
],
|
|
605
|
+
})}
|
|
606
|
+
${renderTypeGenerationSelect({
|
|
607
|
+
label: 'Nullable handling',
|
|
608
|
+
field: 'nullableMode',
|
|
609
|
+
value: options.nullableMode ?? 'native',
|
|
610
|
+
options: [
|
|
611
|
+
{ value: 'native', label: 'Native' },
|
|
612
|
+
{ value: 'optional', label: 'Optional (TypeScript)' },
|
|
613
|
+
],
|
|
614
|
+
})}
|
|
615
|
+
${
|
|
616
|
+
target === 'typescript'
|
|
617
|
+
? renderTypeGenerationSelect({
|
|
618
|
+
label: 'JSON type',
|
|
619
|
+
field: 'jsonType',
|
|
620
|
+
value: options.jsonType ?? 'unknown',
|
|
621
|
+
options: [
|
|
622
|
+
{ value: 'unknown', label: 'unknown' },
|
|
623
|
+
{ value: 'record', label: 'Record<string, unknown>' },
|
|
624
|
+
{ value: 'json-value', label: 'JsonValue' },
|
|
625
|
+
],
|
|
626
|
+
})
|
|
627
|
+
: ''
|
|
628
|
+
}
|
|
629
|
+
</div>
|
|
630
|
+
<div class="grid gap-2">
|
|
631
|
+
${renderTypeGenerationCheckbox({
|
|
632
|
+
label: 'Export declaration',
|
|
633
|
+
field: 'exportDeclaration',
|
|
634
|
+
checked: options.exportDeclaration !== false,
|
|
635
|
+
})}
|
|
636
|
+
${renderTypeGenerationCheckbox({
|
|
637
|
+
label: 'Include default values as comments',
|
|
638
|
+
field: 'includeDefaultsAsComments',
|
|
639
|
+
checked: Boolean(options.includeDefaultsAsComments),
|
|
640
|
+
})}
|
|
641
|
+
${renderTypeGenerationCheckbox({
|
|
642
|
+
label: 'Include generated columns',
|
|
643
|
+
field: 'includeGeneratedColumns',
|
|
644
|
+
checked: options.includeGeneratedColumns !== false,
|
|
645
|
+
})}
|
|
646
|
+
${renderTypeGenerationCheckbox({
|
|
647
|
+
label: 'Include hidden columns',
|
|
648
|
+
field: 'includeHiddenColumns',
|
|
649
|
+
checked: Boolean(options.includeHiddenColumns),
|
|
650
|
+
})}
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
<div class="min-w-0 space-y-3">
|
|
654
|
+
<div class="flex items-center justify-between gap-3">
|
|
655
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container">Code Preview</div>
|
|
656
|
+
<div class="truncate font-mono text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
657
|
+
${escapeHtml(result.fileName ?? '')}
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
${
|
|
661
|
+
result.files?.length
|
|
662
|
+
? `<div class="flex flex-wrap gap-2">${result.files
|
|
663
|
+
.map(
|
|
664
|
+
file =>
|
|
665
|
+
`<span class="border border-outline-variant/20 bg-surface-highest px-2 py-1 font-mono text-[10px] uppercase tracking-[0.14em] text-on-surface-variant/70">${escapeHtml(
|
|
666
|
+
file.fileName ?? file.tableName ?? 'types',
|
|
667
|
+
)}</span>`,
|
|
668
|
+
)
|
|
669
|
+
.join('')}</div>`
|
|
670
|
+
: ''
|
|
671
|
+
}
|
|
672
|
+
<pre class="type-generation-code-preview custom-scrollbar border border-outline-variant/10 bg-surface-container-lowest px-4 py-4 font-mono text-xs leading-6 text-on-surface"><code>${renderTypeGenerationCodePreview(
|
|
673
|
+
modal.loading ? 'Generating...' : result.code ?? '',
|
|
674
|
+
)}</code></pre>
|
|
675
|
+
${renderError(modal.error)}
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
679
|
+
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
680
|
+
<div class="flex items-center gap-2">
|
|
681
|
+
<button class="standard-button" data-action="copy-generated-types" type="button" ${
|
|
682
|
+
result.code ? '' : 'disabled'
|
|
683
|
+
}>
|
|
684
|
+
<span class="material-symbols-outlined text-sm">content_copy</span>
|
|
685
|
+
Copy Code
|
|
686
|
+
</button>
|
|
687
|
+
<button class="signature-button" data-action="download-generated-types" type="button" ${
|
|
688
|
+
result.code ? '' : 'disabled'
|
|
689
|
+
}>
|
|
690
|
+
<span class="material-symbols-outlined text-sm">download</span>
|
|
691
|
+
${escapeHtml(downloadLabel)}
|
|
692
|
+
</button>
|
|
693
|
+
</div>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
`;
|
|
697
|
+
}
|
|
698
|
+
|
|
515
699
|
function renderDeleteRowConfirmForm(modal) {
|
|
516
700
|
const rowPreview = modal.rowPreview ?? [];
|
|
517
701
|
const rowLabelMarkup = modal.rowLabel
|
|
@@ -1172,7 +1356,7 @@ function renderTextExportModal(modal, action) {
|
|
|
1172
1356
|
<span class="material-symbols-outlined text-xl">${escapeHtml(option.icon)}</span>
|
|
1173
1357
|
</span>
|
|
1174
1358
|
<span class="min-w-0">
|
|
1175
|
-
<span class="block truncate font-
|
|
1359
|
+
<span class="block truncate font-body text-lg font-black uppercase tracking-normal text-primary-container">
|
|
1176
1360
|
${escapeHtml(option.label)}
|
|
1177
1361
|
</span>
|
|
1178
1362
|
<span class="mt-1 block font-mono text-[10px] uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
@@ -1806,16 +1990,21 @@ export function renderModal(state) {
|
|
|
1806
1990
|
title: 'Delete Backup',
|
|
1807
1991
|
body: renderDeleteBackupForm(modal),
|
|
1808
1992
|
},
|
|
1809
|
-
'edit-backup
|
|
1810
|
-
eyebrow: 'Backups //
|
|
1811
|
-
title: 'Edit Backup
|
|
1812
|
-
body:
|
|
1993
|
+
'edit-backup': {
|
|
1994
|
+
eyebrow: 'Backups // Edit',
|
|
1995
|
+
title: 'Edit Backup',
|
|
1996
|
+
body: renderEditBackupForm(modal),
|
|
1813
1997
|
},
|
|
1814
1998
|
'backup-safety': {
|
|
1815
1999
|
eyebrow: 'Backups // Safety check',
|
|
1816
2000
|
title: 'Create a safety backup?',
|
|
1817
2001
|
body: renderBackupSafetyForm(modal),
|
|
1818
2002
|
},
|
|
2003
|
+
'generate-types': {
|
|
2004
|
+
eyebrow: 'Structure // Type generation',
|
|
2005
|
+
title: 'Generate Types',
|
|
2006
|
+
body: renderGenerateTypesForm(modal),
|
|
2007
|
+
},
|
|
1819
2008
|
'edit-connection': {
|
|
1820
2009
|
eyebrow: 'Registry // Update saved SQLite target',
|
|
1821
2010
|
title: 'Edit Connection',
|
|
@@ -1906,23 +2095,27 @@ export function renderModal(state) {
|
|
|
1906
2095
|
|
|
1907
2096
|
return `
|
|
1908
2097
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-background/85 px-4 backdrop-blur-sm">
|
|
1909
|
-
<div class="w-full ${
|
|
2098
|
+
<div class="app-modal-shell w-full ${
|
|
1910
2099
|
modal.kind === 'chart-editor' ||
|
|
1911
2100
|
modal.kind === 'document-insert-table' ||
|
|
1912
2101
|
modal.kind === 'document-insert-note' ||
|
|
1913
2102
|
modal.kind === 'row-update-preview' ||
|
|
1914
2103
|
modal.kind === 'table-designer-constraints'
|
|
1915
2104
|
? 'max-w-3xl'
|
|
1916
|
-
: modal.kind === '
|
|
2105
|
+
: modal.kind === 'generate-types'
|
|
2106
|
+
? 'max-w-6xl'
|
|
2107
|
+
: modal.kind === 'query-export' ||
|
|
2108
|
+
modal.kind === 'data-export' ||
|
|
2109
|
+
modal.kind === 'backup-safety'
|
|
1917
2110
|
? 'max-w-4xl'
|
|
1918
2111
|
: 'max-w-xl'
|
|
1919
2112
|
} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
|
|
1920
|
-
<div class="flex items-start justify-between gap-4 border-b border-outline-variant/10 bg-surface-container-low px-6 py-5">
|
|
2113
|
+
<div class="shrink-0 flex items-start justify-between gap-4 border-b border-outline-variant/10 bg-surface-container-low px-6 py-5">
|
|
1921
2114
|
<div>
|
|
1922
2115
|
<div class="text-[10px] font-mono uppercase tracking-[0.26em] text-primary-container/70">
|
|
1923
2116
|
${escapeHtml(config.eyebrow)}
|
|
1924
2117
|
</div>
|
|
1925
|
-
<h2 class="mt-2 font-
|
|
2118
|
+
<h2 class="mt-2 font-body text-3xl font-black uppercase tracking-tight text-primary-container">
|
|
1926
2119
|
${escapeHtml(config.title)}
|
|
1927
2120
|
</h2>
|
|
1928
2121
|
</div>
|
|
@@ -1934,7 +2127,7 @@ export function renderModal(state) {
|
|
|
1934
2127
|
<span class="material-symbols-outlined">close</span>
|
|
1935
2128
|
</button>
|
|
1936
2129
|
</div>
|
|
1937
|
-
<div class="space-y-5 px-6 py-6">${config.body}</div>
|
|
2130
|
+
<div class="app-modal-body custom-scrollbar space-y-5 px-6 py-6">${config.body}</div>
|
|
1938
2131
|
</div>
|
|
1939
2132
|
</div>
|
|
1940
2133
|
`;
|
|
@@ -16,7 +16,7 @@ export function renderPageHeader({ eyebrow = '', title, subtitle = '', actions =
|
|
|
16
16
|
`
|
|
17
17
|
: ''
|
|
18
18
|
}
|
|
19
|
-
<h1 class="text-5xl font-
|
|
19
|
+
<h1 class="text-5xl font-body font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(title)}</h1>
|
|
20
20
|
${
|
|
21
21
|
subtitle
|
|
22
22
|
? `<p class="text-xs font-mono text-on-surface/40 mt-1 uppercase tracking-widest">${escapeHtml(
|
|
@@ -100,7 +100,7 @@ export function renderQueryHistoryDetail({
|
|
|
100
100
|
<section class="flex h-full min-h-0 flex-col bg-surface-low">
|
|
101
101
|
<div class="border-b border-outline-variant/10 px-5 py-4">
|
|
102
102
|
<div class="flex items-center justify-between gap-3">
|
|
103
|
-
<span class="font-
|
|
103
|
+
<span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
|
|
104
104
|
Query Detail
|
|
105
105
|
</span>
|
|
106
106
|
<button
|
|
@@ -124,7 +124,7 @@ export function renderQueryHistoryDetail({
|
|
|
124
124
|
<section class="flex h-full min-h-0 flex-col bg-surface-low">
|
|
125
125
|
<div class="border-b border-outline-variant/10 px-5 py-4">
|
|
126
126
|
<div class="flex items-center justify-between gap-3">
|
|
127
|
-
<span class="font-
|
|
127
|
+
<span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
|
|
128
128
|
Query Detail
|
|
129
129
|
</span>
|
|
130
130
|
<button
|
|
@@ -178,7 +178,7 @@ export function renderQueryHistoryDetail({
|
|
|
178
178
|
'<div class="border-b border-outline-variant/10 px-5 py-4">',
|
|
179
179
|
'<div class="flex items-center justify-between gap-3"><div>',
|
|
180
180
|
'<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">Query Detail</div>',
|
|
181
|
-
'<h2 class="mt-1 font-
|
|
181
|
+
'<h2 class="mt-1 font-body text-lg font-black uppercase tracking-tight text-on-surface">',
|
|
182
182
|
escapeHtml(item.displayTitle),
|
|
183
183
|
"</h2></div>",
|
|
184
184
|
'<button class="query-history-icon-button" data-action="clear-query-history-selection" type="button"><span class="material-symbols-outlined text-[18px]">close</span></button>',
|
|
@@ -11,7 +11,7 @@ export function renderQueryHistoryHeader({
|
|
|
11
11
|
<div class="flex items-center justify-between gap-3">
|
|
12
12
|
<div class="flex items-center gap-2">
|
|
13
13
|
<span class="material-symbols-outlined text-[18px] text-primary-container">${escapeHtml(icon)}</span>
|
|
14
|
-
<span class="font-
|
|
14
|
+
<span class="font-body text-xs font-black uppercase tracking-[0.18em] text-primary-container">
|
|
15
15
|
${escapeHtml(title)}
|
|
16
16
|
</span>
|
|
17
17
|
</div>
|
|
@@ -114,7 +114,7 @@ export function renderQueryHistoryPanel({
|
|
|
114
114
|
? `
|
|
115
115
|
<div class="flex h-full min-h-[240px] flex-col items-center justify-center px-6 text-center">
|
|
116
116
|
<span class="material-symbols-outlined text-4xl text-on-surface-variant/25">manage_search</span>
|
|
117
|
-
<p class="mt-4 font-
|
|
117
|
+
<p class="mt-4 font-body text-lg font-bold uppercase tracking-tight text-on-surface">
|
|
118
118
|
No Matching Queries
|
|
119
119
|
</p>
|
|
120
120
|
<p class="mt-2 max-w-xs text-sm leading-6 text-on-surface-variant/65">
|
|
@@ -176,6 +176,14 @@ function getFieldTimestampPreview(field = {}, tableMeta = {}) {
|
|
|
176
176
|
});
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
function isNumberInputCompatibleValue(value) {
|
|
180
|
+
if (value === null || value === undefined || value === "") {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return /^[-+]?(?:\d+|\d*\.\d+)(?:e[-+]?\d+)?$/i.test(String(value).trim());
|
|
185
|
+
}
|
|
186
|
+
|
|
179
187
|
function renderTimestampPreview(field = {}, tableMeta = {}) {
|
|
180
188
|
const preview = getFieldTimestampPreview(field, tableMeta);
|
|
181
189
|
|
|
@@ -361,7 +369,9 @@ function renderEditableField(field, tableMeta = {}) {
|
|
|
361
369
|
const baseBadges = withCheckBadge(Array.isArray(field.badges) ? field.badges : [], allowedValues);
|
|
362
370
|
const badges = withEmailBadge(withFilePathBadge(withUrlBadge(baseBadges, url), filePathPreview), email);
|
|
363
371
|
const jsonPreview = formatJsonPreview(field.value);
|
|
364
|
-
const
|
|
372
|
+
const canUseNumberInput =
|
|
373
|
+
field.inputType === "number" && isNumberInputCompatibleValue(getFieldRawValue(field));
|
|
374
|
+
const inputType = canUseNumberInput ? "number" : "text";
|
|
365
375
|
const numberStep = field.numberStep === "1" ? "1" : "any";
|
|
366
376
|
const valueState = getRowEditorValueState(getFieldRawValue(field));
|
|
367
377
|
|
|
@@ -577,7 +587,7 @@ export function renderRowEditorPanel({
|
|
|
577
587
|
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
578
588
|
${escapeHtml(sectionLabel)}
|
|
579
589
|
</div>
|
|
580
|
-
<h2 class="mt-2 font-
|
|
590
|
+
<h2 class="mt-2 font-body text-3xl font-black uppercase tracking-tight text-primary-container">
|
|
581
591
|
${escapeHtml(title)}
|
|
582
592
|
</h2>
|
|
583
593
|
${
|
|
@@ -647,7 +657,7 @@ export function renderRowEditorPanel({
|
|
|
647
657
|
saveError
|
|
648
658
|
? `
|
|
649
659
|
<div class="border border-error/20 bg-error-container/10 px-4 py-4 text-sm text-on-surface">
|
|
650
|
-
<div class="font-
|
|
660
|
+
<div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
|
|
651
661
|
${escapeHtml(saveError.code)}
|
|
652
662
|
</div>
|
|
653
663
|
<div class="mt-2">${escapeHtml(saveError.message)}</div>
|
package/frontend/js/router.js
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
function decodeRouteValue(value) {
|
|
2
|
+
try {
|
|
3
|
+
return decodeURIComponent(value);
|
|
4
|
+
} catch {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
export function parseHash(hash = window.location.hash) {
|
|
2
10
|
const normalized = hash.startsWith('#') ? hash.slice(1) : hash;
|
|
3
|
-
const
|
|
11
|
+
const queryIndex = normalized.indexOf('?');
|
|
12
|
+
const pathWithFragment = queryIndex >= 0 ? normalized.slice(0, queryIndex) : normalized;
|
|
13
|
+
const queryWithFragment = queryIndex >= 0 ? normalized.slice(queryIndex + 1) : '';
|
|
14
|
+
const pathFragmentIndex = pathWithFragment.indexOf('#');
|
|
15
|
+
const queryFragmentIndex = queryWithFragment.indexOf('#');
|
|
16
|
+
const pathname =
|
|
17
|
+
pathFragmentIndex >= 0 ? pathWithFragment.slice(0, pathFragmentIndex) : pathWithFragment || '/';
|
|
18
|
+
const routeFragment =
|
|
19
|
+
pathFragmentIndex >= 0
|
|
20
|
+
? pathWithFragment.slice(pathFragmentIndex + 1)
|
|
21
|
+
: queryFragmentIndex >= 0
|
|
22
|
+
? queryWithFragment.slice(queryFragmentIndex + 1)
|
|
23
|
+
: null;
|
|
4
24
|
const cleanPath = pathname || '/';
|
|
5
25
|
const segments = cleanPath.split('/').filter(Boolean);
|
|
26
|
+
const decodedRouteFragment = routeFragment ? decodeRouteValue(routeFragment) : null;
|
|
6
27
|
|
|
7
28
|
if (segments.length === 0) {
|
|
8
29
|
return { name: 'landing', path: '/', params: {} };
|
|
@@ -20,7 +41,7 @@ export function parseHash(hash = window.location.hash) {
|
|
|
20
41
|
name: 'charts',
|
|
21
42
|
path: cleanPath,
|
|
22
43
|
params: {
|
|
23
|
-
historyId: segments[1] ?
|
|
44
|
+
historyId: segments[1] ? decodeRouteValue(segments[1]) : null,
|
|
24
45
|
},
|
|
25
46
|
};
|
|
26
47
|
case 'documents':
|
|
@@ -28,7 +49,7 @@ export function parseHash(hash = window.location.hash) {
|
|
|
28
49
|
name: 'documents',
|
|
29
50
|
path: cleanPath,
|
|
30
51
|
params: {
|
|
31
|
-
documentId: segments[1] ?
|
|
52
|
+
documentId: segments[1] ? decodeRouteValue(segments[1]) : null,
|
|
32
53
|
},
|
|
33
54
|
};
|
|
34
55
|
case 'editor':
|
|
@@ -42,7 +63,8 @@ export function parseHash(hash = window.location.hash) {
|
|
|
42
63
|
name: 'data',
|
|
43
64
|
path: cleanPath,
|
|
44
65
|
params: {
|
|
45
|
-
tableName: segments[1] ?
|
|
66
|
+
tableName: segments[1] ? decodeRouteValue(segments[1]) : null,
|
|
67
|
+
rowPrimaryKey: decodedRouteFragment,
|
|
46
68
|
},
|
|
47
69
|
};
|
|
48
70
|
case 'structure':
|
|
@@ -50,7 +72,7 @@ export function parseHash(hash = window.location.hash) {
|
|
|
50
72
|
name: 'structure',
|
|
51
73
|
path: cleanPath,
|
|
52
74
|
params: {
|
|
53
|
-
tableName: segments[1] ?
|
|
75
|
+
tableName: segments[1] ? decodeRouteValue(segments[1]) : null,
|
|
54
76
|
},
|
|
55
77
|
};
|
|
56
78
|
case 'table-designer':
|
|
@@ -59,7 +81,7 @@ export function parseHash(hash = window.location.hash) {
|
|
|
59
81
|
path: cleanPath,
|
|
60
82
|
params: {
|
|
61
83
|
isNew: segments[1] === 'new',
|
|
62
|
-
tableName: segments[1] && segments[1] !== 'new' ?
|
|
84
|
+
tableName: segments[1] && segments[1] !== 'new' ? decodeRouteValue(segments[1]) : null,
|
|
63
85
|
},
|
|
64
86
|
};
|
|
65
87
|
case 'media-tagging':
|