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.
Files changed (45) hide show
  1. package/changelog.md +25 -0
  2. package/docs/DESIGN_GUIDELINES.md +36 -0
  3. package/frontend/index.html +79 -0
  4. package/frontend/js/api.js +67 -0
  5. package/frontend/js/app.js +1401 -921
  6. package/frontend/js/components/connectionCard.js +3 -4
  7. package/frontend/js/components/emptyState.js +4 -4
  8. package/frontend/js/components/modal.js +239 -30
  9. package/frontend/js/components/queryEditor.js +11 -8
  10. package/frontend/js/components/queryHistoryDetail.js +27 -8
  11. package/frontend/js/components/queryHistoryPanel.js +6 -5
  12. package/frontend/js/components/rowEditorPanel.js +84 -58
  13. package/frontend/js/components/sidebar.js +76 -33
  14. package/frontend/js/components/structureGraph.js +629 -715
  15. package/frontend/js/components/tableDesignerEditor.js +9 -8
  16. package/frontend/js/components/tableDesignerSidebar.js +11 -10
  17. package/frontend/js/components/tableDesignerSqlPreview.js +61 -30
  18. package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
  19. package/frontend/js/router.js +81 -71
  20. package/frontend/js/store.js +3095 -2165
  21. package/frontend/js/views/charts.js +68 -40
  22. package/frontend/js/views/connections.js +5 -17
  23. package/frontend/js/views/data.js +40 -27
  24. package/frontend/js/views/editor.js +172 -177
  25. package/frontend/js/views/mediaTagging.js +861 -0
  26. package/frontend/js/views/overview.js +149 -10
  27. package/frontend/js/views/settings.js +2 -2
  28. package/frontend/js/views/structure.js +74 -70
  29. package/frontend/js/views/tableDesigner.js +7 -2
  30. package/frontend/styles/base.css +73 -1
  31. package/frontend/styles/components.css +105 -105
  32. package/frontend/styles/structure-graph.css +19 -82
  33. package/frontend/styles/tokens.css +2 -0
  34. package/frontend/styles/views.css +823 -30
  35. package/package.json +1 -1
  36. package/server/routes/charts.js +4 -1
  37. package/server/routes/data.js +14 -0
  38. package/server/routes/mediaTagging.js +166 -0
  39. package/server/routes/sql.js +1 -0
  40. package/server/server.js +4 -0
  41. package/server/services/sqlite/dataBrowserService.js +25 -0
  42. package/server/services/sqlite/exportService.js +31 -1
  43. package/server/services/sqlite/mediaTaggingService.js +1689 -0
  44. package/server/services/sqlite/overviewService.js +68 -0
  45. 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="table-designer-delete"
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="table-designer-main__secondary"
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="table-designer-main__primary ${draft.canSave ? "" : "is-disabled"}"
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="table-designer-main__secondary"
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="table-designer-sidebar__new-button"
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 "../utils/format.js";
1
+ import { escapeHtml } from '../utils/format.js';
2
2
 
3
- export function renderTableDesignerSqlPreview(draft) {
4
- if (!draft) {
3
+ function renderPreviewHeader(draft, isVisible) {
5
4
  return `
6
- <section class="table-designer-preview shell-section">
7
- <div class="table-designer-preview__header">
8
- <div>
9
- <div class="table-designer-preview__eyebrow">SQL Preview</div>
10
- <div class="table-designer-preview__title">No Draft Selected</div>
11
- </div>
12
- </div>
13
- <div class="table-designer-preview__empty">
14
- Select a table or create a new one to inspect the generated SQLite statements.
15
- </div>
16
- </section>
17
- `;
18
- }
19
-
20
- return `
21
- <section class="table-designer-preview shell-section">
22
- <div class="table-designer-preview__header">
23
- <div>
24
- <div class="table-designer-preview__eyebrow">SQL Preview</div>
25
- <div class="table-designer-preview__title">Live SQLite Output</div>
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="table-designer-preview__copy"
29
- data-action="copy-table-designer-sql"
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
- Copy SQL
31
+ <span class="material-symbols-outlined">${isVisible ? 'visibility_off' : 'expand_less'}</span>
32
+ ${isVisible ? 'Hide' : 'Show'}
33
33
  </button>
34
34
  </div>
35
- <pre class="table-designer-preview__body custom-scrollbar">${escapeHtml(
36
- draft.sqlPreview || "-- SQL preview unavailable."
37
- )}</pre>
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
+ }
@@ -1,84 +1,94 @@
1
1
  export function parseHash(hash = window.location.hash) {
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);
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
- if (segments.length === 0) {
8
- return { name: "landing", path: "/", params: {} };
9
- }
7
+ if (segments.length === 0) {
8
+ return { name: 'landing', path: '/', params: {} };
9
+ }
10
10
 
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
- }
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
- 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 { name: "structure", path: "/structure", params: {} };
40
- case "table-designer":
41
- return {
42
- name: "tableDesigner",
43
- path: cleanPath,
44
- params: {
45
- isNew: segments[1] === "new",
46
- tableName:
47
- segments[1] && segments[1] !== "new" ? decodeURIComponent(segments[1]) : null,
48
- },
49
- };
50
- case "settings":
51
- return { name: "settings", path: "/settings", params: {} };
52
- default:
53
- return { name: "notFound", path: cleanPath, params: {} };
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
- const handleRouteChange = () => {
59
- onRouteChange(parseHash(window.location.hash));
60
- };
68
+ const handleRouteChange = () => {
69
+ onRouteChange(parseHash(window.location.hash));
70
+ };
61
71
 
62
- return {
63
- start() {
64
- window.addEventListener("hashchange", handleRouteChange);
72
+ return {
73
+ start() {
74
+ window.addEventListener('hashchange', handleRouteChange);
65
75
 
66
- if (!window.location.hash) {
67
- window.location.hash = "#/";
68
- return;
69
- }
76
+ if (!window.location.hash) {
77
+ window.location.hash = '#/';
78
+ return;
79
+ }
70
80
 
71
- handleRouteChange();
72
- },
73
- navigate(path) {
74
- const nextHash = path.startsWith("#") ? path : `#${path}`;
81
+ handleRouteChange();
82
+ },
83
+ navigate(path) {
84
+ const nextHash = path.startsWith('#') ? path : `#${path}`;
75
85
 
76
- if (window.location.hash === nextHash) {
77
- handleRouteChange();
78
- return;
79
- }
86
+ if (window.location.hash === nextHash) {
87
+ handleRouteChange();
88
+ return;
89
+ }
80
90
 
81
- window.location.hash = nextHash;
82
- },
83
- };
91
+ window.location.hash = nextHash;
92
+ },
93
+ };
84
94
  }