sqlite-hub 0.9.0 → 0.9.3
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 +12 -0
- package/frontend/index.html +21 -20
- package/frontend/js/app.js +386 -8
- package/frontend/js/components/modal.js +13 -4
- package/frontend/js/components/queryHistoryDetail.js +14 -2
- package/frontend/js/components/queryHistoryPanel.js +13 -9
- package/frontend/js/components/rowEditorPanel.js +48 -25
- package/frontend/js/lib/queryChartOptions.js +5 -1
- package/frontend/js/lib/queryCharts.js +50 -2
- package/frontend/js/store.js +177 -21
- package/frontend/js/views/charts.js +103 -30
- package/frontend/js/views/data.js +31 -1
- package/frontend/js/views/editor.js +1 -0
- package/frontend/js/views/mediaTagging.js +146 -76
- package/frontend/styles/base.css +57 -13
- package/frontend/styles/components.css +172 -96
- package/frontend/styles/layout.css +1 -1
- package/frontend/styles/structure-graph.css +11 -11
- package/frontend/styles/tokens.css +47 -4
- package/frontend/styles/utilities.css +21 -0
- package/frontend/styles/views.css +218 -96
- package/package.json +1 -1
- package/server/services/storage/appStateStore.js +9 -6
- package/server/services/storage/queryHistoryChartUtils.js +19 -2
- package/server/services/storage/queryHistoryUtils.js +165 -2
- package/shortkeys.md +5 -0
|
@@ -50,15 +50,7 @@ function renderReadonlyField(label, value) {
|
|
|
50
50
|
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3">
|
|
51
51
|
<div class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
52
52
|
<span>${escapeHtml(displayLabel)}</span>
|
|
53
|
-
${badges
|
|
54
|
-
.map(
|
|
55
|
-
(badge) => `
|
|
56
|
-
<span class="border border-outline-variant/20 bg-surface-container px-2 py-1 text-[9px] text-primary-container">
|
|
57
|
-
${escapeHtml(badge)}
|
|
58
|
-
</span>
|
|
59
|
-
`
|
|
60
|
-
)
|
|
61
|
-
.join("")}
|
|
53
|
+
${badges.map((badge) => renderFieldBadge(badge)).join("")}
|
|
62
54
|
</div>
|
|
63
55
|
${
|
|
64
56
|
jsonPreview
|
|
@@ -72,37 +64,68 @@ function renderReadonlyField(label, value) {
|
|
|
72
64
|
function renderEditableField(field) {
|
|
73
65
|
const badges = Array.isArray(field.badges) ? field.badges : [];
|
|
74
66
|
const jsonPreview = getJsonPreview(field.value);
|
|
67
|
+
const inputType = field.inputType === "number" ? "number" : "text";
|
|
68
|
+
const numberStep = field.numberStep === "1" ? "1" : "any";
|
|
75
69
|
|
|
76
70
|
return `
|
|
77
71
|
<label class="block space-y-2">
|
|
78
72
|
<span class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
79
73
|
<span>${escapeHtml(field.label ?? field.name)}</span>
|
|
80
|
-
${badges
|
|
81
|
-
.map(
|
|
82
|
-
(badge) => `
|
|
83
|
-
<span class="border border-outline-variant/20 bg-surface-container px-2 py-1 text-[9px] text-primary-container">
|
|
84
|
-
${escapeHtml(badge)}
|
|
85
|
-
</span>
|
|
86
|
-
`
|
|
87
|
-
)
|
|
88
|
-
.join("")}
|
|
74
|
+
${badges.map((badge) => renderFieldBadge(badge)).join("")}
|
|
89
75
|
</span>
|
|
90
76
|
${
|
|
91
77
|
jsonPreview
|
|
92
78
|
? renderJsonViewer(jsonPreview, "JSON Preview")
|
|
93
79
|
: ""
|
|
94
80
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
81
|
+
${
|
|
82
|
+
inputType === "number" && !jsonPreview
|
|
83
|
+
? `
|
|
84
|
+
<input
|
|
85
|
+
class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
86
|
+
name="field:${escapeHtml(field.name)}"
|
|
87
|
+
step="${escapeHtml(numberStep)}"
|
|
88
|
+
type="number"
|
|
89
|
+
value="${escapeHtml(field.value ?? "")}"
|
|
90
|
+
/>
|
|
91
|
+
`
|
|
92
|
+
: `
|
|
93
|
+
<textarea
|
|
94
|
+
class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container ${
|
|
95
|
+
jsonPreview ? "min-h-[14rem] font-mono leading-6" : "min-h-[56px]"
|
|
96
|
+
}"
|
|
97
|
+
name="field:${escapeHtml(field.name)}"
|
|
98
|
+
spellcheck="false"
|
|
99
|
+
>${escapeHtml(field.value ?? "")}</textarea>
|
|
100
|
+
`
|
|
101
|
+
}
|
|
102
102
|
</label>
|
|
103
103
|
`;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
function getFieldBadgeClassName(tone) {
|
|
107
|
+
if (tone === "primary-key") {
|
|
108
|
+
return "border-primary-container/35 bg-primary-container/15 text-primary-container";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (tone === "foreign-key") {
|
|
112
|
+
return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return "border-outline-variant/20 bg-surface-container text-on-surface-variant";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function renderFieldBadge(badge) {
|
|
119
|
+
const label = typeof badge === "object" ? badge.label : badge;
|
|
120
|
+
const tone = typeof badge === "object" ? badge.tone : "";
|
|
121
|
+
|
|
122
|
+
return `
|
|
123
|
+
<span class="border px-2 py-1 text-[9px] ${getFieldBadgeClassName(tone)}">
|
|
124
|
+
${escapeHtml(label)}
|
|
125
|
+
</span>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
export function renderRowEditorPanel({
|
|
107
130
|
title,
|
|
108
131
|
sectionLabel = "Row Editor",
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
formatQueryChartAxisValue,
|
|
6
6
|
getAnalysisColumn,
|
|
7
7
|
sortQueryChartRows,
|
|
8
|
+
sortQueryChartRowsByNumericColumn,
|
|
8
9
|
} from "./queryCharts.js";
|
|
9
10
|
|
|
10
11
|
const CHART_PALETTE = ["#FCE300", "#2DFAFF", "#FFB4AB", "#CDC7AB", "#7DD3FC", "#86EFAC"];
|
|
@@ -55,7 +56,10 @@ function buildLineLabelConfig(enabled) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function buildBarChartOption(chart, rows) {
|
|
58
|
-
const sortedRows =
|
|
59
|
+
const sortedRows =
|
|
60
|
+
chart.config.sort_by === "y"
|
|
61
|
+
? sortQueryChartRowsByNumericColumn(rows, chart.config.y_column, chart.config.sort_direction)
|
|
62
|
+
: sortQueryChartRows(rows, chart.config.x_column, chart.config.sort_direction);
|
|
59
63
|
|
|
60
64
|
return {
|
|
61
65
|
...buildCommonOption(),
|
|
@@ -312,7 +312,43 @@ function getFirstColumn(columns, predicate) {
|
|
|
312
312
|
|
|
313
313
|
function getNextNumericColumn(analysis, excludedNames = []) {
|
|
314
314
|
const excluded = new Set(excludedNames);
|
|
315
|
-
|
|
315
|
+
const candidates = (analysis?.numberColumns ?? []).filter((column) => !excluded.has(column.name));
|
|
316
|
+
|
|
317
|
+
if (!candidates.length) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const scoredCandidates = candidates
|
|
322
|
+
.map((column, index) => ({
|
|
323
|
+
column,
|
|
324
|
+
index,
|
|
325
|
+
score: getNumericMeasureScore(column.name),
|
|
326
|
+
}))
|
|
327
|
+
.sort((left, right) => right.score - left.score || left.index - right.index);
|
|
328
|
+
|
|
329
|
+
return scoredCandidates[0]?.column ?? null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function getNumericMeasureScore(columnName = "") {
|
|
333
|
+
const normalized = String(columnName ?? "")
|
|
334
|
+
.trim()
|
|
335
|
+
.toLowerCase();
|
|
336
|
+
|
|
337
|
+
let score = 0;
|
|
338
|
+
|
|
339
|
+
if (
|
|
340
|
+
/(count|total|sum|avg|average|amount|score|size|value|price|cost|rate|percent|pct|percentage|number)/.test(
|
|
341
|
+
normalized
|
|
342
|
+
)
|
|
343
|
+
) {
|
|
344
|
+
score += 20;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (/(^|_)(id|uuid|pk|key)$/.test(normalized) || /(^|_)id$/.test(normalized)) {
|
|
348
|
+
score -= 40;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return score;
|
|
316
352
|
}
|
|
317
353
|
|
|
318
354
|
function getPrimaryDimensionColumn(analysis) {
|
|
@@ -406,7 +442,8 @@ export function buildSuggestedChartConfig(chartType, analysis) {
|
|
|
406
442
|
y_column: firstNumeric?.name ?? "",
|
|
407
443
|
show_legend: true,
|
|
408
444
|
show_labels: false,
|
|
409
|
-
|
|
445
|
+
sort_by: "y",
|
|
446
|
+
sort_direction: "desc",
|
|
410
447
|
};
|
|
411
448
|
}
|
|
412
449
|
}
|
|
@@ -524,6 +561,17 @@ export function sortQueryChartRows(rows, columnName, direction = "asc") {
|
|
|
524
561
|
);
|
|
525
562
|
}
|
|
526
563
|
|
|
564
|
+
export function sortQueryChartRowsByNumericColumn(rows, columnName, direction = "asc") {
|
|
565
|
+
const multiplier = direction === "desc" ? -1 : 1;
|
|
566
|
+
|
|
567
|
+
return [...(rows ?? [])].sort((left, right) => {
|
|
568
|
+
const leftValue = coerceNumericChartValue(left?.[columnName]);
|
|
569
|
+
const rightValue = coerceNumericChartValue(right?.[columnName]);
|
|
570
|
+
|
|
571
|
+
return compareValues(leftValue, rightValue) * multiplier;
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
527
575
|
export function formatQueryChartAxisValue(value) {
|
|
528
576
|
if (value === null || value === undefined) {
|
|
529
577
|
return "NULL";
|
package/frontend/js/store.js
CHANGED
|
@@ -29,7 +29,9 @@ const DEFAULT_SETTINGS = {
|
|
|
29
29
|
maxPageSize: 200,
|
|
30
30
|
csvDelimiter: ',',
|
|
31
31
|
};
|
|
32
|
+
const DEFAULT_DATA_PAGE_SIZE = 50;
|
|
32
33
|
const DATA_PAGE_SIZES = [25, 50, 100];
|
|
34
|
+
const DATA_ROW_SIZE_STORAGE_KEY = 'data_row_size';
|
|
33
35
|
const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
34
36
|
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
35
37
|
const CHART_HEIGHT_PRESETS = new Set(['small', 'medium', 'large']);
|
|
@@ -46,6 +48,22 @@ let chartsLoadVersion = 0;
|
|
|
46
48
|
let chartsDetailLoadVersion = 0;
|
|
47
49
|
let mediaTaggingPreviewVersion = 0;
|
|
48
50
|
|
|
51
|
+
function readStoredDataPageSize(fallback = DEFAULT_DATA_PAGE_SIZE) {
|
|
52
|
+
try {
|
|
53
|
+
return normalizeDataPageSize(globalThis.localStorage?.getItem(DATA_ROW_SIZE_STORAGE_KEY), fallback);
|
|
54
|
+
} catch {
|
|
55
|
+
return fallback;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function storeDataPageSize(pageSize) {
|
|
60
|
+
try {
|
|
61
|
+
globalThis.localStorage?.setItem(DATA_ROW_SIZE_STORAGE_KEY, String(pageSize));
|
|
62
|
+
} catch {
|
|
63
|
+
// Ignore unavailable browser storage; the in-memory setting still applies.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
const state = {
|
|
50
68
|
ready: false,
|
|
51
69
|
route: { name: 'landing', path: '/', params: {} },
|
|
@@ -79,7 +97,7 @@ const state = {
|
|
|
79
97
|
saving: false,
|
|
80
98
|
deleting: false,
|
|
81
99
|
page: 1,
|
|
82
|
-
pageSize:
|
|
100
|
+
pageSize: readStoredDataPageSize(),
|
|
83
101
|
sortColumn: null,
|
|
84
102
|
sortDirection: null,
|
|
85
103
|
searchQuery: '',
|
|
@@ -124,9 +142,11 @@ const state = {
|
|
|
124
142
|
saveError: null,
|
|
125
143
|
},
|
|
126
144
|
charts: {
|
|
145
|
+
loaded: false,
|
|
127
146
|
queries: [],
|
|
128
147
|
loading: false,
|
|
129
148
|
error: null,
|
|
149
|
+
historyTab: 'recent',
|
|
130
150
|
selectedHistoryId: null,
|
|
131
151
|
chartHeightPreset: 'medium',
|
|
132
152
|
sqlExpanded: false,
|
|
@@ -194,6 +214,7 @@ const state = {
|
|
|
194
214
|
dismissedIssueKeys: [],
|
|
195
215
|
selectedTagKeys: [],
|
|
196
216
|
workflowMediaDetailsVisible: true,
|
|
217
|
+
workflowMediaRotationDegrees: 0,
|
|
197
218
|
skippedMediaKeys: [],
|
|
198
219
|
tagFormValues: {},
|
|
199
220
|
},
|
|
@@ -348,6 +369,35 @@ function requiresActiveDatabase(routeName) {
|
|
|
348
369
|
].includes(routeName);
|
|
349
370
|
}
|
|
350
371
|
|
|
372
|
+
function isMediaTaggingRouteName(routeName) {
|
|
373
|
+
return routeName === 'mediaTaggingSetup' || routeName === 'mediaTaggingQueue';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getConnectionIdentity(connection) {
|
|
377
|
+
return connection?.id ?? connection?.path ?? null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function hasLoadedMediaTaggingForActiveConnection() {
|
|
381
|
+
const activeConnectionId = getConnectionIdentity(state.connections.active);
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
Boolean(activeConnectionId) &&
|
|
385
|
+
getConnectionIdentity(state.mediaTagging.connection) === activeConnectionId &&
|
|
386
|
+
!state.mediaTagging.loading &&
|
|
387
|
+
(state.mediaTagging.draft !== null || state.mediaTagging.suggestedConfig !== null)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function normalizeMediaTaggingRotationDegrees(value) {
|
|
392
|
+
const numericValue = Number(value);
|
|
393
|
+
|
|
394
|
+
if (!Number.isFinite(numericValue)) {
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return ((Math.round(numericValue / 90) * 90) % 360 + 360) % 360;
|
|
399
|
+
}
|
|
400
|
+
|
|
351
401
|
function normalizeDataPageSize(value, fallback = 50) {
|
|
352
402
|
const numericValue = Number(value);
|
|
353
403
|
|
|
@@ -636,9 +686,11 @@ function resetQueryHistoryState({ preserveSearch = true } = {}) {
|
|
|
636
686
|
function resetChartsState() {
|
|
637
687
|
chartsLoadVersion += 1;
|
|
638
688
|
chartsDetailLoadVersion += 1;
|
|
689
|
+
state.charts.loaded = false;
|
|
639
690
|
state.charts.queries = [];
|
|
640
691
|
state.charts.loading = false;
|
|
641
692
|
state.charts.error = null;
|
|
693
|
+
state.charts.historyTab = 'recent';
|
|
642
694
|
state.charts.selectedHistoryId = null;
|
|
643
695
|
state.charts.chartHeightPreset = 'medium';
|
|
644
696
|
state.charts.sqlExpanded = false;
|
|
@@ -659,6 +711,10 @@ function normalizeChartsHeightPreset(value) {
|
|
|
659
711
|
return CHART_HEIGHT_PRESETS.has(normalizedValue) ? normalizedValue : 'medium';
|
|
660
712
|
}
|
|
661
713
|
|
|
714
|
+
function normalizeChartsHistoryTab(value) {
|
|
715
|
+
return ['recent', 'saved'].includes(value) ? value : 'recent';
|
|
716
|
+
}
|
|
717
|
+
|
|
662
718
|
function mergeQueryHistoryItemWithChartSummary(updatedItem, fallbackItem = null) {
|
|
663
719
|
if (!updatedItem) {
|
|
664
720
|
return updatedItem;
|
|
@@ -684,20 +740,24 @@ function syncQueryHistoryItem(updatedItem) {
|
|
|
684
740
|
return;
|
|
685
741
|
}
|
|
686
742
|
|
|
687
|
-
|
|
743
|
+
const updatedItemId = String(updatedItem.id);
|
|
744
|
+
|
|
745
|
+
state.editor.history = state.editor.history.map(entry => (String(entry.id) === updatedItemId ? updatedItem : entry));
|
|
688
746
|
|
|
689
|
-
if (state.editor.historyDetail?.id ===
|
|
747
|
+
if (String(state.editor.historyDetail?.id ?? '') === updatedItemId) {
|
|
690
748
|
state.editor.historyDetail = updatedItem;
|
|
691
749
|
}
|
|
692
750
|
|
|
693
751
|
const existingChartsEntry =
|
|
694
|
-
state.charts.queries.find(entry => entry.id ===
|
|
695
|
-
(state.charts.detail?.item?.id ===
|
|
752
|
+
state.charts.queries.find(entry => String(entry.id) === updatedItemId) ??
|
|
753
|
+
(String(state.charts.detail?.item?.id ?? '') === updatedItemId ? state.charts.detail.item : null);
|
|
696
754
|
const mergedChartsItem = mergeQueryHistoryItemWithChartSummary(updatedItem, existingChartsEntry);
|
|
697
755
|
|
|
698
|
-
state.charts.queries = state.charts.queries.map(entry =>
|
|
756
|
+
state.charts.queries = state.charts.queries.map(entry =>
|
|
757
|
+
String(entry.id) === updatedItemId ? mergedChartsItem : entry,
|
|
758
|
+
);
|
|
699
759
|
|
|
700
|
-
if (state.charts.detail?.item?.id ===
|
|
760
|
+
if (String(state.charts.detail?.item?.id ?? '') === updatedItemId) {
|
|
701
761
|
state.charts.detail = {
|
|
702
762
|
...state.charts.detail,
|
|
703
763
|
item: mergeQueryHistoryItemWithChartSummary(updatedItem, state.charts.detail.item),
|
|
@@ -844,6 +904,7 @@ function setMissingDatabaseState() {
|
|
|
844
904
|
state.mediaTagging.issues = [];
|
|
845
905
|
state.mediaTagging.dismissedIssueKeys = [];
|
|
846
906
|
state.mediaTagging.selectedTagKeys = [];
|
|
907
|
+
state.mediaTagging.workflowMediaRotationDegrees = 0;
|
|
847
908
|
state.mediaTagging.skippedMediaKeys = [];
|
|
848
909
|
state.mediaTagging.tagFormValues = {};
|
|
849
910
|
state.mediaTagging.error = error;
|
|
@@ -1138,7 +1199,56 @@ async function loadChartsDetail(historyId) {
|
|
|
1138
1199
|
}
|
|
1139
1200
|
}
|
|
1140
1201
|
|
|
1141
|
-
|
|
1202
|
+
function getRequestedChartsHistoryId(route) {
|
|
1203
|
+
const requestedHistoryId = Number(route.params?.historyId ?? null);
|
|
1204
|
+
|
|
1205
|
+
return Number.isInteger(requestedHistoryId) && requestedHistoryId > 0 ? requestedHistoryId : null;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function resolveLoadableChartsHistoryId(route) {
|
|
1209
|
+
const requestedHistoryId = getRequestedChartsHistoryId(route);
|
|
1210
|
+
|
|
1211
|
+
if (!requestedHistoryId) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return state.charts.queries.some(item => item.id === requestedHistoryId) ? requestedHistoryId : null;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function hasSettledChartsDetail(historyId) {
|
|
1219
|
+
if (state.charts.detailLoading || state.charts.resultLoading) {
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (historyId === null) {
|
|
1224
|
+
return (
|
|
1225
|
+
state.charts.selectedHistoryId === null &&
|
|
1226
|
+
!state.charts.detail &&
|
|
1227
|
+
!state.charts.result &&
|
|
1228
|
+
!state.charts.detailError &&
|
|
1229
|
+
!state.charts.resultError
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
return (
|
|
1234
|
+
state.charts.selectedHistoryId === historyId &&
|
|
1235
|
+
(state.charts.detail?.item?.id === historyId || Boolean(state.charts.detailError)) &&
|
|
1236
|
+
(Boolean(state.charts.result) || Boolean(state.charts.resultError))
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async function loadCharts(version, route, options = {}) {
|
|
1241
|
+
if (!options.force && state.charts.loaded) {
|
|
1242
|
+
const historyId = resolveLoadableChartsHistoryId(route);
|
|
1243
|
+
|
|
1244
|
+
if (hasSettledChartsDetail(historyId)) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
await loadChartsDetail(historyId);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1142
1252
|
state.charts.loading = true;
|
|
1143
1253
|
state.charts.error = null;
|
|
1144
1254
|
emitChange();
|
|
@@ -1151,6 +1261,7 @@ async function loadCharts(version, route) {
|
|
|
1151
1261
|
}
|
|
1152
1262
|
|
|
1153
1263
|
state.charts.queries = response.data ?? [];
|
|
1264
|
+
state.charts.loaded = true;
|
|
1154
1265
|
state.charts.error = null;
|
|
1155
1266
|
} catch (error) {
|
|
1156
1267
|
if (version !== routeLoadVersion) {
|
|
@@ -1158,6 +1269,7 @@ async function loadCharts(version, route) {
|
|
|
1158
1269
|
}
|
|
1159
1270
|
|
|
1160
1271
|
state.charts.queries = [];
|
|
1272
|
+
state.charts.loaded = false;
|
|
1161
1273
|
state.charts.error = normalizeError(error);
|
|
1162
1274
|
} finally {
|
|
1163
1275
|
if (version === routeLoadVersion) {
|
|
@@ -1170,11 +1282,7 @@ async function loadCharts(version, route) {
|
|
|
1170
1282
|
return;
|
|
1171
1283
|
}
|
|
1172
1284
|
|
|
1173
|
-
|
|
1174
|
-
const canLoadRequestedHistory =
|
|
1175
|
-
Number.isInteger(requestedHistoryId) && state.charts.queries.some(item => item.id === requestedHistoryId);
|
|
1176
|
-
|
|
1177
|
-
await loadChartsDetail(canLoadRequestedHistory ? requestedHistoryId : null);
|
|
1285
|
+
await loadChartsDetail(resolveLoadableChartsHistoryId(route));
|
|
1178
1286
|
}
|
|
1179
1287
|
|
|
1180
1288
|
async function loadOverview(version) {
|
|
@@ -1249,7 +1357,7 @@ async function resolvePendingDataBrowserRow(version) {
|
|
|
1249
1357
|
|
|
1250
1358
|
async function loadDataTable(version) {
|
|
1251
1359
|
const tableName = state.dataBrowser.selectedTable;
|
|
1252
|
-
const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize,
|
|
1360
|
+
const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, DEFAULT_DATA_PAGE_SIZE);
|
|
1253
1361
|
const page = Math.max(1, Number(state.dataBrowser.page) || 1);
|
|
1254
1362
|
const sortColumn = state.dataBrowser.sortColumn;
|
|
1255
1363
|
const sortDirection = normalizeSortDirection(state.dataBrowser.sortDirection);
|
|
@@ -1579,6 +1687,9 @@ function applyMediaTaggingResponse(data, options = {}) {
|
|
|
1579
1687
|
};
|
|
1580
1688
|
state.mediaTagging.tags = data?.tags ?? [];
|
|
1581
1689
|
state.mediaTagging.workflow = data?.workflow ?? null;
|
|
1690
|
+
if (previousCurrentKey !== nextCurrentKey) {
|
|
1691
|
+
state.mediaTagging.workflowMediaRotationDegrees = 0;
|
|
1692
|
+
}
|
|
1582
1693
|
state.mediaTagging.issues = data?.issues ?? [];
|
|
1583
1694
|
state.mediaTagging.error = null;
|
|
1584
1695
|
syncDismissedMediaTaggingIssues();
|
|
@@ -1650,6 +1761,7 @@ async function loadMediaTagging(version) {
|
|
|
1650
1761
|
state.mediaTagging.issues = [];
|
|
1651
1762
|
state.mediaTagging.dismissedIssueKeys = [];
|
|
1652
1763
|
state.mediaTagging.selectedTagKeys = [];
|
|
1764
|
+
state.mediaTagging.workflowMediaRotationDegrees = 0;
|
|
1653
1765
|
state.mediaTagging.skippedMediaKeys = [];
|
|
1654
1766
|
state.mediaTagging.tagFormValues = {};
|
|
1655
1767
|
state.mediaTagging.removingTagKey = null;
|
|
@@ -1759,11 +1871,12 @@ function invalidateDatabaseCaches() {
|
|
|
1759
1871
|
state.mediaTagging.issues = [];
|
|
1760
1872
|
state.mediaTagging.dismissedIssueKeys = [];
|
|
1761
1873
|
state.mediaTagging.selectedTagKeys = [];
|
|
1874
|
+
state.mediaTagging.workflowMediaRotationDegrees = 0;
|
|
1762
1875
|
state.mediaTagging.skippedMediaKeys = [];
|
|
1763
1876
|
state.mediaTagging.tagFormValues = {};
|
|
1764
1877
|
}
|
|
1765
1878
|
|
|
1766
|
-
async function loadRouteData(route) {
|
|
1879
|
+
async function loadRouteData(route, options = {}) {
|
|
1767
1880
|
clearRouteSlices();
|
|
1768
1881
|
|
|
1769
1882
|
if (requiresActiveDatabase(route.name) && !state.connections.active) {
|
|
@@ -1772,6 +1885,10 @@ async function loadRouteData(route) {
|
|
|
1772
1885
|
return;
|
|
1773
1886
|
}
|
|
1774
1887
|
|
|
1888
|
+
if (!options.force && isMediaTaggingRouteName(route.name) && hasLoadedMediaTaggingForActiveConnection()) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1775
1892
|
const version = ++routeLoadVersion;
|
|
1776
1893
|
|
|
1777
1894
|
if (route.name === 'landing' || route.name === 'connections') {
|
|
@@ -1787,7 +1904,7 @@ async function loadRouteData(route) {
|
|
|
1787
1904
|
await loadData(version, route);
|
|
1788
1905
|
return;
|
|
1789
1906
|
case 'charts':
|
|
1790
|
-
await loadCharts(version, route);
|
|
1907
|
+
await loadCharts(version, route, options);
|
|
1791
1908
|
return;
|
|
1792
1909
|
case 'editor':
|
|
1793
1910
|
case 'editorResults':
|
|
@@ -2161,6 +2278,21 @@ export function setChartsHeightPreset(preset) {
|
|
|
2161
2278
|
emitChange();
|
|
2162
2279
|
}
|
|
2163
2280
|
|
|
2281
|
+
export function setChartsHistoryTab(tab) {
|
|
2282
|
+
const nextTab = normalizeChartsHistoryTab(
|
|
2283
|
+
String(tab ?? '')
|
|
2284
|
+
.trim()
|
|
2285
|
+
.toLowerCase(),
|
|
2286
|
+
);
|
|
2287
|
+
|
|
2288
|
+
if (state.charts.historyTab === nextTab) {
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
state.charts.historyTab = nextTab;
|
|
2293
|
+
emitChange();
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2164
2296
|
export function updateCurrentQueryChartDraftField(field, value) {
|
|
2165
2297
|
if (state.modal?.kind !== 'chart-editor' || !state.modal.draft) {
|
|
2166
2298
|
return;
|
|
@@ -2585,16 +2717,25 @@ export async function runQueryHistoryItem(historyId) {
|
|
|
2585
2717
|
return executeCurrentQuery();
|
|
2586
2718
|
}
|
|
2587
2719
|
|
|
2588
|
-
export async function toggleQueryHistorySavedState(historyId, nextValue) {
|
|
2720
|
+
export async function toggleQueryHistorySavedState(historyId, nextValue, options = {}) {
|
|
2589
2721
|
try {
|
|
2590
2722
|
const response = await api.toggleQueryHistorySaved(historyId, nextValue);
|
|
2591
2723
|
syncQueryHistoryItem(response.data);
|
|
2592
|
-
|
|
2593
|
-
|
|
2724
|
+
if (options.toast !== false) {
|
|
2725
|
+
pushToast(response.message || 'Query save state updated.', 'muted');
|
|
2726
|
+
}
|
|
2727
|
+
if (options.notify !== false) {
|
|
2728
|
+
emitChange();
|
|
2729
|
+
}
|
|
2730
|
+
if (options.refresh !== false) {
|
|
2731
|
+
await refreshQueryHistoryState();
|
|
2732
|
+
}
|
|
2594
2733
|
return true;
|
|
2595
2734
|
} catch (error) {
|
|
2596
2735
|
state.editor.historyDetailError = normalizeError(error);
|
|
2597
|
-
|
|
2736
|
+
if (options.notify !== false) {
|
|
2737
|
+
emitChange();
|
|
2738
|
+
}
|
|
2598
2739
|
return false;
|
|
2599
2740
|
}
|
|
2600
2741
|
}
|
|
@@ -2878,6 +3019,20 @@ export function setMediaTaggingWorkflowMediaDetailsVisible(value, options = {})
|
|
|
2878
3019
|
}
|
|
2879
3020
|
}
|
|
2880
3021
|
|
|
3022
|
+
export function setMediaTaggingWorkflowMediaRotationDegrees(value, options = {}) {
|
|
3023
|
+
const nextValue = normalizeMediaTaggingRotationDegrees(value);
|
|
3024
|
+
|
|
3025
|
+
if (state.mediaTagging.workflowMediaRotationDegrees === nextValue) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
state.mediaTagging.workflowMediaRotationDegrees = nextValue;
|
|
3030
|
+
|
|
3031
|
+
if (options.notify !== false) {
|
|
3032
|
+
emitChange();
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
|
|
2881
3036
|
export async function refreshMediaTaggingPreview() {
|
|
2882
3037
|
return previewMediaTaggingDraft({
|
|
2883
3038
|
keepSelectedTags: true,
|
|
@@ -3286,6 +3441,7 @@ export async function setDataPageSize(pageSize) {
|
|
|
3286
3441
|
}
|
|
3287
3442
|
|
|
3288
3443
|
state.dataBrowser.pageSize = normalizedPageSize;
|
|
3444
|
+
storeDataPageSize(normalizedPageSize);
|
|
3289
3445
|
state.dataBrowser.page = 1;
|
|
3290
3446
|
clearDataBrowserRowSelectionState();
|
|
3291
3447
|
state.dataBrowser.saveError = null;
|
|
@@ -3681,7 +3837,7 @@ export function sortEditorResultsByColumn(columnName) {
|
|
|
3681
3837
|
}
|
|
3682
3838
|
|
|
3683
3839
|
export async function refreshCurrentRoute() {
|
|
3684
|
-
await loadRouteData(state.route);
|
|
3840
|
+
await loadRouteData(state.route, { force: true });
|
|
3685
3841
|
}
|
|
3686
3842
|
|
|
3687
3843
|
export function showToast(message, tone = 'muted') {
|