Merge pull request #25 from nesquena/fix/project-picker-overflow
fix: project picker clipped and width issues
This commit is contained in:
@@ -287,7 +287,8 @@ function renderSessionListFromCache(){
|
|||||||
trash.onclick=async(e)=>{e.stopPropagation();e.preventDefault();await deleteSession(s.session_id);};
|
trash.onclick=async(e)=>{e.stopPropagation();e.preventDefault();await deleteSession(s.session_id);};
|
||||||
// Project move button (folder icon)
|
// Project move button (folder icon)
|
||||||
const move=document.createElement('button');
|
const move=document.createElement('button');
|
||||||
move.className='session-action-btn'+(s.project_id?' has-project':'');move.innerHTML='📂';move.title='Move to project';
|
move.className='session-action-btn'+(s.project_id?' has-project':'');
|
||||||
|
move.innerHTML='📂';move.title='Move to project';
|
||||||
move.onclick=async(e)=>{e.stopPropagation();e.preventDefault();_showProjectPicker(s,move);};
|
move.onclick=async(e)=>{e.stopPropagation();e.preventDefault();_showProjectPicker(s,move);};
|
||||||
// Project dot indicator
|
// Project dot indicator
|
||||||
if(s.project_id){
|
if(s.project_id){
|
||||||
@@ -360,15 +361,13 @@ function _showProjectPicker(session, anchorEl){
|
|||||||
document.querySelectorAll('.project-picker').forEach(p=>p.remove());
|
document.querySelectorAll('.project-picker').forEach(p=>p.remove());
|
||||||
const picker=document.createElement('div');
|
const picker=document.createElement('div');
|
||||||
picker.className='project-picker';
|
picker.className='project-picker';
|
||||||
// Close on outside click
|
|
||||||
const close=(e)=>{if(!picker.contains(e.target)&&e.target!==anchorEl){picker.remove();document.removeEventListener('click',close);}};
|
|
||||||
// "No project" option
|
// "No project" option
|
||||||
const none=document.createElement('div');
|
const none=document.createElement('div');
|
||||||
none.className='project-picker-item'+(!session.project_id?' active':'');
|
none.className='project-picker-item'+(!session.project_id?' active':'');
|
||||||
none.textContent='No project';
|
none.textContent='No project';
|
||||||
none.onclick=async()=>{
|
none.onclick=async()=>{
|
||||||
document.removeEventListener('click',close);
|
|
||||||
picker.remove();
|
picker.remove();
|
||||||
|
document.removeEventListener('click',close);
|
||||||
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:null})});
|
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:null})});
|
||||||
session.project_id=null;
|
session.project_id=null;
|
||||||
renderSessionListFromCache();
|
renderSessionListFromCache();
|
||||||
@@ -389,8 +388,8 @@ function _showProjectPicker(session, anchorEl){
|
|||||||
name.textContent=p.name;
|
name.textContent=p.name;
|
||||||
item.appendChild(name);
|
item.appendChild(name);
|
||||||
item.onclick=async()=>{
|
item.onclick=async()=>{
|
||||||
document.removeEventListener('click',close);
|
|
||||||
picker.remove();
|
picker.remove();
|
||||||
|
document.removeEventListener('click',close);
|
||||||
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:p.project_id})});
|
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:p.project_id})});
|
||||||
session.project_id=p.project_id;
|
session.project_id=p.project_id;
|
||||||
renderSessionListFromCache();
|
renderSessionListFromCache();
|
||||||
@@ -398,19 +397,21 @@ function _showProjectPicker(session, anchorEl){
|
|||||||
};
|
};
|
||||||
picker.appendChild(item);
|
picker.appendChild(item);
|
||||||
}
|
}
|
||||||
// "+ New project" item
|
// "+ New project" shortcut at the bottom
|
||||||
const createItem=document.createElement('div');
|
const createItem=document.createElement('div');
|
||||||
createItem.className='project-picker-item project-picker-create';
|
createItem.className='project-picker-item project-picker-create';
|
||||||
createItem.textContent='+ New project';
|
createItem.textContent='+ New project';
|
||||||
createItem.onclick=async()=>{
|
createItem.onclick=async()=>{
|
||||||
picker.remove();
|
picker.remove();
|
||||||
document.removeEventListener('click',close);
|
document.removeEventListener('click',close);
|
||||||
|
// Prompt for name inline
|
||||||
const name=prompt('Project name:');
|
const name=prompt('Project name:');
|
||||||
if(!name||!name.trim()) return;
|
if(!name||!name.trim()) return;
|
||||||
const color=PROJECT_COLORS[_allProjects.length%PROJECT_COLORS.length];
|
const color=PROJECT_COLORS[_allProjects.length%PROJECT_COLORS.length];
|
||||||
const res=await api('/api/projects/create',{method:'POST',body:JSON.stringify({name:name.trim(),color})});
|
const res=await api('/api/projects/create',{method:'POST',body:JSON.stringify({name:name.trim(),color})});
|
||||||
if(res.project){
|
if(res.project){
|
||||||
_allProjects.push(res.project);
|
_allProjects.push(res.project);
|
||||||
|
// Now move session into it
|
||||||
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:res.project.project_id})});
|
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:res.project.project_id})});
|
||||||
session.project_id=res.project.project_id;
|
session.project_id=res.project.project_id;
|
||||||
await renderSessionList();
|
await renderSessionList();
|
||||||
@@ -418,11 +419,13 @@ function _showProjectPicker(session, anchorEl){
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
picker.appendChild(createItem);
|
picker.appendChild(createItem);
|
||||||
// Position picker on document.body to avoid overflow:hidden clipping
|
// Append to body and position using getBoundingClientRect so it isn't clipped
|
||||||
|
// by overflow:hidden on .session-item ancestors
|
||||||
document.body.appendChild(picker);
|
document.body.appendChild(picker);
|
||||||
const rect=anchorEl.getBoundingClientRect();
|
const rect=anchorEl.getBoundingClientRect();
|
||||||
picker.style.position='fixed';
|
picker.style.position='fixed';
|
||||||
picker.style.zIndex='999';
|
picker.style.zIndex='999';
|
||||||
|
// Prefer opening below; flip above if too close to bottom of viewport
|
||||||
const spaceBelow=window.innerHeight-rect.bottom;
|
const spaceBelow=window.innerHeight-rect.bottom;
|
||||||
if(spaceBelow<160&&rect.top>160){
|
if(spaceBelow<160&&rect.top>160){
|
||||||
picker.style.bottom=(window.innerHeight-rect.top+4)+'px';
|
picker.style.bottom=(window.innerHeight-rect.top+4)+'px';
|
||||||
@@ -431,10 +434,13 @@ function _showProjectPicker(session, anchorEl){
|
|||||||
picker.style.top=(rect.bottom+4)+'px';
|
picker.style.top=(rect.bottom+4)+'px';
|
||||||
picker.style.bottom='auto';
|
picker.style.bottom='auto';
|
||||||
}
|
}
|
||||||
const pickerW=160;
|
// Align right edge of picker with right edge of button; keep within viewport
|
||||||
|
const pickerW=Math.min(220,Math.max(160,picker.scrollWidth||160));
|
||||||
let left=rect.right-pickerW;
|
let left=rect.right-pickerW;
|
||||||
if(left<8) left=8;
|
if(left<8) left=8;
|
||||||
picker.style.left=left+'px';
|
picker.style.left=left+'px';
|
||||||
|
// Close on outside click
|
||||||
|
const close=(e)=>{if(!picker.contains(e.target)&&e.target!==anchorEl){picker.remove();document.removeEventListener('click',close);}};
|
||||||
setTimeout(()=>document.addEventListener('click',close),0);
|
setTimeout(()=>document.addEventListener('click',close),0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ body.resizing{user-select:none;cursor:col-resize;}
|
|||||||
.project-create-btn{font-size:10px;padding:3px 6px;border-radius:12px;cursor:pointer;border:1px dashed var(--border2);background:none;color:var(--muted);opacity:.6;transition:all .15s;}
|
.project-create-btn{font-size:10px;padding:3px 6px;border-radius:12px;cursor:pointer;border:1px dashed var(--border2);background:none;color:var(--muted);opacity:.6;transition:all .15s;}
|
||||||
.project-create-btn:hover{opacity:1;border-color:var(--blue);color:var(--blue);}
|
.project-create-btn:hover{opacity:1;border-color:var(--blue);color:var(--blue);}
|
||||||
.project-create-input{font-size:10px;padding:3px 8px;border-radius:12px;border:1px solid rgba(124,185,255,.6);background:rgba(20,32,60,.9);color:var(--text);outline:none;width:100px;font-family:inherit;box-shadow:0 0 0 2px rgba(124,185,255,.15);}
|
.project-create-input{font-size:10px;padding:3px 8px;border-radius:12px;border:1px solid rgba(124,185,255,.6);background:rgba(20,32,60,.9);color:var(--text);outline:none;width:100px;font-family:inherit;box-shadow:0 0 0 2px rgba(124,185,255,.15);}
|
||||||
.project-picker{position:absolute;right:0;top:100%;background:var(--sidebar);border:1px solid var(--border2);border-radius:8px;padding:4px;z-index:30;min-width:140px;max-width:220px;width:max-content;box-shadow:0 4px 16px rgba(0,0,0,.3);}
|
.project-picker{position:absolute;right:0;top:100%;background:var(--sidebar);border:1px solid var(--border2);border-radius:8px;padding:4px;z-index:30;min-width:160px;max-width:220px;width:max-content;box-shadow:0 4px 16px rgba(0,0,0,.3);}
|
||||||
.project-picker-item{padding:5px 10px;font-size:11px;border-radius:6px;cursor:pointer;color:var(--muted);transition:all .1s;display:flex;align-items:center;gap:6px;}
|
.project-picker-item{padding:5px 10px;font-size:11px;border-radius:6px;cursor:pointer;color:var(--muted);transition:all .1s;display:flex;align-items:center;gap:6px;}
|
||||||
.project-picker-item:hover{background:rgba(255,255,255,.08);color:var(--text);}
|
.project-picker-item:hover{background:rgba(255,255,255,.08);color:var(--text);}
|
||||||
.project-picker-item.active{color:var(--blue);}
|
.project-picker-item.active{color:var(--blue);}
|
||||||
|
|||||||
Reference in New Issue
Block a user