diff --git a/tests/file_manager_fixture.tsx b/tests/file_manager_fixture.tsx index cdeccc6..30d2413 100644 --- a/tests/file_manager_fixture.tsx +++ b/tests/file_manager_fixture.tsx @@ -7,6 +7,7 @@ import '../ui/src/style.css'; const wait=(ms=40)=>new Promise(r=>setTimeout(r,ms)); const until=async(f:()=>unknown)=>{for(let n=0;n<150;n++){if(f())return;await wait();}throw Error('Timeout '+document.body.innerText.slice(-800));}; const home='/home/steamos',downloads=home+'/Downloads'; +const longFolder='一个很长的文件夹名称 - Project documents and archived screenshots'; let preferences:any={view:'list',folders:{[home]:{sort:'size',descending:true},[downloads]:{sort:'modified',descending:false}}}; const calls:any[]=[]; const files=(directory:string)=>[ @@ -26,7 +27,10 @@ window.fetch=async(url,init)=>{ }else if(p.operation==='list'){ let directory=p.path||home;if(directory==='/alias')directory=home; let entries=files(directory); - if(p.hidden)entries=[]; // Tree requests do not consume the sort checks below. + if(p.hidden){ // Tree requests do not consume the sort checks below. + const child=directory==='/'?{name:'home',path:'/home'}:directory==='/home'?{name:'steamos',path:home}:directory===home?{name:longFolder,path:home+'/'+longFolder}:null; + entries=child?[{...child,directory:true,symlink:false,size:0,mode:493,uid:1000,gid:1000}]:[]; + } result={path:directory,parent:'/',entries,total:entries.length,roots:[{name:'Home',path:home},{name:'Downloads',path:downloads}]}; }else if(p.operation==='read')result={text:'fixture text',revision:'revision'}; else throw Error('Unexpected operation '+p.operation); @@ -42,6 +46,13 @@ const changeAddress=async(value:string)=>{const input=document.querySelector{try{ configureLanguage('en-US',[]);render('one');await until(ready); if(listing().sort!=='size'||listing().descending!==true)throw Error('Home sort not restored on initial canonical path'); + await until(()=>[...document.querySelectorAll('.file-tree-entry button>span')].some(node=>node.textContent===longFolder)); + const tree=document.querySelector('.file-tree-scroll')!,name=[...tree.querySelectorAll('button>span')].find(node=>node.textContent===longFolder)!; + if(getComputedStyle(name).textOverflow==='ellipsis'||name.scrollWidth>name.clientWidth+1)throw Error('Tree folder name clipped'); + if(tree.scrollWidth<=tree.clientWidth)throw Error('Tree does not expose horizontal overflow'); + tree.scrollLeft=tree.scrollWidth;if(tree.scrollLeft<=0)throw Error('Cannot scroll long tree names');tree.scrollLeft=0; + if(name.closest('button')?.title!==home+'/'+longFolder)throw Error('Full path tooltip missing'); + await select('View','Large icons');await until(()=>document.querySelector('.file-icon-grid.icons-large')); if(document.querySelector('.file-table'))throw Error('Table remained in icon view'); const checkbox=document.querySelector('.file-tile-check input')!;checkbox.click();await until(()=>document.querySelector('.file-icon-tile.selected')); diff --git a/ui/src/file-manager.css b/ui/src/file-manager.css index b5f4e38..e1bf3ca 100644 --- a/ui/src/file-manager.css +++ b/ui/src/file-manager.css @@ -31,6 +31,10 @@ .file-manager .file-tree-entry{gap:0;min-height:38px} .file-manager .file-tree-entry>button.file-tree-toggle{flex:none;padding:4px;width:26px;min-width:26px;border:0} .file-manager .file-tree-entry>button:last-child{width:auto;flex:1;min-width:0;padding-left:4px;gap:8px} +/* Keep complete tree names on one line; the tree scrolls horizontally. */ +.file-manager .file-tree-scroll>div{width:max-content;min-width:100%} +.file-manager .file-tree-entry>button:last-child{flex:1 0 auto;min-width:max-content} +.file-manager .file-tree-entry>button>span{overflow:visible;text-overflow:clip;white-space:nowrap;flex:none} .file-manager .file-directory-tree .file-tree{padding-left:12px;border-left:1px solid #ffffff0a;margin-left:12px} .file-manager .file-side-entry{display:flex;align-items:center;min-width:0;position:relative} .file-manager .file-side-entry>button:first-child{min-width:0} diff --git a/ui/src/file-manager.tsx b/ui/src/file-manager.tsx index 910854b..e2fd055 100644 --- a/ui/src/file-manager.tsx +++ b/ui/src/file-manager.tsx @@ -29,7 +29,7 @@ function FolderTree({name,path,onOpen,current,depth=0}:{name:string;path:string; }catch(e){if(id===request.current){setError(String(e));setExpanded(true);}}} useEffect(()=>{if(depth<20&&(depth===0||ancestor||current===path))void expand(0,ancestor);return()=>{request.current++;};},[path,current]); useEffect(()=>{if(current!==path||!row.current)return;const node=row.current,sidebar=node.closest('.file-tree-scroll');if(!sidebar)return;const a=node.getBoundingClientRect(),b=sidebar.getBoundingClientRect();if(a.topb.bottom)sidebar.scrollTop+=a.bottom-b.bottom+8;},[current,path]); - return
{error&&expanded&&{error}}{expanded&&
{children.map(e=>)}{more&&}
}
; + return
{error&&expanded&&{error}}{expanded&&
{children.map(e=>)}{more&&}
}
; } export function FileManager({active=true}:{active?:boolean}){ const[data,setData]=useState({path:'',entries:[],total:0,roots:[]}),[path,setPath]=useState(''),[address,setAddress]=useState(''),[query,setQuery]=useState(''),[hidden,setHidden]=useState(false),[offset,setOffset]=useState(0),[selected,setSelected]=useState([]),[bookmarks,setBookmarks]=useState([]),[error,setError]=useState(''),[busy,setBusy]=useState(false),[loading,setLoading]=useState(true),[dialog,setDialog]=useState(null),[value,setValue]=useState(''),[policy,setPolicy]=useState('keep'),[permanent,setPermanent]=useState(false),[job,setJob]=useState(null),[clipboard,setClipboard]=useState<{paths:string[];cut:boolean}|null>(null),[preview,setPreview]=useState<{entry:Entry;url:string;video:boolean}|null>(null),[editor,setEditor]=useState<{path:string;text:string;revision:string}|null>(null),[modified,setModified]=useState(false),[trash,setTrash]=useState(null),[upload,setUpload]=useState<{name:string;received:number;total:number}|null>(null),[results,setResults]=useState(null),[counter,setCounter]=useState(0),[context,setContext]=useState(false),[bookmarkMenu,setBookmarkMenu]=useState(null),[outcomes,setOutcomes]=useState([]),[menu,setMenu]=useState<{x:number;y:number;blank:boolean}|null>(null);