@youtyan/code-viewer 0.5.3 → 0.5.5
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/dist/code-viewer.js +37 -13
- package/package.json +1 -1
- package/web/app.js +733 -152
- package/web/style.css +561 -111
package/web/app.js
CHANGED
|
@@ -680,12 +680,16 @@
|
|
|
680
680
|
const table = params.get("table") || undefined;
|
|
681
681
|
const tabRaw = params.get("tab");
|
|
682
682
|
const tab = tabRaw === "data" || tabRaw === "query" || tabRaw === "schema" || tabRaw === "er" || tabRaw === "search" || tabRaw === "snapshot" ? tabRaw : undefined;
|
|
683
|
+
const diffBefore = params.get("diffBefore") || undefined;
|
|
684
|
+
const diffAfter = params.get("diffAfter") || undefined;
|
|
683
685
|
return {
|
|
684
686
|
screen: "database",
|
|
685
687
|
...db ? { db } : {},
|
|
686
688
|
...schema ? { schema } : {},
|
|
687
689
|
...table ? { table } : {},
|
|
688
690
|
...tab ? { tab } : {},
|
|
691
|
+
...diffBefore ? { diffBefore } : {},
|
|
692
|
+
...diffAfter ? { diffAfter } : {},
|
|
689
693
|
range
|
|
690
694
|
};
|
|
691
695
|
}
|
|
@@ -752,6 +756,10 @@
|
|
|
752
756
|
params.set("table", route.table);
|
|
753
757
|
if (route.tab)
|
|
754
758
|
params.set("tab", route.tab);
|
|
759
|
+
if (route.diffBefore)
|
|
760
|
+
params.set("diffBefore", route.diffBefore);
|
|
761
|
+
if (route.diffAfter)
|
|
762
|
+
params.set("diffAfter", route.diffAfter);
|
|
755
763
|
const qs = params.toString();
|
|
756
764
|
return `/database${qs ? `?${qs}` : ""}`;
|
|
757
765
|
}
|
|
@@ -9629,20 +9637,33 @@ ${frontmatter.yaml}
|
|
|
9629
9637
|
calculatingDiff: "Computing diff…",
|
|
9630
9638
|
diffError: "Failed to compute diff",
|
|
9631
9639
|
noChanges: "No changes detected.",
|
|
9632
|
-
before: "Before",
|
|
9633
9640
|
loading: "Loading…",
|
|
9634
9641
|
loadError: "Failed to load",
|
|
9635
9642
|
saveNote: "Save",
|
|
9636
9643
|
noteInputPlaceholder: "Enter a note",
|
|
9637
|
-
inserted: "Added",
|
|
9638
|
-
updated: "Updated",
|
|
9639
|
-
deleted: "Deleted",
|
|
9640
9644
|
tableCount: (n2) => `${n2} tables`,
|
|
9641
9645
|
rowCount: (n2) => ` (${n2})`,
|
|
9642
9646
|
listTitle: (n2) => `Snapshots (${n2})`,
|
|
9643
9647
|
diffSummary: (changed, unchanged) => `${changed} tables changed` + (unchanged > 0 ? `, ${unchanged} unchanged` : ""),
|
|
9644
9648
|
showingRows: (shown, total) => `Showing ${shown} of ${total}`,
|
|
9645
|
-
label: (date, tables, note) => `${date} (${tables} tables)${note}
|
|
9649
|
+
label: (date, tables, note) => `${date} (${tables} tables)${note}`,
|
|
9650
|
+
statusRunning: "running",
|
|
9651
|
+
statusError: "error",
|
|
9652
|
+
statusDone: "done",
|
|
9653
|
+
progressLabel: (table2, index, total) => table2 ? `Snapshotting ${table2} (${index + 1} / ${total})` : `Finalizing… (${total} / ${total})`,
|
|
9654
|
+
progressFinalizing: "Finalizing…",
|
|
9655
|
+
aborted: "cancelled",
|
|
9656
|
+
failed: "Failed",
|
|
9657
|
+
errorPrefix: "Error",
|
|
9658
|
+
compareBeforeBadge: "before",
|
|
9659
|
+
compareAfterBadge: "after",
|
|
9660
|
+
diffShowing: (before, after) => `Showing: ${before} → ${after}`,
|
|
9661
|
+
filterPlaceholder: "Filter tables…",
|
|
9662
|
+
tableCounter: (selected, total) => `${selected} / ${total} selected`,
|
|
9663
|
+
coverageBeforeOnly: "not snapshotted in after",
|
|
9664
|
+
coverageAfterOnly: "not snapshotted in before",
|
|
9665
|
+
coverageNoteBeforeOnly: (rows) => `Only in the before snapshot (${rows} rows). Not selected for the after snapshot, so no comparison.`,
|
|
9666
|
+
coverageNoteAfterOnly: (rows) => `Only in the after snapshot (${rows} rows). Not selected for the before snapshot, so no comparison.`
|
|
9646
9667
|
},
|
|
9647
9668
|
explorer: {
|
|
9648
9669
|
common: {
|
|
@@ -9901,20 +9922,33 @@ ${frontmatter.yaml}
|
|
|
9901
9922
|
calculatingDiff: "差分を計算中...",
|
|
9902
9923
|
diffError: "差分の計算に失敗しました",
|
|
9903
9924
|
noChanges: "変更は検出されませんでした。",
|
|
9904
|
-
before: "更新前",
|
|
9905
9925
|
loading: "読み込み中...",
|
|
9906
9926
|
loadError: "読み込みに失敗しました",
|
|
9907
9927
|
saveNote: "保存",
|
|
9908
9928
|
noteInputPlaceholder: "メモを入力",
|
|
9909
|
-
inserted: "追加",
|
|
9910
|
-
updated: "更新",
|
|
9911
|
-
deleted: "削除",
|
|
9912
9929
|
tableCount: (n2) => `${n2}テーブル`,
|
|
9913
9930
|
rowCount: (n2) => ` (${n2}件)`,
|
|
9914
9931
|
listTitle: (n2) => `スナップショット (${n2}件)`,
|
|
9915
9932
|
diffSummary: (changed, unchanged) => `${changed}テーブルに変更あり` + (unchanged > 0 ? `、${unchanged}テーブルは変更なし` : ""),
|
|
9916
9933
|
showingRows: (shown, total) => `全${total}件中 ${shown}件を表示中`,
|
|
9917
|
-
label: (date, tables, note) => `${date} (${tables}テーブル)${note}
|
|
9934
|
+
label: (date, tables, note) => `${date} (${tables}テーブル)${note}`,
|
|
9935
|
+
statusRunning: "取得中",
|
|
9936
|
+
statusError: "エラー",
|
|
9937
|
+
statusDone: "完了",
|
|
9938
|
+
progressLabel: (table2, index, total) => table2 ? `スナップショット取得中: ${table2} (${index + 1} / ${total})` : `仕上げ中… (${total} / ${total})`,
|
|
9939
|
+
progressFinalizing: "仕上げ中…",
|
|
9940
|
+
aborted: "キャンセル",
|
|
9941
|
+
failed: "失敗",
|
|
9942
|
+
errorPrefix: "エラー",
|
|
9943
|
+
compareBeforeBadge: "比較元",
|
|
9944
|
+
compareAfterBadge: "比較先",
|
|
9945
|
+
diffShowing: (before, after) => `比較中: ${before} → ${after}`,
|
|
9946
|
+
filterPlaceholder: "テーブル名で絞り込み…",
|
|
9947
|
+
tableCounter: (selected, total) => `${selected} / ${total} 件選択中`,
|
|
9948
|
+
coverageBeforeOnly: "比較先で未取得",
|
|
9949
|
+
coverageAfterOnly: "比較元で未取得",
|
|
9950
|
+
coverageNoteBeforeOnly: (rows) => `比較元のみに存在 (${rows}行)。比較先では対象テーブルに選ばれていないため比較できません。`,
|
|
9951
|
+
coverageNoteAfterOnly: (rows) => `比較先のみに存在 (${rows}行)。比較元では対象テーブルに選ばれていないため比較できません。`
|
|
9918
9952
|
},
|
|
9919
9953
|
explorer: {
|
|
9920
9954
|
common: {
|
|
@@ -14495,24 +14529,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14495
14529
|
const sb = [...b2].sort();
|
|
14496
14530
|
return sa.every((v, i2) => v === sb[i2]);
|
|
14497
14531
|
}
|
|
14498
|
-
function
|
|
14499
|
-
|
|
14532
|
+
function createSnapshotView(deps) {
|
|
14533
|
+
const text2 = () => deps.getText?.() ?? dbText("en");
|
|
14534
|
+
const trackLoad = (p2) => deps.trackLoad ? deps.trackLoad(p2) : p2;
|
|
14535
|
+
const postJson = (path, body) => trackLoad(fetch(path, {
|
|
14500
14536
|
method: "POST",
|
|
14501
14537
|
headers: {
|
|
14502
14538
|
"Content-Type": "application/json",
|
|
14503
14539
|
"X-Code-Viewer-Action": "1"
|
|
14504
14540
|
},
|
|
14505
14541
|
body: JSON.stringify(body)
|
|
14506
|
-
});
|
|
14507
|
-
|
|
14508
|
-
function createSnapshotView(deps) {
|
|
14509
|
-
const text2 = () => deps.getText?.() ?? dbText("en");
|
|
14542
|
+
}));
|
|
14543
|
+
const getJson = (path) => trackLoad(fetch(path));
|
|
14510
14544
|
const changeTypeLabel = (type) => {
|
|
14511
14545
|
if (type === "inserted")
|
|
14512
|
-
return
|
|
14513
|
-
|
|
14514
|
-
return text2().snapshot.updated;
|
|
14515
|
-
return text2().snapshot.deleted;
|
|
14546
|
+
return "+";
|
|
14547
|
+
return "−";
|
|
14516
14548
|
};
|
|
14517
14549
|
const snapshotLabel = (s2) => {
|
|
14518
14550
|
const date = new Date(s2.createdAt).toLocaleString();
|
|
@@ -14540,18 +14572,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14540
14572
|
refreshBtn.type = "button";
|
|
14541
14573
|
refreshBtn.className = "db-snapshot-refresh-btn";
|
|
14542
14574
|
refreshBtn.textContent = text2().snapshot.refresh;
|
|
14543
|
-
|
|
14544
|
-
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
snapshotCancelBtn.hidden = true;
|
|
14548
|
-
toolbar.append(createBtn, refreshBtn, snapshotCancelBtn);
|
|
14575
|
+
toolbar.append(createBtn, refreshBtn);
|
|
14576
|
+
const progressArea = document.createElement("div");
|
|
14577
|
+
progressArea.className = "db-snapshot-progress-area";
|
|
14578
|
+
progressArea.hidden = true;
|
|
14549
14579
|
const tableSelector = document.createElement("div");
|
|
14550
14580
|
tableSelector.className = "db-snapshot-table-selector";
|
|
14551
14581
|
tableSelector.hidden = true;
|
|
14552
14582
|
const tableSelectorHeader = document.createElement("div");
|
|
14553
14583
|
tableSelectorHeader.className = "db-snapshot-table-selector-header";
|
|
14554
|
-
|
|
14584
|
+
const tableSelectorHeaderLabel = document.createElement("span");
|
|
14585
|
+
tableSelectorHeaderLabel.textContent = text2().snapshot.selectTablesHeader;
|
|
14586
|
+
const tableSelectorCounter = document.createElement("span");
|
|
14587
|
+
tableSelectorCounter.className = "db-snapshot-table-counter";
|
|
14588
|
+
tableSelectorHeader.append(tableSelectorHeaderLabel, tableSelectorCounter);
|
|
14589
|
+
const filterRow = document.createElement("div");
|
|
14590
|
+
filterRow.className = "db-snapshot-table-filter";
|
|
14591
|
+
const filterInput = document.createElement("input");
|
|
14592
|
+
filterInput.type = "search";
|
|
14593
|
+
filterInput.className = "db-snapshot-table-filter-input";
|
|
14594
|
+
filterInput.placeholder = text2().snapshot.filterPlaceholder;
|
|
14595
|
+
filterRow.appendChild(filterInput);
|
|
14555
14596
|
const tableCheckboxes = document.createElement("div");
|
|
14556
14597
|
tableCheckboxes.className = "db-snapshot-table-checkboxes";
|
|
14557
14598
|
const selectActions = document.createElement("div");
|
|
@@ -14562,16 +14603,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14562
14603
|
const deselectAllBtn = document.createElement("button");
|
|
14563
14604
|
deselectAllBtn.type = "button";
|
|
14564
14605
|
deselectAllBtn.textContent = text2().snapshot.deselectAll;
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
noteInput.placeholder = text2().snapshot.notePlaceholder;
|
|
14606
|
+
selectActions.append(selectAllBtn, deselectAllBtn);
|
|
14607
|
+
const noteRow = document.createElement("div");
|
|
14608
|
+
noteRow.className = "db-snapshot-note-row";
|
|
14569
14609
|
const noteField = document.createElement("label");
|
|
14570
14610
|
noteField.className = "db-snapshot-note-field";
|
|
14571
14611
|
const noteLabel = document.createElement("span");
|
|
14572
14612
|
noteLabel.className = "db-snapshot-note-label";
|
|
14573
14613
|
noteLabel.textContent = text2().snapshot.noteLabel;
|
|
14614
|
+
const noteInput = document.createElement("input");
|
|
14615
|
+
noteInput.type = "text";
|
|
14616
|
+
noteInput.className = "db-snapshot-note-input";
|
|
14617
|
+
noteInput.placeholder = text2().snapshot.notePlaceholder;
|
|
14574
14618
|
noteField.append(noteLabel, noteInput);
|
|
14619
|
+
noteRow.appendChild(noteField);
|
|
14620
|
+
const actionRow = document.createElement("div");
|
|
14621
|
+
actionRow.className = "db-snapshot-action-row";
|
|
14575
14622
|
const confirmBtn = document.createElement("button");
|
|
14576
14623
|
confirmBtn.type = "button";
|
|
14577
14624
|
confirmBtn.className = "db-snapshot-confirm-btn";
|
|
@@ -14580,41 +14627,178 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14580
14627
|
cancelBtn.type = "button";
|
|
14581
14628
|
cancelBtn.className = "db-snapshot-cancel-btn";
|
|
14582
14629
|
cancelBtn.textContent = text2().snapshot.cancel;
|
|
14583
|
-
|
|
14584
|
-
tableSelector.append(tableSelectorHeader, tableCheckboxes, selectActions);
|
|
14630
|
+
actionRow.append(confirmBtn, cancelBtn);
|
|
14631
|
+
tableSelector.append(tableSelectorHeader, filterRow, tableCheckboxes, selectActions, noteRow, actionRow);
|
|
14585
14632
|
const mainArea = document.createElement("div");
|
|
14586
14633
|
mainArea.className = "db-snapshot-main-area";
|
|
14587
|
-
|
|
14634
|
+
const splitContainer = document.createElement("div");
|
|
14635
|
+
splitContainer.className = "db-snapshot-split";
|
|
14636
|
+
const leftPane = document.createElement("div");
|
|
14637
|
+
leftPane.className = "db-snapshot-left-pane";
|
|
14638
|
+
const rightPane = document.createElement("div");
|
|
14639
|
+
rightPane.className = "db-snapshot-right-pane";
|
|
14640
|
+
splitContainer.append(leftPane, rightPane);
|
|
14641
|
+
mainArea.appendChild(splitContainer);
|
|
14642
|
+
el.append(guide, toolbar, progressArea, tableSelector, mainArea);
|
|
14588
14643
|
let snapshots = [];
|
|
14589
|
-
let activeSnapshotId = null;
|
|
14590
14644
|
let currentDiff = null;
|
|
14591
14645
|
let disposed = false;
|
|
14592
14646
|
const autoRefreshTimers = new Set;
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14647
|
+
const activeJobs = new Map;
|
|
14648
|
+
const cancellingJobs = new Set;
|
|
14649
|
+
const jobClearTimers = new Map;
|
|
14650
|
+
function upsertJob(id, patch) {
|
|
14651
|
+
const prev = activeJobs.get(id) ?? { id, total: 0, done: false };
|
|
14652
|
+
activeJobs.set(id, { ...prev, ...patch, id });
|
|
14653
|
+
renderProgress();
|
|
14654
|
+
}
|
|
14655
|
+
function scheduleJobRemove(id) {
|
|
14656
|
+
const existing = jobClearTimers.get(id);
|
|
14657
|
+
if (existing)
|
|
14658
|
+
clearTimeout(existing);
|
|
14659
|
+
const t2 = setTimeout(() => {
|
|
14660
|
+
jobClearTimers.delete(id);
|
|
14661
|
+
cancellingJobs.delete(id);
|
|
14662
|
+
activeJobs.delete(id);
|
|
14663
|
+
renderProgress();
|
|
14664
|
+
}, 5000);
|
|
14665
|
+
jobClearTimers.set(id, t2);
|
|
14666
|
+
}
|
|
14667
|
+
async function cancelJob(id) {
|
|
14668
|
+
if (cancellingJobs.has(id))
|
|
14669
|
+
return;
|
|
14670
|
+
cancellingJobs.add(id);
|
|
14671
|
+
renderProgress();
|
|
14672
|
+
try {
|
|
14673
|
+
await postJson("/_db/snapshot/cancel", { id });
|
|
14674
|
+
} catch {}
|
|
14675
|
+
}
|
|
14676
|
+
function renderProgress() {
|
|
14677
|
+
progressArea.innerHTML = "";
|
|
14678
|
+
if (activeJobs.size === 0) {
|
|
14679
|
+
progressArea.hidden = true;
|
|
14680
|
+
return;
|
|
14681
|
+
}
|
|
14682
|
+
progressArea.hidden = false;
|
|
14683
|
+
const t2 = text2().snapshot;
|
|
14684
|
+
for (const job of activeJobs.values()) {
|
|
14685
|
+
const row = document.createElement("div");
|
|
14686
|
+
row.className = "db-snapshot-progress-row";
|
|
14687
|
+
row.dataset.snapshotId = job.id;
|
|
14688
|
+
const label = document.createElement("span");
|
|
14689
|
+
label.className = "db-snapshot-progress-label";
|
|
14690
|
+
if (job.errorMessage) {
|
|
14691
|
+
label.textContent = `${t2.errorPrefix}: ${job.errorMessage}`;
|
|
14692
|
+
label.title = job.errorMessage;
|
|
14693
|
+
row.classList.add("error");
|
|
14694
|
+
} else if (job.aborted) {
|
|
14695
|
+
label.textContent = t2.aborted;
|
|
14696
|
+
row.classList.add("aborted");
|
|
14697
|
+
} else if (job.done) {
|
|
14698
|
+
label.textContent = t2.statusDone;
|
|
14699
|
+
row.classList.add("done");
|
|
14700
|
+
} else if (job.total > 0 && job.table) {
|
|
14701
|
+
label.textContent = t2.progressLabel(job.table, job.index ?? 0, job.total);
|
|
14702
|
+
row.classList.add("running");
|
|
14703
|
+
} else {
|
|
14704
|
+
label.textContent = t2.creating;
|
|
14705
|
+
row.classList.add("running");
|
|
14706
|
+
}
|
|
14707
|
+
row.appendChild(label);
|
|
14708
|
+
if (!job.done) {
|
|
14709
|
+
const cancelRowBtn = document.createElement("button");
|
|
14710
|
+
cancelRowBtn.type = "button";
|
|
14711
|
+
cancelRowBtn.className = "db-snapshot-job-cancel-btn";
|
|
14712
|
+
if (cancellingJobs.has(job.id)) {
|
|
14713
|
+
cancelRowBtn.disabled = true;
|
|
14714
|
+
cancelRowBtn.textContent = t2.cancelling;
|
|
14715
|
+
} else {
|
|
14716
|
+
cancelRowBtn.textContent = t2.cancel;
|
|
14717
|
+
cancelRowBtn.addEventListener("click", () => {
|
|
14718
|
+
cancelJob(job.id);
|
|
14719
|
+
});
|
|
14720
|
+
}
|
|
14721
|
+
row.appendChild(cancelRowBtn);
|
|
14722
|
+
}
|
|
14723
|
+
progressArea.appendChild(row);
|
|
14724
|
+
}
|
|
14725
|
+
}
|
|
14726
|
+
let tableSelectorTables = [];
|
|
14727
|
+
const selectedTableSet = new Set;
|
|
14728
|
+
let tableFilterValue = "";
|
|
14729
|
+
function setTableSelectorVisible(visible) {
|
|
14730
|
+
tableSelector.hidden = !visible;
|
|
14731
|
+
createBtn.disabled = visible;
|
|
14732
|
+
createBtn.classList.toggle("is-active-dialog", visible);
|
|
14598
14733
|
}
|
|
14599
14734
|
function showTableSelector() {
|
|
14600
|
-
|
|
14601
|
-
const
|
|
14735
|
+
tableSelectorTables = deps.getTables().filter((t2) => t2.type === "table");
|
|
14736
|
+
const currentTableNames = new Set(tableSelectorTables.map((t2) => t2.name));
|
|
14737
|
+
const stored = deps.getStoredSelectedTables?.();
|
|
14738
|
+
const rawTables = stored?.length ? stored : getLastTables();
|
|
14739
|
+
const lastTables = rawTables.filter((name) => currentTableNames.has(name));
|
|
14740
|
+
selectedTableSet.clear();
|
|
14741
|
+
if (lastTables.length === 0) {
|
|
14742
|
+
for (const t2 of tableSelectorTables)
|
|
14743
|
+
selectedTableSet.add(t2.name);
|
|
14744
|
+
} else {
|
|
14745
|
+
for (const name of lastTables)
|
|
14746
|
+
selectedTableSet.add(name);
|
|
14747
|
+
}
|
|
14748
|
+
tableFilterValue = "";
|
|
14749
|
+
filterInput.value = "";
|
|
14750
|
+
renderTableList();
|
|
14751
|
+
setTableSelectorVisible(true);
|
|
14752
|
+
noteInput.value = "";
|
|
14753
|
+
filterInput.focus();
|
|
14754
|
+
}
|
|
14755
|
+
function persistSelectedTables() {
|
|
14756
|
+
deps.setStoredSelectedTables?.(Array.from(selectedTableSet).sort());
|
|
14757
|
+
}
|
|
14758
|
+
function renderTableList() {
|
|
14759
|
+
const needle = tableFilterValue.trim().toLowerCase();
|
|
14602
14760
|
tableCheckboxes.innerHTML = "";
|
|
14603
|
-
|
|
14604
|
-
|
|
14761
|
+
const t2 = text2().snapshot;
|
|
14762
|
+
let visible = 0;
|
|
14763
|
+
for (const tbl of tableSelectorTables) {
|
|
14764
|
+
if (needle && !tbl.name.toLowerCase().includes(needle))
|
|
14605
14765
|
continue;
|
|
14606
|
-
|
|
14607
|
-
|
|
14766
|
+
visible++;
|
|
14767
|
+
const row = document.createElement("label");
|
|
14768
|
+
row.className = "db-snapshot-table-row";
|
|
14608
14769
|
const cb = document.createElement("input");
|
|
14609
14770
|
cb.type = "checkbox";
|
|
14610
|
-
cb.
|
|
14611
|
-
cb.
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14771
|
+
cb.value = tbl.name;
|
|
14772
|
+
cb.checked = selectedTableSet.has(tbl.name);
|
|
14773
|
+
cb.addEventListener("change", () => {
|
|
14774
|
+
if (cb.checked)
|
|
14775
|
+
selectedTableSet.add(tbl.name);
|
|
14776
|
+
else
|
|
14777
|
+
selectedTableSet.delete(tbl.name);
|
|
14778
|
+
updateSelectorState();
|
|
14779
|
+
persistSelectedTables();
|
|
14780
|
+
});
|
|
14781
|
+
const name = document.createElement("span");
|
|
14782
|
+
name.className = "db-snapshot-table-row-name";
|
|
14783
|
+
name.textContent = tbl.name;
|
|
14784
|
+
const count = document.createElement("span");
|
|
14785
|
+
count.className = "db-snapshot-table-row-count";
|
|
14786
|
+
count.textContent = tbl.rowCount != null ? t2.rowCount(tbl.rowCount).trim() : "";
|
|
14787
|
+
row.append(cb, name, count);
|
|
14788
|
+
tableCheckboxes.appendChild(row);
|
|
14615
14789
|
}
|
|
14616
|
-
|
|
14617
|
-
|
|
14790
|
+
if (visible === 0 && needle) {
|
|
14791
|
+
const empty = document.createElement("div");
|
|
14792
|
+
empty.className = "db-snapshot-table-row-empty";
|
|
14793
|
+
empty.textContent = t2.empty;
|
|
14794
|
+
tableCheckboxes.appendChild(empty);
|
|
14795
|
+
}
|
|
14796
|
+
updateSelectorState();
|
|
14797
|
+
}
|
|
14798
|
+
function updateSelectorState() {
|
|
14799
|
+
const t2 = text2().snapshot;
|
|
14800
|
+
tableSelectorCounter.textContent = t2.tableCounter(selectedTableSet.size, tableSelectorTables.length);
|
|
14801
|
+
confirmBtn.disabled = selectedTableSet.size === 0;
|
|
14618
14802
|
}
|
|
14619
14803
|
function getLastTables() {
|
|
14620
14804
|
const dbId = deps.getDbId();
|
|
@@ -14624,36 +14808,47 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14624
14808
|
return done.length > 0 ? done[0].tables : [];
|
|
14625
14809
|
}
|
|
14626
14810
|
function getSelectedTables() {
|
|
14627
|
-
return Array.from(
|
|
14811
|
+
return Array.from(selectedTableSet);
|
|
14628
14812
|
}
|
|
14813
|
+
filterInput.addEventListener("input", () => {
|
|
14814
|
+
tableFilterValue = filterInput.value;
|
|
14815
|
+
renderTableList();
|
|
14816
|
+
});
|
|
14817
|
+
filterInput.addEventListener("keydown", (e2) => {
|
|
14818
|
+
if (e2.key === "Escape" && filterInput.value) {
|
|
14819
|
+
e2.preventDefault();
|
|
14820
|
+
filterInput.value = "";
|
|
14821
|
+
tableFilterValue = "";
|
|
14822
|
+
renderTableList();
|
|
14823
|
+
}
|
|
14824
|
+
});
|
|
14629
14825
|
selectAllBtn.addEventListener("click", () => {
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14826
|
+
const needle = tableFilterValue.trim().toLowerCase();
|
|
14827
|
+
for (const tbl of tableSelectorTables) {
|
|
14828
|
+
if (needle && !tbl.name.toLowerCase().includes(needle))
|
|
14829
|
+
continue;
|
|
14830
|
+
selectedTableSet.add(tbl.name);
|
|
14831
|
+
}
|
|
14832
|
+
renderTableList();
|
|
14833
|
+
persistSelectedTables();
|
|
14633
14834
|
});
|
|
14634
14835
|
deselectAllBtn.addEventListener("click", () => {
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14836
|
+
const needle = tableFilterValue.trim().toLowerCase();
|
|
14837
|
+
if (!needle) {
|
|
14838
|
+
selectedTableSet.clear();
|
|
14839
|
+
} else {
|
|
14840
|
+
for (const tbl of tableSelectorTables) {
|
|
14841
|
+
if (tbl.name.toLowerCase().includes(needle)) {
|
|
14842
|
+
selectedTableSet.delete(tbl.name);
|
|
14843
|
+
}
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14846
|
+
renderTableList();
|
|
14847
|
+
persistSelectedTables();
|
|
14638
14848
|
});
|
|
14639
14849
|
createBtn.addEventListener("click", showTableSelector);
|
|
14640
14850
|
cancelBtn.addEventListener("click", () => {
|
|
14641
|
-
|
|
14642
|
-
});
|
|
14643
|
-
snapshotCancelBtn.addEventListener("click", async () => {
|
|
14644
|
-
if (!activeSnapshotId)
|
|
14645
|
-
return;
|
|
14646
|
-
const snapshotId = activeSnapshotId;
|
|
14647
|
-
snapshotCancelBtn.disabled = true;
|
|
14648
|
-
snapshotCancelBtn.textContent = text2().snapshot.cancelling;
|
|
14649
|
-
try {
|
|
14650
|
-
await postJson("/_db/snapshot/cancel", { id: snapshotId });
|
|
14651
|
-
} catch {} finally {
|
|
14652
|
-
if (!disposed && activeSnapshotId === snapshotId) {
|
|
14653
|
-
snapshotCancelBtn.disabled = false;
|
|
14654
|
-
snapshotCancelBtn.textContent = text2().snapshot.cancel;
|
|
14655
|
-
}
|
|
14656
|
-
}
|
|
14851
|
+
setTableSelectorVisible(false);
|
|
14657
14852
|
});
|
|
14658
14853
|
confirmBtn.addEventListener("click", async () => {
|
|
14659
14854
|
const dbId = deps.getDbId();
|
|
@@ -14672,7 +14867,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14672
14867
|
tables,
|
|
14673
14868
|
note: noteInput.value.trim()
|
|
14674
14869
|
});
|
|
14675
|
-
|
|
14870
|
+
setTableSelectorVisible(false);
|
|
14676
14871
|
scheduleAutoRefresh(dbId, schema);
|
|
14677
14872
|
} catch {} finally {
|
|
14678
14873
|
if (!disposed) {
|
|
@@ -14692,7 +14887,20 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14692
14887
|
autoRefreshTimers.add(timer);
|
|
14693
14888
|
}
|
|
14694
14889
|
refreshBtn.addEventListener("click", refresh);
|
|
14890
|
+
let inflightRefresh = null;
|
|
14695
14891
|
async function refresh() {
|
|
14892
|
+
if (inflightRefresh) {
|
|
14893
|
+
await inflightRefresh;
|
|
14894
|
+
return;
|
|
14895
|
+
}
|
|
14896
|
+
inflightRefresh = doRefresh();
|
|
14897
|
+
try {
|
|
14898
|
+
await inflightRefresh;
|
|
14899
|
+
} finally {
|
|
14900
|
+
inflightRefresh = null;
|
|
14901
|
+
}
|
|
14902
|
+
}
|
|
14903
|
+
async function doRefresh() {
|
|
14696
14904
|
const dbId = deps.getDbId();
|
|
14697
14905
|
const schema = deps.getSchema();
|
|
14698
14906
|
if (!dbId)
|
|
@@ -14701,7 +14909,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14701
14909
|
const params = new URLSearchParams({ db: dbId });
|
|
14702
14910
|
if (schema)
|
|
14703
14911
|
params.set("schema", schema);
|
|
14704
|
-
const snapRes = await
|
|
14912
|
+
const snapRes = await getJson(`/_db/snapshot/list?${params}`);
|
|
14705
14913
|
if (snapRes.ok) {
|
|
14706
14914
|
const data = await snapRes.json();
|
|
14707
14915
|
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema) {
|
|
@@ -14712,6 +14920,18 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14712
14920
|
if (disposed || deps.getDbId() !== dbId || deps.getSchema() !== schema)
|
|
14713
14921
|
return;
|
|
14714
14922
|
renderMain();
|
|
14923
|
+
if (currentDiff) {
|
|
14924
|
+
const stillValid = snapshots.some((s2) => s2.id === currentDiff?.beforeId) && snapshots.some((s2) => s2.id === currentDiff?.afterId);
|
|
14925
|
+
if (!stillValid) {
|
|
14926
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
14927
|
+
if (existing) {
|
|
14928
|
+
const prev = existing;
|
|
14929
|
+
prev.__tocObserver?.disconnect();
|
|
14930
|
+
existing.remove();
|
|
14931
|
+
}
|
|
14932
|
+
currentDiff = null;
|
|
14933
|
+
}
|
|
14934
|
+
}
|
|
14715
14935
|
} catch {}
|
|
14716
14936
|
}
|
|
14717
14937
|
async function refreshAndAutoDiff() {
|
|
@@ -14726,21 +14946,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14726
14946
|
showDiffInline(prev.id, newest.id);
|
|
14727
14947
|
}
|
|
14728
14948
|
function renderMain() {
|
|
14729
|
-
|
|
14949
|
+
leftPane.innerHTML = "";
|
|
14730
14950
|
const done = snapshots.filter((s2) => s2.status === "done");
|
|
14731
14951
|
if (snapshots.length === 0) {
|
|
14732
14952
|
const empty = document.createElement("div");
|
|
14733
14953
|
empty.className = "db-snapshot-empty";
|
|
14734
14954
|
empty.textContent = text2().snapshot.empty;
|
|
14735
|
-
|
|
14955
|
+
leftPane.appendChild(empty);
|
|
14736
14956
|
return;
|
|
14737
14957
|
}
|
|
14958
|
+
const stickyHeader = document.createElement("div");
|
|
14959
|
+
stickyHeader.className = "db-snapshot-sticky-header";
|
|
14738
14960
|
const snapshotSection = document.createElement("div");
|
|
14739
14961
|
snapshotSection.className = "db-snapshot-list-section";
|
|
14740
14962
|
const snapTitle = document.createElement("h3");
|
|
14741
14963
|
snapTitle.className = "db-snapshot-section-title";
|
|
14742
|
-
snapTitle.textContent = text2().snapshot.listTitle(
|
|
14964
|
+
snapTitle.textContent = text2().snapshot.listTitle(snapshots.length);
|
|
14743
14965
|
snapshotSection.appendChild(snapTitle);
|
|
14966
|
+
const t2 = text2().snapshot;
|
|
14744
14967
|
for (const snap of snapshots) {
|
|
14745
14968
|
const item = document.createElement("div");
|
|
14746
14969
|
item.className = "db-snapshot-item";
|
|
@@ -14750,17 +14973,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14750
14973
|
const date = document.createElement("span");
|
|
14751
14974
|
date.className = "db-snapshot-date";
|
|
14752
14975
|
date.textContent = new Date(snap.createdAt).toLocaleString();
|
|
14976
|
+
const status = document.createElement("span");
|
|
14977
|
+
status.className = `db-snapshot-status ${snap.status}`;
|
|
14978
|
+
status.textContent = snap.status === "running" ? t2.statusRunning : snap.status === "error" ? t2.statusError : t2.statusDone;
|
|
14753
14979
|
const tables = document.createElement("span");
|
|
14754
14980
|
tables.className = "db-snapshot-tables-count";
|
|
14755
14981
|
tables.title = snap.tables.join(", ");
|
|
14756
|
-
tables.textContent =
|
|
14757
|
-
info.append(date, tables);
|
|
14982
|
+
tables.textContent = t2.tableCount(snap.tables.length);
|
|
14983
|
+
info.append(date, status, tables);
|
|
14758
14984
|
if (snap.note) {
|
|
14759
14985
|
const note = document.createElement("span");
|
|
14760
14986
|
note.className = "db-snapshot-note";
|
|
14761
14987
|
note.textContent = snap.note;
|
|
14762
14988
|
info.appendChild(note);
|
|
14763
14989
|
}
|
|
14990
|
+
if (snap.status === "error" && snap.errorMessage) {
|
|
14991
|
+
const errMsg = document.createElement("span");
|
|
14992
|
+
errMsg.className = "db-snapshot-error-message";
|
|
14993
|
+
errMsg.textContent = `${t2.errorPrefix}: ${snap.errorMessage}`;
|
|
14994
|
+
errMsg.title = snap.errorMessage;
|
|
14995
|
+
info.appendChild(errMsg);
|
|
14996
|
+
}
|
|
14764
14997
|
const actions = document.createElement("div");
|
|
14765
14998
|
actions.className = "db-snapshot-actions";
|
|
14766
14999
|
const noteBtn = document.createElement("button");
|
|
@@ -14797,7 +15030,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14797
15030
|
beforeSelect.className = "db-snapshot-diff-select";
|
|
14798
15031
|
const afterSelect = document.createElement("select");
|
|
14799
15032
|
afterSelect.className = "db-snapshot-diff-select";
|
|
14800
|
-
const sorted = [...done].sort((a2, b2) => new Date(
|
|
15033
|
+
const sorted = [...done].sort((a2, b2) => new Date(b2.createdAt).getTime() - new Date(a2.createdAt).getTime());
|
|
14801
15034
|
for (const s2 of sorted) {
|
|
14802
15035
|
const optB = document.createElement("option");
|
|
14803
15036
|
optB.value = s2.id;
|
|
@@ -14809,8 +15042,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14809
15042
|
afterSelect.appendChild(optA);
|
|
14810
15043
|
}
|
|
14811
15044
|
if (sorted.length >= 2) {
|
|
14812
|
-
beforeSelect.selectedIndex =
|
|
14813
|
-
afterSelect.selectedIndex =
|
|
15045
|
+
beforeSelect.selectedIndex = 1;
|
|
15046
|
+
afterSelect.selectedIndex = 0;
|
|
14814
15047
|
}
|
|
14815
15048
|
const diffBtn = document.createElement("button");
|
|
14816
15049
|
diffBtn.type = "button";
|
|
@@ -14820,32 +15053,89 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14820
15053
|
diffBtn.disabled = true;
|
|
14821
15054
|
diffBtn.textContent = text2().snapshot.comparing;
|
|
14822
15055
|
try {
|
|
14823
|
-
showDiffInline(beforeSelect.value, afterSelect.value);
|
|
15056
|
+
await showDiffInline(beforeSelect.value, afterSelect.value);
|
|
15057
|
+
scrollDiffIntoView();
|
|
14824
15058
|
} finally {
|
|
14825
15059
|
diffBtn.disabled = false;
|
|
14826
15060
|
diffBtn.textContent = text2().snapshot.diffManual;
|
|
14827
15061
|
}
|
|
14828
15062
|
});
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
15063
|
+
const applyDiffSelectionHighlight = () => {
|
|
15064
|
+
const items = snapshotSection.querySelectorAll(".db-snapshot-item");
|
|
15065
|
+
const before = beforeSelect.value;
|
|
15066
|
+
const after = afterSelect.value;
|
|
15067
|
+
for (const it of items) {
|
|
15068
|
+
it.classList.remove("is-before", "is-after");
|
|
15069
|
+
const old = it.querySelector(".db-snapshot-compare-badge");
|
|
15070
|
+
if (old)
|
|
15071
|
+
old.remove();
|
|
15072
|
+
}
|
|
15073
|
+
for (const it of items) {
|
|
15074
|
+
const id = it.dataset.snapshotId ?? "";
|
|
15075
|
+
let label = null;
|
|
15076
|
+
if (id === before) {
|
|
15077
|
+
it.classList.add("is-before");
|
|
15078
|
+
label = t2.compareBeforeBadge;
|
|
15079
|
+
} else if (id === after) {
|
|
15080
|
+
it.classList.add("is-after");
|
|
15081
|
+
label = t2.compareAfterBadge;
|
|
15082
|
+
}
|
|
15083
|
+
if (label) {
|
|
15084
|
+
const badge = document.createElement("span");
|
|
15085
|
+
badge.className = "db-snapshot-compare-badge";
|
|
15086
|
+
badge.textContent = label;
|
|
15087
|
+
const info = it.querySelector(".db-snapshot-info");
|
|
15088
|
+
if (info)
|
|
15089
|
+
info.appendChild(badge);
|
|
15090
|
+
}
|
|
15091
|
+
}
|
|
15092
|
+
};
|
|
15093
|
+
beforeSelect.addEventListener("change", applyDiffSelectionHighlight);
|
|
15094
|
+
afterSelect.addEventListener("change", applyDiffSelectionHighlight);
|
|
15095
|
+
applyDiffSelectionHighlight();
|
|
15096
|
+
const beforeRow = document.createElement("label");
|
|
15097
|
+
beforeRow.className = "db-snapshot-diff-row is-before";
|
|
15098
|
+
const beforeLabel = document.createElement("span");
|
|
15099
|
+
beforeLabel.className = "db-snapshot-diff-row-label";
|
|
15100
|
+
beforeLabel.textContent = t2.compareBeforeBadge;
|
|
15101
|
+
beforeRow.append(beforeLabel, beforeSelect);
|
|
15102
|
+
const afterRow = document.createElement("label");
|
|
15103
|
+
afterRow.className = "db-snapshot-diff-row is-after";
|
|
15104
|
+
const afterLabel = document.createElement("span");
|
|
15105
|
+
afterLabel.className = "db-snapshot-diff-row-label";
|
|
15106
|
+
afterLabel.textContent = t2.compareAfterBadge;
|
|
15107
|
+
afterRow.append(afterLabel, afterSelect);
|
|
15108
|
+
diffCreate.append(beforeRow, afterRow, diffBtn);
|
|
15109
|
+
stickyHeader.appendChild(diffCreate);
|
|
15110
|
+
}
|
|
15111
|
+
leftPane.append(stickyHeader, snapshotSection);
|
|
15112
|
+
}
|
|
15113
|
+
function scrollDiffIntoView() {
|
|
15114
|
+
const section = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15115
|
+
if (!section)
|
|
15116
|
+
return;
|
|
15117
|
+
section.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
14833
15118
|
}
|
|
14834
15119
|
async function showDiffInline(beforeId, afterId) {
|
|
14835
15120
|
currentDiff = { beforeId, afterId };
|
|
14836
|
-
|
|
14837
|
-
|
|
15121
|
+
deps.setRouteDiff?.({ before: beforeId, after: afterId });
|
|
15122
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15123
|
+
if (existing) {
|
|
15124
|
+
const prev = existing;
|
|
15125
|
+
prev.__tocObserver?.disconnect();
|
|
14838
15126
|
existing.remove();
|
|
15127
|
+
}
|
|
14839
15128
|
const loading = document.createElement("div");
|
|
14840
15129
|
loading.className = "db-snapshot-diff-inline";
|
|
14841
15130
|
const loadingText = document.createElement("div");
|
|
14842
15131
|
loadingText.className = "db-snapshot-loading";
|
|
14843
15132
|
loadingText.textContent = text2().snapshot.calculatingDiff;
|
|
14844
15133
|
loading.appendChild(loadingText);
|
|
14845
|
-
|
|
15134
|
+
rightPane.appendChild(loading);
|
|
14846
15135
|
try {
|
|
14847
|
-
const res = await
|
|
15136
|
+
const res = await getJson(`/_db/snapshot/diff/tables?before=${encodeURIComponent(beforeId)}&after=${encodeURIComponent(afterId)}`);
|
|
14848
15137
|
if (!res.ok) {
|
|
15138
|
+
currentDiff = null;
|
|
14849
15139
|
loading.innerHTML = "";
|
|
14850
15140
|
const error2 = document.createElement("div");
|
|
14851
15141
|
error2.className = "db-snapshot-error";
|
|
@@ -14857,6 +15147,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14857
15147
|
loading.remove();
|
|
14858
15148
|
renderDiffInline(beforeId, afterId, data.tables);
|
|
14859
15149
|
} catch {
|
|
15150
|
+
currentDiff = null;
|
|
14860
15151
|
loading.innerHTML = "";
|
|
14861
15152
|
const error2 = document.createElement("div");
|
|
14862
15153
|
error2.className = "db-snapshot-error";
|
|
@@ -14865,26 +15156,76 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14865
15156
|
}
|
|
14866
15157
|
}
|
|
14867
15158
|
function renderDiffInline(beforeId, afterId, tables) {
|
|
14868
|
-
const existing =
|
|
14869
|
-
if (existing)
|
|
15159
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15160
|
+
if (existing) {
|
|
15161
|
+
const prev = existing;
|
|
15162
|
+
prev.__tocObserver?.disconnect();
|
|
14870
15163
|
existing.remove();
|
|
15164
|
+
}
|
|
14871
15165
|
const section = document.createElement("div");
|
|
14872
15166
|
section.className = "db-snapshot-diff-inline";
|
|
14873
|
-
const
|
|
15167
|
+
const beforeMeta = snapshots.find((s2) => s2.id === beforeId);
|
|
15168
|
+
const afterMeta = snapshots.find((s2) => s2.id === afterId);
|
|
15169
|
+
if (beforeMeta && afterMeta) {
|
|
15170
|
+
const header = document.createElement("div");
|
|
15171
|
+
header.className = "db-snapshot-diff-header";
|
|
15172
|
+
const beforeLabel = new Date(beforeMeta.createdAt).toLocaleString();
|
|
15173
|
+
const afterLabel = new Date(afterMeta.createdAt).toLocaleString();
|
|
15174
|
+
header.textContent = text2().snapshot.diffShowing(beforeLabel, afterLabel);
|
|
15175
|
+
section.appendChild(header);
|
|
15176
|
+
}
|
|
15177
|
+
const changedTables = tables.filter((t2) => t2.insertedCount + t2.updatedCount + t2.deletedCount > 0 || t2.coverage && t2.coverage !== "both");
|
|
14874
15178
|
const unchangedCount = tables.length - changedTables.length;
|
|
14875
15179
|
const summary = document.createElement("div");
|
|
14876
15180
|
summary.className = "db-snapshot-diff-summary";
|
|
14877
15181
|
if (changedTables.length === 0) {
|
|
14878
15182
|
summary.textContent = text2().snapshot.noChanges;
|
|
14879
15183
|
section.appendChild(summary);
|
|
14880
|
-
|
|
15184
|
+
rightPane.appendChild(section);
|
|
14881
15185
|
return;
|
|
14882
15186
|
}
|
|
14883
15187
|
summary.textContent = text2().snapshot.diffSummary(changedTables.length, unchangedCount);
|
|
14884
15188
|
section.appendChild(summary);
|
|
15189
|
+
const toc = document.createElement("div");
|
|
15190
|
+
toc.className = "db-snapshot-diff-toc";
|
|
15191
|
+
const tocChips = new Map;
|
|
15192
|
+
section.appendChild(toc);
|
|
15193
|
+
function buildStatSpans(target, inserted, updated, deleted) {
|
|
15194
|
+
const make = (cls, prefix, n2) => {
|
|
15195
|
+
if (n2 <= 0)
|
|
15196
|
+
return;
|
|
15197
|
+
const s2 = document.createElement("span");
|
|
15198
|
+
s2.className = `db-snapshot-diff-stat ${cls}`;
|
|
15199
|
+
s2.textContent = `${prefix}${n2}`;
|
|
15200
|
+
target.appendChild(s2);
|
|
15201
|
+
};
|
|
15202
|
+
make("add", "+", inserted);
|
|
15203
|
+
make("upd", "~", updated);
|
|
15204
|
+
make("del", "-", deleted);
|
|
15205
|
+
}
|
|
15206
|
+
function renderCoverageOrStats(target, summary2) {
|
|
15207
|
+
const tx = text2().snapshot;
|
|
15208
|
+
if (summary2.coverage === "before-only") {
|
|
15209
|
+
const lbl = document.createElement("span");
|
|
15210
|
+
lbl.className = "db-snapshot-diff-coverage-badge before-only";
|
|
15211
|
+
lbl.textContent = tx.coverageBeforeOnly;
|
|
15212
|
+
target.appendChild(lbl);
|
|
15213
|
+
} else if (summary2.coverage === "after-only") {
|
|
15214
|
+
const lbl = document.createElement("span");
|
|
15215
|
+
lbl.className = "db-snapshot-diff-coverage-badge after-only";
|
|
15216
|
+
lbl.textContent = tx.coverageAfterOnly;
|
|
15217
|
+
target.appendChild(lbl);
|
|
15218
|
+
} else {
|
|
15219
|
+
buildStatSpans(target, summary2.insertedCount, summary2.updatedCount, summary2.deletedCount);
|
|
15220
|
+
}
|
|
15221
|
+
}
|
|
15222
|
+
const pendingLoads = [];
|
|
15223
|
+
let scrollTarget = null;
|
|
14885
15224
|
for (const t2 of changedTables) {
|
|
14886
15225
|
const tableEl = document.createElement("div");
|
|
14887
15226
|
tableEl.className = "db-snapshot-diff-table";
|
|
15227
|
+
const sectionId = `diff-table-${t2.tableName.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
15228
|
+
tableEl.id = sectionId;
|
|
14888
15229
|
const tableHeader = document.createElement("div");
|
|
14889
15230
|
tableHeader.className = "db-snapshot-diff-table-header";
|
|
14890
15231
|
const nameSpan = document.createElement("span");
|
|
@@ -14892,26 +15233,82 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14892
15233
|
nameSpan.textContent = t2.tableName;
|
|
14893
15234
|
const statsSpan = document.createElement("span");
|
|
14894
15235
|
statsSpan.className = "db-snapshot-diff-table-stats";
|
|
14895
|
-
|
|
14896
|
-
if (t2.insertedCount > 0)
|
|
14897
|
-
parts.push(`+${t2.insertedCount}`);
|
|
14898
|
-
if (t2.updatedCount > 0)
|
|
14899
|
-
parts.push(`~${t2.updatedCount}`);
|
|
14900
|
-
if (t2.deletedCount > 0)
|
|
14901
|
-
parts.push(`-${t2.deletedCount}`);
|
|
14902
|
-
statsSpan.textContent = parts.join(" ");
|
|
15236
|
+
renderCoverageOrStats(statsSpan, t2);
|
|
14903
15237
|
tableHeader.append(nameSpan, statsSpan);
|
|
14904
15238
|
tableHeader.style.cursor = "pointer";
|
|
14905
15239
|
const rowsContainer = document.createElement("div");
|
|
14906
15240
|
rowsContainer.className = "db-snapshot-diff-rows-container";
|
|
14907
|
-
|
|
15241
|
+
if (t2.coverage === "before-only" || t2.coverage === "after-only") {
|
|
15242
|
+
const tx = text2().snapshot;
|
|
15243
|
+
const note = document.createElement("div");
|
|
15244
|
+
note.className = "db-snapshot-diff-coverage-note";
|
|
15245
|
+
const rows = t2.unsnapshottedRowCount ?? 0;
|
|
15246
|
+
note.textContent = t2.coverage === "before-only" ? tx.coverageNoteBeforeOnly(rows) : tx.coverageNoteAfterOnly(rows);
|
|
15247
|
+
rowsContainer.appendChild(note);
|
|
15248
|
+
} else {
|
|
15249
|
+
pendingLoads.push(loadDiffRows(beforeId, afterId, t2.tableName, rowsContainer));
|
|
15250
|
+
}
|
|
14908
15251
|
tableHeader.addEventListener("click", () => {
|
|
14909
15252
|
rowsContainer.hidden = !rowsContainer.hidden;
|
|
14910
15253
|
});
|
|
14911
15254
|
tableEl.append(tableHeader, rowsContainer);
|
|
14912
15255
|
section.appendChild(tableEl);
|
|
15256
|
+
const chip = document.createElement("button");
|
|
15257
|
+
chip.type = "button";
|
|
15258
|
+
chip.className = "db-snapshot-diff-toc-chip";
|
|
15259
|
+
chip.dataset.targetId = sectionId;
|
|
15260
|
+
const chipName = document.createElement("span");
|
|
15261
|
+
chipName.className = "db-snapshot-diff-toc-chip-name";
|
|
15262
|
+
chipName.textContent = t2.tableName;
|
|
15263
|
+
const chipStats = document.createElement("span");
|
|
15264
|
+
chipStats.className = "db-snapshot-diff-toc-chip-stats";
|
|
15265
|
+
renderCoverageOrStats(chipStats, t2);
|
|
15266
|
+
if (t2.coverage === "before-only" || t2.coverage === "after-only") {
|
|
15267
|
+
chip.classList.add("coverage-incomplete");
|
|
15268
|
+
}
|
|
15269
|
+
chip.append(chipName, chipStats);
|
|
15270
|
+
chip.addEventListener("click", () => {
|
|
15271
|
+
scrollTarget = tableEl;
|
|
15272
|
+
tableEl.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
15273
|
+
});
|
|
15274
|
+
toc.appendChild(chip);
|
|
15275
|
+
tocChips.set(sectionId, chip);
|
|
15276
|
+
}
|
|
15277
|
+
Promise.allSettled(pendingLoads).then(() => {
|
|
15278
|
+
if (scrollTarget && rightPane.contains(scrollTarget)) {
|
|
15279
|
+
scrollTarget.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
15280
|
+
}
|
|
15281
|
+
});
|
|
15282
|
+
const observer = new IntersectionObserver((entries) => {
|
|
15283
|
+
const intersecting = entries.filter((e2) => e2.isIntersecting);
|
|
15284
|
+
if (intersecting.length === 0)
|
|
15285
|
+
return;
|
|
15286
|
+
intersecting.sort((a2, b2) => a2.boundingClientRect.top - b2.boundingClientRect.top);
|
|
15287
|
+
const activeId = intersecting[0].target.id;
|
|
15288
|
+
for (const [id, chip] of tocChips) {
|
|
15289
|
+
chip.classList.toggle("active", id === activeId);
|
|
15290
|
+
}
|
|
15291
|
+
const activeChip = tocChips.get(activeId);
|
|
15292
|
+
if (activeChip) {
|
|
15293
|
+
const tocBox = toc.getBoundingClientRect();
|
|
15294
|
+
const chipBox = activeChip.getBoundingClientRect();
|
|
15295
|
+
const margin = 16;
|
|
15296
|
+
if (chipBox.left < tocBox.left + margin) {
|
|
15297
|
+
toc.scrollLeft += chipBox.left - tocBox.left - margin;
|
|
15298
|
+
} else if (chipBox.right > tocBox.right - margin) {
|
|
15299
|
+
toc.scrollLeft += chipBox.right - tocBox.right + margin;
|
|
15300
|
+
}
|
|
15301
|
+
}
|
|
15302
|
+
}, {
|
|
15303
|
+
root: rightPane,
|
|
15304
|
+
rootMargin: "-48px 0px -60% 0px",
|
|
15305
|
+
threshold: 0
|
|
15306
|
+
});
|
|
15307
|
+
for (const el2 of section.querySelectorAll(".db-snapshot-diff-table")) {
|
|
15308
|
+
observer.observe(el2);
|
|
14913
15309
|
}
|
|
14914
|
-
|
|
15310
|
+
section.__tocObserver = observer;
|
|
15311
|
+
rightPane.appendChild(section);
|
|
14915
15312
|
}
|
|
14916
15313
|
async function loadDiffRows(beforeId, afterId, table2, container) {
|
|
14917
15314
|
container.innerHTML = "";
|
|
@@ -14920,7 +15317,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14920
15317
|
loading.textContent = text2().snapshot.loading;
|
|
14921
15318
|
container.appendChild(loading);
|
|
14922
15319
|
try {
|
|
14923
|
-
const res = await
|
|
15320
|
+
const res = await getJson(`/_db/snapshot/diff/rows?before=${encodeURIComponent(beforeId)}&after=${encodeURIComponent(afterId)}&table=${encodeURIComponent(table2)}&limit=200`);
|
|
14924
15321
|
if (!res.ok) {
|
|
14925
15322
|
container.innerHTML = "";
|
|
14926
15323
|
const error2 = document.createElement("div");
|
|
@@ -14953,11 +15350,19 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14953
15350
|
allCols.add(k);
|
|
14954
15351
|
}
|
|
14955
15352
|
const columns = [...allCols];
|
|
14956
|
-
const
|
|
14957
|
-
|
|
15353
|
+
const colHeaderWrap = document.createElement("div");
|
|
15354
|
+
colHeaderWrap.className = "db-snapshot-diff-col-header-wrap";
|
|
15355
|
+
const colHeaderTable = document.createElement("table");
|
|
15356
|
+
colHeaderTable.className = "db-snap-diff-grid";
|
|
15357
|
+
const colHeaderColgroup = document.createElement("colgroup");
|
|
15358
|
+
for (let i2 = 0;i2 < columns.length + 1; i2++) {
|
|
15359
|
+
colHeaderColgroup.appendChild(document.createElement("col"));
|
|
15360
|
+
}
|
|
15361
|
+
colHeaderTable.appendChild(colHeaderColgroup);
|
|
14958
15362
|
const thead = document.createElement("thead");
|
|
14959
15363
|
const headRow = document.createElement("tr");
|
|
14960
15364
|
const thType = document.createElement("th");
|
|
15365
|
+
thType.className = "snap-diff-type-cell";
|
|
14961
15366
|
thType.textContent = "";
|
|
14962
15367
|
headRow.appendChild(thType);
|
|
14963
15368
|
for (const col of columns) {
|
|
@@ -14966,16 +15371,25 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14966
15371
|
headRow.appendChild(th);
|
|
14967
15372
|
}
|
|
14968
15373
|
thead.appendChild(headRow);
|
|
14969
|
-
|
|
15374
|
+
colHeaderTable.appendChild(thead);
|
|
15375
|
+
colHeaderWrap.appendChild(colHeaderTable);
|
|
15376
|
+
const rowsViewport = document.createElement("div");
|
|
15377
|
+
rowsViewport.className = "db-snapshot-diff-rows-viewport";
|
|
15378
|
+
const bodyTable = document.createElement("table");
|
|
15379
|
+
bodyTable.className = "db-snap-diff-grid";
|
|
15380
|
+
const bodyColgroup = document.createElement("colgroup");
|
|
15381
|
+
for (let i2 = 0;i2 < columns.length + 1; i2++) {
|
|
15382
|
+
bodyColgroup.appendChild(document.createElement("col"));
|
|
15383
|
+
}
|
|
15384
|
+
bodyTable.appendChild(bodyColgroup);
|
|
14970
15385
|
const tbody = document.createElement("tbody");
|
|
14971
15386
|
for (const row of rows) {
|
|
14972
15387
|
if (row.changeType === "updated" && row.beforeValues && row.afterValues) {
|
|
14973
15388
|
const trBefore = document.createElement("tr");
|
|
14974
|
-
trBefore.className = "snap-diff-del";
|
|
15389
|
+
trBefore.className = "snap-diff-del snap-diff-record-start";
|
|
14975
15390
|
const tdTypeBefore = document.createElement("td");
|
|
14976
15391
|
tdTypeBefore.className = "snap-diff-type-cell";
|
|
14977
|
-
tdTypeBefore.textContent =
|
|
14978
|
-
tdTypeBefore.rowSpan = 2;
|
|
15392
|
+
tdTypeBefore.textContent = "−";
|
|
14979
15393
|
trBefore.appendChild(tdTypeBefore);
|
|
14980
15394
|
for (const col of columns) {
|
|
14981
15395
|
const td = document.createElement("td");
|
|
@@ -14988,7 +15402,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
14988
15402
|
}
|
|
14989
15403
|
tbody.appendChild(trBefore);
|
|
14990
15404
|
const trAfter = document.createElement("tr");
|
|
14991
|
-
trAfter.className = "snap-diff-add";
|
|
15405
|
+
trAfter.className = "snap-diff-add snap-diff-record-end";
|
|
15406
|
+
const tdTypeAfter = document.createElement("td");
|
|
15407
|
+
tdTypeAfter.className = "snap-diff-type-cell";
|
|
15408
|
+
tdTypeAfter.textContent = "+";
|
|
15409
|
+
trAfter.appendChild(tdTypeAfter);
|
|
14992
15410
|
for (const col of columns) {
|
|
14993
15411
|
const td = document.createElement("td");
|
|
14994
15412
|
const bs = row.beforeValues[col] == null ? "NULL" : String(row.beforeValues[col]);
|
|
@@ -15001,7 +15419,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15001
15419
|
tbody.appendChild(trAfter);
|
|
15002
15420
|
} else {
|
|
15003
15421
|
const tr = document.createElement("tr");
|
|
15004
|
-
|
|
15422
|
+
const sideClass = row.changeType === "inserted" ? "snap-diff-add" : "snap-diff-del";
|
|
15423
|
+
tr.className = `${sideClass} snap-diff-record-start snap-diff-record-end`;
|
|
15005
15424
|
const tdType = document.createElement("td");
|
|
15006
15425
|
tdType.className = "snap-diff-type-cell";
|
|
15007
15426
|
tdType.textContent = changeTypeLabel(row.changeType);
|
|
@@ -15009,6 +15428,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15009
15428
|
const values = row.changeType === "inserted" ? row.afterValues : row.beforeValues;
|
|
15010
15429
|
for (const col of columns) {
|
|
15011
15430
|
const td = document.createElement("td");
|
|
15431
|
+
td.classList.add("snap-diff-changed-cell");
|
|
15012
15432
|
const v = values?.[col];
|
|
15013
15433
|
td.textContent = v == null ? "NULL" : String(v);
|
|
15014
15434
|
tr.appendChild(td);
|
|
@@ -15016,8 +15436,32 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15016
15436
|
tbody.appendChild(tr);
|
|
15017
15437
|
}
|
|
15018
15438
|
}
|
|
15019
|
-
|
|
15020
|
-
|
|
15439
|
+
bodyTable.appendChild(tbody);
|
|
15440
|
+
rowsViewport.appendChild(bodyTable);
|
|
15441
|
+
container.append(colHeaderWrap, rowsViewport);
|
|
15442
|
+
rowsViewport.addEventListener("scroll", () => {
|
|
15443
|
+
colHeaderWrap.scrollLeft = rowsViewport.scrollLeft;
|
|
15444
|
+
});
|
|
15445
|
+
const syncColumnWidths = () => {
|
|
15446
|
+
if (disposed)
|
|
15447
|
+
return;
|
|
15448
|
+
const firstRow = tbody.querySelector("tr");
|
|
15449
|
+
if (!firstRow)
|
|
15450
|
+
return;
|
|
15451
|
+
const cells = firstRow.children;
|
|
15452
|
+
const bodyCols = bodyColgroup.children;
|
|
15453
|
+
const headerCols = colHeaderColgroup.children;
|
|
15454
|
+
for (let i2 = 0;i2 < cells.length; i2++) {
|
|
15455
|
+
const w = cells[i2].offsetWidth;
|
|
15456
|
+
if (bodyCols[i2])
|
|
15457
|
+
bodyCols[i2].style.width = `${w}px`;
|
|
15458
|
+
if (headerCols[i2])
|
|
15459
|
+
headerCols[i2].style.width = `${w}px`;
|
|
15460
|
+
}
|
|
15461
|
+
};
|
|
15462
|
+
requestAnimationFrame(syncColumnWidths);
|
|
15463
|
+
const ro = new ResizeObserver(syncColumnWidths);
|
|
15464
|
+
ro.observe(bodyTable);
|
|
15021
15465
|
if (total > rows.length) {
|
|
15022
15466
|
const more = document.createElement("div");
|
|
15023
15467
|
more.className = "db-snapshot-diff-more";
|
|
@@ -15069,7 +15513,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15069
15513
|
if (item) {
|
|
15070
15514
|
item.after(dialog);
|
|
15071
15515
|
} else {
|
|
15072
|
-
|
|
15516
|
+
leftPane.prepend(dialog);
|
|
15073
15517
|
}
|
|
15074
15518
|
input.focus();
|
|
15075
15519
|
}
|
|
@@ -15082,6 +15526,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15082
15526
|
refresh();
|
|
15083
15527
|
}
|
|
15084
15528
|
function handleSse(data) {
|
|
15529
|
+
if (disposed)
|
|
15530
|
+
return;
|
|
15085
15531
|
try {
|
|
15086
15532
|
const parsed = JSON.parse(data);
|
|
15087
15533
|
const dbId = deps.getDbId();
|
|
@@ -15090,21 +15536,57 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15090
15536
|
return;
|
|
15091
15537
|
if (parsed.schema && parsed.schema !== schema)
|
|
15092
15538
|
return;
|
|
15093
|
-
|
|
15094
|
-
|
|
15539
|
+
const id = parsed.id ?? null;
|
|
15540
|
+
if (parsed.action === "started" && id) {
|
|
15541
|
+
upsertJob(id, {
|
|
15542
|
+
done: false,
|
|
15543
|
+
total: parsed.total ?? 0,
|
|
15544
|
+
index: 0
|
|
15545
|
+
});
|
|
15546
|
+
}
|
|
15547
|
+
if (parsed.action === "progress" && id) {
|
|
15548
|
+
upsertJob(id, {
|
|
15549
|
+
table: parsed.table,
|
|
15550
|
+
index: parsed.index,
|
|
15551
|
+
total: parsed.total ?? 0,
|
|
15552
|
+
done: parsed.done === true
|
|
15553
|
+
});
|
|
15095
15554
|
}
|
|
15096
|
-
if (parsed.action === "created"
|
|
15097
|
-
|
|
15555
|
+
if (parsed.action === "created" && id) {
|
|
15556
|
+
upsertJob(id, { done: true });
|
|
15557
|
+
scheduleJobRemove(id);
|
|
15098
15558
|
refreshAndAutoDiff();
|
|
15099
15559
|
}
|
|
15560
|
+
if (parsed.action === "aborted" && id) {
|
|
15561
|
+
upsertJob(id, { done: true, aborted: true });
|
|
15562
|
+
scheduleJobRemove(id);
|
|
15563
|
+
refresh();
|
|
15564
|
+
}
|
|
15565
|
+
if (parsed.action === "error" && id) {
|
|
15566
|
+
upsertJob(id, {
|
|
15567
|
+
done: true,
|
|
15568
|
+
errorMessage: parsed.error || text2().snapshot.failed
|
|
15569
|
+
});
|
|
15570
|
+
scheduleJobRemove(id);
|
|
15571
|
+
refresh();
|
|
15572
|
+
}
|
|
15100
15573
|
} catch {}
|
|
15101
15574
|
}
|
|
15102
15575
|
function dispose() {
|
|
15103
15576
|
disposed = true;
|
|
15104
|
-
setActiveSnapshotId(null);
|
|
15105
15577
|
for (const timer of autoRefreshTimers)
|
|
15106
15578
|
clearTimeout(timer);
|
|
15107
15579
|
autoRefreshTimers.clear();
|
|
15580
|
+
for (const timer of jobClearTimers.values())
|
|
15581
|
+
clearTimeout(timer);
|
|
15582
|
+
jobClearTimers.clear();
|
|
15583
|
+
activeJobs.clear();
|
|
15584
|
+
cancellingJobs.clear();
|
|
15585
|
+
const existing = rightPane.querySelector(".db-snapshot-diff-inline");
|
|
15586
|
+
if (existing) {
|
|
15587
|
+
const prev = existing;
|
|
15588
|
+
prev.__tocObserver?.disconnect();
|
|
15589
|
+
}
|
|
15108
15590
|
}
|
|
15109
15591
|
function localize() {
|
|
15110
15592
|
const t2 = text2().snapshot;
|
|
@@ -15112,8 +15594,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15112
15594
|
guideBody.textContent = t2.guideBody;
|
|
15113
15595
|
createBtn.textContent = t2.create;
|
|
15114
15596
|
refreshBtn.textContent = t2.refresh;
|
|
15115
|
-
|
|
15116
|
-
snapshotCancelBtn.textContent = t2.cancel;
|
|
15597
|
+
renderProgress();
|
|
15117
15598
|
tableSelectorHeader.textContent = t2.selectTablesHeader;
|
|
15118
15599
|
selectAllBtn.textContent = t2.selectAll;
|
|
15119
15600
|
deselectAllBtn.textContent = t2.deselectAll;
|
|
@@ -15122,21 +15603,54 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
15122
15603
|
if (!confirmBtn.disabled)
|
|
15123
15604
|
confirmBtn.textContent = t2.confirm;
|
|
15124
15605
|
cancelBtn.textContent = t2.cancel;
|
|
15125
|
-
const prevSelects =
|
|
15606
|
+
const prevSelects = leftPane.querySelectorAll(".db-snapshot-diff-select");
|
|
15126
15607
|
const prevBeforeVal = prevSelects[0]?.value;
|
|
15127
15608
|
const prevAfterVal = prevSelects[1]?.value;
|
|
15128
|
-
const hadDiff =
|
|
15609
|
+
const hadDiff = rightPane.querySelector(".db-snapshot-diff-inline") != null;
|
|
15129
15610
|
renderMain();
|
|
15130
|
-
const newSelects =
|
|
15131
|
-
if (newSelects[0] && prevBeforeVal)
|
|
15611
|
+
const newSelects = leftPane.querySelectorAll(".db-snapshot-diff-select");
|
|
15612
|
+
if (newSelects[0] && prevBeforeVal) {
|
|
15132
15613
|
newSelects[0].value = prevBeforeVal;
|
|
15133
|
-
|
|
15614
|
+
newSelects[0].dispatchEvent(new Event("change"));
|
|
15615
|
+
}
|
|
15616
|
+
if (newSelects[1] && prevAfterVal) {
|
|
15134
15617
|
newSelects[1].value = prevAfterVal;
|
|
15618
|
+
newSelects[1].dispatchEvent(new Event("change"));
|
|
15619
|
+
}
|
|
15135
15620
|
if (hadDiff && currentDiff) {
|
|
15136
15621
|
showDiffInline(currentDiff.beforeId, currentDiff.afterId);
|
|
15137
15622
|
}
|
|
15138
15623
|
}
|
|
15139
|
-
|
|
15624
|
+
async function restoreDiffFromRoute() {
|
|
15625
|
+
const target = deps.getRouteDiff?.();
|
|
15626
|
+
if (!target || !target.before || !target.after)
|
|
15627
|
+
return;
|
|
15628
|
+
if (currentDiff && currentDiff.beforeId === target.before && currentDiff.afterId === target.after) {
|
|
15629
|
+
return;
|
|
15630
|
+
}
|
|
15631
|
+
if (snapshots.length === 0) {
|
|
15632
|
+
await refresh();
|
|
15633
|
+
}
|
|
15634
|
+
if (disposed)
|
|
15635
|
+
return;
|
|
15636
|
+
const hasBefore = snapshots.some((s2) => s2.id === target.before);
|
|
15637
|
+
const hasAfter = snapshots.some((s2) => s2.id === target.after);
|
|
15638
|
+
if (!hasBefore || !hasAfter) {
|
|
15639
|
+
deps.setRouteDiff?.(null);
|
|
15640
|
+
return;
|
|
15641
|
+
}
|
|
15642
|
+
await showDiffInline(target.before, target.after);
|
|
15643
|
+
}
|
|
15644
|
+
return {
|
|
15645
|
+
el,
|
|
15646
|
+
refresh,
|
|
15647
|
+
handleSse,
|
|
15648
|
+
dispose,
|
|
15649
|
+
localize,
|
|
15650
|
+
restoreDiffFromRoute: () => {
|
|
15651
|
+
restoreDiffFromRoute();
|
|
15652
|
+
}
|
|
15653
|
+
};
|
|
15140
15654
|
}
|
|
15141
15655
|
|
|
15142
15656
|
// web-src/views/database/table-grid.ts
|
|
@@ -17377,6 +17891,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
17377
17891
|
onClick: () => {
|
|
17378
17892
|
setActiveTab("snapshot");
|
|
17379
17893
|
snapshotView.refresh();
|
|
17894
|
+
snapshotView.restoreDiffFromRoute();
|
|
17380
17895
|
}
|
|
17381
17896
|
});
|
|
17382
17897
|
const disposeInferFkPrefSub = outerDeps.onDbUiPrefChange(() => {
|
|
@@ -17481,7 +17996,45 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
17481
17996
|
getDbId: () => currentDbInfo?.id || null,
|
|
17482
17997
|
getSchema: () => currentSchema,
|
|
17483
17998
|
getTables: () => schemaCache?.tables || [],
|
|
17484
|
-
getText: () => paneText()
|
|
17999
|
+
getText: () => paneText(),
|
|
18000
|
+
trackLoad: deps.trackLoad,
|
|
18001
|
+
getStoredSelectedTables: () => {
|
|
18002
|
+
if (!currentDbInfo)
|
|
18003
|
+
return;
|
|
18004
|
+
const scope = dbUiTableExpansionScope(currentDbInfo.id, currentSchema);
|
|
18005
|
+
return outerDeps.getSnapshotSelectedTables(scope);
|
|
18006
|
+
},
|
|
18007
|
+
setStoredSelectedTables: (tables) => {
|
|
18008
|
+
if (!currentDbInfo)
|
|
18009
|
+
return;
|
|
18010
|
+
const scope = dbUiTableExpansionScope(currentDbInfo.id, currentSchema);
|
|
18011
|
+
outerDeps.setSnapshotSelectedTables(scope, tables);
|
|
18012
|
+
},
|
|
18013
|
+
getRouteDiff: () => {
|
|
18014
|
+
const parsed = parseRoute(window.location.pathname, window.location.search, deps.currentRange());
|
|
18015
|
+
if (parsed.screen !== "database")
|
|
18016
|
+
return;
|
|
18017
|
+
const before = parsed.diffBefore;
|
|
18018
|
+
const after = parsed.diffAfter;
|
|
18019
|
+
if (!before && !after)
|
|
18020
|
+
return;
|
|
18021
|
+
return { before, after };
|
|
18022
|
+
},
|
|
18023
|
+
setRouteDiff: (diff) => {
|
|
18024
|
+
const dbId = currentDbInfo?.id;
|
|
18025
|
+
if (!dbId)
|
|
18026
|
+
return;
|
|
18027
|
+
deps.setRoute({
|
|
18028
|
+
screen: "database",
|
|
18029
|
+
db: dbId,
|
|
18030
|
+
schema: currentSchema ?? undefined,
|
|
18031
|
+
table: currentTable ?? undefined,
|
|
18032
|
+
tab: "snapshot",
|
|
18033
|
+
...diff?.before ? { diffBefore: diff.before } : {},
|
|
18034
|
+
...diff?.after ? { diffAfter: diff.after } : {},
|
|
18035
|
+
range: deps.currentRange()
|
|
18036
|
+
}, true);
|
|
18037
|
+
}
|
|
17485
18038
|
});
|
|
17486
18039
|
const historyView = createQueryHistoryView({
|
|
17487
18040
|
getDbId: () => currentDbInfo?.id || null,
|
|
@@ -18337,6 +18890,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18337
18890
|
renderErDiagram();
|
|
18338
18891
|
} else if (normalizedView === "snapshot") {
|
|
18339
18892
|
snapshotView.refresh();
|
|
18893
|
+
snapshotView.restoreDiffFromRoute();
|
|
18340
18894
|
}
|
|
18341
18895
|
}
|
|
18342
18896
|
applyAnnotationTarget(options.annotationTarget);
|
|
@@ -18610,33 +19164,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18610
19164
|
return;
|
|
18611
19165
|
});
|
|
18612
19166
|
}
|
|
18613
|
-
function
|
|
18614
|
-
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
const expandedTables = { ...dbUiState.expandedTables || {} };
|
|
18618
|
-
const tables = new Set(expandedTables[scopeKey] || []);
|
|
18619
|
-
if (expanded)
|
|
18620
|
-
tables.add(table2);
|
|
18621
|
-
else
|
|
18622
|
-
tables.delete(table2);
|
|
19167
|
+
function updateScopedTableList(stateKey, scopeKey, mutate) {
|
|
19168
|
+
const current = { ...dbUiState[stateKey] || {} };
|
|
19169
|
+
const tables = new Set(current[scopeKey] || []);
|
|
19170
|
+
mutate(tables);
|
|
18623
19171
|
const nextTables = [...tables].sort((a2, b2) => a2.localeCompare(b2));
|
|
18624
19172
|
if (nextTables.length > 0)
|
|
18625
|
-
|
|
19173
|
+
current[scopeKey] = nextTables;
|
|
18626
19174
|
else
|
|
18627
|
-
delete
|
|
18628
|
-
const
|
|
19175
|
+
delete current[scopeKey];
|
|
19176
|
+
const nextMap = Object.keys(current).length > 0 ? current : undefined;
|
|
18629
19177
|
const nextState = { ...dbUiState, version: 1 };
|
|
18630
|
-
if (
|
|
18631
|
-
nextState
|
|
19178
|
+
if (nextMap)
|
|
19179
|
+
nextState[stateKey] = nextMap;
|
|
18632
19180
|
else
|
|
18633
|
-
delete nextState
|
|
19181
|
+
delete nextState[stateKey];
|
|
18634
19182
|
applyDbUiState(nextState);
|
|
18635
19183
|
fetch("/_db/ui", {
|
|
18636
19184
|
method: "PATCH",
|
|
18637
19185
|
headers: actionHeaders(),
|
|
18638
19186
|
body: JSON.stringify({
|
|
18639
|
-
|
|
19187
|
+
[stateKey]: {
|
|
18640
19188
|
[scopeKey]: nextTables.length > 0 ? nextTables : null
|
|
18641
19189
|
}
|
|
18642
19190
|
})
|
|
@@ -18644,6 +19192,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18644
19192
|
return;
|
|
18645
19193
|
});
|
|
18646
19194
|
}
|
|
19195
|
+
function getExpandedTables(scopeKey) {
|
|
19196
|
+
return [...dbUiState.expandedTables?.[scopeKey] || []];
|
|
19197
|
+
}
|
|
19198
|
+
function setExpandedTable(scopeKey, table2, expanded) {
|
|
19199
|
+
updateScopedTableList("expandedTables", scopeKey, (tables) => {
|
|
19200
|
+
if (expanded)
|
|
19201
|
+
tables.add(table2);
|
|
19202
|
+
else
|
|
19203
|
+
tables.delete(table2);
|
|
19204
|
+
});
|
|
19205
|
+
}
|
|
19206
|
+
function getSnapshotSelectedTables(scopeKey) {
|
|
19207
|
+
return [...dbUiState.snapshotSelectedTables?.[scopeKey] || []];
|
|
19208
|
+
}
|
|
19209
|
+
function setSnapshotSelectedTables(scopeKey, tables) {
|
|
19210
|
+
updateScopedTableList("snapshotSelectedTables", scopeKey, (set2) => {
|
|
19211
|
+
set2.clear();
|
|
19212
|
+
for (const t2 of tables)
|
|
19213
|
+
set2.add(t2);
|
|
19214
|
+
});
|
|
19215
|
+
}
|
|
18647
19216
|
const dbUiPrefListeners = new Set;
|
|
18648
19217
|
function applyDbUiState(next) {
|
|
18649
19218
|
dbUiState = next;
|
|
@@ -18865,12 +19434,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18865
19434
|
if (!entry)
|
|
18866
19435
|
return;
|
|
18867
19436
|
const s2 = entry.pane.getState();
|
|
19437
|
+
const isSnapshot = s2.view === "snapshot";
|
|
19438
|
+
let diffBefore;
|
|
19439
|
+
let diffAfter;
|
|
19440
|
+
if (isSnapshot) {
|
|
19441
|
+
const url = new URL(window.location.href);
|
|
19442
|
+
diffBefore = url.searchParams.get("diffBefore") ?? undefined;
|
|
19443
|
+
diffAfter = url.searchParams.get("diffAfter") ?? undefined;
|
|
19444
|
+
}
|
|
18868
19445
|
deps.setRoute({
|
|
18869
19446
|
screen: "database",
|
|
18870
19447
|
db: s2.dbId ?? undefined,
|
|
18871
19448
|
schema: s2.schema ?? undefined,
|
|
18872
19449
|
table: s2.table ?? undefined,
|
|
18873
19450
|
tab: s2.view === "data" ? undefined : s2.view,
|
|
19451
|
+
...diffBefore ? { diffBefore } : {},
|
|
19452
|
+
...diffAfter ? { diffAfter } : {},
|
|
18874
19453
|
range: deps.currentRange()
|
|
18875
19454
|
}, true);
|
|
18876
19455
|
}
|
|
@@ -19120,6 +19699,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19120
19699
|
setColumnWidths,
|
|
19121
19700
|
getExpandedTables,
|
|
19122
19701
|
setExpandedTable,
|
|
19702
|
+
getSnapshotSelectedTables,
|
|
19703
|
+
setSnapshotSelectedTables,
|
|
19123
19704
|
getDbUiPref,
|
|
19124
19705
|
setDbUiPref,
|
|
19125
19706
|
onDbUiPrefChange,
|