//형태는 ?path=fullPath(ex.http://gsim.hanmac..../1.jpg)&data=eadNadfl //data는 json({key:value})을 stringify한 후에 btoa로 base64로 변경해서 달기. //ex) //const jsonData = JSON.stringify({ key1: "value1", key2: "value2" }); //const encodedData = btoa(jsonData); //?path=${fullPath}&data=${encodedData} //넘어간 data는 모두 표출됨, 숨길 data는 key 앞에 $붙여주기(type, password는 그냥써도 무조건 표출X) //받는 형태는 pdf, img(png, jpg, jpeg, panorama), mp4, gsim(gsimViewer로 redirect), ifc, 미지원 6가지 const searchParam = new URLSearchParams(window.location.search); //serchParam 비워주기 - debug off // window.history.replaceState(null, null, '/') const fullPath = searchParam.get('path'); let ext = fullPath.toLowerCase().split('.').pop();//가장 마지막.이후 확장자 const dataA = searchParam.get('data'); // title filename으로 변경 document.title = fullPath.split('/').pop(); let data = (dataA)?JSON.parse((dataA)?decodeURIComponent(escape(atob(dataA))):'{}'):undefined; //data로 ext 넘어오면 무조건 변경 ext = data.$ext; // 🚩3d모델뷰어 썸네일 생성을 위해 아래의 변수 추가 const thumbnail_key = decodeURIComponent(searchParam.get('thumbnail_key')); const path_name = searchParam.get('path_name'); const resourcePath = decodeURIComponent(searchParam.get('resourcePath')); const dataId = searchParam.get('dataId'); if(data && Object.keys(data).length>0 && (data.$type == 'text'|| data.type == 'text')){ //type text로 넘어왔을때 무조건 text뷰어로 연결 _openText(fullPath); }else{ switch(ext){ case 'pdf': _openPdf(fullPath,data); break; case 'mp4': case 'mov': case 'webm': _openVideo(fullPath); break; case 'png': case 'jpg': case 'jpeg': case 'webp': case 'gif': if(data && Object.keys(data).length>0 && (data.$type == 'panorama'||data.type == 'panorama')){ // data 있으면서 type이 panorama인 경우 _openPano(fullPath); }else{ _openImage(fullPath); } break; case 'gsim': // gsim viewer 주소로 변경 필요 window.location.href = `/libs/gsimViewer/gsimViewer.html?${searchParam.toString()}`; break; case 'ifc': _openIfc(fullPath, thumbnail_key, resourcePath, dataId, path_name); break; case 'txt': case 'log': case 'md': _openText(fullPath); break; case 'url': _openUrl(fullPath); break; case 'zip': _openZip(fullPath); break; case 'glb': case 'gltf': case 'obj': case 'stl': case 'fbx': case '3dm': _open3d(fullPath, thumbnail_key, resourcePath, dataId, path_name); break; case 'html': _openHtml(fullPath); break; } if(data && Object.keys(data).length>0){ let keys = Object.keys(data).filter(key=>key.indexOf('$') != 0); if(keys.length > 0){ _drawMeta(data); } } } function _openText(path){ // ext를 매개변수로 받아도 md파일이 txt로 넘어오기때문에 경로에서 직접 ext 추출 let ext = path.split('.').pop(); ext = ext.split('%')[0]; fetch(path).then(res => res.text()).then(data=>{ let pre; // md 파일 확장자 일때 파싱 후 HTML 삽입 if(ext === 'md'){ pre = document.createElement('div'); pre.classList.add('markdown-wrap'); // mermaid 시퀀스 다이어 그램 변환 const renderer = new marked.Renderer(); const originalCode = renderer.code.bind(renderer); renderer.code = (code) => { if(code.lang === 'mermaid') return `
${code.text}
` return originalCode(code); } marked.setOptions({ gfm: true, breaks: true, renderer}); const div = document.createElement('div'); div.classList.add('markdown-body'); div.innerHTML = marked.parse(data); // mermaid 초기화 및 생성 const mermaidEls = div.querySelectorAll('.mermaid'); if(mermaidEls.length > 0){ mermaid.initialize({ startOnLoad: false }); mermaid.init(undefined, mermaidEls); } pre.appendChild(div); } else { pre = document.createElement('pre'); pre.style.width = '100%'; pre.style.height = '100%'; pre.style.objectFit = 'contain'; pre.textContent = data; } document.getElementById('popup_viewer').appendChild(pre); if(ext === 'md')hljs.highlightAll(); }).catch(err=>console.error('파일 로드 실패', err)); } function _openZip(path){ fetch(path).then(async data=>{ let zblob = await data.blob(); const zip = new JSZip(); await zip.loadAsync(zblob); let folderText = ``; let fileText = ``; zip.forEach((relativePath, zipEntry) => { let slashIdx = relativePath.indexOf('/'); if(slashIdx == -1 || slashIdx == relativePath.length -1){ if(zipEntry.dir){ folderText += `(폴더) ${zipEntry.name.split('/')[0]} \n`; }else{ fileText += `(파일) ${zipEntry.name} \n` } } }); const pre = document.createElement('pre'); pre.style.width = '100%'; pre.style.height = '100%'; pre.style.objectFit = 'contain'; pre.textContent = `${folderText}${(folderText == ``)?'':'\n'}${fileText}`; document.getElementById('popup_viewer').appendChild(pre); }) } function _openUrl(path){ fetch(path).then(res => res.text()).then(data=>{ let url = data.split('URL=')[1]; let iframe = document.createElement('iframe'); iframe.src = url; iframe.style.width = '100%'; // 컨테이너에 맞게 너비 설정 iframe.style.height = '100%'; // 컨테이너에 맞게 높이 설정 iframe.style.border = 'none'; // 테두리 제거 (선택 사항) document.getElementById('popup_viewer').appendChild(iframe); }).catch(err=>console.error('파일 로드 실패', err)); } function _openHtml(path){ fetch(path).then(res => res.text()).then(data=>{ let iframe = document.createElement('iframe'); iframe.srcdoc = data; iframe.style.width = '100%'; // 컨테이너에 맞게 너비 설정 iframe.style.height = '100%'; // 컨테이너에 맞게 높이 설정 iframe.style.border = 'none'; // 테두리 제거 (선택 사항) document.getElementById('popup_viewer').appendChild(iframe); }).catch(err=>console.error('파일 로드 실패', err)); } // function _openPdf(path,data){ // let pdf_options = { // url: path, // initialPage: 1, // }; // if(data && Object.keys(data).length > 0 && (data.$password || data.password)){ // pdf_options.password = (data.$password)?data.$password:data.password; // } // let iframe = document.createElement('iframe'); // iframe.src = `/libs/pdfViewer/web/viewer.html`; // document.getElementById('popup_viewer').appendChild(iframe); // iframe.addEventListener('load', () => { // // pdf 실행 시 무조건 1페이지부터 보이도록 기존 pdf 히스토리 삭제 // try { // let appWin = iframe.contentWindow; // // PDF.js의 기본 히스토리 키 // appWin.localStorage.removeItem('pdfjs.history'); // // 또는 여러 키 삭제 // Object.keys(appWin.localStorage).forEach(k => { // if (k.startsWith('pdfjs.history') || k.startsWith('pdfjs.preferences')) { // appWin.localStorage.removeItem(k); // } // }); // } catch (e) { /* ignore */ } // let app = document.querySelector('#popup_viewer iframe').contentWindow.PDFViewerApplication; // app.pdfCursorTools._handTool.activate(); // app.open(pdf_options); // iframe.width = '100%'; // iframe.height = '100%'; // }); // } function _openPdf(path, data) { let pdf_options = { url: path, initialPage: 1, }; if (data && Object.keys(data).length > 0 && (data.$password || data.password)) { pdf_options.password = (data.$password) ? data.$password : data.password; } let iframe = document.createElement('iframe'); iframe.src = `/libs/pdfViewer/web/viewer.html`; document.getElementById('popup_viewer').appendChild(iframe); iframe.addEventListener('load', () => { try { let appWin = iframe.contentWindow; // 히스토리 삭제 appWin.localStorage.removeItem('pdfjs.history'); Object.keys(appWin.localStorage).forEach(k => { if (k.startsWith('pdfjs.history') || k.startsWith('pdfjs.preferences')) { appWin.localStorage.removeItem(k); } }); let app = appWin.PDFViewerApplication; let isRendering = false; let lastScale = 1; app.initializedPromise.then(() => { const PDFPageView = appWin.PDFPageView; // draw 함수를 완전히 오버라이드 const originalDraw = PDFPageView.prototype.draw; PDFPageView.prototype.draw = function() { // 고해상도 설정 this.outputScale = { sx: (window.devicePixelRatio || 1) * 2, sy: (window.devicePixelRatio || 1) * 2 }; return originalDraw.call(this); }; // reset 함수도 오버라이드하여 캔버스 완전 삭제 const originalReset = PDFPageView.prototype.reset; PDFPageView.prototype.reset = function() { // 캔버스 완전히 제거 if (this.canvas) { if (this.canvas.parentNode) { this.canvas.parentNode.removeChild(this.canvas); } this.canvas.width = 0; this.canvas.height = 0; this.canvas = null; } // 렌더 태스크 취소 if (this.renderTask) { this.renderTask.cancel(); this.renderTask = null; } return originalReset.call(this); }; if (app.pdfViewer) { app.pdfViewer.textLayerMode = 1; app.pdfViewer.useOnlyCssZoom = false; } // 확대 시 완전 재렌더링 app.eventBus.on('scalechanging', function(evt) { if (isRendering) return; const newScale = evt.scale; const scaleDiff = Math.abs(newScale - lastScale); // 스케일 변화가 있을 때만 재렌더링 if (scaleDiff > 0.01) { isRendering = true; setTimeout(() => { if (app.pdfViewer && app.pdfViewer._pages) { app.pdfViewer._pages.forEach(pageView => { // 1. 렌더 태스크 강제 취소 if (pageView.renderTask) { pageView.renderTask.cancel(); pageView.renderTask = null; } // 2. 캔버스 완전 제거 if (pageView.canvas) { const parent = pageView.canvas.parentNode; if (parent) { parent.removeChild(pageView.canvas); } pageView.canvas.width = 0; pageView.canvas.height = 0; pageView.canvas = null; } // 3. viewport 업데이트 if (pageView.pdfPage) { pageView.viewport = pageView.pdfPage.getViewport({ scale: newScale, rotation: pageView.rotation || 0 }); } // 4. 페이지 완전 리셋 pageView.reset(); // 5. 새로 그리기 pageView.draw(); }); } lastScale = newScale; isRendering = false; }, 200); // 딜레이 증가 } }); // 스크롤/페이지 변경 시에도 재렌더링 app.eventBus.on('updateviewarea', function(evt) { if (isRendering) return; // 현재 보이는 페이지만 재렌더링 const visiblePages = app.pdfViewer._getVisiblePages(); if (visiblePages && visiblePages.views) { visiblePages.views.forEach(view => { if (view && view.div && view.div.classList.contains('pdfPage')) { const pageView = app.pdfViewer._pages[view.id - 1]; if (pageView && !pageView.renderTask) { pageView.draw(); } } }); } }); }); app.pdfCursorTools._handTool.activate(); app.open(pdf_options); } catch (e) { console.error('PDF 설정 오류:', e); } iframe.width = '100%'; iframe.height = '100%'; }); } function _openVideo(path){ const popupVideo = document.createElement('video'); popupVideo.autoplay = true; popupVideo.muted = true; popupVideo.playsInline = true; popupVideo.controls = true; popupVideo.crossOrigin = 'anonymous'; const sourceElement = document.createElement('source'); sourceElement.src = path; popupVideo.style.width = '100%'; popupVideo.style.height = '100%'; popupVideo.style.objectFit = 'contain'; document.getElementById('popup_viewer').appendChild(popupVideo); popupVideo.appendChild(sourceElement); } function _openPano(path){ let panorama = pannellum.viewer('popup_viewer',{ "type": "equirectangular", "panorama": path, "autoLoad": true, "compass":false, }) } function _openImage(path){ const img = document.createElement('img'); img.src = path; document.getElementById('popup_viewer').appendChild(img); img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'contain'; img.addEventListener('click',()=>{ document.getElementById('large-img-container').style.display = 'block'; centerImage(); }) const container = document.getElementById('large-img-container'); const image = document.getElementById('large-image'); const zoomInfo = document.getElementById('large-img-zoomInfo'); image.src = path; let isDragging = false; let startX, startY; let translateX = 0; let translateY = 0; let scale = 1; // 드래그 시작 function dragStart(e) { if (e.target !== image) return; isDragging = true; startX = e.clientX - translateX; startY = e.clientY - translateY; // 드래그 중일 때 transition 제거 image.style.transition = 'none'; } // 드래그 중 function drag(e) { if (!isDragging) return; e.preventDefault(); // 현재 마우스 위치에서 시작 위치를 빼서 이동 거리 계산 translateX = e.clientX - startX; translateY = e.clientY - startY; updateImageTransform(); } // 드래그 종료 function dragEnd() { if (!isDragging) return; isDragging = false; // 드래그 종료 시 부드러운 transition 효과 복원 image.style.transition = 'transform 0.1s ease-out'; } // 확대/축소 function zoom(e) { e.preventDefault(); // 현재 이미지의 실제 위치와 크기 const imageRect = image.getBoundingClientRect(); // 마우스 포인터의 화면상 좌표 const mouseX = e.clientX; const mouseY = e.clientY; // 마우스 포인터의 이미지상 상대 좌표 const mouseImageX = mouseX - imageRect.left; const mouseImageY = mouseY - imageRect.top; // 이전 스케일 저장 const prevScale = scale; // 새로운 스케일 계산 const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail)); const scaleFactor = 0.1; if (delta > 0) { scale = Math.min(4, scale + scaleFactor); } else { scale = Math.max(0.1, scale - scaleFactor); } // 스케일 변화율 const scaleRatio = scale / prevScale; // 새로운 translate 값 계산 // 마우스 포인터 위치는 고정되어야 하므로, 스케일 변화에 따른 위치 조정 translateX = mouseX - (((mouseX - translateX) * scaleRatio)); translateY = mouseY - (((mouseY - translateY) * scaleRatio)); updateImageTransform(); updateZoomInfo(); } // 이미지 transform 업데이트 function updateImageTransform() { // transform의 원점을 이미지의 중심으로 설정 const originX = image.offsetWidth / 2; const originY = image.offsetHeight / 2; image.style.transformOrigin = `${originX}px ${originY}px`; image.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } // 줌 정보 업데이트 function updateZoomInfo() { zoomInfo.textContent = `${Math.round(scale * 100)}%`; } // 이미지 초기 위치 설정 function centerImage() { translateX = (container.offsetWidth - image.offsetWidth) / 2; translateY = (container.offsetHeight - image.offsetHeight) / 2; updateImageTransform(); } // 이벤트 리스너 등록 container.addEventListener('mousedown', dragStart); window.addEventListener('mousemove', drag); window.addEventListener('mouseup', dragEnd); container.addEventListener('wheel', zoom); container.addEventListener('contextmenu',(e)=>{ e.preventDefault(); container.style.display = 'none'; }) // 더블클릭으로 원래 크기로 복원 container.addEventListener('dblclick', () => { scale = 1; updateZoomInfo(); }); } function _openIfc(path, thumbnail_key, resourcePath, dataId, path_name){ const iframe = document.createElement('iframe'); iframe.onload = () => { iframe.contentWindow.postMessage({ path, thumbnail_key, resourcePath, dataId, path_name }, '*'); // path 값을 iframe에 전달 }; iframe.src = `/libs/ifcViewer/index.html`; iframe.width = '100%'; iframe.height = '100%'; document.getElementById('popup_viewer').appendChild(iframe); } function _open3d(path, thumbnail_key, resourcePath, dataId, path_name){ const iframe = document.createElement('iframe'); iframe.onload = () => { iframe.contentWindow.postMessage({ path, thumbnail_key, resourcePath, dataId, path_name }, '*'); // path 값을 iframe에 전달 }; iframe.src = `/libs/3dViewer/index.html`; iframe.width = '100%'; iframe.height = '100%'; document.getElementById('popup_viewer').appendChild(iframe); } function _drawMeta(data){ let container = document.getElementById('meta-data'); container.style.display = 'block'; Object.keys(data).forEach(item=>{ if(!item.startsWith('$') && item != 'type' && item != 'password'){ let line = `${item} : ${data[item]}
`; container.innerHTML += line; } }) }