Save tree state

This commit is contained in:
2026-03-16 13:36:04 -05:00
parent 1e78ea2c52
commit fc4e0a6721
3 changed files with 81 additions and 2 deletions
+77
View File
@@ -1,5 +1,7 @@
import { getSortedKeys } from './sort.js';
const STORAGE_KEY = 'tree-state';
let treeContainer = null;
let onSelectionChangeHandler = null;
@@ -124,6 +126,7 @@ function createTreeNode(name, value, path) {
event.stopPropagation();
childrenContainer.classList.toggle('collapsed');
toggle.textContent = childrenContainer.classList.contains('collapsed') ? '▶' : '▼';
saveTreeState();
});
container.appendChild(toggle);
@@ -213,6 +216,7 @@ function handleCheckboxChange(event) {
if (typeof onSelectionChangeHandler === 'function') {
onSelectionChangeHandler();
}
saveTreeState();
}
function updateParentCheckboxes(checkbox) {
@@ -250,3 +254,76 @@ function updateParentCheckboxes(checkbox) {
parentItem = currentItem.parentElement.closest('.tree-item');
}
}
function saveTreeState() {
if (!treeContainer) {
return;
}
const checkedPaths = Array.from(
treeContainer.querySelectorAll('input[type="checkbox"]:checked[data-has-files="1"]')
).map(cb => cb.dataset.path).filter(Boolean);
const collapsedPaths = Array.from(
treeContainer.querySelectorAll('.tree-children.collapsed')
).map(children => {
const cb = children.parentElement.querySelector(':scope > label > input[type="checkbox"]');
return cb?.dataset.path ?? null;
}).filter(Boolean);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ checkedPaths, collapsedPaths }));
} catch {
// Storage unavailable or quota exceeded — silently ignore
}
}
export function restoreTreeState() {
if (!treeContainer) {
return;
}
let state;
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) {
return;
}
state = JSON.parse(raw);
} catch {
return;
}
if (!state || typeof state !== 'object') {
return;
}
if (Array.isArray(state.checkedPaths) && state.checkedPaths.length > 0) {
const checkedSet = new Set(state.checkedPaths);
treeContainer.querySelectorAll('input[type="checkbox"][data-has-files="1"]').forEach(cb => {
cb.checked = checkedSet.has(cb.dataset.path);
});
treeContainer.querySelectorAll('input[type="checkbox"][data-has-files="1"]:checked').forEach(cb => {
updateParentCheckboxes(cb);
});
}
if (Array.isArray(state.collapsedPaths)) {
const collapsedSet = new Set(state.collapsedPaths);
treeContainer.querySelectorAll('.tree-children').forEach(childrenDiv => {
const cb = childrenDiv.parentElement.querySelector(':scope > label > input[type="checkbox"]');
if (!cb) {
return;
}
const toggle = childrenDiv.parentElement.querySelector(':scope > .tree-toggle');
const shouldBeCollapsed = collapsedSet.has(cb.dataset.path);
const isCollapsed = childrenDiv.classList.contains('collapsed');
if (shouldBeCollapsed !== isCollapsed) {
childrenDiv.classList.toggle('collapsed');
if (toggle) {
toggle.textContent = shouldBeCollapsed ? '▶' : '▼';
}
}
});
}
}
+3 -1
View File
@@ -1,5 +1,5 @@
import { loadIssuesForCheckedPaths } from './js/issues.js';
import { getCheckedFileRefs, initializeTree, renderProductTree } from './js/tree.js';
import { getCheckedFileRefs, initializeTree, renderProductTree, restoreTreeState } from './js/tree.js';
import { applyIssueSearchFilter, getIssueTypeFilters, initializeUI } from './js/ui.js';
document.addEventListener('DOMContentLoaded', () => {
@@ -24,6 +24,8 @@ function loadProductTree() {
.then(data => {
productsData = expandFilePrefixLevel(data);
renderProductTree(productsData);
restoreTreeState();
refreshIssuesForCurrentSelection();
})
.catch(error => console.error('Error loading products:', error));
}