67 lines
7.7 KiB
TypeScript
67 lines
7.7 KiB
TypeScript
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);
|