574 lines
21 KiB
JavaScript
574 lines
21 KiB
JavaScript
//형태는 ?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 `<pre class="mermaid">${code.text}</pre>`
|
|
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 = `<span class="key">${item}</span> : <span>${data[item]}</span><br>`;
|
|
container.innerHTML += line;
|
|
}
|
|
})
|
|
} |