8beb7331dd
CI / test (push) Has been cancelled
Three problems were stacked:
1. The 200 serving path didn't set currentImage when a locked image was
served (RotationService.advance bypassed). The frame got the locked
photo; the DB kept the previous one; Home showed the old one.
2. The 304 path didn't flush at all. lastSeenAt (markSeen) was lost on
every no-change poll, and any drift in currentImage couldn't self-heal.
For a frame that's been locked for a while, polls cycle as 304 forever
and the DB stays wrong indefinitely.
3. Pull-to-refresh fetched via fetchDevices(), which flips loading=true
and replaces the cards with "Loading…" mid-fetch. The PTR spinner was
working but users couldn't see the result of their refresh.
Fixes:
- Both 200 and 304 paths now set currentImage = $image and flush. The
304 path becomes self-healing for any device whose currentImage drifted
from reality (e.g., from before the 200-path fix).
- fetchDevices / fetchImages take an optional { silent: true } that
skips toggling loading.value. PTR refresh callbacks pass silent so
the cards stay visible during background refresh.
- HomeView also listens on visibilitychange and silently re-fetches when
the PWA returns to foreground, so reopening the app shows current
state without a manual pull.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 line
14 KiB
JavaScript
1 line
14 KiB
JavaScript
import{A as e,G as t,L as n,O as r,R as i,T as a,V as o,_ as s,d as c,f as l,ft as u,g as d,h as f,l as p,o as m,p as h,t as g,u as _,ut as v,z as y}from"./_plugin-vue_export-helper-CeYnMxKK.js";import{a as b,d as x,f as S,i as C,o as w,s as T,u as E}from"./index-DwuxDERh.js";import{i as D,n as O,r as ee,t as k}from"./BaseBottomSheet-lUhdoq2-.js";import{t as te}from"./PullToRefresh-DZt-0188.js";import{t as A}from"./DevicePicker-DB6TSbzz.js";var j={class:`approve-card`},M=[`src`,`alt`],N={class:`approve-card__body`},P={class:`approve-card__from`},F={class:`approve-card__date`},I={key:0,class:`approve-card__status`},L={class:`approve-card__actions`},ne=g(s({__name:`ApproveCard`,props:{item:{}},emits:[`updated`],setup(e,{emit:n}){let a=e,s=n,g=b(),y=D(),x=o(!1),S=o(!1),C=o([]),w=p(()=>new Date(a.item.sharedAt).toLocaleDateString(void 0,{month:`short`,day:`numeric`,year:`numeric`}));async function T(){x.value=!1,S.value=!0;try{s(`updated`,await g.approveShared(a.item.id,C.value))}finally{S.value=!1,C.value=[]}}async function E(){S.value=!0;try{s(`updated`,await g.declineShared(a.item.id))}finally{S.value=!1}}return(n,a)=>(r(),h(m,null,[_(`div`,j,[_(`img`,{src:e.item.thumbnailUrl,alt:`Photo from ${e.item.sharedBy}`,class:`approve-card__thumb`,loading:`lazy`},null,8,M),_(`div`,N,[_(`p`,P,[a[3]||=f(`From `,-1),_(`strong`,null,u(e.item.sharedBy),1)]),_(`p`,F,u(w.value),1),e.item.status===`pending`?l(``,!0):(r(),h(`div`,I,[_(`span`,{class:v([`approve-card__badge`,`approve-card__badge--${e.item.status}`])},u(e.item.status),3)])),_(`div`,L,[e.item.status===`pending`||e.item.status===`declined`?(r(),c(O,{key:0,variant:`primary`,size:`sm`,disabled:S.value,onClick:a[0]||=e=>x.value=!0},{default:i(()=>[f(u(e.item.status===`declined`?`Add anyway`:`Add to frame`),1)]),_:1},8,[`disabled`])):l(``,!0),e.item.status===`pending`||e.item.status===`approved`?(r(),c(O,{key:1,variant:`ghost`,size:`sm`,disabled:S.value,onClick:E},{default:i(()=>[f(u(e.item.status===`approved`?`Remove`:`Decline`),1)]),_:1},8,[`disabled`])):l(``,!0)])])]),d(A,{modelValue:x.value,"onUpdate:modelValue":a[1]||=e=>x.value=e,devices:t(y).devices,selected:C.value,uploading:S.value,"confirm-label":`Add to frames`,"onUpdate:selected":a[2]||=e=>C.value=e,onConfirm:T},null,8,[`modelValue`,`devices`,`selected`,`uploading`])],64))}}),[[`__scopeId`,`data-v-6d3dd8b4`]]),R={class:`share-sheet__field`},z=[`onKeydown`],B={key:0,class:`share-sheet__error`},V={key:1,class:`share-sheet__success`},re=g(s({__name:`ShareSheet`,props:{modelValue:{type:Boolean},imageId:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,a=b(),s=o(``),p=o(!1),m=o(``),g=o(``);async function v(){if(m.value=``,g.value=``,s.value.trim()){p.value=!0;try{await a.shareImage(n.imageId,s.value.trim()),g.value=`Invite sent to ${s.value.trim()}`,s.value=``}catch(e){m.value=e instanceof Error?e.message:`Failed to send`}finally{p.value=!1}}}return(t,n)=>(r(),c(k,{"model-value":e.modelValue,label:`Share photo`,"onUpdate:modelValue":n[1]||=e=>t.$emit(`update:modelValue`,e)},{default:i(()=>[n[2]||=_(`h2`,{class:`share-sheet__title`},`Share with someone`,-1),n[3]||=_(`p`,{class:`share-sheet__sub`},`They'll get an email and can add it to their frame.`,-1),_(`div`,R,[y(_(`input`,{"onUpdate:modelValue":n[0]||=e=>s.value=e,type:`email`,class:`share-sheet__input`,placeholder:`their@email.com`,autocomplete:`email`,onKeydown:x(S(v,[`prevent`]),[`enter`])},null,40,z),[[E,s.value]])]),m.value?(r(),h(`p`,B,u(m.value),1)):l(``,!0),g.value?(r(),h(`p`,V,u(g.value),1)):l(``,!0),d(O,{variant:`primary`,class:`share-sheet__btn`,disabled:p.value||!s.value.trim(),onClick:v},{default:i(()=>[f(u(p.value?`Sending…`:`Send invite`),1)]),_:1},8,[`disabled`])]),_:1},8,[`model-value`]))}}),[[`__scopeId`,`data-v-24296e7b`]]),ie={class:`library`},ae={class:`library__header`},oe={class:`library__tabs`,role:`tablist`},se=[`aria-selected`,`onClick`],ce={key:0,class:`library__loading`},le={key:0,class:`library__empty`},ue={key:1,class:`library__grid`},de={class:`library__thumb`},fe=[`src`,`alt`],pe={class:`library__thumb-actions`},me=[`aria-label`,`title`,`onClick`],he=[`aria-label`,`disabled`,`onClick`],ge={key:0,width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},_e={key:1,width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"aria-hidden":`true`},ve=[`aria-label`,`onClick`],ye=[`onClick`],be={key:0,class:`library__approvals`},xe=[`aria-label`,`onClick`],Se={key:1,class:`library__locks`},Ce=[`aria-label`,`onClick`],we={width:`10`,height:`10`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},Te={key:0,d:`M7 11V7a5 5 0 0 1 10 0v4`},Ee={key:1,d:`M7 11V7a5 5 0 0 1 9.9-1`},De={class:`library__subtabs`,role:`tablist`},Oe=[`aria-selected`,`onClick`],ke={key:0,class:`library__loading`},Ae={key:1,class:`library__shared-empty`},H={class:`library__empty-title`},je={class:`library__empty-sub`},Me={key:2,class:`library__shared-list`},Ne={key:3,class:`library__pagination`},Pe=[`disabled`],Fe={class:`library__page-info`},Ie=[`disabled`],Le={class:`library__sheet-actions`},U=g(s({__name:`LibraryView`,setup(s){let g=T(),y=b(),x=D(),S=ee(),E=C(),A=w(),j=[{id:`all`,label:`All`},{id:`mine`,label:`Mine`},{id:`shared`,label:`Shared`}];function M(e){return j.some(t=>t.id===e)?e:`all`}let N=o(M(A.query.tab));n(()=>A.query.tab,e=>{let t=M(e);t!==N.value&&(N.value=t,t===`shared`&&B(F.value))});let P=[{id:`pending`,label:`Pending`},{id:`approved`,label:`Approved`},{id:`declined`,label:`Declined`}],F=o(`pending`),I=o([]),L=o(!1),R=o(1),z=o(1);async function B(e,t=1){L.value=!0;try{let n=await y.fetchSharedImages(e,t);I.value=n.items,R.value=n.page,z.value=n.totalPages}finally{L.value=!1}}function V(e){F.value=e,B(e,1)}function U(e){B(F.value,e)}function Re(e){let t=I.value.findIndex(t=>t.id===e.id);t!==-1&&(I.value[t]=e)}a(()=>{y.fetchImages(),x.fetchDevices(),y.fetchPendingCount(),N.value===`shared`&&B(F.value)});function ze(){let e=document.createElement(`input`);e.type=`file`,e.accept=`image/jpeg,image/png,image/webp,image/gif`,e.onchange=()=>{let t=e.files?.[0];t&&(S.init(t),g.push(`/upload`))},e.click()}function Be(){return window.scrollY===0}async function Ve(){await Promise.all([y.fetchImages({silent:!0}),y.fetchPendingCount(),x.fetchDevices({silent:!0}),N.value===`shared`?B(F.value,R.value):Promise.resolve()])}let W=p(()=>y.images),G=o(!1),K=o(null);function He(e){K.value=e,G.value=!0}let q=o(null);async function J(e,t){if(!q.value){q.value=e.id;try{await S.initEdit(e,t),g.push(`/upload`)}catch{E.show(`Could not load photo for editing`,`error`)}finally{q.value=null}}}function Y(e){if(e.cropOrientation)return e.cropOrientation;let t=e.cropParams;return!t?.natW||!t?.natH?null:t.natW>=t.natH?`landscape`:`portrait`}function X(e){let t=Y(e);if(!t)return null;for(let n of e.approvedDeviceIds){let e=x.devices.find(e=>e.id===n);if(e&&e.orientation!==t)return e}return null}async function Ue(e,t){try{t.lockedImageId===e?await x.unlockImage(t.id):await x.lockImage(t.id,e)}catch{E.show(`Failed to update lock`,`error`)}}async function We(e,t,n){try{await y.setApproval(e,t,n)}catch{E.show(`Failed to update frame approval`,`error`)}}let Z=o(!1),Q=o(null),$=o(!1);function Ge(e){Q.value=e,Z.value=!0}async function Ke(){if(Q.value){$.value=!0;try{await y.deleteImage(Q.value),Z.value=!1,E.show(`Photo deleted`,`success`)}catch{E.show(`Delete failed`,`error`)}finally{$.value=!1}}}return(n,a)=>(r(),h(`main`,ie,[d(te,{"is-at-top":Be,"on-refresh":Ve},{default:i(()=>[_(`div`,ae,[d(O,{variant:`primary`,class:`library__add-btn`,onClick:ze},{default:i(()=>[...a[5]||=[f(` + Add Photo `,-1)]]),_:1})]),_(`div`,oe,[(r(),h(m,null,e(j,e=>_(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":N.value===e.id,class:v([`library__tab`,{"library__tab--active":N.value===e.id}]),onClick:t=>N.value=e.id},u(e.label),11,se)),64))]),t(y).loading?(r(),h(`div`,ce,`Loading…`)):N.value===`shared`?(r(),h(m,{key:2},[_(`div`,De,[(r(),h(m,null,e(P,e=>_(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":F.value===e.id,class:v([`library__subtab`,{"library__subtab--active":F.value===e.id}]),onClick:t=>V(e.id)},u(e.label),11,Oe)),64))]),L.value?(r(),h(`div`,ke,`Loading…`)):I.value.length===0?(r(),h(`div`,Ae,[a[13]||=_(`svg`,{width:`48`,height:`48`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.5`,"aria-hidden":`true`},[_(`circle`,{cx:`18`,cy:`5`,r:`3`}),_(`circle`,{cx:`6`,cy:`12`,r:`3`}),_(`circle`,{cx:`18`,cy:`19`,r:`3`}),_(`line`,{x1:`8.59`,y1:`13.51`,x2:`15.42`,y2:`17.49`}),_(`line`,{x1:`15.41`,y1:`6.51`,x2:`8.59`,y2:`10.49`})],-1),_(`p`,H,u(F.value===`pending`?`No pending photos`:F.value===`approved`?`No approved photos`:`No declined photos`),1),_(`p`,je,u(F.value===`pending`?`Photos shared with you will appear here.`:`Photos you've added to a frame will appear here.`),1)])):(r(),h(`div`,Me,[(r(!0),h(m,null,e(I.value,e=>(r(),c(ne,{key:e.id,item:e,onUpdated:Re},null,8,[`item`]))),128))])),z.value>1?(r(),h(`div`,Ne,[_(`button`,{class:`library__page-btn`,disabled:R.value<=1,onClick:a[0]||=e=>U(R.value-1)},`← Prev`,8,Pe),_(`span`,Fe,u(R.value)+` / `+u(z.value),1),_(`button`,{class:`library__page-btn`,disabled:R.value>=z.value,onClick:a[1]||=e=>U(R.value+1)},`Next →`,8,Ie)])):l(``,!0)],64)):(r(),h(m,{key:1},[W.value.length===0?(r(),h(`div`,le,[...a[6]||=[_(`svg`,{width:`48`,height:`48`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.5`,"aria-hidden":`true`},[_(`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`}),_(`circle`,{cx:`8.5`,cy:`8.5`,r:`1.5`}),_(`polyline`,{points:`21,15 16,10 5,21`})],-1),_(`p`,{class:`library__empty-title`},`No photos yet`,-1),_(`p`,{class:`library__empty-sub`},`Tap "+ Add Photo" above to upload your first one.`,-1)]])):(r(),h(`div`,ue,[(r(!0),h(m,null,e(W.value,n=>(r(),h(`div`,{key:n.id,class:`library__item`},[_(`div`,de,[_(`img`,{src:n.thumbnailUrl,alt:n.originalFilename,class:`library__img`,loading:`lazy`},null,8,fe),_(`div`,pe,[X(n)?(r(),h(`button`,{key:0,class:`library__action-btn library__action-btn--warn`,type:`button`,"aria-label":`Crop orientation does not match ${X(n).name}; tap to re-crop`,title:`Cropped ${Y(n)}, but ${X(n).name} is set to ${X(n).orientation}.`,onClick:e=>J(n,X(n).id)},[...a[7]||=[_(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},[_(`path`,{d:`M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z`}),_(`line`,{x1:`12`,y1:`9`,x2:`12`,y2:`13`}),_(`line`,{x1:`12`,y1:`17`,x2:`12.01`,y2:`17`})],-1)]],8,me)):l(``,!0),_(`button`,{class:`library__action-btn`,type:`button`,"aria-label":`Edit ${n.originalFilename}`,disabled:q.value===n.id,onClick:e=>J(n)},[q.value===n.id?(r(),h(`svg`,_e,[...a[9]||=[_(`circle`,{cx:`12`,cy:`12`,r:`10`},null,-1),_(`line`,{x1:`12`,y1:`8`,x2:`12`,y2:`12`},null,-1),_(`line`,{x1:`12`,y1:`16`,x2:`12.01`,y2:`16`},null,-1)]])):(r(),h(`svg`,ge,[...a[8]||=[_(`path`,{d:`M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7`},null,-1),_(`path`,{d:`M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z`},null,-1)]]))],8,he),_(`button`,{class:`library__action-btn`,type:`button`,"aria-label":`Share ${n.originalFilename}`,onClick:e=>He(n.id)},[...a[10]||=[_(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},[_(`circle`,{cx:`18`,cy:`5`,r:`3`}),_(`circle`,{cx:`6`,cy:`12`,r:`3`}),_(`circle`,{cx:`18`,cy:`19`,r:`3`}),_(`line`,{x1:`8.59`,y1:`13.51`,x2:`15.42`,y2:`17.49`}),_(`line`,{x1:`15.41`,y1:`6.51`,x2:`8.59`,y2:`10.49`})],-1)]],8,ve),_(`button`,{class:`library__action-btn library__action-btn--danger`,type:`button`,"aria-label":`Delete photo`,onClick:e=>Ge(n.id)},[...a[11]||=[_(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},[_(`polyline`,{points:`3 6 5 6 21 6`}),_(`path`,{d:`M19 6l-1 14H6L5 6`}),_(`path`,{d:`M10 11v6M14 11v6`}),_(`path`,{d:`M9 6V4h6v2`})],-1)]],8,ye)])]),t(x).devices.length>0?(r(),h(`div`,be,[(r(!0),h(m,null,e(t(x).devices,e=>(r(),h(`button`,{key:e.id,class:v([`library__approval-chip`,{"library__approval-chip--on":n.approvedDeviceIds.includes(e.id)}]),type:`button`,"aria-label":`${n.approvedDeviceIds.includes(e.id)?`Remove from`:`Add to`} ${e.name}`,onClick:t=>We(n.id,e.id,!n.approvedDeviceIds.includes(e.id))},u(e.name),11,xe))),128))])):l(``,!0),t(x).devices.length>0?(r(),h(`div`,Se,[(r(!0),h(m,null,e(t(x).devices.filter(e=>n.approvedDeviceIds.includes(e.id)),e=>(r(),h(`button`,{key:e.id,class:v([`library__lock-chip`,{"library__lock-chip--on":e.lockedImageId===n.id}]),type:`button`,"aria-label":`${e.lockedImageId===n.id?`Unlock from`:`Lock to`} ${e.name}`,onClick:t=>Ue(n.id,e)},[(r(),h(`svg`,we,[a[12]||=_(`rect`,{x:`3`,y:`11`,width:`18`,height:`11`,rx:`2`,ry:`2`},null,-1),e.lockedImageId===n.id?(r(),h(`path`,Te)):(r(),h(`path`,Ee))])),f(` `+u(e.name),1)],10,Ce))),128))])):l(``,!0)]))),128))]))],64))]),_:1}),K.value===null?l(``,!0):(r(),c(re,{key:0,modelValue:G.value,"onUpdate:modelValue":a[2]||=e=>G.value=e,"image-id":K.value},null,8,[`modelValue`,`image-id`])),d(k,{modelValue:Z.value,"onUpdate:modelValue":a[4]||=e=>Z.value=e,label:`Delete photo`},{default:i(()=>[a[15]||=_(`h2`,{class:`library__sheet-title`},`Delete this photo?`,-1),a[16]||=_(`p`,{class:`library__sheet-sub`},`It will be removed from all frames.`,-1),_(`div`,Le,[d(O,{variant:`secondary`,onClick:a[3]||=e=>Z.value=!1},{default:i(()=>[...a[14]||=[f(`Cancel`,-1)]]),_:1}),d(O,{variant:`destructive`,disabled:$.value,onClick:Ke},{default:i(()=>[f(u($.value?`Deleting…`:`Delete`),1)]),_:1},8,[`disabled`])])]),_:1},8,[`modelValue`])]))}}),[[`__scopeId`,`data-v-d22a9085`]]);export{U as default}; |