Files
pictureFrame-webApp/public/build/assets/LibraryView-DTpzIhGk.js
T
football2801 82a42011d8
CI / test (push) Has been cancelled
fix(upload): persistent file <input> to survive iOS PWA cold launch
A dynamically-created <input type="file"> that's never attached to the
DOM drops its first `change` event on a cold-launched iOS PWA — the
native photo picker resolves out of the original user-gesture context
and the closure that captured the input is gone. Symptom Matt hit
2026-05-14: first image-pick after hard-close + reopen of the PWA
silently failed to advance to the crop tool; the second attempt worked.

HomeView and LibraryView now keep a hidden <input ref="fileInputEl"
type="file"> live in their templates. onAddPhoto clicks that input
inside the user-gesture context; @change fires reliably even on cold
launches. The picker resets input.value between selections so picking
the same file twice still fires.

Tests updated to query the template input via wrapper.find() instead
of stubbing document.createElement; 347/347 frontend tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:02:26 -04:00

1 line
14 KiB
JavaScript

import{A as e,B as t,C as n,F as r,K as i,N as a,S as o,U as s,V as c,Y as l,_ as u,d,f,g as p,h as m,p as h,q as g,t as _,u as v,v as y,x as b,y as x,z as S}from"./_plugin-vue_export-helper-BNDVmFr7.js";import{a as C,i as w,o as T,s as E}from"./index-DHJU4XGZ.js";import{i as D,n as O,r as ee,t as k}from"./BaseBottomSheet-Bsol3Sat.js";import{t as te}from"./PullToRefresh-CSjUpm5h.js";import{t as A}from"./DevicePicker-BnLOaG74.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=_(n({__name:`ApproveCard`,props:{item:{}},emits:[`updated`],setup(e,{emit:n}){let r=e,c=n,d=C(),f=D(),_=s(!1),v=s(!1),S=s([]),w=m(()=>new Date(r.item.sharedAt).toLocaleDateString(void 0,{month:`short`,day:`numeric`,year:`numeric`}));async function T(){_.value=!1,v.value=!0;try{c(`updated`,await d.approveShared(r.item.id,S.value))}finally{v.value=!1,S.value=[]}}async function E(){v.value=!0;try{c(`updated`,await d.declineShared(r.item.id))}finally{v.value=!1}}return(n,r)=>(a(),x(h,null,[p(`div`,j,[p(`img`,{src:e.item.thumbnailUrl,alt:`Photo from ${e.item.sharedBy}`,class:`approve-card__thumb`,loading:`lazy`},null,8,M),p(`div`,N,[p(`p`,P,[r[3]||=b(`From `,-1),p(`strong`,null,l(e.item.sharedBy),1)]),p(`p`,F,l(w.value),1),e.item.status===`pending`?y(``,!0):(a(),x(`div`,I,[p(`span`,{class:g([`approve-card__badge`,`approve-card__badge--${e.item.status}`])},l(e.item.status),3)])),p(`div`,L,[e.item.status===`pending`||e.item.status===`declined`?(a(),u(O,{key:0,variant:`primary`,size:`sm`,disabled:v.value,onClick:r[0]||=e=>_.value=!0},{default:t(()=>[b(l(e.item.status===`declined`?`Add anyway`:`Add to frame`),1)]),_:1},8,[`disabled`])):y(``,!0),e.item.status===`pending`||e.item.status===`approved`?(a(),u(O,{key:1,variant:`ghost`,size:`sm`,disabled:v.value,onClick:E},{default:t(()=>[b(l(e.item.status===`approved`?`Remove`:`Decline`),1)]),_:1},8,[`disabled`])):y(``,!0)])])]),o(A,{modelValue:_.value,"onUpdate:modelValue":r[1]||=e=>_.value=e,devices:i(f).devices,selected:S.value,uploading:v.value,"confirm-label":`Add to frames`,"onUpdate:selected":r[2]||=e=>S.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=_(n({__name:`ShareSheet`,props:{modelValue:{type:Boolean},imageId:{}},emits:[`update:modelValue`],setup(e,{emit:n}){let r=e,i=C(),m=s(``),h=s(!1),g=s(``),_=s(``);async function S(){if(g.value=``,_.value=``,m.value.trim()){h.value=!0;try{await i.shareImage(r.imageId,m.value.trim()),_.value=`Invite sent to ${m.value.trim()}`,m.value=``}catch(e){g.value=e instanceof Error?e.message:`Failed to send`}finally{h.value=!1}}}return(n,r)=>(a(),u(k,{"model-value":e.modelValue,label:`Share photo`,"onUpdate:modelValue":r[1]||=e=>n.$emit(`update:modelValue`,e)},{default:t(()=>[r[2]||=p(`h2`,{class:`share-sheet__title`},`Share with someone`,-1),r[3]||=p(`p`,{class:`share-sheet__sub`},`They'll get an email and can add it to their frame.`,-1),p(`div`,R,[c(p(`input`,{"onUpdate:modelValue":r[0]||=e=>m.value=e,type:`email`,class:`share-sheet__input`,placeholder:`their@email.com`,autocomplete:`email`,onKeydown:d(f(S,[`prevent`]),[`enter`])},null,40,z),[[v,m.value]])]),g.value?(a(),x(`p`,B,l(g.value),1)):y(``,!0),_.value?(a(),x(`p`,V,l(_.value),1)):y(``,!0),o(O,{variant:`primary`,class:`share-sheet__btn`,disabled:h.value||!m.value.trim(),onClick:S},{default:t(()=>[b(l(h.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`},je={class:`library__empty-title`},Me={class:`library__empty-sub`},Ne={key:2,class:`library__shared-list`},Pe={key:3,class:`library__pagination`},Fe=[`disabled`],Ie={class:`library__page-info`},Le=[`disabled`],Re={class:`library__sheet-actions`},H=_(n({__name:`LibraryView`,setup(n){let c=E(),d=C(),f=D(),_=ee(),v=w(),A=T(),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=s(M(A.query.tab));S(()=>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=s(`pending`),I=s([]),L=s(!1),R=s(1),z=s(1);async function B(e,t=1){L.value=!0;try{let n=await d.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 H(e){B(F.value,e)}function ze(e){let t=I.value.findIndex(t=>t.id===e.id);t!==-1&&(I.value[t]=e)}e(()=>{d.fetchImages(),f.fetchDevices(),d.fetchPendingCount(),N.value===`shared`&&B(F.value)});let U=s(null);function Be(){U.value?.click()}function Ve(e){let t=e.target,n=t.files?.[0];t.value=``,n&&(_.init(n),c.push(`/upload`))}function He(){return window.scrollY===0}async function Ue(){await Promise.all([d.fetchImages({silent:!0}),d.fetchPendingCount(),f.fetchDevices({silent:!0}),N.value===`shared`?B(F.value,R.value):Promise.resolve()])}let W=m(()=>d.images),G=s(!1),K=s(null);function We(e){K.value=e,G.value=!0}let q=s(null);async function J(e,t){if(!q.value){q.value=e.id;try{await _.initEdit(e,t),c.push(`/upload`)}catch{v.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=f.devices.find(e=>e.id===n);if(e&&e.orientation!==t)return e}return null}async function Ge(e,t){try{t.lockedImageId===e?await f.unlockImage(t.id):await f.lockImage(t.id,e)}catch{v.show(`Failed to update lock`,`error`)}}async function Ke(e,t,n){try{await d.setApproval(e,t,n)}catch{v.show(`Failed to update frame approval`,`error`)}}let Z=s(!1),Q=s(null),$=s(!1);function qe(e){Q.value=e,Z.value=!0}async function Je(){if(Q.value){$.value=!0;try{await d.deleteImage(Q.value),Z.value=!1,v.show(`Photo deleted`,`success`)}catch{v.show(`Delete failed`,`error`)}finally{$.value=!1}}}return(e,n)=>(a(),x(`main`,ie,[o(te,{"is-at-top":He,"on-refresh":Ue},{default:t(()=>[p(`div`,ae,[o(O,{variant:`primary`,class:`library__add-btn`,onClick:Be},{default:t(()=>[...n[5]||=[b(` + Add Photo `,-1)]]),_:1})]),p(`div`,oe,[(a(),x(h,null,r(j,e=>p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":N.value===e.id,class:g([`library__tab`,{"library__tab--active":N.value===e.id}]),onClick:t=>N.value=e.id},l(e.label),11,se)),64))]),i(d).loading?(a(),x(`div`,ce,`Loading…`)):N.value===`shared`?(a(),x(h,{key:2},[p(`div`,De,[(a(),x(h,null,r(P,e=>p(`button`,{key:e.id,type:`button`,role:`tab`,"aria-selected":F.value===e.id,class:g([`library__subtab`,{"library__subtab--active":F.value===e.id}]),onClick:t=>V(e.id)},l(e.label),11,Oe)),64))]),L.value?(a(),x(`div`,ke,`Loading…`)):I.value.length===0?(a(),x(`div`,Ae,[n[13]||=p(`svg`,{width:`48`,height:`48`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.5`,"aria-hidden":`true`},[p(`circle`,{cx:`18`,cy:`5`,r:`3`}),p(`circle`,{cx:`6`,cy:`12`,r:`3`}),p(`circle`,{cx:`18`,cy:`19`,r:`3`}),p(`line`,{x1:`8.59`,y1:`13.51`,x2:`15.42`,y2:`17.49`}),p(`line`,{x1:`15.41`,y1:`6.51`,x2:`8.59`,y2:`10.49`})],-1),p(`p`,je,l(F.value===`pending`?`No pending photos`:F.value===`approved`?`No approved photos`:`No declined photos`),1),p(`p`,Me,l(F.value===`pending`?`Photos shared with you will appear here.`:`Photos you've added to a frame will appear here.`),1)])):(a(),x(`div`,Ne,[(a(!0),x(h,null,r(I.value,e=>(a(),u(ne,{key:e.id,item:e,onUpdated:ze},null,8,[`item`]))),128))])),z.value>1?(a(),x(`div`,Pe,[p(`button`,{class:`library__page-btn`,disabled:R.value<=1,onClick:n[0]||=e=>H(R.value-1)},`← Prev`,8,Fe),p(`span`,Ie,l(R.value)+` / `+l(z.value),1),p(`button`,{class:`library__page-btn`,disabled:R.value>=z.value,onClick:n[1]||=e=>H(R.value+1)},`Next →`,8,Le)])):y(``,!0)],64)):(a(),x(h,{key:1},[W.value.length===0?(a(),x(`div`,le,[...n[6]||=[p(`svg`,{width:`48`,height:`48`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.5`,"aria-hidden":`true`},[p(`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`}),p(`circle`,{cx:`8.5`,cy:`8.5`,r:`1.5`}),p(`polyline`,{points:`21,15 16,10 5,21`})],-1),p(`p`,{class:`library__empty-title`},`No photos yet`,-1),p(`p`,{class:`library__empty-sub`},`Tap "+ Add Photo" above to upload your first one.`,-1)]])):(a(),x(`div`,ue,[(a(!0),x(h,null,r(W.value,e=>(a(),x(`div`,{key:e.id,class:`library__item`},[p(`div`,de,[p(`img`,{src:e.thumbnailUrl,alt:e.originalFilename,class:`library__img`,loading:`lazy`},null,8,fe),p(`div`,pe,[X(e)?(a(),x(`button`,{key:0,class:`library__action-btn library__action-btn--warn`,type:`button`,"aria-label":`Crop orientation does not match ${X(e).name}; tap to re-crop`,title:`Cropped ${Y(e)}, but ${X(e).name} is set to ${X(e).orientation}.`,onClick:t=>J(e,X(e).id)},[...n[7]||=[p(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},[p(`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`}),p(`line`,{x1:`12`,y1:`9`,x2:`12`,y2:`13`}),p(`line`,{x1:`12`,y1:`17`,x2:`12.01`,y2:`17`})],-1)]],8,me)):y(``,!0),p(`button`,{class:`library__action-btn`,type:`button`,"aria-label":`Edit ${e.originalFilename}`,disabled:q.value===e.id,onClick:t=>J(e)},[q.value===e.id?(a(),x(`svg`,_e,[...n[9]||=[p(`circle`,{cx:`12`,cy:`12`,r:`10`},null,-1),p(`line`,{x1:`12`,y1:`8`,x2:`12`,y2:`12`},null,-1),p(`line`,{x1:`12`,y1:`16`,x2:`12.01`,y2:`16`},null,-1)]])):(a(),x(`svg`,ge,[...n[8]||=[p(`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),p(`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),p(`button`,{class:`library__action-btn`,type:`button`,"aria-label":`Share ${e.originalFilename}`,onClick:t=>We(e.id)},[...n[10]||=[p(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},[p(`circle`,{cx:`18`,cy:`5`,r:`3`}),p(`circle`,{cx:`6`,cy:`12`,r:`3`}),p(`circle`,{cx:`18`,cy:`19`,r:`3`}),p(`line`,{x1:`8.59`,y1:`13.51`,x2:`15.42`,y2:`17.49`}),p(`line`,{x1:`15.41`,y1:`6.51`,x2:`8.59`,y2:`10.49`})],-1)]],8,ve),p(`button`,{class:`library__action-btn library__action-btn--danger`,type:`button`,"aria-label":`Delete photo`,onClick:t=>qe(e.id)},[...n[11]||=[p(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"aria-hidden":`true`},[p(`polyline`,{points:`3 6 5 6 21 6`}),p(`path`,{d:`M19 6l-1 14H6L5 6`}),p(`path`,{d:`M10 11v6M14 11v6`}),p(`path`,{d:`M9 6V4h6v2`})],-1)]],8,ye)])]),i(f).devices.length>0?(a(),x(`div`,be,[(a(!0),x(h,null,r(i(f).devices,t=>(a(),x(`button`,{key:t.id,class:g([`library__approval-chip`,{"library__approval-chip--on":e.approvedDeviceIds.includes(t.id)}]),type:`button`,"aria-label":`${e.approvedDeviceIds.includes(t.id)?`Remove from`:`Add to`} ${t.name}`,onClick:n=>Ke(e.id,t.id,!e.approvedDeviceIds.includes(t.id))},l(t.name),11,xe))),128))])):y(``,!0),i(f).devices.length>0?(a(),x(`div`,Se,[(a(!0),x(h,null,r(i(f).devices.filter(t=>e.approvedDeviceIds.includes(t.id)),t=>(a(),x(`button`,{key:t.id,class:g([`library__lock-chip`,{"library__lock-chip--on":t.lockedImageId===e.id}]),type:`button`,"aria-label":`${t.lockedImageId===e.id?`Unlock from`:`Lock to`} ${t.name}`,onClick:n=>Ge(e.id,t)},[(a(),x(`svg`,we,[n[12]||=p(`rect`,{x:`3`,y:`11`,width:`18`,height:`11`,rx:`2`,ry:`2`},null,-1),t.lockedImageId===e.id?(a(),x(`path`,Te)):(a(),x(`path`,Ee))])),b(` `+l(t.name),1)],10,Ce))),128))])):y(``,!0)]))),128))]))],64))]),_:1}),K.value===null?y(``,!0):(a(),u(re,{key:0,modelValue:G.value,"onUpdate:modelValue":n[2]||=e=>G.value=e,"image-id":K.value},null,8,[`modelValue`,`image-id`])),o(k,{modelValue:Z.value,"onUpdate:modelValue":n[4]||=e=>Z.value=e,label:`Delete photo`},{default:t(()=>[n[15]||=p(`h2`,{class:`library__sheet-title`},`Delete this photo?`,-1),n[16]||=p(`p`,{class:`library__sheet-sub`},`It will be removed from all frames.`,-1),p(`div`,Re,[o(O,{variant:`secondary`,onClick:n[3]||=e=>Z.value=!1},{default:t(()=>[...n[14]||=[b(`Cancel`,-1)]]),_:1}),o(O,{variant:`destructive`,disabled:$.value,onClick:Je},{default:t(()=>[b(l($.value?`Deleting…`:`Delete`),1)]),_:1},8,[`disabled`])])]),_:1},8,[`modelValue`]),p(`input`,{ref_key:`fileInputEl`,ref:U,type:`file`,accept:`image/jpeg,image/png,image/webp,image/gif`,hidden:``,onChange:Ve},null,544)]))}}),[[`__scopeId`,`data-v-010dc991`]]);export{H as default};