'use strict';
// Page features share the same authenticated API and display operation pipeline.
async function currentPanel(container){
const content=await api('/api/v1/devices/'+state.device+'/current-content');
state.currentDisplay=content;
const known=content.source_known&&content.source?.type==='template';
container.innerHTML=`
${t('currentContent')} #${esc(content.image_id??'—')} · ${t(content.synced?'synced':'syncing')}
${known?`${t('currentTemplate')}: ${esc(content.source.template)} `:content.source_known?t('sourceKnown'):t('sourceUnknown')}
${known?buttons('edit-current-template','editCurrent'):''}${content.overrides.length?`${t('temporaryDisplay')} ${content.overrides.map(x=>`
#${esc(x.image_id)} · ${t('priority')}: ${x.priority} · ${fmt(x.expires_at)}
`).join('')}
`:''}`;
bind('edit-current-template',()=>editTemplateSource(content.source));
return content;
}
function editTemplateSource(source){state.selectedTemplate=source.template;state.pendingTemplateData=structuredClone(source);return navigate('templates');}
async function enhanceDashboard(){
const epoch=state.epoch;const frame=$('.frame-demo');const panel=document.createElement('section');panel.className='card';$('#main').append(panel);
const current=await currentPanel(panel);if(epoch!==state.epoch)return;
if(current.preview_url){frame.innerHTML=`${t('currentContent')} #${current.image_id} `;await blobImage($('#current-frame-image'),current.preview_url);}
const weather=await api('/api/v1/providers/weather');if(epoch!==state.epoch)return;const box=document.createElement('section');box.className='card';box.innerHTML=`${t('weather')} ${esc(weather.current.location)} · ${esc(weather.current.temperature)} · ${esc(weather.current.summary)}
${weather.current.mock?t('weatherMock'):t('weatherAttribution')}${weather.current.stale?' · '+t('stale'):''}
`;$('#main').append(box);
}
async function enhanceTemplates(){
const panel=document.createElement('section');panel.className='card';$('.template-grid').before(panel);await currentPanel(panel);if(!panel.isConnected)return;
const download=document.createElement('button');download.id='download-template';download.textContent=t('download');$('#display-template').after(download);
download.onclick=busy(async()=>{if(!state.templatePreview){toast(t('previewHint'));return;}await downloadImage('/api/v1/assets/'+state.templatePreview,'frame-flow-template.jpg');});
const photo=$('#field-photo_id');if(photo){const file=document.createElement('input');file.type='file';file.id='template-photo-upload';file.accept='image/jpeg,image/png,image/webp';file.setAttribute('aria-label',t('selectFile'));photo.after(file);file.onchange=busy(async()=>{if(!file.files[0])return;const body=new FormData();body.append('file',file.files[0]);body.append('fit','cover');body.append('profile','color');const asset=await api('/api/v1/preview',{method:'POST',body});const accepted=await api('/api/v1/devices/'+state.device+'/upload',{method:'POST',body:{type:'image',asset_id:asset.asset_id},headers:{'Idempotency-Key':'photo-'+clientUUID()}});await waitOperation(accepted.operation_id);const option=document.createElement('option');option.value=asset.asset_id;option.textContent=asset.name;photo.append(option);photo.value=asset.asset_id;photo.oninput();toast(t('done'));});}
}
async function downloadImage(path,name){const blob=await api(path,{binary:true});const url=URL.createObjectURL(blob);const link=document.createElement('a');link.href=url;link.download=name;document.body.append(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(url),10000);}
function displayDialog(content){
if(!liveWriteAllowed()){toast(t('readOnlyWarning'));return;}
showModal(`${t('displayOptions')} `);
$('#display-options').onsubmit=busy(async e=>{e.preventDefault();const restore=$('#override-restore').checked;const options={duration:restore?Number($('#override-duration').value):0,restore_previous:restore,priority:Number($('#override-priority').value)};await accepted('/api/v1/display',{device_id:state.device,content,options});$('#modal').close();await navigate('history');});
}
function enhanceGallery(){
const grid=$('.gallery');if(!grid)return;
const controls=document.createElement('div');controls.className='form-grid';controls.innerHTML=`${t('searchImages')}
${t('mode')} ${t('sortNewest')} ${t('sortOldest')} ${t('sortName')}
`;grid.before(controls);
const cards=[...grid.querySelectorAll('.image-card')];$('#gallery-search').oninput=()=>{const query=$('#gallery-search').value.toLowerCase();cards.forEach(card=>card.hidden=!card.textContent.toLowerCase().includes(query));};$('#gallery-sort').onchange=()=>{const order=$('#gallery-sort').value;const indexes=cards.map((card,index)=>({card,item:state.gallery[index]}));indexes.sort((a,b)=>order==='name'?a.item.name.localeCompare(b.item.name):order==='oldest'?String(a.item.created_at).localeCompare(String(b.item.created_at)):String(b.item.created_at).localeCompare(String(a.item.created_at)));indexes.forEach(x=>grid.append(x.card));};
cards.forEach((card,i)=>{const item=state.gallery[i];if(item.width&&item.height){const dimensions=document.createElement('small');dimensions.textContent=item.width+' × '+item.height;card.querySelector('h3').after(dimensions);}const download=document.createElement('button');download.textContent=t('download');card.querySelector('.actions').append(download);download.onclick=busy(()=>downloadImage(item.url,item.name));if(state.tab==='local'){const edit=document.createElement('button');edit.textContent=t('continueEdit');card.querySelector('.actions').append(edit);edit.onclick=busy(async()=>{const meta=await api('/api/v1/assets/'+item.asset_id+'/source');if(meta.source?.type==='template')await editTemplateSource(meta.source);else toast(t('sourceUnknown'));});}});
}
async function weatherSettings(){
const weather=await api('/api/v1/providers/weather');const box=document.createElement('section');box.className='card';box.innerHTML=`${t('weatherSettings')}
`;$('#main').append(box);$('#weather-provider').value=weather.config.provider;
function result(w){$('#weather-result').innerHTML=`${esc(w.current.temperature)} · ${esc(w.current.summary)}
${w.current.mock?t('weatherMock'):t('weatherAttribution')}${w.current.stale?' · '+t('stale'):''}
${t('forecast')} ${w.forecast.map(x=>`${esc(x.date)} · ${esc(x.summary)} · ${x.minimum_c}–${x.maximum_c}°C · ${x.rain_probability}%
`).join('')}`;}
result(weather);$('#weather-form').onsubmit=busy(async e=>{e.preventDefault();await api('/api/v1/settings/weather',{method:'PUT',body:{provider:$('#weather-provider').value,location:$('#weather-location').value,latitude:Number($('#weather-latitude').value),longitude:Number($('#weather-longitude').value)}});result(await api('/api/v1/providers/weather'));toast(t('done'));});
}
async function enhanceHistory(){
const rows=[...document.querySelectorAll('tbody tr')];const ops=await api('/api/v1/operations');rows.forEach((row,i)=>{const op=ops[i];if(!op)return;const button=document.createElement('button');button.textContent=t('details');row.lastElementChild.append(button);button.onclick=busy(async()=>{const current=await api('/api/v1/operations/'+op.operation_id);showModal(`${t('details')} ${t(current.status)} · ${esc(current.action)}
${esc(current.error?.message||'')}
${esc(JSON.stringify({operation_id:current.operation_id,request_id:current.request_id,created_at:current.created_at,updated_at:current.updated_at,result:current.result},null,2))} ${current.result?.url?' ':''}${buttons('modal-cancel','cancel')}
`);if(current.result?.url)await blobImage($('#operation-preview'),current.result.url);});});}
async function enhanceAutomation(){
const jobs=await api('/api/v1/jobs');const rows=[...document.querySelectorAll('tbody tr')];rows.forEach((row,i)=>{const job=jobs[i];if(!job)return;const actions=row.querySelector('.row-actions');const run=document.createElement('button');run.textContent=t('runNow');run.disabled=!liveWriteAllowed();run.title=run.disabled?t('readOnlyWarning'):'';actions.append(run);run.onclick=()=>confirmDialog(t('runConfirm'),async()=>{await accepted('/api/v1/jobs/'+job.id+'/run',{});await navigate('history');});if(job.source.type==='template'){const preview=document.createElement('button');preview.textContent=t('jobPreview');actions.append(preview);preview.onclick=busy(async()=>{const id=await accepted('/api/v1/jobs/'+job.id+'/preview',{});const op=await waitOperation(id);showModal(`${t('jobPreview')} ${buttons('modal-cancel','cancel')}
`);await blobImage($('#job-preview-image'),op.result.url);});}});
}
async function jobSourceEditor(job){
const raw=$('#job-source');const advanced=document.createElement('details');advanced.innerHTML=`${t('advanced')} `;raw.parentElement.before(advanced);advanced.append(raw.parentElement);
const box=document.createElement('div');advanced.before(box);let source=structuredClone(job.source);let images=[],assets=[];try{[images,assets]=await Promise.all([api('/api/v1/devices/'+state.device+'/images'),api('/api/v1/assets')]);}catch(e){toast(e.message);}if(!raw.isConnected)return;
box.innerHTML=`${t('sourceType')} ${['template','image','image_url','restore'].map(k=>`${k==='template'?t('template'):k==='image'?t('imageChoice'):k==='restore'?t('restoreSource'):t('imageURL')} `).join('')}
`;
$('#job-content-type').value=source.type;const old=$('#job-template')?.parentElement;if(old)old.remove();
function update(){raw.value=JSON.stringify(source,null,2);}
function fields(){const type=$('#job-content-type').value;const panel=$('#job-content-fields');if(type==='restore'){source={type};panel.innerHTML='';$('#job-restore').checked=false;$('#job-duration').value=0;update();return;}
if(type==='template'){const selected=state.templates.find(tpl=>tpl.id===source.template)||state.templates[0];source={type,template:selected.id,data:source.type==='template'?source.data||{}:{},profile:source.profile||'color'};panel.innerHTML=`${t('template')} ${state.templates.map(x=>`${esc(x.name)} `).join('')}
${t('profile')} ${t('color')} ${t('grayscale')}
`;$('#job-source-profile').value=source.profile;$('#job-source-profile').onchange=()=>{source.profile=$('#job-source-profile').value;update();};const defaults={...selected.defaults,...source.data};const keys=[...new Set([...Object.keys(defaults).filter(k=>typeof defaults[k]!=='object'||Array.isArray(defaults[k])),...(['calendar','daily-dashboard'].includes(selected.id)?['events']:[])])];$('#job-data-fields').innerHTML=keys.map(k=>`${esc({title:state.lang==='zh'?'标题':'Title',message:state.lang==='zh'?'正文':'Message',quote:state.lang==='zh'?'一句话':'Quote',author:state.lang==='zh'?'作者':'Author',events:t('eventsHelp')}[k]||k)}
`).join('');$('#job-template').onchange=()=>{source={type:'template',template:$('#job-template').value,data:{}};fields();};panel.querySelectorAll('[data-job-field]').forEach(el=>el.oninput=()=>{source.data[el.dataset.jobField]=el.dataset.jobField==='events'?el.value.split('\n').filter(x=>x.trim()):el.value;update();});update();}
else if(type==='image'){const selected=source.image_id?'cloud:'+source.image_id:source.asset_id?'local:'+source.asset_id:'';panel.innerHTML=`${t('imageChoice')} — ${images.map(i=>`${esc(i.name)} · #${i.image_id} `).join('')}${assets.map(a=>`${esc(a.name)} · LOCAL `).join('')}
`;$('#job-image').value=selected;$('#job-image').onchange=()=>{const [kind,id]=$('#job-image').value.split(':');source=kind==='cloud'?{type:'image',image_id:Number(id)}:{type:'image',asset_id:id};update();};}
else{panel.innerHTML=`${t('imageURL')}
`;$('#job-url').oninput=()=>{source={type:'image_url',url:$('#job-url').value};update();};}}
$('#job-content-type').onchange=()=>{source={type:$('#job-content-type').value};update();fields();};raw.oninput=()=>{try{source=JSON.parse(raw.value);raw.setCustomValidity('');$('#job-content-type').value=source.type;fields();}catch{raw.setCustomValidity(t('INVALID_REQUEST'));}};fields();
}
async function playlistImagePicker(playlist){
const raw=$('#playlist-images');const box=document.createElement('div');raw.parentElement.after(box);box.innerHTML=`${t('imageChoice')}
${t('loading')}
`;const images=await api('/api/v1/devices/'+state.device+'/images');if(!box.isConnected)return;const chosen=new Set(playlist.image_ids);
$('#playlist-image-choices').innerHTML=images.map(image=>` ${esc(image.name)} · #${image.image_id}${image.current?' · '+t('current'):''} `).join('');box.querySelectorAll('[data-playlist-image]').forEach(el=>el.onchange=()=>{const id=Number(el.dataset.playlistImage);if(el.checked)chosen.add(id);else chosen.delete(id);raw.value=[...chosen].join(',');});raw.oninput=()=>{chosen.clear();raw.value.split(',').map(Number).filter(x=>x>0).forEach(x=>chosen.add(x));box.querySelectorAll('[data-playlist-image]').forEach(el=>el.checked=chosen.has(Number(el.dataset.playlistImage)));};
}
async function enhanceSettings(){await weatherSettings();const config=await api('/api/v1/settings/image-url');const box=document.createElement('section');box.className='card';box.innerHTML=`${t('urlSettings')} `;$('#main').append(box);$('#image-host-form').onsubmit=busy(async e=>{e.preventDefault();await api('/api/v1/settings/image-url',{method:'PUT',body:{allowed_hosts:$('#image-hosts').value.split('\n').map(x=>x.trim()).filter(Boolean)}});toast(t('done'));});}
function enhanceUpload(){const box=document.createElement('div');box.innerHTML=`${t('uploadURL')} ${buttons('preview-url','uploadURL')}`;$('#make-preview').parentElement.append(box);bind('preview-url',async()=>{if(!$('#upload-url').checkValidity())return;const id=await accepted('/api/v1/devices/'+state.device+'/upload',{type:'image_url',url:$('#upload-url').value,fit:$('#fit').value,profile:$('#profile').value});const op=await waitOperation(id);state.preview={asset_id:op.result.asset_id,url:op.result.url};$('#preview-box').className='';$('#preview-box').innerHTML=' ';await blobImage($('#upload-preview'),state.preview.url);$('#save-local').disabled=false;$('#display-upload').disabled=!liveWriteAllowed();});}