82a42011d8
CI / test (push) Has been cancelled
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>
1 line
14 KiB
JavaScript
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}; |