This commit is contained in:
+89
-15
@@ -1,16 +1,90 @@
|
||||
const form = document.querySelector('#upload'), result = document.querySelector('#result');
|
||||
form.addEventListener('submit',async e => {
|
||||
e.preventDefault();
|
||||
const button = form.querySelector('button'); button.disabled = true; result.textContent = 'Uploading…';
|
||||
const headers = {}, key = document.querySelector('#key').value;
|
||||
if (key) headers['X-API-Key'] = key;
|
||||
const data = new FormData(); data.append('file',document.querySelector('#file').files[0]);
|
||||
try {
|
||||
const response = await fetch('/api/v1/schematics/render',{ method:'POST',headers,body:data });
|
||||
const body = await response.json();
|
||||
if (!response.ok) throw new Error(body.error?.message || 'Upload failed');
|
||||
result.textContent = 'Queued. ';
|
||||
const link = document.createElement('a'); link.href = body.viewer_url; link.textContent = 'Open schematic'; result.append(link);
|
||||
} catch (e) { result.textContent = e.message; }
|
||||
finally { button.disabled = false; }
|
||||
const operations = {
|
||||
schematic: { endpoint:'/api/v1/schematics/render', suffix:'.SchDoc', label:'Render schematic', fileLabel:'Schematic file', comparison:false },
|
||||
'schematic-comparison': { endpoint:'/api/v1/comparisons', suffix:'.SchDoc', label:'Compare schematics', comparison:true },
|
||||
pcb: { endpoint:'/api/v1/pcbs/render', suffix:'.PcbDoc', label:'Render PCB', fileLabel:'PCB file', comparison:false },
|
||||
'pcb-comparison': { endpoint:'/api/v1/pcbs/comparisons', suffix:'.PcbDoc', label:'Compare PCBs', comparison:true }
|
||||
};
|
||||
const form=document.querySelector('#upload'), result=document.querySelector('#result');
|
||||
const operation=document.querySelector('#operation'), button=document.querySelector('#submit-upload');
|
||||
const fields=document.querySelector('#upload-fields'), viewerLink=document.querySelector('#viewer-link');
|
||||
const details=document.querySelector('#response-details'), json=document.querySelector('#response-json');
|
||||
let polling=null, uploading=false;
|
||||
function reset(){
|
||||
polling?.abort();polling=null;result.textContent='';viewerLink.replaceChildren();details.hidden=true;json.textContent='';
|
||||
}
|
||||
function configure(){
|
||||
if(uploading)return;
|
||||
reset();const config=operations[operation.value];
|
||||
document.querySelector('#selected-endpoint').textContent=`POST ${config.endpoint}`;
|
||||
document.querySelector('#single-input').hidden=config.comparison;
|
||||
document.querySelector('#comparison-inputs').hidden=!config.comparison;
|
||||
document.querySelector('#file-label').textContent=config.fileLabel||'Schematic file';
|
||||
for(const [selector,active]of [['#file',!config.comparison],['#old-file',config.comparison],['#new-file',config.comparison]]){
|
||||
const input=document.querySelector(selector);input.accept=config.suffix;input.required=active;input.disabled=!active;input.value='';
|
||||
}
|
||||
button.textContent=config.label+' ↗';
|
||||
for(const choice of document.querySelectorAll('[data-operation]'))choice.setAttribute('aria-pressed',String(choice.dataset.operation===operation.value));
|
||||
}
|
||||
operation.addEventListener('change',configure);
|
||||
for(const choice of document.querySelectorAll('[data-operation]'))choice.addEventListener('click',()=>{
|
||||
if(uploading)return;operation.value=choice.dataset.operation;configure();operation.focus();
|
||||
});
|
||||
configure();
|
||||
function showResponse(body){json.textContent=JSON.stringify(body,null,2);details.hidden=false;}
|
||||
async function decode(response){
|
||||
let body;
|
||||
try{body=await response.json();}catch{throw new Error(`Server returned HTTP ${response.status} without a JSON response.`);}
|
||||
if(!response.ok)throw new Error(body.error?.message||`Request failed (HTTP ${response.status}).`);
|
||||
return body;
|
||||
}
|
||||
function pause(signal){
|
||||
return new Promise((resolve,reject)=>{
|
||||
const abort=()=>{clearTimeout(timer);reject(new DOMException('Stopped','AbortError'));};
|
||||
const timer=setTimeout(()=>{signal.removeEventListener('abort',abort);resolve();},1000);
|
||||
signal.addEventListener('abort',abort,{once:true});
|
||||
if(signal.aborted)abort();
|
||||
});
|
||||
}
|
||||
async function poll(created,controller){
|
||||
const headers=created.viewer_token?{'X-Viewer-Token':created.viewer_token}:{};
|
||||
const status=new URL(created.status_url,location.origin);
|
||||
if(status.origin!==location.origin)throw new Error('Status URL must use this service origin. Check PUBLIC_BASE_URL.');
|
||||
for(;;){
|
||||
await pause(controller.signal);
|
||||
const body=await decode(await fetch(status.pathname+status.search,{headers,signal:controller.signal,cache:'no-store'}));
|
||||
if(controller.signal.aborted)return;
|
||||
showResponse(body);
|
||||
if(body.status==='failed')throw new Error(body.error?.message||'Processing failed.');
|
||||
if(body.status==='completed'){
|
||||
const summary=body.summary?` ${Object.entries(body.summary).map(([name,count])=>`${count} ${name}`).join(' · ')}.`:'';
|
||||
result.textContent=`Completed.${summary} Open the viewer below.`;return;
|
||||
}
|
||||
result.textContent=body.status==='processing'?'Processing your files…':'Queued. Waiting for a worker…';
|
||||
}
|
||||
}
|
||||
form.addEventListener('submit',async event=>{
|
||||
event.preventDefault();if(uploading)return;reset();
|
||||
const config=operations[operation.value],data=new FormData();
|
||||
const inputs=config.comparison?[['old_file','#old-file'],['new_file','#new-file']]:[['file','#file']];
|
||||
for(const [name,selector]of inputs){
|
||||
const file=document.querySelector(selector).files[0];
|
||||
if(!file||!file.name.toLowerCase().endsWith(config.suffix.toLowerCase())){result.textContent=`Select ${config.suffix} files for this operation.`;return;}
|
||||
data.append(name,file);
|
||||
}
|
||||
data.append('access',document.querySelector('#access').value);
|
||||
const expiry=document.querySelector('#expires-in').value;
|
||||
if(expiry){if(!/^\d+$/.test(expiry)||Number(expiry)<1){result.textContent='Expiry must be a positive whole number of seconds.';return;}data.append('expires_in',expiry);}
|
||||
const headers={},key=document.querySelector('#key').value;if(key)headers['X-API-Key']=key;
|
||||
uploading=true;button.disabled=true;fields.disabled=true;result.textContent='Uploading…';
|
||||
try{
|
||||
const created=await decode(await fetch(config.endpoint,{method:'POST',headers,body:data}));showResponse(created);
|
||||
const url=new URL(created.viewer_url||created.url,location.origin);
|
||||
if(url.origin!==location.origin)throw new Error('Viewer URL must use this service origin. Check PUBLIC_BASE_URL.');
|
||||
const link=document.createElement('a');link.href=url.href;link.target='_blank';link.rel='noopener noreferrer';link.textContent=config.comparison?'Open comparison ↗':'Open viewer ↗';viewerLink.append(link);
|
||||
result.textContent='Queued. You can open the viewer while processing continues.';
|
||||
const controller=new AbortController();polling=controller;
|
||||
poll(created,controller).catch(error=>{if(error.name!=='AbortError'&&!controller.signal.aborted)result.textContent=`${error.message} The accepted upload is not retried automatically.`;});
|
||||
}catch(error){result.textContent=`${error.message} No automatic upload retry was made.`;}
|
||||
finally{uploading=false;button.disabled=false;fields.disabled=false;}
|
||||
});
|
||||
addEventListener('pagehide',()=>polling?.abort(),{once:true});
|
||||
|
||||
مرجع در شماره جدید
مسدود کردن کاربر