feat: persist workspace tree expanded state across refreshes
Store expanded directory paths in localStorage keyed by workspace path
(key: 'hermes-webui-expanded:{workspacePath}'). On root load (loadDir('.')),
restore the saved set for the current workspace and pre-fetch dir contents
for any restored expanded directories so the tree renders fully on first
paint without requiring a second click to expand.
Saves on every expand/collapse toggle. Switching workspaces automatically
picks up that workspace's own saved state. Per-workspace (not per-session)
so the same tree state is shared across sessions using the same workspace,
which is the natural expectation.
This commit is contained in:
@@ -919,9 +919,11 @@ function _renderTreeItems(container, entries, depth){
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if(S._expandedDirs.has(item.path)){
|
if(S._expandedDirs.has(item.path)){
|
||||||
S._expandedDirs.delete(item.path);
|
S._expandedDirs.delete(item.path);
|
||||||
|
if(typeof _saveExpandedDirs==='function')_saveExpandedDirs();
|
||||||
renderFileTree();
|
renderFileTree();
|
||||||
}else{
|
}else{
|
||||||
S._expandedDirs.add(item.path);
|
S._expandedDirs.add(item.path);
|
||||||
|
if(typeof _saveExpandedDirs==='function')_saveExpandedDirs();
|
||||||
// Fetch children if not cached
|
// Fetch children if not cached
|
||||||
if(!S._dirCache[item.path]){
|
if(!S._dirCache[item.path]){
|
||||||
try{
|
try{
|
||||||
|
|||||||
@@ -12,13 +12,46 @@ async function api(path,opts={}){
|
|||||||
return ct.includes('application/json')?res.json():res.text();
|
return ct.includes('application/json')?res.json():res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist/restore expanded directory state per workspace in localStorage
|
||||||
|
function _wsExpandKey(){
|
||||||
|
const ws=S.session&&S.session.workspace;
|
||||||
|
return ws?'hermes-webui-expanded:'+ws:null;
|
||||||
|
}
|
||||||
|
function _saveExpandedDirs(){
|
||||||
|
const key=_wsExpandKey();if(!key)return;
|
||||||
|
try{localStorage.setItem(key,JSON.stringify([...(S._expandedDirs||new Set())]));}catch(e){}
|
||||||
|
}
|
||||||
|
function _restoreExpandedDirs(){
|
||||||
|
const key=_wsExpandKey();
|
||||||
|
if(!key){S._expandedDirs=new Set();return;}
|
||||||
|
try{
|
||||||
|
const raw=localStorage.getItem(key);
|
||||||
|
S._expandedDirs=raw?new Set(JSON.parse(raw)):new Set();
|
||||||
|
}catch(e){S._expandedDirs=new Set();}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadDir(path){
|
async function loadDir(path){
|
||||||
if(!S.session)return;
|
if(!S.session)return;
|
||||||
try{
|
try{
|
||||||
if(!path||path==='.'){ S._dirCache={}; if(S._expandedDirs)S._expandedDirs=new Set(); }
|
if(!path||path==='.'){
|
||||||
|
S._dirCache={};
|
||||||
|
_restoreExpandedDirs(); // restore per-workspace expanded state on root load
|
||||||
|
}
|
||||||
S.currentDir=path||'.';
|
S.currentDir=path||'.';
|
||||||
const data=await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`);
|
const data=await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`);
|
||||||
S.entries=data.entries||[];renderBreadcrumb();renderFileTree();
|
S.entries=data.entries||[];renderBreadcrumb();renderFileTree();
|
||||||
|
// Pre-fetch contents of restored expanded dirs so they render without a second click
|
||||||
|
if(!path||path==='.'){
|
||||||
|
for(const dirPath of (S._expandedDirs||[])){
|
||||||
|
if(!S._dirCache[dirPath]){
|
||||||
|
try{
|
||||||
|
const dc=await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(dirPath)}`);
|
||||||
|
S._dirCache[dirPath]=dc.entries||[];
|
||||||
|
}catch(e2){S._dirCache[dirPath]=[];}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(S._expandedDirs&&S._expandedDirs.size>0)renderFileTree();
|
||||||
|
}
|
||||||
if(typeof clearPreview==='function'){
|
if(typeof clearPreview==='function'){
|
||||||
if(typeof _previewDirty!=='undefined'&&_previewDirty){
|
if(typeof _previewDirty!=='undefined'&&_previewDirty){
|
||||||
if(confirm('You have unsaved changes in the preview. Discard and navigate?'))clearPreview();
|
if(confirm('You have unsaved changes in the preview. Discard and navigate?'))clearPreview();
|
||||||
|
|||||||
Reference in New Issue
Block a user