From f08421e4301861ba42f5e14a6aae7d5a856d6371 Mon Sep 17 00:00:00 2001 From: Pigway Date: Mon, 5 Oct 2026 16:18:38 +0800 Subject: [PATCH] Disable display action for the current gallery image --- pigway_frame_flow/web/app.js | 2 +- scripts/ui-gallery-current.cjs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 scripts/ui-gallery-current.cjs diff --git a/pigway_frame_flow/web/app.js b/pigway_frame_flow/web/app.js index 843693d..dc82b47 100644 --- a/pigway_frame_flow/web/app.js +++ b/pigway_frame_flow/web/app.js @@ -57,7 +57,7 @@ function accountSettings(){const section=$('#logout').parentElement;const form=d const views={ 'photo-sources':async()=>{ $('#main').innerHTML='';await photoSourcesPanel(); }, async dashboard(epoch){const d=await api('/api/v1/devices/'+state.device);if(epoch!==state.epoch)return;const s=d.slideshow;$('#main').innerHTML=notice()+`${d.power.battery_percent!==null&&d.power.battery_percent<20?`
${t('lowBattery')}
`:''}
● ${t(d.status).toUpperCase()}

${esc(d.name)}

${esc(d.model)} · ${esc(d.model_code)} · ${d.display.width} × ${d.display.height}

${buttons('go-upload','upload',true)}${buttons('restore','restore')}${buttons('refresh-device','refresh')}

${t('refreshInterval')}

${t('testLabel')}
${[['battery',d.power.battery_percent===null?'—':d.power.battery_percent+'%'],['current',d.current_content.id??'—'],['sync',t(d.current_content.synced?'synced':'syncing')],['firmware',d.firmware]].map(([l,v])=>`
${t(l)}
${esc(v)}
`).join('')}

${t('device')}

${detail('heartbeat',fmt(d.last_seen_at))}${detail('rotation',d.display.rotation+'°')}${detail('timezone',d.timezone)}${detail('coverId',d.current_content.cover_id??'—')}${detail('storage',d.storage_used_bytes===null?'—':(d.storage_used_bytes/1024/1024).toFixed(1)+' MiB')}${detail('model',t(d.model_verified?'modelVerified':'modelConfigured'))}

${t('cloudRotation')}

${detail('mode',s.mode?t(s.mode):'—')}${detail('interval',s.interval_minutes==null?'—':s.interval_minutes+' '+t('minutes'))}

${t('rotationNotice')}

${t('playlists')} →
`;bind('go-upload',()=>navigate('upload'));bind('refresh-device',async()=>{await api('/api/v1/devices/'+state.device+'?refresh=true');await navigate();});$('#restore').disabled=!d.can_restore;bind('restore',()=>accepted('/api/v1/devices/'+state.device+'/restore',{}));$('#go-playlists').onclick=e=>{e.preventDefault();navigate('playlists');};}, - async gallery(epoch){let items=state.tab==='cloud'?await api('/api/v1/devices/'+state.device+'/images'):await api('/api/v1/assets');if(epoch!==state.epoch)return;state.gallery=items;$('#main').innerHTML=notice()+`
${buttons('gallery-refresh','refresh')}${state.tab==='cloud'?buttons('show-name','displayByName'):''}${buttons('gallery-upload','upload',true)}
${state.tab==='local'?`
${t('uploadNotice')}
`:''}${items.length?``:`
${t('noImages')}${t('emptyHint')}
`}`;bind('cloud-tab',()=>{state.tab='cloud';return navigate();});bind('local-tab',()=>{state.tab='local';return navigate();});bind('gallery-refresh',()=>navigate());bind('gallery-upload',()=>navigate('upload'));bind('show-name',()=>{showModal(`

${t('displayByName')}

`);bind('show-name-confirm',async()=>{const name=$('#image-name').value;$('#modal').close();displayDialog({type:'image',image_name:name});});});hydrateImages();document.querySelectorAll('[data-show]').forEach(b=>b.onclick=busy(()=>{const x=items[b.dataset.show];return displayDialog(state.tab==='cloud'?{type:'image',image_id:x.image_id}:{type:'image',asset_id:x.asset_id});}));document.querySelectorAll('[data-original]').forEach(b=>b.onclick=busy(async()=>{const x=items[b.dataset.original];showModal(`

${esc(x.name)}

${esc(x.name)}`);await blobImage($('#original-image'),x.url);}));document.querySelectorAll('[data-delete]').forEach(b=>b.onclick=()=>{const x=items[b.dataset.delete];confirmDialog(x.current?t('deleteCurrent'):t('deleteConfirm'),async()=>{if(state.tab==='local'){await api('/api/v1/assets/'+x.asset_id,{method:'DELETE'});}else{const result=await api(`/api/v1/devices/${state.device}/images/${x.image_id}?confirm_current=${x.current}`,{method:'DELETE',headers:{'Idempotency-Key':'web-'+clientUUID()}});await waitOperation(result.operation_id);}await navigate();});});}, + async gallery(epoch){let items=state.tab==='cloud'?await api('/api/v1/devices/'+state.device+'/images'):await api('/api/v1/assets');if(epoch!==state.epoch)return;state.gallery=items;$('#main').innerHTML=notice()+`
${buttons('gallery-refresh','refresh')}${state.tab==='cloud'?buttons('show-name','displayByName'):''}${buttons('gallery-upload','upload',true)}
${state.tab==='local'?`
${t('uploadNotice')}
`:''}${items.length?``:`
${t('noImages')}${t('emptyHint')}
`}`;bind('cloud-tab',()=>{state.tab='cloud';return navigate();});bind('local-tab',()=>{state.tab='local';return navigate();});bind('gallery-refresh',()=>navigate());bind('gallery-upload',()=>navigate('upload'));bind('show-name',()=>{showModal(`

${t('displayByName')}

`);bind('show-name-confirm',async()=>{const name=$('#image-name').value;$('#modal').close();displayDialog({type:'image',image_name:name});});});hydrateImages();document.querySelectorAll('[data-show]').forEach(b=>b.onclick=busy(()=>{const x=items[b.dataset.show];if(x.current)return;return displayDialog(state.tab==='cloud'?{type:'image',image_id:x.image_id}:{type:'image',asset_id:x.asset_id});}));document.querySelectorAll('[data-original]').forEach(b=>b.onclick=busy(async()=>{const x=items[b.dataset.original];showModal(`

${esc(x.name)}

${esc(x.name)}`);await blobImage($('#original-image'),x.url);}));document.querySelectorAll('[data-delete]').forEach(b=>b.onclick=()=>{const x=items[b.dataset.delete];confirmDialog(x.current?t('deleteCurrent'):t('deleteConfirm'),async()=>{if(state.tab==='local'){await api('/api/v1/assets/'+x.asset_id,{method:'DELETE'});}else{const result=await api(`/api/v1/devices/${state.device}/images/${x.image_id}?confirm_current=${x.current}`,{method:'DELETE',headers:{'Idempotency-Key':'web-'+clientUUID()}});await waitOperation(result.operation_id);}await navigate();});});}, async upload(){state.preview=null;$('#main').innerHTML=notice()+`
${t('uploadNotice')}

${t('upload')}

${t('selectFile')}

${t('uploadHint')}

${buttons('make-preview','preview',true)}

${t('previewHint')}

${t('preview')}

${t('previewHint')}
`;bind('make-preview',async()=>{const file=$('#file').files[0];if(!file)throw new Error(t('selectFile'));const form=new FormData();form.append('file',file);form.append('fit',$('#fit').value);form.append('profile',$('#profile').value);state.preview=await api('/api/v1/preview',{method:'POST',body:form});$('#preview-box').className='';$('#preview-box').innerHTML='Preview';await blobImage($('#upload-preview'),state.preview.url);$('#save-local').disabled=false;$('#display-upload').disabled=!liveWriteAllowed();});for(const id of ['file','fit','profile'])$('#'+id).onchange=()=>{state.preview=null;$('#save-local').disabled=true;$('#display-upload').disabled=true;};bind('save-local',async()=>{const id=await accepted('/api/v1/devices/'+state.device+'/upload',{type:'image',asset_id:state.preview.asset_id});await waitOperation(id);state.tab='local';return navigate('gallery');});bind('display-upload',()=>displayDialog({type:'image',asset_id:state.preview.asset_id}));}, async templates(){state.templates=await api('/api/v1/templates');const selected=state.templates.find(x=>x.id===state.selectedTemplate)||state.templates[0];state.selectedTemplate=selected.id;if(state.pendingTemplateData?.template===selected.id){selected.defaults=state.pendingTemplateData.data;state.initialProfile=state.pendingTemplateData.profile;state.pendingTemplateData=null;}state.templatePreview=null;$('#main').innerHTML=notice()+`
${t('templateHint')}
${state.templates.map((x,i)=>`
${{'daily-poetry':'詩','calendar':'▦','daily-dashboard':'☀','photo-text':'▣','quote':'❝','system-alert':'◒','weather':'☁'}[x.id]||'▦'}

${esc(x.name.split(' / ')[state.lang==='zh'?1:0]||x.name)}

1200 × 1600 · ${esc(x.id)}
`).join('')}

${t('templateData')}

${buttons('render-template','render',true)}${buttons('save-template','saveTemplate')}

${t('preview')}

${t('previewHint')}
`;document.querySelectorAll('[data-template]').forEach(b=>{b.onclick=()=>{state.selectedTemplate=b.dataset.template;navigate();};b.onkeydown=e=>{if(e.key==='Enter')b.click();};});bind('render-template',async()=>{const content={type:'template',template:selected.id,data:JSON.parse($('#template-data').value),profile:$('#template-profile').value};if([...document.querySelectorAll('#template-fields textarea'),$('#template-data')].some(x=>!x.checkValidity())){toast(t('INVALID_REQUEST'));return;}const id=await accepted('/api/v1/render',content);$('#template-preview').textContent=t('previewLoading');const op=await waitOperation(id);state.templatePreview=op.result.asset_id;$('#template-preview').className='';$('#template-preview').innerHTML='Template preview';await blobImage($('#template-image'),op.result.url);$('#display-template').disabled=!liveWriteAllowed();});bind('save-template',async()=>{await api('/api/v1/templates/'+selected.id+'/config',{method:'PUT',body:{data:JSON.parse($('#template-data').value)}});toast(t('done'));});$('#template-data').oninput=()=>$('#display-template').disabled=true;$('#template-profile').onchange=()=>$('#display-template').disabled=true;bind('display-template',()=>displayDialog({type:'image',asset_id:state.templatePreview}));await templateEditor(selected);if(state.initialProfile){$('#template-profile').value=state.initialProfile;state.initialProfile=null;}}, async automation(epoch){const jobs=await api('/api/v1/jobs');if(epoch!==state.epoch)return;$('#main').innerHTML=notice()+`

Asia/Shanghai · ${t('automation')}

${buttons('new-job','newJob',true)}
${jobs.length?`
${['name','schedule','status','nextRun','lastRun','result','action'].map(x=>``).join('')}${jobs.map((j,i)=>``).join('')}
${t(x)}
${esc(j.name)}
${esc(j.device_id)}
${t(j.schedule.type)}
${esc(j.schedule.time||j.schedule.cron||fmt(j.schedule.at))}
${t(j.enabled?'enabled':'disabled')}${fmt(j.next_run)}${fmt(j.last_run)}${esc(j.last_result)}
`:`
${t('noTasks')}
`}
`;bind('new-job',()=>jobEditor());document.querySelectorAll('[data-job-edit]').forEach(b=>b.onclick=()=>jobEditor(jobs[b.dataset.jobEdit]));document.querySelectorAll('[data-job-toggle]').forEach(b=>b.onclick=busy(async()=>{const j=jobs[b.dataset.jobToggle];await api('/api/v1/jobs/'+j.id,{method:'PUT',body:jobPayload(j,{enabled:!j.enabled})});await navigate();}));document.querySelectorAll('[data-job-delete]').forEach(b=>b.onclick=()=>confirmDialog(t('removeConfirm'),async()=>{await api('/api/v1/jobs/'+jobs[b.dataset.jobDelete].id,{method:'DELETE'});await navigate();}));}, diff --git a/scripts/ui-gallery-current.cjs b/scripts/ui-gallery-current.cjs new file mode 100644 index 0000000..199e3ca --- /dev/null +++ b/scripts/ui-gallery-current.cjs @@ -0,0 +1,14 @@ +/* Mock current-image metadata; never performs a device write. */ +const assert=require('assert'),{chromium}=require('playwright'); +(async()=>{ + const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'}),page=await browser.newPage();let checks=0,writes=0; + await page.route('**/api/v1/devices/*/images',r=>r.fulfill({json:[{image_id:1,name:'Current fixture',current:true,url:'/mock-image.jpg',created_at:null},{image_id:2,name:'Other fixture',current:false,url:'/mock-image.jpg',created_at:null}]})); + page.on('request',r=>{if(r.method()==='POST'&&r.url().endsWith('/display'))writes++;}); + try{ + await page.goto('http://127.0.0.1:18787');await page.locator('#username').fill(process.env.FRAME_FLOW_TEST_USERNAME);await page.locator('#password').fill(process.env.FRAME_FLOW_TEST_PASSWORD);await page.locator('#login-form button').click();await page.locator('.hero').waitFor();assert(await page.evaluate(()=>state.settings.mock)); + for(const lang of ['zh','en'])for(const width of [1440,390])for(const theme of ['dark','light']){ + await page.setViewportSize({width,height:1000});await page.evaluate(({lang,theme})=>{state.lang=lang;document.documentElement.dataset.theme=theme;shell();},{lang,theme});await page.locator('[data-page="gallery"]').click();await page.locator('[data-show="0"]').waitFor();assert(await page.locator('[data-show="0"]').isDisabled());assert(await page.locator('[data-show="0"]').textContent()===(lang==='zh'?'当前图片':'Current image'));assert(await page.locator('[data-show="1"]').isEnabled());await page.locator('[data-show="0"]').evaluate(b=>b.onclick());assert(!await page.locator('#modal').evaluate(d=>d.open));assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));checks++; + } + assert(writes===0);console.log('Current-image gallery button: '+checks+' profiles passed; disabled label, other image enabled, no display request'); + }finally{await browser.close();} +})().catch(e=>{console.error(e);process.exit(1)});