Files
pigway-frame-flow/pigway_frame_flow/web/thumbnails.js
T

33 lines
7.5 KiB
JavaScript
Raw Normal View History

'use strict';
Object.assign(MESSAGES.zh, {thumbnails:'小图方案',thumbnailPlanInUse:'此方案已被任务或待处理操作使用,请先取消关联或更换方案。'});
Object.assign(MESSAGES.en, {thumbnails:'Thumbnail plans',thumbnailPlanInUse:'This plan is used by a task or pending operation. Unlink it or select another plan first.'});
views.thumbnails=async function(epoch){
const [plans,sources]=await Promise.all([api('/api/v1/thumbnail-plans'),api('/api/v1/photo-sources')]);
if(epoch!==state.epoch)return;
$('#main').innerHTML=`<section class="card"><div class="actions"><select id="thumb-plan"><option value="">${tr('新建方案','New plan')}</option>${plans.map(p=>`<option value="${esc(p.id)}">${esc(p.name)} · ${p.width}×${p.height}</option>`).join('')}</select><button id="thumb-delete" class="danger">${t('delete')}</button></div><form id="thumb-form"><div class="field"><label for="thumb-name">${t('name')}</label><input id="thumb-name" required maxlength="120"></div><div class="form-grid">${[['width',tr('宽度(像素)','Width (px)'),410,16,2048,1],['height',tr('高度(像素)','Height (px)'),502,16,2048,1],['center_x',tr('水平裁切位置(0 左 / 1 右)','Crop X (0 left / 1 right)'),0.5,0,1,0.01],['center_y',tr('垂直裁切位置(0 上 / 1 下)','Crop Y (0 top / 1 bottom)'),0.5,0,1,0.01],['quality',tr('JPEG 质量','JPEG quality'),85,40,95,1]].map(([key,label,value,min,max,step])=>`<div class="field"><label for="thumb-${key}">${label}</label><input id="thumb-${key}" type="number" required min="${min}" max="${max}" step="${step}" value="${value}"></div>`).join('')}<div class="field"><label for="thumb-fit">${tr('适配方式','Fit')}</label><select id="thumb-fit"><option value="cover">${tr('等比裁切铺满(默认)','Fill with proportional crop (default)')}</option><option value="contain">${tr('完整画面(必要时补白)','Full picture (white padding if needed)')}</option></select></div></div><p class="hint">${tr('不拉伸、不叠加文字;裁切位置仅在铺满时生效。Photos 使用可取得的源预览。','No stretching or text. Crop positions apply to fill mode. Photos uses available source previews.')}</p><button type="submit" class="primary">${tr('保存方案','Save plan')}</button></form></section><section class="card"><h2>${tr('预览与生成','Preview and generate')}</h2><div class="field"><label for="thumb-source">${t('source')}</label><select id="thumb-source"><option value="">${tr('选择照片数据源','Choose photo source')}</option>${sources.map(s=>`<option value="${esc(s.id)}">${esc(s.name)}</option>`).join('')}</select></div><div class="field"><label for="thumb-item">${tr('源图片','Source image')}</label><select id="thumb-item"></select></div><div class="actions"><button id="thumb-prev">${tr('上一页','Previous page')}</button><button id="thumb-next">${tr('下一页','Next page')}</button><button id="thumb-preview">${tr('预览小图','Preview thumbnail')}</button><button id="thumb-generate" class="primary">${tr('生成并保存到图库','Generate and save to gallery')}</button></div><p id="thumb-result" class="hint" aria-live="polite"></p><img id="thumb-image" alt="${tr('小图预览','Thumbnail preview')}" hidden style="max-width:100%;max-height:502px;width:auto;height:auto"></section>`;
const defaults={name:'',width:410,height:502,center_x:0.5,center_y:0.5,quality:85,fit:'cover'};
const fill=p=>{for(const key of Object.keys(defaults))$('#thumb-'+key).value=p[key];};
const read=()=>{const p={};for(const key of Object.keys(defaults))p[key]=['name','fit'].includes(key)?$('#thumb-'+key).value:Number($('#thumb-'+key).value);return p;};
fill(defaults);$('#thumb-delete').disabled=true;
$('#thumb-form').oninput=()=>{$('#thumb-image').hidden=true;$('#thumb-result').textContent=tr('参数已修改,请重新预览','Parameters changed; preview again');};
$('#thumb-plan').onchange=()=>{fill(plans.find(p=>p.id===$('#thumb-plan').value)||defaults);$('#thumb-delete').disabled=!$('#thumb-plan').value;$('#thumb-image').hidden=true;};
$('#thumb-form').onsubmit=busy(async e=>{e.preventDefault();const id=$('#thumb-plan').value;await api('/api/v1/thumbnail-plans'+(id?'/'+id:''),{method:id?'PUT':'POST',body:read()});toast(t('save'));await navigate();});
bind('thumb-delete',()=>confirmDialog(t('removeConfirm'),async()=>{await api('/api/v1/thumbnail-plans/'+$('#thumb-plan').value,{method:'DELETE'});await navigate();}));
let offset=0,version=0;
async function images(){const source=$('#thumb-source').value;const ticket=++version;$('#thumb-item').innerHTML='';if(!source)return;const page=await api('/api/v1/photo-sources/'+encodeURIComponent(source)+'/images?offset='+offset+'&limit=50');if(ticket!==version||epoch!==state.epoch)return;$('#thumb-item').innerHTML=page.items.map(i=>`<option value="${esc(i.id)}">${esc(i.name)}</option>`).join('');$('#thumb-prev').disabled=offset===0;$('#thumb-next').disabled=offset+50>=page.total;}
$('#thumb-source').onchange=busy(async()=>{offset=0;await images();});
bind('thumb-prev',async()=>{offset=Math.max(0,offset-50);await images();});bind('thumb-next',async()=>{offset+=50;await images();});
function payload(){if(!$('#thumb-form').reportValidity())throw new Error(tr('请补全方案参数','Complete the plan parameters'));if(!$('#thumb-item').value)throw new Error(tr('请选择源图片','Choose a source image'));return {source_id:$('#thumb-source').value,item_id:$('#thumb-item').value,plan:read()};}
bind('thumb-preview',async()=>{const blob=await api('/api/v1/thumbnails/preview',{method:'POST',body:payload(),binary:true});if(epoch!==state.epoch)return;const url=URL.createObjectURL(blob);state.blobs.push(url);$('#thumb-image').src=url;$('#thumb-image').hidden=false;$('#thumb-result').textContent=tr('预览未保存','Preview is not saved');});
bind('thumb-generate',async()=>{const asset=await api('/api/v1/thumbnails/generate',{method:'POST',body:payload()});if(epoch!==state.epoch)return;$('#thumb-image').hidden=false;await blobImage($('#thumb-image'),asset.url);$('#thumb-result').textContent=tr('已保存到本地图库:','Saved to local gallery: ')+asset.name;});
};
async function thumbnailJobFields(job){
const plans=await api('/api/v1/thumbnail-plans');if(!$('#job-form'))return;
const box=document.createElement('section');box.innerHTML=`<label class="check-row"><input type="checkbox" id="job-thumbnail" ${job.generate_thumbnail?'checked':''}>${tr('生成小图','Generate thumbnail')}</label><div class="field"><label for="job-thumbnail-plan">${tr('小图生成方案(单选)','Thumbnail plan (choose one)')}</label><select id="job-thumbnail-plan"><option value="">${tr('请选择已保存方案','Choose a saved plan')}</option>${plans.map(p=>`<option value="${esc(p.id)}" ${job.thumbnail_plan_id===p.id?'selected':''}>${esc(p.name)} · ${p.width}×${p.height}</option>`).join('')}</select></div><p class="hint">${tr('仅支持照片数据源任务;上传成功后使用源画面生成。请先在“小图方案”中保存方案。','For photo-source tasks only; generates from source pixels after upload succeeds. Save a plan in Thumbnail plans first.')}</p>`;
$('#job-form .modal-actions').before(box);
const update=()=>{const supported=$('#job-content-type')?.value==='photo_source';$('#job-thumbnail').disabled=!supported;if(!supported)$('#job-thumbnail').checked=false;$('#job-thumbnail-plan').disabled=!$('#job-thumbnail').checked;$('#job-thumbnail-plan').required=$('#job-thumbnail').checked;};
$('#job-thumbnail').onchange=update;$('#job-content-type')?.addEventListener('change',update);update();
}