Refine iOS-style session swipe actions
This commit is contained in:
parent
b4fa00b793
commit
e2bff58964
3 changed files with 60 additions and 13 deletions
|
|
@ -1908,12 +1908,15 @@ async function _archiveSession(session, archived=true, beforeListRender=null){
|
|||
try{
|
||||
const response=await api('/api/session/archive',{method:'POST',body:JSON.stringify({session_id:session.session_id,archived})});
|
||||
session.archived=archived;
|
||||
const cached=(_allSessions||[]).find(s=>s&&s.session_id===session.session_id);
|
||||
if(cached) cached.archived=archived;
|
||||
if(S.session&&S.session.session_id===session.session_id) S.session.archived=archived;
|
||||
showToast(session.archived?_sessionArchiveToast(response,session):t('session_restored'));
|
||||
if(renderHold) await renderHold;
|
||||
if(_showArchived&&!_sessionPrefersReducedMotion()) _sessionSwipeReturnOffsets.set(session.session_id,'0px');
|
||||
_pendingSessionReflowPositions=reflowPositions;
|
||||
await renderSessionList();
|
||||
showToast(session.archived?_sessionArchiveToast(response,session):t('session_restored'));
|
||||
renderSessionListFromCache();
|
||||
void renderSessionList();
|
||||
return true;
|
||||
}catch(err){if(renderHold) await renderHold.catch(()=>{});_pendingSessionReflowPositions=null;showToast(t('session_archive_failed')+err.message);return false;}
|
||||
}
|
||||
|
|
@ -3767,7 +3770,7 @@ function renderSessionListFromCache(){
|
|||
|
||||
if(!readOnly){
|
||||
el.append(
|
||||
_makeSessionSwipeAffordance('right',s.archived?'undo':'archive',s.archived?t('session_restore'):t('session_batch_archive')),
|
||||
_makeSessionSwipeAffordance('right',s.archived?'undo':'archive',s.archived?'Restore':t('session_batch_archive')),
|
||||
_makeSessionSwipeAffordance('left','trash-2',t('session_batch_delete')),
|
||||
);
|
||||
}
|
||||
|
|
@ -3860,10 +3863,12 @@ function renderSessionListFromCache(){
|
|||
const offset=Math.sign(rawOffset)*(Math.abs(revealedOffset)+Math.sqrt(overshoot)*5);
|
||||
const progress=Math.min(1,Math.abs(revealedOffset)/72);
|
||||
const reveal=Math.abs(offset);
|
||||
const iconScale=1+Math.min(.45,Math.max(0,Math.abs(rawOffset)-52)/130);
|
||||
const iconScale=Math.min(1,Math.max(.01,progress*1.12));
|
||||
const badgeStretch=Math.min(Math.max(0,reveal-34),overshoot*1.15);
|
||||
el.style.setProperty('--session-swipe-offset',offset+'px');
|
||||
el.style.setProperty('--session-swipe-reveal',reveal+'px');
|
||||
el.style.setProperty('--session-swipe-icon-scale',iconScale);
|
||||
el.style.setProperty('--session-swipe-badge-stretch',badgeStretch+'px');
|
||||
el.style.setProperty('--session-swipe-progress',Math.pow(progress,1.5));
|
||||
el.classList.toggle('swiping-right',offset>0);
|
||||
el.classList.toggle('swiping-left',offset<0);
|
||||
|
|
@ -3872,6 +3877,7 @@ function renderSessionListFromCache(){
|
|||
el.style.removeProperty('--session-swipe-offset');
|
||||
el.style.removeProperty('--session-swipe-reveal');
|
||||
el.style.removeProperty('--session-swipe-icon-scale');
|
||||
el.style.removeProperty('--session-swipe-badge-stretch');
|
||||
el.style.removeProperty('--session-swipe-progress');
|
||||
el.style.removeProperty('height');
|
||||
el.style.removeProperty('min-height');
|
||||
|
|
|
|||
|
|
@ -735,7 +735,7 @@
|
|||
justify-content:center;
|
||||
gap:3px;
|
||||
width:var(--session-swipe-reveal,0px);
|
||||
padding:0 8px;
|
||||
padding:0;
|
||||
box-sizing:border-box;
|
||||
opacity:0;
|
||||
overflow:hidden;
|
||||
|
|
@ -746,16 +746,18 @@
|
|||
.session-swipe-affordance-right{
|
||||
left:0;
|
||||
color:#fff;
|
||||
background:var(--warning);
|
||||
border-radius:8px 6px 6px 8px;
|
||||
background:transparent;
|
||||
border-radius:0;
|
||||
transform-origin:left center;
|
||||
--session-swipe-action-color:var(--warning);
|
||||
}
|
||||
.session-swipe-affordance-left{
|
||||
right:0;
|
||||
color:#fff;
|
||||
background:var(--error);
|
||||
border-radius:6px 8px 8px 6px;
|
||||
background:transparent;
|
||||
border-radius:0;
|
||||
transform-origin:right center;
|
||||
--session-swipe-action-color:var(--error);
|
||||
}
|
||||
.session-item.swiping-right .session-swipe-affordance-right,
|
||||
.session-item.swiping-left .session-swipe-affordance-left{
|
||||
|
|
@ -763,13 +765,15 @@
|
|||
}
|
||||
.session-text,.session-state-indicator,.session-actions{z-index:1;}
|
||||
.session-swipe-badge{
|
||||
width:28px;
|
||||
height:28px;
|
||||
border-radius:0;
|
||||
width:calc(34px + var(--session-swipe-badge-stretch,0px));
|
||||
height:34px;
|
||||
border-radius:999px;
|
||||
background:var(--session-swipe-action-color);
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
color:#fff;
|
||||
flex:0 0 auto;
|
||||
transform:scale(var(--session-swipe-icon-scale,1));
|
||||
transform-origin:center;
|
||||
}
|
||||
|
|
@ -804,7 +808,7 @@
|
|||
.session-item.active{background:var(--accent-bg);color:var(--accent);}
|
||||
.session-item.active.swiping-right{background:color-mix(in srgb,var(--warning) 20%,var(--accent-bg));}
|
||||
.session-item.active.swiping-left{background:color-mix(in srgb,var(--error) 18%,var(--accent-bg));}
|
||||
.session-item.archived .session-swipe-affordance-right{background:var(--success);}
|
||||
.session-item.archived .session-swipe-affordance-right{--session-swipe-action-color:var(--success);}
|
||||
.session-item.archived.swiping-right,
|
||||
.session-item.archived.dragging.swiping-right{background:color-mix(in srgb,var(--success) 16%,var(--surface));box-shadow:0 0 0 1px color-mix(in srgb,var(--success) 48%,transparent);}
|
||||
.session-item.active.archived.swiping-right{background:color-mix(in srgb,var(--success) 20%,var(--accent-bg));}
|
||||
|
|
|
|||
|
|
@ -121,12 +121,16 @@ def test_session_swipe_paint_uses_transform_only_exit():
|
|||
assert "--session-swipe-offset" in paint
|
||||
assert "--session-swipe-reveal" in paint
|
||||
assert "--session-swipe-progress" in paint
|
||||
assert "--session-swipe-badge-stretch" in paint
|
||||
assert "const reveal=Math.abs(offset);" in paint
|
||||
assert "const iconScale=Math.min(1,Math.max(.01,progress*1.12));" in paint
|
||||
assert "const badgeStretch=Math.min(Math.max(0,reveal-34),overshoot*1.15);" in paint
|
||||
assert "window.innerWidth+'px'" in complete
|
||||
assert "el.style.height=rect.height+'px'" in complete
|
||||
assert "requestAnimationFrame(()=>el.classList.add('swipe-removing'))" in complete
|
||||
assert "requestAnimationFrame(()=>requestAnimationFrame(_clearSessionSwipePaint))" in SESSIONS_JS
|
||||
assert "el.classList.remove('swiping-right','swiping-left','swipe-committed','swipe-removing')" in clear
|
||||
assert "el.style.removeProperty('--session-swipe-badge-stretch');" in clear
|
||||
assert ".session-item.swipe-committed,\n .session-item.swipe-removing{transition:" in STYLE_CSS
|
||||
assert "transform:translate3d(calc(-1 * var(--session-swipe-offset,0px)),0,0)" in STYLE_CSS
|
||||
assert "transform:scale(var(--session-swipe-icon-scale,1))" in STYLE_CSS
|
||||
|
|
@ -140,6 +144,29 @@ def test_session_swipe_paint_uses_transform_only_exit():
|
|||
assert "transform:translate3d(var(--session-swipe-offset,0),var(--session-reflow-offset,0),0)" in STYLE_CSS
|
||||
|
||||
|
||||
def test_session_swipe_actions_use_circular_icon_badges():
|
||||
right_start = STYLE_CSS.find(".session-swipe-affordance-right{")
|
||||
left_start = STYLE_CSS.find(".session-swipe-affordance-left{")
|
||||
badge_start = STYLE_CSS.find(".session-swipe-badge{")
|
||||
right = STYLE_CSS[right_start:left_start]
|
||||
left = STYLE_CSS[left_start:STYLE_CSS.find(".session-item.swiping-right", left_start)]
|
||||
badge = STYLE_CSS[badge_start:STYLE_CSS.find(".session-swipe-badge svg", badge_start)]
|
||||
assert "background:transparent" in right
|
||||
assert "background:transparent" in left
|
||||
assert "--session-swipe-action-color:var(--warning)" in right
|
||||
assert "--session-swipe-action-color:var(--error)" in left
|
||||
assert "width:calc(34px + var(--session-swipe-badge-stretch,0px))" in badge
|
||||
assert "height:34px" in badge
|
||||
assert "border-radius:999px" in badge
|
||||
assert "background:var(--session-swipe-action-color)" in badge
|
||||
assert ".session-item.archived .session-swipe-affordance-right{--session-swipe-action-color:var(--success);}" in STYLE_CSS
|
||||
assert "_makeSessionSwipeAffordance('right',s.archived?'undo':'archive',s.archived?'Restore':t('session_batch_archive'))" in SESSIONS_JS
|
||||
label = STYLE_CSS[STYLE_CSS.find(".session-swipe-label{"):STYLE_CSS.find(".session-item.dragging")]
|
||||
assert "session-swipe-label" in SESSIONS_JS
|
||||
assert "max-width:58px" in label
|
||||
assert "font-size:10px" in label
|
||||
|
||||
|
||||
def test_session_removal_reflows_surviving_rows_smoothly():
|
||||
assert "let _pendingSessionReflowPositions = null;" in SESSIONS_JS
|
||||
assert "const _optimisticallyRemovedSessionIds = new Set();" in SESSIONS_JS
|
||||
|
|
@ -159,6 +186,16 @@ def test_session_removal_reflows_surviving_rows_smoothly():
|
|||
assert "_playSessionRowsReflowFromPositions(reflowBefore,reflowTimeout,_sessionPrefersReducedMotion);" in SESSIONS_JS
|
||||
assert "async function _archiveSession(session, archived=true, beforeListRender=null){" in SESSIONS_JS
|
||||
assert "const renderHold=beforeListRender?Promise.resolve().then(beforeListRender):null;" in SESSIONS_JS
|
||||
assert "const cached=(_allSessions||[]).find(s=>s&&s.session_id===session.session_id);" in SESSIONS_JS
|
||||
assert "if(cached) cached.archived=archived;" in SESSIONS_JS
|
||||
archive_start = SESSIONS_JS.find("async function _archiveSession(session, archived=true, beforeListRender=null){")
|
||||
archive_end = SESSIONS_JS.find("function _openSessionActionMenu", archive_start)
|
||||
archive_body = SESSIONS_JS[archive_start:archive_end]
|
||||
toast_idx = archive_body.find("showToast(session.archived?_sessionArchiveToast(response,session):t('session_restored'));")
|
||||
hold_idx = archive_body.find("if(renderHold) await renderHold;")
|
||||
cache_render_idx = archive_body.find("renderSessionListFromCache();")
|
||||
reconcile_idx = archive_body.find("void renderSessionList();")
|
||||
assert 0 <= toast_idx < hold_idx < cache_render_idx < reconcile_idx
|
||||
assert "if(renderHold) await renderHold;" in SESSIONS_JS
|
||||
assert "const serverSessions=_optimisticallyRemovedSessionIds.size" in SESSIONS_JS
|
||||
assert "? (sessData.sessions||[]).filter(s=>s&&!_optimisticallyRemovedSessionIds.has(s.session_id))" in SESSIONS_JS
|
||||
|
|
|
|||
Loading…
Reference in a new issue