Files

67 lines
7.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import './ground-popup.css';
const API='/__pierzainer/ground-link';
let pending:{file:File;folder?:string}|undefined,staged:Promise<string|undefined>=Promise.resolve(undefined),version=0;
const request=async(action:string,body:object)=>{const r=await fetch(`${API}/${action}`,{method:'POST',headers:{'Content-Type':'application/json','X-Pierzainer-Action':'ground-link'},body:JSON.stringify(body)});const value=await r.json();if(!r.ok)throw new Error(value.error??'지반 프로그램 연동 실패');return value;};
const base64=(file:File)=>new Promise<string>((resolve,reject)=>{const reader=new FileReader();reader.onerror=()=>reject(new Error('Excel 파일을 읽지 못했습니다.'));reader.onload=()=>resolve(String(reader.result).split(',')[1]);reader.readAsDataURL(file);});
document.addEventListener('change',event=>{const input=event.target as HTMLInputElement;if(input.id==='file'&&input.files?.[0])pending={file:input.files[0]};if(input.id==='project-file'){pending=undefined;version++;staged=Promise.resolve(undefined);}},true);
window.addEventListener('pierzainer:ground-source',(event:Event)=>{const d=(event as CustomEvent).detail;pending={file:new File([d.bytes],d.name,{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}),folder:d.folder};});
document.addEventListener('click',event=>{if(!(event.target as Element).closest('#dlg-apply')||!pending)return;const source=pending;pending=undefined;version++;staged=(async()=>{const data=await request('stage',{name:source.file.name,base64:await base64(source.file),folder:source.folder});return data.id as string;})();staged.catch(()=>{});},true);
let opening=false,running=false,launchSerial=0;
let popup:HTMLDialogElement;
function getPopup(){
if(popup)return popup;
popup=document.createElement('dialog');popup.className='ground-popup';popup.setAttribute('aria-labelledby','ground-popup-title');
popup.innerHTML=`<header><div><small>GROUND MODEL</small><h2 id="ground-popup-title">지반 모델 생성</h2></div><button type="button" data-close aria-label="닫기">×</button></header>
<p>종평면도와 교각 Excel로 지반면·위성사진·2D 지도 JSON을 생성합니다.</p>
<label for="ground-drawing-file">종평면도 DXF / DWG</label><div class="ground-file-row"><span data-drawing-name></span><button type="button" data-pick="drawing">종평면도 선택</button><input id="ground-drawing-file" data-drawing type="file" accept=".dwg,.dxf" hidden></div>
<label for="ground-excel-file">교각 Excel</label><div class="ground-file-row"><span data-excel-name></span><button type="button" data-pick="excel">Excel 선택</button><input id="ground-excel-file" data-excel type="file" accept=".xlsx" hidden></div>
<p class="ground-popup-note">기존 생성 엔진을 사용합니다. 지도 다운로드에는 인터넷 연결이 필요합니다.</p>
<button type="button" data-run>지반 생성</button><p data-status role="status" aria-live="polite"></p><pre data-logs tabindex="0" aria-label="생성 진행 로그"></pre><footer><button type="button" data-close>닫기</button></footer>`;
popup.querySelectorAll<HTMLButtonElement>('[data-pick]').forEach(button=>button.addEventListener('click',()=>popup.querySelector<HTMLInputElement>(`[data-${button.dataset.pick}]`)!.click()));
popup.querySelectorAll('[data-close]').forEach(button=>button.addEventListener('click',()=>popup.close()));document.body.append(popup);return popup;
}
window.addEventListener('pierzainer:open-ground',async()=>{
if(opening&&popup?.open)return;if(running&&popup){popup.showModal();opening=true;return;}opening=true;
const dialog=getPopup(),ticket=++launchSerial,snapshot=version;
const status=dialog.querySelector<HTMLElement>('[data-status]')!,logs=dialog.querySelector<HTMLElement>('[data-logs]')!,run=dialog.querySelector<HTMLButtonElement>('[data-run]')!;
const drawingInput=dialog.querySelector<HTMLInputElement>('[data-drawing]')!,excelInput=dialog.querySelector<HTMLInputElement>('[data-excel]')!;
drawingInput.value='';excelInput.value='';drawingInput.disabled=excelInput.disabled=false;dialog.querySelector('[data-drawing-name]')!.textContent='파일 확인 중…';dialog.querySelector('[data-excel-name]')!.textContent='파일 확인 중…';run.disabled=true;status.textContent='입력 파일 확인 중…';logs.textContent='';dialog.showModal();
let stageId:string|undefined,drawing='';
try{stageId=await staged;const prepared=await request('prepare',{stageId});drawing=prepared.drawing;
const display=(kind:string,name:string,connected:boolean)=>{dialog.querySelector(`[data-${kind}-name]`)!.textContent=name;dialog.querySelector(`[data-pick="${kind}"]`)!.textContent=connected?'파일 변경':kind==='drawing'?'종평면도 선택':'Excel 선택';};
const drawingName=drawing.split(/[\\/]/).pop()||'종평면도를 선택하세요.';const excelName=prepared.excelName||'교각 Excel을 선택하세요.';
display('drawing',drawingName,!!drawing);display('excel',excelName,prepared.excelSelected);
drawingInput.onchange=()=>display('drawing',drawingInput.files?.[0]?.name||drawingName,!!drawingInput.files?.[0]||!!drawing);
excelInput.onchange=()=>display('excel',excelInput.files?.[0]?.name||excelName,!!excelInput.files?.[0]||prepared.excelSelected);status.textContent='입력을 확인하고 지반 생성을 누르세요.';run.disabled=false;
}catch(error){status.textContent=(error as Error).message;opening=false;return;}
run.onclick=async()=>{
if(snapshot!==version){status.textContent='프로젝트가 변경되었습니다. 닫고 다시 열어 주세요.';return;}
run.disabled=true;drawingInput.disabled=excelInput.disabled=true;
try{
const upload=async(file:File)=>(await request('upload',{name:file.name,base64:await base64(file)})).file as string;
status.textContent='입력 파일 전달 중…';const chosen=drawingInput.files?.[0],excel=excelInput.files?.[0];
if(chosen)drawing=await upload(chosen);if(!drawing)throw new Error('종평면도를 선택하세요.');
const excelPath=excel?await upload(excel):undefined;
if(!excelPath&&!stageId)throw new Error('교각 Excel을 선택하세요.');
const job=await request('launch',{stageId,drawing,excel:excelPath});running=true;status.textContent='지반·지도 생성 중… 닫아도 생성은 계속됩니다.';
const deadline=Date.now()+30*60*1000;
const poll=async()=>{
if(snapshot!==version||ticket!==launchSerial){running=false;return;}
try{
const result=await request('result',{id:job.id});logs.textContent=result.logs??'';logs.scrollTop=logs.scrollHeight;
if(result.state==='error')throw new Error('지반 생성 실패. 진행 로그를 확인하세요.');
if(result.ready){
const input=document.querySelector<HTMLInputElement>('#ground-import');
status.textContent=input?'지반 생성 완료. 프로젝트에 불러왔습니다.':'지반 생성 완료. 지반·지층 화면으로 돌아오면 불러옵니다.';
if(input){const transfer=new DataTransfer();transfer.items.add(new File([result.text],result.filename,{type:'application/json'}));input.files=transfer.files;input.dispatchEvent(new Event('change',{bubbles:true}));running=false;run.disabled=false;drawingInput.disabled=excelInput.disabled=false;return;}
}
if(Date.now()>deadline)throw new Error('응답 대기 시간이 지났습니다. 결과 폴더를 확인하세요.');setTimeout(poll,2000);
}catch(error){status.textContent=(error as Error).message;running=false;run.disabled=false;drawingInput.disabled=excelInput.disabled=false;}
};setTimeout(poll,1000);
}catch(error){status.textContent=(error as Error).message;running=false;run.disabled=false;drawingInput.disabled=excelInput.disabled=false;}
};
});
// Closing hides the panel; the current job and its polling continue.
document.addEventListener('close',event=>{if(event.target===popup)opening=false;},true);