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.
@@ -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
- <textarea
96
- 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 ${
97
- jsonPreview ? "min-h-[14rem] font-mono leading-6" : "min-h-[56px]"
98
- }"
99
- name="field:${escapeHtml(field.name)}"
100
- spellcheck="false"
101
- >${escapeHtml(field.value ?? "")}</textarea>
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 = sortQueryChartRows(rows, chart.config.x_column, chart.config.sort_direction);
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
- return getFirstColumn(analysis?.numberColumns, (column) => !excluded.has(column.name));
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
- sort_direction: "asc",
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";
@@ -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: 50,
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
- state.editor.history = state.editor.history.map(entry => (entry.id === updatedItem.id ? updatedItem : entry));
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 === updatedItem.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 === updatedItem.id) ??
695
- (state.charts.detail?.item?.id === updatedItem.id ? state.charts.detail.item : null);
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 => (entry.id === updatedItem.id ? mergedChartsItem : 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 === updatedItem.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
- async function loadCharts(version, route) {
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
- const requestedHistoryId = Number(route.params?.historyId ?? null);
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, 50);
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
- pushToast(response.message || 'Query save state updated.', 'muted');
2593
- await refreshQueryHistoryState();
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
- emitChange();
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') {