Save tree state
This commit is contained in:
+1
-1
@@ -7,4 +7,4 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"marked": "^16.3.0"
|
"marked": "^16.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { getSortedKeys } from './sort.js';
|
import { getSortedKeys } from './sort.js';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'tree-state';
|
||||||
|
|
||||||
let treeContainer = null;
|
let treeContainer = null;
|
||||||
let onSelectionChangeHandler = null;
|
let onSelectionChangeHandler = null;
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ function createTreeNode(name, value, path) {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
childrenContainer.classList.toggle('collapsed');
|
childrenContainer.classList.toggle('collapsed');
|
||||||
toggle.textContent = childrenContainer.classList.contains('collapsed') ? '▶' : '▼';
|
toggle.textContent = childrenContainer.classList.contains('collapsed') ? '▶' : '▼';
|
||||||
|
saveTreeState();
|
||||||
});
|
});
|
||||||
|
|
||||||
container.appendChild(toggle);
|
container.appendChild(toggle);
|
||||||
@@ -213,6 +216,7 @@ function handleCheckboxChange(event) {
|
|||||||
if (typeof onSelectionChangeHandler === 'function') {
|
if (typeof onSelectionChangeHandler === 'function') {
|
||||||
onSelectionChangeHandler();
|
onSelectionChangeHandler();
|
||||||
}
|
}
|
||||||
|
saveTreeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateParentCheckboxes(checkbox) {
|
function updateParentCheckboxes(checkbox) {
|
||||||
@@ -250,3 +254,76 @@ function updateParentCheckboxes(checkbox) {
|
|||||||
parentItem = currentItem.parentElement.closest('.tree-item');
|
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
@@ -1,5 +1,5 @@
|
|||||||
import { loadIssuesForCheckedPaths } from './js/issues.js';
|
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';
|
import { applyIssueSearchFilter, getIssueTypeFilters, initializeUI } from './js/ui.js';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
@@ -24,6 +24,8 @@ function loadProductTree() {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
productsData = expandFilePrefixLevel(data);
|
productsData = expandFilePrefixLevel(data);
|
||||||
renderProductTree(productsData);
|
renderProductTree(productsData);
|
||||||
|
restoreTreeState();
|
||||||
|
refreshIssuesForCurrentSelection();
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Error loading products:', error));
|
.catch(error => console.error('Error loading products:', error));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user