cf6623de67
CI / test (push) Has been cancelled
Adds two settings exposed in the PWA frame-settings sheet:
- rotationMode (enum: random | least_recently_shown | oldest_upload |
newest_upload). Default oldest_upload preserves the legacy
hard-coded sort, so existing devices behave identically until the
user changes it.
- prioritizeNeverShown (bool). When set, the candidate set is narrowed
to never-shown images first (if any exist) before the mode runs —
useful for "burn through new uploads before re-shuffling the catalog."
RotationService pipeline:
1. Pull approved/ready pool.
2. Drop the last `uniquenessWindow` served (existing).
3. If prioritizeNeverShown AND any candidates have never been served,
narrow to those.
4. Apply the selection mode.
Backend: enum, entity columns + accessors, migration, serializer,
PATCH validator. Frontend: types, stores, settings sheet section
(dropdown + checkbox), test fixtures, save-flow test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 line
4.9 KiB
JavaScript
1 line
4.9 KiB
JavaScript
import{C as e,H as t,M as n,P as r,R as i,_ as a,c as o,d as s,dt as c,f as l,ft as u,k as d,m as f,p,r as m,t as h,v as g,w as _,z as v}from"./_plugin-vue_export-helper-DRLwVS0w.js";import{c as y,f as b,p as x}from"./index-DNhLsB-t.js";var S=m(`devices`,()=>{let e=t([]),n=t(!1),r=t(null);async function i(t={}){t.silent||(n.value=!0),r.value=null;try{let t=await fetch(`/api/devices`);if(!t.ok)throw Error(`Failed to load devices`);e.value=await t.json()}catch(e){r.value=e instanceof Error?e.message:`Unknown error`}finally{n.value=!1}}async function a(t,n){let r=await fetch(`/api/devices/${t}`,{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify(n)});if(!r.ok)throw Error(`Failed to update device`);let i=await r.json(),a=e.value.findIndex(e=>e.id===t);return a!==-1&&(e.value[a]=i),i}async function o(t,n){let r=await fetch(`/api/devices/${t}/lock`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({imageId:n})});if(!r.ok)throw Error(`Failed to lock image`);let i=await r.json(),a=e.value.findIndex(e=>e.id===t);return a!==-1&&(e.value[a]=i),i}async function s(t){let n=await fetch(`/api/devices/${t}/lock`,{method:`DELETE`});if(!n.ok)throw Error(`Failed to unlock`);let r=await n.json(),i=e.value.findIndex(e=>e.id===t);return i!==-1&&(e.value[i]=r),r}return{devices:e,loading:n,error:r,fetchDevices:i,updateDevice:a,lockImage:o,unlockImage:s}}),C=m(`upload`,()=>{let e=t(null),n=t(null),r=t(null),i=t(null),a=t(null),o=t(null),s=t([]),c=t(null),l=t([]),u=t(null);function d(t,r){_(),e.value=t,n.value=URL.createObjectURL(t),c.value=r??null,l.value=r?[r]:[]}async function f(t,r){_();let i=await(await fetch(t.originalUrl)).blob();e.value=new File([i],t.originalFilename,{type:i.type}),n.value=URL.createObjectURL(i),u.value=t.id,a.value=t.cropParams??null,o.value=t.cropOrientation??null,s.value=t.stickerState?[...t.stickerState]:[],l.value=t.approvedDeviceIds,c.value=r??null}function p(e,t,n){i.value&&URL.revokeObjectURL(i.value),r.value=e,i.value=URL.createObjectURL(e),a.value=t,o.value=n}function m(e){s.value=[...s.value,e]}function h(e,t){s.value=s.value.map(n=>n.id===e?{...n,...t}:n)}function g(e){s.value=s.value.filter(t=>t.id!==e)}function _(){n.value&&URL.revokeObjectURL(n.value),i.value&&URL.revokeObjectURL(i.value),e.value=null,n.value=null,r.value=null,i.value=null,a.value=null,o.value=null,s.value=[],c.value=null,l.value=[],u.value=null}return{originalFile:e,originalUrl:n,croppedBlob:r,croppedUrl:i,cropParams:a,cropOrientation:o,stickers:s,contextDeviceId:c,selectedDeviceIds:l,editingImageId:u,init:d,initEdit:f,setCrop:p,addSticker:m,updateSticker:h,removeSticker:g,cleanup:_}}),w={key:0,class:`btn__spinner`,"aria-hidden":`true`},T=h(g({__name:`BaseButton`,props:{variant:{default:`primary`},tag:{default:`button`},type:{default:`button`},disabled:{type:Boolean,default:!1},loading:{type:Boolean,default:!1}},setup(t){return(i,a)=>(d(),l(r(t.tag),e({type:t.tag===`button`?t.type:void 0,disabled:t.disabled||t.loading,class:[`btn`,`btn--${t.variant}`,{"btn--loading":t.loading}]},i.$attrs),{default:v(()=>[t.loading?(d(),f(`span`,w)):p(``,!0),n(i.$slots,`default`,{},void 0,!0)]),_:3},16,[`type`,`disabled`,`class`]))}}),[[`__scopeId`,`data-v-7d3f1e61`]]),E=[`aria-label`],D=80,O=h(g({__name:`BaseBottomSheet`,props:{modelValue:{type:Boolean},label:{}},emits:[`update:modelValue`],setup(e,{emit:r}){let m=e,h=r,g=t(null),S=t(0),C=t(!1),w=0,T=null,O=null;function k(){h(`update:modelValue`,!1)}function A(e){w=e.touches[0].clientY,C.value=!0,S.value=0}function j(e){if(!C.value)return;let t=e.touches[0].clientY-w;S.value=t>0?t:0}function M(){C.value&&(C.value=!1,S.value>D&&k(),S.value=0)}function N(e){e.pointerType!==`touch`&&(w=e.clientY,C.value=!0,S.value=0,T=e.pointerId,e.currentTarget.setPointerCapture(e.pointerId),window.addEventListener(`pointermove`,P),window.addEventListener(`pointerup`,F),window.addEventListener(`pointercancel`,F))}function P(e){if(!C.value||e.pointerId!==T)return;let t=e.clientY-w;S.value=t>0?t:0}function F(e){e.pointerId===T&&(T=null,window.removeEventListener(`pointermove`,P),window.removeEventListener(`pointerup`,F),window.removeEventListener(`pointercancel`,F),M())}return i(()=>m.modelValue,async e=>{e?(O=document.activeElement,await _(),g.value?.focus()):(O?.focus(),O=null,S.value=0,C.value=!1)}),(t,r)=>(d(),l(o,{to:`body`},[a(y,{name:`sheet`},{default:v(()=>[e.modelValue?(d(),f(`div`,{key:0,class:`sheet-overlay`,role:`dialog`,"aria-label":e.label,"aria-modal":`true`,onClick:x(k,[`self`]),onKeydown:b(k,[`esc`])},[s(`div`,{ref_key:`sheetRef`,ref:g,class:c([`sheet`,{"sheet--dragging":C.value}]),style:u(S.value>0?{transform:`translateY(${S.value}px)`}:void 0),tabindex:`-1`},[s(`div`,{class:`sheet__handle-target`,onTouchstartPassive:A,onTouchmovePassive:j,onTouchend:M,onTouchcancel:M,onPointerdown:N,"aria-hidden":`true`},[...r[0]||=[s(`div`,{class:`sheet__handle`},null,-1)]],32),n(t.$slots,`default`,{},void 0,!0)],6)],40,E)):p(``,!0)]),_:3})]))}}),[[`__scopeId`,`data-v-967683c3`]]);export{S as i,T as n,C as r,O as t}; |