sqlite-hub 0.7.0 → 0.9.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/changelog.md +25 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/index.html +79 -0
- package/frontend/js/api.js +67 -0
- package/frontend/js/app.js +1401 -921
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +239 -30
- package/frontend/js/components/queryEditor.js +11 -8
- package/frontend/js/components/queryHistoryDetail.js +27 -8
- package/frontend/js/components/queryHistoryPanel.js +6 -5
- package/frontend/js/components/rowEditorPanel.js +84 -58
- package/frontend/js/components/sidebar.js +76 -33
- package/frontend/js/components/structureGraph.js +629 -715
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +11 -10
- package/frontend/js/components/tableDesignerSqlPreview.js +61 -30
- package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
- package/frontend/js/router.js +81 -71
- package/frontend/js/store.js +3095 -2165
- package/frontend/js/views/charts.js +68 -40
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +40 -27
- package/frontend/js/views/editor.js +172 -177
- package/frontend/js/views/mediaTagging.js +861 -0
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +74 -70
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +73 -1
- package/frontend/styles/components.css +105 -105
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +823 -30
- package/package.json +1 -1
- package/server/routes/charts.js +4 -1
- package/server/routes/data.js +14 -0
- package/server/routes/mediaTagging.js +166 -0
- package/server/routes/sql.js +1 -0
- package/server/server.js +4 -0
- package/server/services/sqlite/dataBrowserService.js +25 -0
- package/server/services/sqlite/exportService.js +31 -1
- package/server/services/sqlite/mediaTaggingService.js +1689 -0
- package/server/services/sqlite/overviewService.js +68 -0
- package/server/services/storage/appStateStore.js +321 -2
|
@@ -86,7 +86,7 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
86
86
|
)
|
|
87
87
|
.join("")}
|
|
88
88
|
</select>
|
|
89
|
-
<label class="table-designer-check">
|
|
89
|
+
<label class="standard-checkbox table-designer-check table-designer-checkbox-override">
|
|
90
90
|
<input
|
|
91
91
|
data-bind="table-designer-column-flag"
|
|
92
92
|
data-column-id="${escapeHtml(column.id)}"
|
|
@@ -96,7 +96,7 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
96
96
|
/>
|
|
97
97
|
<span>Not null</span>
|
|
98
98
|
</label>
|
|
99
|
-
<label class="table-designer-check">
|
|
99
|
+
<label class="standard-checkbox table-designer-check table-designer-checkbox-override">
|
|
100
100
|
<input
|
|
101
101
|
data-bind="table-designer-column-flag"
|
|
102
102
|
data-column-id="${escapeHtml(column.id)}"
|
|
@@ -106,7 +106,7 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
106
106
|
/>
|
|
107
107
|
<span>Unique</span>
|
|
108
108
|
</label>
|
|
109
|
-
<label class="table-designer-check">
|
|
109
|
+
<label class="standard-checkbox table-designer-check table-designer-checkbox-override">
|
|
110
110
|
<input
|
|
111
111
|
data-bind="table-designer-column-flag"
|
|
112
112
|
data-column-id="${escapeHtml(column.id)}"
|
|
@@ -164,12 +164,13 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
164
164
|
.join("")}
|
|
165
165
|
</select>
|
|
166
166
|
<button
|
|
167
|
-
class="
|
|
167
|
+
class="delete-button"
|
|
168
168
|
data-action="remove-table-designer-column"
|
|
169
169
|
data-column-id="${escapeHtml(column.id)}"
|
|
170
170
|
type="button"
|
|
171
171
|
>
|
|
172
172
|
<span class="material-symbols-outlined text-base">delete</span>
|
|
173
|
+
<span>Remove</span>
|
|
173
174
|
</button>
|
|
174
175
|
</div>
|
|
175
176
|
`;
|
|
@@ -204,7 +205,7 @@ function renderFillToggle(draft) {
|
|
|
204
205
|
const hasImportedRows = (draft.importedCsvRows?.length ?? 0) > 0;
|
|
205
206
|
|
|
206
207
|
return `
|
|
207
|
-
<label class="table-designer-fill-toggle ${hasImportedRows ? "" : "is-disabled"}">
|
|
208
|
+
<label class="standard-checkbox table-designer-fill-toggle table-designer-checkbox-override ${hasImportedRows ? "" : "is-disabled"}">
|
|
208
209
|
<input
|
|
209
210
|
data-bind="table-designer-field"
|
|
210
211
|
data-field="fillImportedRows"
|
|
@@ -301,14 +302,14 @@ export function renderTableDesignerEditor(state) {
|
|
|
301
302
|
<div class="table-designer-main__actions">
|
|
302
303
|
${renderFillToggle(draft)}
|
|
303
304
|
<button
|
|
304
|
-
class="
|
|
305
|
+
class="standard-button"
|
|
305
306
|
data-action="refresh-view"
|
|
306
307
|
type="button"
|
|
307
308
|
>
|
|
308
309
|
Reload Schema
|
|
309
310
|
</button>
|
|
310
311
|
<button
|
|
311
|
-
class="
|
|
312
|
+
class="standard-button"
|
|
312
313
|
data-action="save-table-designer"
|
|
313
314
|
${draft.canSave ? "" : "disabled"}
|
|
314
315
|
type="button"
|
|
@@ -342,7 +343,7 @@ export function renderTableDesignerEditor(state) {
|
|
|
342
343
|
<div class="table-designer-main__section-title">Columns</div>
|
|
343
344
|
</div>
|
|
344
345
|
<button
|
|
345
|
-
class="
|
|
346
|
+
class="standard-button"
|
|
346
347
|
data-action="add-table-designer-column"
|
|
347
348
|
type="button"
|
|
348
349
|
>
|
|
@@ -28,15 +28,7 @@ export function renderTableDesignerSidebar(state) {
|
|
|
28
28
|
</div>
|
|
29
29
|
<div class="table-designer-sidebar__header-actions">
|
|
30
30
|
<button
|
|
31
|
-
class="
|
|
32
|
-
data-action="navigate"
|
|
33
|
-
data-to="/table-designer/new"
|
|
34
|
-
type="button"
|
|
35
|
-
>
|
|
36
|
-
+ New Table
|
|
37
|
-
</button>
|
|
38
|
-
<button
|
|
39
|
-
class="table-designer-sidebar__import-button"
|
|
31
|
+
class="standard-button"
|
|
40
32
|
data-action="import-table-designer-csv"
|
|
41
33
|
type="button"
|
|
42
34
|
>
|
|
@@ -47,7 +39,16 @@ export function renderTableDesignerSidebar(state) {
|
|
|
47
39
|
class="table-designer-sidebar__file-input"
|
|
48
40
|
data-bind="table-designer-import-file"
|
|
49
41
|
type="file"
|
|
50
|
-
/>
|
|
42
|
+
/>
|
|
43
|
+
<button
|
|
44
|
+
class="signature-button"
|
|
45
|
+
data-action="navigate"
|
|
46
|
+
data-to="/table-designer/new"
|
|
47
|
+
type="button"
|
|
48
|
+
>
|
|
49
|
+
+ New Table
|
|
50
|
+
</button>
|
|
51
|
+
|
|
51
52
|
</div>
|
|
52
53
|
</div>
|
|
53
54
|
|
|
@@ -1,40 +1,71 @@
|
|
|
1
|
-
import { escapeHtml } from
|
|
1
|
+
import { escapeHtml } from '../utils/format.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
if (!draft) {
|
|
3
|
+
function renderPreviewHeader(draft, isVisible) {
|
|
5
4
|
return `
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
</div>
|
|
5
|
+
<div class="table-designer-preview__header">
|
|
6
|
+
<div>
|
|
7
|
+
<div class="table-designer-preview__eyebrow">SQL Preview</div>
|
|
8
|
+
<div class="table-designer-preview__title">${draft ? 'Live SQLite Output' : 'No Draft Selected'}</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="table-designer-preview__actions">
|
|
11
|
+
${
|
|
12
|
+
draft && isVisible
|
|
13
|
+
? `
|
|
14
|
+
<button
|
|
15
|
+
class="standard-button"
|
|
16
|
+
data-action="copy-table-designer-sql"
|
|
17
|
+
type="button"
|
|
18
|
+
>
|
|
19
|
+
<span class="material-symbols-outlined">content_copy</span>
|
|
20
|
+
Copy SQL to clipboard
|
|
21
|
+
</button>
|
|
22
|
+
`
|
|
23
|
+
: ''
|
|
24
|
+
}
|
|
27
25
|
<button
|
|
28
|
-
class="
|
|
29
|
-
data-action="
|
|
26
|
+
class="standard-button"
|
|
27
|
+
data-action="toggle-table-designer-sql-preview"
|
|
28
|
+
data-next-value="${isVisible ? 'false' : 'true'}"
|
|
30
29
|
type="button"
|
|
31
30
|
>
|
|
32
|
-
|
|
31
|
+
<span class="material-symbols-outlined">${isVisible ? 'visibility_off' : 'expand_less'}</span>
|
|
32
|
+
${isVisible ? 'Hide' : 'Show'}
|
|
33
33
|
</button>
|
|
34
34
|
</div>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function renderTableDesignerSqlPreview(draft, isVisible = true) {
|
|
40
|
+
if (!draft) {
|
|
41
|
+
return `
|
|
42
|
+
<section class="table-designer-preview shell-section${isVisible ? '' : ' is-collapsed'}">
|
|
43
|
+
${renderPreviewHeader(draft, isVisible)}
|
|
44
|
+
${
|
|
45
|
+
isVisible
|
|
46
|
+
? `
|
|
47
|
+
<div class="table-designer-preview__empty">
|
|
48
|
+
Select a table or create a new one to inspect the generated SQLite statements.
|
|
49
|
+
</div>
|
|
50
|
+
`
|
|
51
|
+
: ''
|
|
52
|
+
}
|
|
53
|
+
</section>
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return `
|
|
58
|
+
<section class="table-designer-preview shell-section${isVisible ? '' : ' is-collapsed'}">
|
|
59
|
+
${renderPreviewHeader(draft, isVisible)}
|
|
60
|
+
${
|
|
61
|
+
isVisible
|
|
62
|
+
? `
|
|
63
|
+
<pre class="table-designer-preview__body custom-scrollbar">${escapeHtml(
|
|
64
|
+
draft.sqlPreview || '-- SQL preview unavailable.',
|
|
65
|
+
)}</pre>
|
|
66
|
+
`
|
|
67
|
+
: ''
|
|
68
|
+
}
|
|
38
69
|
</section>
|
|
39
70
|
`;
|
|
40
71
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const MEDIA_TAGGING_DEFAULT_TAG_TABLE = 'media_tags';
|
|
2
|
+
export const MEDIA_TAGGING_DEFAULT_MAPPING_TABLE = 'media_asset_tags';
|
|
3
|
+
export const MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL = [
|
|
4
|
+
'CREATE TABLE "media_tags" (',
|
|
5
|
+
' id INTEGER PRIMARY KEY,',
|
|
6
|
+
' name TEXT NOT NULL UNIQUE,',
|
|
7
|
+
' isParentTag INTEGER NOT NULL DEFAULT 0,',
|
|
8
|
+
' parentTagId INTEGER REFERENCES media_tags(id) ON DELETE SET NULL',
|
|
9
|
+
')',
|
|
10
|
+
].join('\n');
|
|
11
|
+
export const MEDIA_TAGGING_DEFAULT_MAPPING_TABLE_SQL = [
|
|
12
|
+
'CREATE TABLE media_asset_tags (',
|
|
13
|
+
' media_asset_id INTEGER NOT NULL,',
|
|
14
|
+
' media_tag_id INTEGER NOT NULL,',
|
|
15
|
+
' PRIMARY KEY (media_asset_id, media_tag_id),',
|
|
16
|
+
' FOREIGN KEY (media_asset_id) REFERENCES media_assets(id) ON DELETE CASCADE,',
|
|
17
|
+
' FOREIGN KEY (media_tag_id) REFERENCES media_tags(id) ON DELETE CASCADE',
|
|
18
|
+
')',
|
|
19
|
+
].join('\n');
|
|
20
|
+
|
|
21
|
+
export function hasDefaultMediaTaggingTagTable(schemaTables = []) {
|
|
22
|
+
return schemaTables.some(table => String(table?.name ?? '').trim() === MEDIA_TAGGING_DEFAULT_TAG_TABLE);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function hasDefaultMediaTaggingMappingTable(schemaTables = []) {
|
|
26
|
+
return schemaTables.some(table => String(table?.name ?? '').trim() === MEDIA_TAGGING_DEFAULT_MAPPING_TABLE);
|
|
27
|
+
}
|
package/frontend/js/router.js
CHANGED
|
@@ -1,84 +1,94 @@
|
|
|
1
1
|
export function parseHash(hash = window.location.hash) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const normalized = hash.startsWith('#') ? hash.slice(1) : hash;
|
|
3
|
+
const [pathname = '/'] = normalized.split('?');
|
|
4
|
+
const cleanPath = pathname || '/';
|
|
5
|
+
const segments = cleanPath.split('/').filter(Boolean);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
if (segments.length === 0) {
|
|
8
|
+
return { name: 'landing', path: '/', params: {} };
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
switch (segments[0]) {
|
|
12
|
+
case 'connections':
|
|
13
|
+
return { name: 'connections', path: '/connections', params: {} };
|
|
14
|
+
case 'overview':
|
|
15
|
+
return { name: 'overview', path: '/overview', params: {} };
|
|
16
|
+
case 'charts':
|
|
17
|
+
return {
|
|
18
|
+
name: 'charts',
|
|
19
|
+
path: cleanPath,
|
|
20
|
+
params: {
|
|
21
|
+
historyId: segments[1] ? decodeURIComponent(segments[1]) : null,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
case 'editor':
|
|
25
|
+
if (segments[1] === 'results') {
|
|
26
|
+
return { name: 'editorResults', path: '/editor/results', params: {} };
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
return { name: 'editor', path: '/editor', params: {} };
|
|
30
|
+
case 'data':
|
|
31
|
+
return {
|
|
32
|
+
name: 'data',
|
|
33
|
+
path: cleanPath,
|
|
34
|
+
params: {
|
|
35
|
+
tableName: segments[1] ? decodeURIComponent(segments[1]) : null,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
case 'structure':
|
|
39
|
+
return {
|
|
40
|
+
name: 'structure',
|
|
41
|
+
path: cleanPath,
|
|
42
|
+
params: {
|
|
43
|
+
tableName: segments[1] ? decodeURIComponent(segments[1]) : null,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
case 'table-designer':
|
|
47
|
+
return {
|
|
48
|
+
name: 'tableDesigner',
|
|
49
|
+
path: cleanPath,
|
|
50
|
+
params: {
|
|
51
|
+
isNew: segments[1] === 'new',
|
|
52
|
+
tableName: segments[1] && segments[1] !== 'new' ? decodeURIComponent(segments[1]) : null,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
case 'media-tagging':
|
|
56
|
+
if (segments[1] === 'queue') {
|
|
57
|
+
return { name: 'mediaTaggingQueue', path: '/media-tagging/queue', params: {} };
|
|
58
|
+
}
|
|
59
|
+
return { name: 'mediaTaggingSetup', path: '/media-tagging', params: {} };
|
|
60
|
+
case 'settings':
|
|
61
|
+
return { name: 'settings', path: '/settings', params: {} };
|
|
62
|
+
default:
|
|
63
|
+
return { name: 'notFound', path: cleanPath, params: {} };
|
|
64
|
+
}
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
export function createRouter(onRouteChange) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
const handleRouteChange = () => {
|
|
69
|
+
onRouteChange(parseHash(window.location.hash));
|
|
70
|
+
};
|
|
61
71
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
return {
|
|
73
|
+
start() {
|
|
74
|
+
window.addEventListener('hashchange', handleRouteChange);
|
|
65
75
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
if (!window.location.hash) {
|
|
77
|
+
window.location.hash = '#/';
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
handleRouteChange();
|
|
82
|
+
},
|
|
83
|
+
navigate(path) {
|
|
84
|
+
const nextHash = path.startsWith('#') ? path : `#${path}`;
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
if (window.location.hash === nextHash) {
|
|
87
|
+
handleRouteChange();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
window.location.hash = nextHash;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
84
94
|
}
|