Space template controls and prevent cancel buttons submitting forms
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
# KoKonna official content and subscriptions
|
||||||
|
|
||||||
|
Checked 2026-10-02 against official public sources:
|
||||||
|
|
||||||
|
- Product: https://kokonna.art/products/kokonna-ai-e-ink-frame-7
|
||||||
|
- Chinese FAQ: https://kokonna.art/zh-hans/pages/常见问问题
|
||||||
|
- OpenAPI: https://kokonna.art/pages/openapi
|
||||||
|
|
||||||
|
Official product materials list calendar/local weather, word of the day, traditional almanac, global travel recommendations and daily news. FAQ also lists image rotation and AI art modes (voice, text, sketch, photo style transformation). These product/widget categories are not a verified complete catalog of current mainland WeChat mini-program subscriptions. The user's daily-poetry subscription is firsthand evidence of that option; public materials reviewed do not document its subscription contract.
|
||||||
|
|
||||||
|
The current OpenAPI documents device, upload, listImages, deleteImage, image retrieval, displayImageById and displayImageByName. Device metadata includes rotation mode/interval and current image/cover; it does not document selected subscription IDs or original template data. No subscription catalog/read/create/update/cancel interface is listed. Therefore an official subscription integration is blocked on a documented provider contract, not on a Frame Flow rendering limitation.
|
||||||
|
|
||||||
|
Frame Flow displays official categories and explicit unsupported status, with links to original docs. It does not present its own poetry provider as the official subscription, fabricate endpoints, infer subscription from an image, or alter existing official subscriptions. A future documented API should be implemented in the KoKonna driver, preserving the public capability model, rather than through vendor-specific core behavior.
|
||||||
|
|
||||||
|
To complete official integration: obtain a current mini-program subscription-page screenshot/catalog and official subscription API documentation (endpoint, auth type, schema, effect of writes). Credentials or session tokens must not appear in screenshots or documentation. No private endpoint guesses or authenticated cloud writes were used in this investigation.
|
||||||
@@ -7,7 +7,7 @@ const icons={dashboard:'◫',gallery:'▧',upload:'↥',templates:'▦',automati
|
|||||||
const t=k=>window.MESSAGES[state.lang][k]||k;
|
const t=k=>window.MESSAGES[state.lang][k]||k;
|
||||||
const esc=v=>String(v??'—').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
const esc=v=>String(v??'—').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||||
const fmt=v=>v?new Date(v).toLocaleString(state.lang==='zh'?'zh-CN':'en-GB',{timeZone:'Asia/Shanghai'}):'—';
|
const fmt=v=>v?new Date(v).toLocaleString(state.lang==='zh'?'zh-CN':'en-GB',{timeZone:'Asia/Shanghai'}):'—';
|
||||||
const buttons=(id,text,primary=false)=>`<button id="${id}" class="${primary?'primary':''}">${t(text)}</button>`;
|
const buttons=(id,text,primary=false)=>`<button type="button" id="${id}" class="${primary?'primary':''}">${t(text)}</button>`;
|
||||||
let toastTimer;
|
let toastTimer;
|
||||||
function toast(message){$('#toast').textContent=message;$('#toast').style.display='block';clearTimeout(toastTimer);toastTimer=setTimeout(()=>$('#toast').style.display='none',6000);}
|
function toast(message){$('#toast').textContent=message;$('#toast').style.display='block';clearTimeout(toastTimer);toastTimer=setTimeout(()=>$('#toast').style.display='none',6000);}
|
||||||
async function api(path,options={}){
|
async function api(path,options={}){
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ async function enhanceDashboard(){
|
|||||||
}
|
}
|
||||||
async function enhanceTemplates(){
|
async function enhanceTemplates(){
|
||||||
const panel=document.createElement('section');panel.className='card';$('.template-grid').before(panel);await currentPanel(panel);if(!panel.isConnected)return;
|
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);
|
const download=document.createElement('button');download.id='download-template';download.textContent=t('download');const previewActions=document.createElement('div');previewActions.className='actions template-preview-actions';$('#display-template').before(previewActions);previewActions.append($('#display-template'),download);
|
||||||
download.onclick=busy(async()=>{if(!state.templatePreview){toast(t('previewHint'));return;}await downloadImage('/api/v1/assets/'+state.templatePreview,'frame-flow-template.jpg');});
|
download.onclick=busy(async()=>{if(!state.templatePreview){toast(t('previewHint'));return;}await downloadImage('/api/v1/assets/'+state.templatePreview,'frame-flow-template.jpg');});
|
||||||
await poetryControls();
|
await poetryControls();
|
||||||
|
officialContentInfo();
|
||||||
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'));});}
|
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);}
|
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);}
|
||||||
@@ -71,3 +72,9 @@ async function poetryControls(){
|
|||||||
description();$('#poem-choice').onchange=()=>{const data=JSON.parse($('#template-data').value);delete data.poem_title;delete data.poem_author;delete data.poem_lines;delete data.poem_source;delete data.poem_edition;if($('#poem-choice').value)data.poem_id=$('#poem-choice').value;else{delete data.poem_id;delete data.date;}$('#template-data').value=JSON.stringify(data,null,2);$('#template-data').oninput();description();};
|
description();$('#poem-choice').onchange=()=>{const data=JSON.parse($('#template-data').value);delete data.poem_title;delete data.poem_author;delete data.poem_lines;delete data.poem_source;delete data.poem_edition;if($('#poem-choice').value)data.poem_id=$('#poem-choice').value;else{delete data.poem_id;delete data.date;}$('#template-data').value=JSON.stringify(data,null,2);$('#template-data').oninput();description();};
|
||||||
bind('daily-poetry-job',()=>{showModal(`<h2>${t('dailyPoetryJob')}</h2><form id="poetry-job-form"><div class="field"><label for="poetry-job-time">${t('time')}</label><input id="poetry-job-time" type="time" required value="07:00"></div><label class="check-row"><input id="poetry-job-enabled" type="checkbox">${t('enabled')}</label><p class="hint">${t('poetryScheduleHint')}</p><div class="modal-actions">${buttons('modal-cancel','cancel')}<button type="submit" class="primary">${t('save')}</button></div></form>`);$('#poetry-job-form').onsubmit=busy(async e=>{e.preventDefault();await api('/api/v1/jobs',{method:'POST',body:{device_id:state.device,name:t('dailyPoetryJob'),enabled:$('#poetry-job-enabled').checked,schedule:{type:'daily',time:$('#poetry-job-time').value},source:{type:'template',template:'daily-poetry',data:{},profile:$('#template-profile').value},display_duration:0,restore_previous:false,priority:50}});$('#modal').close();await navigate('automation');});});
|
bind('daily-poetry-job',()=>{showModal(`<h2>${t('dailyPoetryJob')}</h2><form id="poetry-job-form"><div class="field"><label for="poetry-job-time">${t('time')}</label><input id="poetry-job-time" type="time" required value="07:00"></div><label class="check-row"><input id="poetry-job-enabled" type="checkbox">${t('enabled')}</label><p class="hint">${t('poetryScheduleHint')}</p><div class="modal-actions">${buttons('modal-cancel','cancel')}<button type="submit" class="primary">${t('save')}</button></div></form>`);$('#poetry-job-form').onsubmit=busy(async e=>{e.preventDefault();await api('/api/v1/jobs',{method:'POST',body:{device_id:state.device,name:t('dailyPoetryJob'),enabled:$('#poetry-job-enabled').checked,schedule:{type:'daily',time:$('#poetry-job-time').value},source:{type:'template',template:'daily-poetry',data:{},profile:$('#template-profile').value},display_duration:0,restore_previous:false,priority:50}});$('#modal').close();await navigate('automation');});});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function officialContentInfo(){
|
||||||
|
const info=document.createElement('details');info.className='official-content-info';
|
||||||
|
info.innerHTML=`<summary>${t('officialContentTitle')}</summary><p>${t('officialContentDescription')}</p><ul>${['officialCalendarWeather','officialWordAlmanacTravel','officialNews','officialCreative'].map(k=>`<li>${t(k)}</li>`).join('')}</ul><p class="notice">${t('officialSubscriptionUnavailable')}</p><div class="actions"><a href="https://kokonna.art/products/kokonna-ai-e-ink-frame-7" target="_blank" rel="noopener noreferrer">${t('officialProductDocs')}</a><a href="https://kokonna.art/pages/openapi" target="_blank" rel="noopener noreferrer">${t('officialAPIDocs')}</a></div>`;
|
||||||
|
$('.template-grid').before(info);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,3 +13,6 @@ window.MESSAGES.zh.templateHint='先生成预览,再选择显示方式。天
|
|||||||
Object.assign(window.MESSAGES.zh,{allowedHosts:'允许下载图片的域名(每行一个)',urlHint:'只允许 HTTPS 和设置中明确允许的域名,内网地址始终禁止。',uploadURL:'从链接导入',urlSettings:'图片 URL 安全设置'});Object.assign(window.MESSAGES.en,{allowedHosts:'Allowed image hostnames (one per line)',urlHint:'HTTPS and explicitly allowed hosts only. Private network addresses are blocked.',uploadURL:'Import URL',urlSettings:'Image URL security'});
|
Object.assign(window.MESSAGES.zh,{allowedHosts:'允许下载图片的域名(每行一个)',urlHint:'只允许 HTTPS 和设置中明确允许的域名,内网地址始终禁止。',uploadURL:'从链接导入',urlSettings:'图片 URL 安全设置'});Object.assign(window.MESSAGES.en,{allowedHosts:'Allowed image hostnames (one per line)',urlHint:'HTTPS and explicitly allowed hosts only. Private network addresses are blocked.',uploadURL:'Import URL',urlSettings:'Image URL security'});
|
||||||
|
|
||||||
Object.assign(window.MESSAGES.zh,{poetryNotice:'Frame Flow 每日诗词:内置古典诗词,按上海日期每天换一首。独立于官方小程序的订阅,官方订阅设置无法通过公开 API 读取或修改。',poemChoice:'选择诗词',dailyAutomatic:'每日自动选诗',poetryCatalog:'内置篇数:',dailyPoetryJob:'每日一首诗词',poetryScheduleHint:'每天按上海时区生成当天诗词并显示。开启任务后会按时改变画框显示。',poem_title:'诗名',poem_author:'作者',poem_lines:'诗词正文'});Object.assign(window.MESSAGES.en,{poetryNotice:'Frame Flow Daily Poetry selects a classical poem by Shanghai date. It is independent of the official mini-app subscription, whose settings are not exposed by the public API.',poemChoice:'Choose poem',dailyAutomatic:'Automatic daily selection',poetryCatalog:'Catalog size: ',dailyPoetryJob:'A poem every day',poetryScheduleHint:'Generate and display the daily poem in Shanghai time. Enabling this job will change the frame on schedule.',poem_title:'Poem title',poem_author:'Poet',poem_lines:'Poem text'});
|
Object.assign(window.MESSAGES.zh,{poetryNotice:'Frame Flow 每日诗词:内置古典诗词,按上海日期每天换一首。独立于官方小程序的订阅,官方订阅设置无法通过公开 API 读取或修改。',poemChoice:'选择诗词',dailyAutomatic:'每日自动选诗',poetryCatalog:'内置篇数:',dailyPoetryJob:'每日一首诗词',poetryScheduleHint:'每天按上海时区生成当天诗词并显示。开启任务后会按时改变画框显示。',poem_title:'诗名',poem_author:'作者',poem_lines:'诗词正文'});Object.assign(window.MESSAGES.en,{poetryNotice:'Frame Flow Daily Poetry selects a classical poem by Shanghai date. It is independent of the official mini-app subscription, whose settings are not exposed by the public API.',poemChoice:'Choose poem',dailyAutomatic:'Automatic daily selection',poetryCatalog:'Catalog size: ',dailyPoetryJob:'A poem every day',poetryScheduleHint:'Generate and display the daily poem in Shanghai time. Enabling this job will change the frame on schedule.',poem_title:'Poem title',poem_author:'Poet',poem_lines:'Poem text'});
|
||||||
|
|
||||||
|
Object.assign(MESSAGES.zh,{officialContentTitle:'KoKonna 官方小组件与订阅',officialContentDescription:'官方产品资料列出的内容类型;下列功能与 Frame Flow 自建模板分开管理。',officialCalendarWeather:'日历与当地天气',officialWordAlmanacTravel:'每日一词、传统黄历、全球旅行推荐',officialNews:'每日新闻',officialCreative:'AI 语音 / 文字 / 涂鸦 / 照片转艺术;照片轮播',officialSubscriptionUnavailable:'公开 OpenAPI 尚未列出订阅目录、当前订阅、开通或取消订阅接口。官方订阅请继续在官方小程序中管理;Frame Flow 不会读取或修改这些设置。你使用的每日诗词属于官方小程序内容,当前无法通过公开 API 确认完整类型列表。',officialProductDocs:'官方内容介绍 ↗',officialAPIDocs:'官方 OpenAPI ↗'});
|
||||||
|
Object.assign(MESSAGES.en,{officialContentTitle:'KoKonna official widgets and subscriptions',officialContentDescription:'Content types listed in official product materials; managed separately from Frame Flow templates.',officialCalendarWeather:'Calendar and local weather',officialWordAlmanacTravel:'Word of the day, traditional almanac and global travel recommendations',officialNews:'Daily news',officialCreative:'AI voice / text / sketch / photo-to-art; photo rotation',officialSubscriptionUnavailable:'Public OpenAPI does not list subscription discovery, current subscription, subscribe or unsubscribe endpoints. Manage official subscriptions in the official mini-program. Frame Flow cannot read or modify these settings. Your daily-poetry subscription is mini-program content; its complete catalog cannot currently be verified through public API.',officialProductDocs:'Official content overview ↗',officialAPIDocs:'Official OpenAPI ↗'});
|
||||||
|
|||||||
@@ -24,3 +24,17 @@ details[open]>summary{margin-bottom:16px}
|
|||||||
#password-form{margin-top:24px;padding-top:24px;border-top:1px solid var(--border)}
|
#password-form{margin-top:24px;padding-top:24px;border-top:1px solid var(--border)}
|
||||||
#modal-content>p,#modal-content>pre{margin:16px 0}
|
#modal-content>p,#modal-content>pre{margin:16px 0}
|
||||||
@media(max-width:720px){#main{gap:18px}.columns,.gallery,.template-grid{gap:14px}.frame-demo{display:flex;padding:24px}.hero-info{padding:24px}.hero h2{margin-top:16px}.image-card .actions{gap:8px}.modal-actions{flex-wrap:wrap}.check-row{margin:14px 0}#current-frame-image{height:240px!important}}
|
@media(max-width:720px){#main{gap:18px}.columns,.gallery,.template-grid{gap:14px}.frame-demo{display:flex;padding:24px}.hero-info{padding:24px}.hero h2{margin-top:16px}.image-card .actions{gap:8px}.modal-actions{flex-wrap:wrap}.check-row{margin:14px 0}#current-frame-image{height:240px!important}}
|
||||||
|
|
||||||
|
/* Keep controls separated inside cards, not just between page sections. */
|
||||||
|
.template-preview-actions{margin-top:20px;gap:12px}
|
||||||
|
.template-preview-actions>button{margin-top:0!important}
|
||||||
|
.poetry-controls{display:flex;flex-direction:column;gap:18px;margin-bottom:24px}
|
||||||
|
.poetry-controls>.field,.poetry-controls>p,.poetry-controls>.notice{margin:0}
|
||||||
|
.poetry-controls>button{align-self:flex-start}
|
||||||
|
.actions,.modal-actions{column-gap:12px;row-gap:12px}
|
||||||
|
.official-content-info{padding:20px 24px}
|
||||||
|
.official-content-info ul{padding-left:22px;margin:16px 0}
|
||||||
|
.official-content-info li+li{margin-top:10px}
|
||||||
|
.official-content-info .notice{margin:20px 0}
|
||||||
|
.official-content-info .actions a{display:inline-block;padding:8px 0}
|
||||||
|
.tabs{gap:10px}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* All-page layout and daily-poetry checks; Mock only, no real device writes. */
|
/* All-page layout and daily-poetry checks; Mock only, no real device writes. */
|
||||||
const assert=require('assert'),{chromium}=require('playwright');
|
const assert=require('assert'),{chromium}=require('playwright');
|
||||||
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();const base='http://127.0.0.1:18787';await page.goto(base);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));let checks=0;for(const width of [1440,390])for(const theme of ['light','dark']){await page.setViewportSize({width,height:1000});await page.evaluate(theme=>{localStorage.setItem('ff-theme',theme);document.documentElement.dataset.theme=theme;},theme);for(const name of ['dashboard','gallery','upload','templates','automation','playlists','history','settings']){await page.locator(`[data-page="${name}"]`).click();await page.waitForTimeout(350);if(name==='settings')await page.locator('#image-host-form').waitFor();const result=await page.evaluate(()=>{const children=[...document.querySelector('#main').children].filter(x=>x.getBoundingClientRect().height>0);return {overflow:document.documentElement.scrollWidth>innerWidth,gaps:children.slice(1).map((x,i)=>x.getBoundingClientRect().top-children[i].getBoundingClientRect().bottom)};});assert(!result.overflow,`${name} ${width} overflow`);assert(result.gaps.every(x=>x>=16),`${name} ${width} gaps ${result.gaps}`);checks++;if(name==='templates'||name==='settings')await page.screenshot({path:`docs/screenshots/${name}-${width}-${theme}.png`,fullPage:true});}}
|
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});const page=await browser.newPage();let unintended=0;page.on('request',r=>{if(r.method()==='POST'&&/\/(jobs|playlists|display)$/.test(r.url()))unintended++;});const base='http://127.0.0.1:18787';await page.goto(base);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));let checks=0;for(const width of [1440,390])for(const theme of ['light','dark']){await page.setViewportSize({width,height:1000});await page.evaluate(theme=>{localStorage.setItem('ff-theme',theme);document.documentElement.dataset.theme=theme;},theme);for(const name of ['dashboard','gallery','upload','templates','automation','playlists','history','settings']){await page.locator(`[data-page="${name}"]`).click();await page.waitForTimeout(350);if(name==='settings')await page.locator('#image-host-form').waitFor();const result=await page.evaluate(()=>{const children=[...document.querySelector('#main').children].filter(x=>x.getBoundingClientRect().height>0);return {overflow:document.documentElement.scrollWidth>innerWidth,buttonGaps:[...document.querySelectorAll('#main button')].flatMap(b=>{const next=b.nextElementSibling;if(next?.tagName!=='BUTTON')return [];const a=b.getBoundingClientRect(),c=next.getBoundingClientRect();if(!a.width||!c.width)return [];return [Math.abs(a.top-c.top)<4?c.left-a.right:c.top-a.bottom];}),gaps:children.slice(1).map((x,i)=>x.getBoundingClientRect().top-children[i].getBoundingClientRect().bottom)};});assert(result.buttonGaps.every(x=>x>=8),`${name} ${width} button gaps ${result.buttonGaps}`);assert(!result.overflow,`${name} ${width} overflow`);assert(result.gaps.every(x=>x>=16),`${name} ${width} gaps ${result.gaps}`);checks++;if(name==='templates'||name==='settings')await page.screenshot({path:`docs/screenshots/${name}-${width}-${theme}.png`,fullPage:true});}}
|
||||||
await page.locator('[data-page="templates"]').click();await page.locator('[data-template="daily-poetry"]').click();await page.locator('#poem-choice').waitFor();assert(await page.locator('#poem-choice option').count()===21);checks++;await page.locator('#poem-choice').selectOption('jing-ye-si');await page.locator('#render-template').click();await page.locator('#template-image').waitFor();await page.waitForFunction(()=>document.querySelector('#template-image')?.naturalWidth===1200);checks++;await page.locator('#daily-poetry-job').click();assert(!await page.locator('#poetry-job-enabled').isChecked());checks++;await page.locator('#modal-cancel').click();await browser.close();console.log(`Layout/poetry browser: ${checks} passed (32 page/profile combinations)`);})().catch(e=>{console.error(e.message);process.exit(1)});
|
await page.locator('[data-page="templates"]').click();await page.locator('[data-template="daily-poetry"]').click();await page.locator('#poem-choice').waitFor();assert(await page.locator('#poem-choice option').count()===21);checks++;await page.locator('#poem-choice').selectOption('jing-ye-si');await page.locator('#render-template').click();await page.locator('#template-image').waitFor();await page.waitForFunction(()=>document.querySelector('#template-image')?.naturalWidth===1200);checks++;await page.locator('#daily-poetry-job').click();assert(!await page.locator('#poetry-job-enabled').isChecked());checks++;const gap=await page.evaluate(()=>{const a=document.querySelector('#daily-poetry-job').getBoundingClientRect(),b=document.querySelector('#template-fields').getBoundingClientRect();return b.top-a.bottom;});assert(gap>=20,'Poetry action touches fields');checks++;await page.locator('#modal-cancel').click();await page.screenshot({path:'docs/screenshots/daily-poetry-controls-mobile-dark.png',fullPage:true});for(const width of [1440,390])for(const theme of ['light','dark']){await page.setViewportSize({width,height:1000});await page.evaluate(theme=>document.documentElement.dataset.theme=theme,theme);await page.locator('[data-page="templates"]').click();for(const id of ['calendar','daily-dashboard','daily-poetry','photo-text','quote','system-alert','weather']){await page.locator(`[data-template="${id}"]`).click();await page.waitForTimeout(250);await page.locator('#download-template').waitFor();const gap=await page.evaluate(()=>{const a=document.querySelector('#display-template').getBoundingClientRect(),b=document.querySelector('#download-template').getBoundingClientRect();return Math.abs(a.top-b.top)<4?b.left-a.right:b.top-a.bottom;});assert(gap>=8,`${id} ${width} preview buttons touch`);checks++;}for(const [name,button] of [['automation','new-job'],['playlists','new-playlist'],['templates','daily-poetry-job']]){await page.locator(`[data-page="${name}"]`).click();if(name==='templates'){await page.locator('[data-template="daily-poetry"]').click();await page.locator('#poem-choice').waitFor();}await page.locator('#'+button).click();const gap=await page.evaluate(()=>{const [a,b]=[...document.querySelectorAll('#modal .modal-actions button')].map(x=>x.getBoundingClientRect());return Math.abs(a.top-b.top)<4?b.left-a.right:b.top-a.bottom;});assert(gap>=8,`${name} ${width} modal buttons touch`);checks++;await page.locator('#modal-cancel').click();}}assert(unintended===0,`Cancel submitted ${unintended} requests`);checks++;await browser.close();console.log(`Layout/poetry browser: ${checks} passed (32 page/profile combinations)`);})().catch(e=>{console.error(e.message);process.exit(1)});
|
||||||
|
|||||||
Reference in New Issue
Block a user