2020 lines
89 KiB
JavaScript
2020 lines
89 KiB
JavaScript
import { checkProjectInactive } from '../main.js';
|
|
import { docVars } from './docVariable.js';
|
|
import { syncDocInfo, getDocDataBySelected, getGroupCompanyData, convertDocPdf } from './docDataManager.js';
|
|
import { getUserSettingsInDB, sendUserDataToServer } from './docModalManager.js';
|
|
import { splitBaseAndExt } from '../archive/common.js';
|
|
|
|
let main = document.querySelector('.official-doc-main'); // 화면
|
|
let fileContainer = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-file');
|
|
|
|
let recipientContainer = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-file .official-doc-top');
|
|
let recipientList = recipientContainer.querySelector('#doc-recipient-container ul');
|
|
let sendingContainer = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-file .official-doc-bottom');
|
|
let sendingList = sendingContainer.querySelector('#doc-sending-container ul');
|
|
let slider = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-file .official-doc-center-division');
|
|
|
|
let totalviewBtn = document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--container .viewer-header .wrap .btn');
|
|
let previewNotice = document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--notice');
|
|
let previewContainer = document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--container');
|
|
let viewerWrap = previewContainer?.querySelector('.viewer-wrap');
|
|
let infoWrap = previewContainer?.querySelector('.info-wrap');
|
|
let metadataContainer = previewContainer?.querySelector('.metadata');
|
|
|
|
export function initCategoryClicked() {
|
|
document.querySelectorAll('.official-doc-category').forEach((btn) => {
|
|
btn.addEventListener('click', async () => {
|
|
document.querySelectorAll('.official-doc-category').forEach((b) => b.classList.remove('official-doc-tab-on'));
|
|
btn.classList.add('official-doc-tab-on');
|
|
|
|
const categoryText = btn.querySelector('h6')?.innerText.trim();
|
|
const docCategory = categoryText === '전체보기' ? '' : categoryText;
|
|
|
|
await syncDocInfo(['official', 'attach', null]);
|
|
await getDocDataBySelected();
|
|
});
|
|
});
|
|
// 처음엔 전체보기(official-doc-category의 첫번째 요소임) 버튼 클릭되게
|
|
const totalListBtn = document.querySelector('.official-doc-category');
|
|
if (totalListBtn) totalListBtn.click();
|
|
}
|
|
|
|
export function initDocSlider() {
|
|
if (checkProjectInactive()) return;
|
|
|
|
let isDocDragging = false;
|
|
|
|
slider.addEventListener('mousedown', (e) => {
|
|
e.preventDefault();
|
|
isDocDragging = true;
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
isDocDragging = false;
|
|
});
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
if (!isDocDragging) return;
|
|
|
|
const containerRect = fileContainer.getBoundingClientRect();
|
|
const newY = containerRect.bottom - e.clientY;
|
|
|
|
const minHeight = 45; // 최소높이
|
|
const maxHeight = containerRect.height;
|
|
const halfMaxHeight = maxHeight / 2;
|
|
const newHeight = Math.min(maxHeight, Math.max(minHeight, newY));
|
|
|
|
sendingContainer.style.height = `${newHeight}px`;
|
|
recipientContainer.style.height = `${maxHeight - newHeight}px`;
|
|
});
|
|
}
|
|
|
|
/*******************************************************************************************************************************/
|
|
/*************************************************************************************************************** 메인화면 리스트 */
|
|
/*******************************************************************************************************************************/
|
|
|
|
export async function drawDocList(data, direction) {
|
|
let entries = sortDocData(data);
|
|
|
|
if (!recipientList || !sendingList) return;
|
|
|
|
if (!direction) {
|
|
recipientList.innerHTML = '';
|
|
sendingList.innerHTML = '';
|
|
} else if (direction == '수신') {
|
|
recipientList.innerHTML = '';
|
|
} else if (direction == '발신') {
|
|
sendingList.innerHTML = '';
|
|
}
|
|
|
|
if (!entries || entries.length === 0) return;
|
|
|
|
entries.forEach(async (entry, idx) => {
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
const filePath = value.file_path; // /test1.pdf
|
|
const groupId = value.group_id;
|
|
|
|
const li = document.createElement('li');
|
|
li.classList.add('main-file-container');
|
|
li.dataset.docId = key;
|
|
li.dataset.groupId = value.group_id;
|
|
li.dataset.resourcePath = value.file_path;
|
|
|
|
const listItemWrapper = document.createElement('div');
|
|
listItemWrapper.classList.add('official-doc-file-info', 'doc-list-item');
|
|
listItemWrapper.dataset.docId = key;
|
|
|
|
// 번호
|
|
const numberDiv = document.createElement('div');
|
|
numberDiv.classList.add('official-doc-file--number', 'doc-sort-desc');
|
|
const numberH6 = document.createElement('h6');
|
|
numberH6.textContent = value.rownum;
|
|
numberDiv.appendChild(numberH6);
|
|
|
|
// 문서번호
|
|
const officeNumberDiv = document.createElement('div');
|
|
officeNumberDiv.classList.add('official-doc-file--office-number');
|
|
const officeNumberH6 = document.createElement('h6');
|
|
officeNumberH6.textContent = value.doc_number;
|
|
officeNumberDiv.appendChild(officeNumberH6);
|
|
|
|
// 날짜
|
|
const dateDiv = document.createElement('div');
|
|
dateDiv.classList.add('official-doc-file--date');
|
|
const dateH6 = document.createElement('h6');
|
|
dateH6.textContent = formatDate(value.doc_date);
|
|
dateDiv.appendChild(dateH6);
|
|
|
|
// 발신처
|
|
const fromDiv = document.createElement('div');
|
|
fromDiv.classList.add('official-doc-file--from');
|
|
const fromH6 = document.createElement('h6');
|
|
fromH6.textContent = value.sender_name_abbr;
|
|
fromDiv.appendChild(fromH6);
|
|
|
|
// 수신처
|
|
const toDiv = document.createElement('div');
|
|
toDiv.classList.add('official-doc-file--to');
|
|
const toH6 = document.createElement('h6');
|
|
toH6.textContent = value.recipient_name_abbr;
|
|
toDiv.appendChild(toH6);
|
|
|
|
// 제목
|
|
const titleDiv = document.createElement('div');
|
|
titleDiv.classList.add('official-doc-file--title');
|
|
const titleH4 = document.createElement('h4');
|
|
titleH4.textContent = value.doc_title;
|
|
titleDiv.appendChild(titleH4);
|
|
|
|
// 상태
|
|
const stateDiv = document.createElement('div');
|
|
stateDiv.classList.add('official-doc-file--state');
|
|
const stateH6 = document.createElement('div');
|
|
stateH6.classList.add('official-doc-file--stateText');
|
|
const fileExt = filePath.split('.').pop().toLowerCase();
|
|
//// 현재 공문파일은 pdf만 받게 되어있음 // ai 연동
|
|
if (fileExt === 'pdf') {
|
|
stateH6.classList.add('viewable');
|
|
stateH6.textContent = '열람가능';
|
|
} else {
|
|
stateH6.classList.add('unsupport');
|
|
stateH6.textContent = '미지원';
|
|
}
|
|
stateDiv.appendChild(stateH6);
|
|
|
|
// 조립
|
|
listItemWrapper.appendChild(numberDiv);
|
|
listItemWrapper.appendChild(officeNumberDiv);
|
|
listItemWrapper.appendChild(dateDiv);
|
|
listItemWrapper.appendChild(fromDiv);
|
|
listItemWrapper.appendChild(toDiv);
|
|
listItemWrapper.appendChild(titleDiv);
|
|
listItemWrapper.appendChild(stateDiv);
|
|
li.appendChild(listItemWrapper);
|
|
|
|
// border 추가
|
|
const border = document.createElement('div');
|
|
border.classList.add('official-doc-border');
|
|
li.appendChild(border);
|
|
|
|
if (direction) {
|
|
if (direction == '수신') {
|
|
recipientList.appendChild(li);
|
|
recipientList.appendChild(border);
|
|
} else if (direction == '발신') {
|
|
sendingList.appendChild(li);
|
|
sendingList.appendChild(border);
|
|
}
|
|
}
|
|
|
|
if (value.doc_direction === '수신') {
|
|
recipientList.appendChild(li);
|
|
recipientList.appendChild(border);
|
|
} else if (value.doc_direction === '발신') {
|
|
sendingList.appendChild(li);
|
|
sendingList.appendChild(border);
|
|
}
|
|
|
|
// 첨부파일 체크
|
|
const attachData = docVars.allDocAttachData;
|
|
let isAttach = false;
|
|
if (groupId && attachData && attachData.length > 0) {
|
|
isAttach = attachData.some((attach) => attach.group_id == groupId);
|
|
}
|
|
if (isAttach) {
|
|
// 기존 첨부파일 wrapper 제거
|
|
const nextElement = li.nextElementSibling;
|
|
if (nextElement?.classList.contains('attachment-wrapper')) {
|
|
nextElement.remove();
|
|
}
|
|
|
|
// 첨부파일 목록 필터링
|
|
const docFilePath = li.dataset.resourcePath;
|
|
const attachList = attachData.filter((file) => {
|
|
const [prefixPath] = file.file_path?.split('__attachment');
|
|
return prefixPath === docFilePath;
|
|
});
|
|
|
|
// 첨부 없으면 그리지 않음
|
|
if (attachList.length === 0) return;
|
|
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'attachment-wrapper';
|
|
|
|
attachList.forEach((file) => {
|
|
const attachLi = document.createElement('li');
|
|
attachLi.className = 'attach-item';
|
|
attachLi.dataset.docId = file.doc_id;
|
|
attachLi.dataset.groupId = file.group_id;
|
|
attachLi.dataset.resourcePath = file.file_path;
|
|
attachLi.dataset.fileExt = file.ext;
|
|
|
|
const attachContainer = document.createElement('div');
|
|
attachContainer.className = 'attach-item-wrap attach doc-list-item';
|
|
attachContainer.dataset.docId = file.doc_id;
|
|
|
|
// 리스트 왼쪽(심볼, 파일명) 그리기
|
|
const attachInfo = document.createElement('div');
|
|
attachInfo.classList.add('info');
|
|
|
|
const attachInfoLeft = document.createElement('div');
|
|
attachInfoLeft.classList.add('doc-attach--left');
|
|
|
|
const attachInfoLeftSymbol = document.createElement('span');
|
|
attachInfoLeftSymbol.classList.add('symbol');
|
|
attachInfoLeftSymbol.innerHTML = '└';
|
|
|
|
const attachInfoLeftText = document.createElement('span');
|
|
const label = file.doc_label;
|
|
attachInfoLeftText.classList.add(label === 'attach' ? 'text--doc-attach' : 'text--doc-version');
|
|
attachInfoLeftText.innerHTML = label === 'attach' ? '첨부' : '버전';
|
|
|
|
const attachInfoRight = document.createElement('div');
|
|
attachInfoRight.classList.add('doc-attach--right');
|
|
const attachInfoRightText = document.createElement('h4');
|
|
let fileNameString = file.file_path.split('/').pop();
|
|
attachInfoRightText.innerHTML = fileNameString;
|
|
|
|
attachInfoRight.appendChild(attachInfoRightText);
|
|
|
|
attachInfoLeft.appendChild(attachInfoLeftSymbol);
|
|
attachInfoLeft.appendChild(attachInfoLeftText);
|
|
|
|
attachInfo.appendChild(attachInfoLeft);
|
|
attachInfo.appendChild(attachInfoRight);
|
|
|
|
// 리스트 오른쪽(열람가능, 변환필요) 그리기
|
|
let state = document.createElement('div');
|
|
state.classList.add('state');
|
|
let stateText = document.createElement('div');
|
|
stateText.classList.add('state-text');
|
|
|
|
let convertBtn = document.createElement('div');
|
|
convertBtn.classList.add('convert-btn');
|
|
convertBtn.dataset.resourcePath = file.file_path;
|
|
let convertBtnImage = document.createElement('div');
|
|
let convertBtnText = document.createElement('div');
|
|
convertBtnImage.classList.add('convert-btn-image');
|
|
convertBtnText.classList.add('convert-btn-text');
|
|
convertBtn.appendChild(convertBtnImage);
|
|
convertBtn.appendChild(convertBtnText);
|
|
|
|
let addBtn = false;
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
let needConvertExtArr = ['hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf', 'grm'];
|
|
let notNeedConvertExtArr = ['pdf', 'ifc', 'gsim', 'mp4', 'webm', 'jpg', 'jpeg', 'png', 'txt', 'log', 'md', 'url', 'zip'];
|
|
let supportedExtArr = [...needConvertExtArr, ...notNeedConvertExtArr];
|
|
|
|
let isSupported = false,
|
|
needConvert = false,
|
|
isConverted = false;
|
|
let ext = file.ext.toLowerCase();
|
|
|
|
// 현재 파일 확장자가 지원여부 확장자 배열에 포함되어 있는지 확인
|
|
if (supportedExtArr.includes(ext)) isSupported = true;
|
|
if (needConvertExtArr.includes(ext)) needConvert = true;
|
|
|
|
// 변환이 필요한 확장자일 때
|
|
if (needConvert) {
|
|
let objectKeyFileName = getFileNameFromKey(file.object_key);
|
|
let previewKeyFileName = getFileNameFromKey(file.preview_key);
|
|
let popupKeyFileName = getFileNameFromKey(file.popup_key);
|
|
isConverted = objectKeyFileName === previewKeyFileName && objectKeyFileName === popupKeyFileName;
|
|
}
|
|
|
|
if (isSupported) {
|
|
if (needConvert && !isConverted) {
|
|
// 변환필요
|
|
addBtn = true;
|
|
state.classList.add('convert');
|
|
convertBtnText.innerHTML = '변환필요';
|
|
} else {
|
|
// 열람가능
|
|
state.classList.add('viewable');
|
|
stateText.innerHTML = '열람가능';
|
|
}
|
|
} else {
|
|
// 미지원
|
|
state.classList.add('unsupport');
|
|
stateText.innerHTML = '미지원';
|
|
}
|
|
state.appendChild(stateText);
|
|
if (addBtn) state.appendChild(convertBtn);
|
|
|
|
attachContainer.appendChild(attachInfo);
|
|
attachContainer.appendChild(state);
|
|
attachLi.appendChild(attachContainer);
|
|
wrapper.appendChild(attachLi);
|
|
|
|
// 클릭 이벤트
|
|
attachLi.addEventListener('click', (e) => {
|
|
resetViewer();
|
|
renderDocViewer(file.file_path, file.doc_id);
|
|
docVars.lastClickedListTarget = e.target;
|
|
docVars.currentDocId = file.doc_id;
|
|
docVars.selectedDoc = attachLi;
|
|
|
|
// 리스트 선택 요소 selected 클래스 지우기
|
|
itemBoxSelected(attachLi);
|
|
});
|
|
|
|
// 오른쪽 클릭: 컨텍스트 메뉴
|
|
attachLi.addEventListener('contextmenu', (e) => {
|
|
e.preventDefault();
|
|
resetViewer();
|
|
renderDocViewer(file.file_path, file.doc_id);
|
|
docVars.lastClickedListTarget = e.target;
|
|
docVars.currentDocId = file.doc_id;
|
|
docVars.selectedDoc = attachLi;
|
|
toggleDocContextmenu('.official-doc-contextmenu.attach', true, e);
|
|
|
|
// 리스트 선택 요소 selected 클래스 지우기
|
|
itemBoxSelected(attachLi);
|
|
});
|
|
|
|
// 컨버트버튼 클릭 이벤트
|
|
convertBtn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
docVars.lastClickedListTarget = e.target;
|
|
convertDocPdf(file.file_path, file.doc_id);
|
|
});
|
|
|
|
if (docVars.lastClickedListTarget) { //리스트(부모), 첨부리스트(자식)
|
|
let target = docVars.lastClickedListTarget
|
|
if (!target.classList.contains('doc-list-item')) {
|
|
target = target.closest('.doc-list-item')
|
|
}
|
|
if (target.classList.contains('item-selected')) {
|
|
target.classList.remove('item-selected')
|
|
}
|
|
if (!target.classList.contains('attach')) {
|
|
if (target.classList.contains('attach-item-wrap')) return;
|
|
let targetList = document.querySelector(`.official-doc-file-info.doc-list-item[data-doc-id="${target.dataset.docId}"]`)
|
|
if (!targetList) return;
|
|
targetList.classList.add('item-selected')
|
|
}
|
|
}
|
|
});
|
|
|
|
// 리스트 최종 조립
|
|
li.appendChild(wrapper);
|
|
const nearestFileInfo = li.closest('li')?.querySelector('.official-doc-file-info');
|
|
if (!nearestFileInfo) return;
|
|
nearestFileInfo.parentNode.insertBefore(wrapper, nearestFileInfo.nextSibling);
|
|
|
|
const docId = docVars.lastClickedListTarget?.dataset?.docId;
|
|
if (docId) {
|
|
const targetList = document.querySelector(`.attach-item-wrap.doc-list-item[data-doc-id="${docId}"]`);
|
|
targetList?.classList.add('item-selected');
|
|
}
|
|
} // 첨부파일 끝
|
|
|
|
const docListItem = li.querySelector('.doc-list-item');
|
|
// 왼쪽 클릭시 리스트 색칠되고 뷰어 띄우기
|
|
docListItem.addEventListener('click', async (e) => {
|
|
e.preventDefault();
|
|
itemBoxSelected(li);
|
|
|
|
docVars.lastClickedListTarget = e.target;
|
|
docVars.selectedDoc = value;
|
|
docVars.currentDocId = key;
|
|
renderDocViewer(value.file_path, key);
|
|
});
|
|
|
|
// 오른쪽 클릭시 컨텍스트메뉴 띄우고 리스트 색칠되고 뷰어 띄우기
|
|
docListItem.addEventListener('contextmenu', (e) => {
|
|
e.preventDefault();
|
|
itemBoxSelected(li);
|
|
|
|
docVars.lastClickedListTarget = e.target;
|
|
docVars.lastContextTarget = e.target;
|
|
docVars.selectedDoc = value;
|
|
docVars.currentDocId = key;
|
|
renderDocViewer(value.file_path, key);
|
|
|
|
if (!document.querySelector('.official-doc-contextmenu.main')) return;
|
|
toggleDocContextmenu('.official-doc-contextmenu.main', true, e);
|
|
});
|
|
|
|
if (docVars.lastClickedListTarget) {
|
|
// 지금 만들어진 리스트 요소 아이디
|
|
const listItemId = docVars.lastClickedListTarget.closest(`li.main-file-container`).dataset.docId;
|
|
// 마지막으로 선택한 리스트 아이디
|
|
const lastClickId = docListItem.querySelector('.official-doc-file--title').closest('li.main-file-container').dataset.docId;
|
|
|
|
if (lastClickId == listItemId) {
|
|
if (!isAttach) {
|
|
docListItem.classList.add('item-selected');
|
|
}
|
|
docListItem.parentNode.classList.add('box-selected');
|
|
}
|
|
}
|
|
})
|
|
|
|
// 리스트 영역 외부 클릭 시 docVars.selectedDoc null 설정 및 뷰어 초기화
|
|
fileContainer?.addEventListener('click', (e) => {
|
|
const topHeader = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-top .official-doc-list');
|
|
const bottomHeader = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-bottom .official-doc-list');
|
|
if(e.target.parentElement == topHeader || e.target.parentElement.parentElement == topHeader) return;
|
|
if(e.target.parentElement == bottomHeader || e.target.parentElement.parentElement == bottomHeader) return;
|
|
|
|
clearMetadata();
|
|
resetViewer();
|
|
|
|
const clickedInsideList = e.target.closest('li.main-file-container');
|
|
const isInContextMenu = e.target.closest('.official-doc-contextmenu');
|
|
|
|
if (!clickedInsideList && !isInContextMenu) {
|
|
docVars.selectedDoc = null;
|
|
docVars.lastClickedListTarget = null;
|
|
docVars.itemSelected = null;
|
|
docVars.boxSelected = null;
|
|
|
|
document.querySelectorAll('.doc-list-item.item-selected').forEach((el) => el.classList.remove('item-selected'));
|
|
document.querySelectorAll('.main-file-container.box-selected').forEach((el) => el.classList.remove('box-selected'));
|
|
|
|
previewNotice.style.display = 'flex';
|
|
previewContainer.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 공문 미리보기 하단 메타데이터 표시
|
|
function createMetadataItem(keyText, itemClass = "", valueText = "") {
|
|
const item = document.createElement('div');
|
|
item.classList.add('item', itemClass);
|
|
|
|
const key = document.createElement('div');
|
|
key.classList.add('key', 'ft-12');
|
|
key.innerHTML = keyText.split('').map(text => `<span class="ft-12">${text}</span>`).join('');
|
|
|
|
const valueWrap = document.createElement('div');
|
|
valueWrap.classList.add('value-wrap');
|
|
|
|
let value;
|
|
|
|
value = document.createElement('div');
|
|
value.classList.add('value', 'ft-14', 'scrollbar');
|
|
value.textContent = valueText;
|
|
|
|
valueWrap.appendChild(value);
|
|
item.appendChild(key);
|
|
item.appendChild(valueWrap);
|
|
|
|
return item;
|
|
}
|
|
|
|
async function viewerMetadata(doc) {
|
|
const metadataWrap = document.createElement('div');
|
|
metadataWrap.classList.add('metadata-item-wrap');
|
|
|
|
const line1 = document.createElement('div');
|
|
line1.classList.add('wrap', 'line1');
|
|
line1.appendChild(createMetadataItem('제목요약', 'doc-title-summary', doc.doc_title_summary));
|
|
metadataWrap.appendChild(line1);
|
|
|
|
const line2 = document.createElement('div');
|
|
line2.classList.add('wrap', 'line2');
|
|
line2.appendChild(createMetadataItem('공문번호', 'doc-number', doc.doc_number));
|
|
line2.appendChild(createMetadataItem('연관공문', 'doc-related-docs', doc.doc_related_docs));
|
|
line2.appendChild(createMetadataItem('종류', 'doc-category', doc.doc_category));
|
|
metadataWrap.appendChild(line2);
|
|
|
|
const line3 = document.createElement('div');
|
|
line3.classList.add('wrap', 'line3');
|
|
line3.appendChild(createMetadataItem('내용요약', 'doc-content-summary', doc.doc_content_summary));
|
|
metadataWrap.appendChild(line3);
|
|
|
|
const line4 = document.createElement('div');
|
|
line4.classList.add('wrap', 'line4');
|
|
line4.appendChild(createMetadataItem('특이사항', 'doc-memo', doc.doc_memo || '-'));
|
|
metadataWrap.appendChild(line4);
|
|
|
|
metadataContainer.appendChild(metadataWrap);
|
|
}
|
|
|
|
function clearMetadata() {
|
|
if (metadataContainer) {
|
|
metadataContainer.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
// 메타데이터 뷰어 토글
|
|
infoWrap?.querySelector('.separator .toggle-btn').addEventListener('click', async (e) => {
|
|
infoWrap.classList.toggle('open');
|
|
infoWrap.classList.toggle('close');
|
|
})
|
|
|
|
// 리스트 색칠하기
|
|
export function itemBoxSelected(li) {
|
|
clearMetadata();
|
|
resetViewer();
|
|
|
|
recipientList.querySelectorAll('li').forEach((li) => li.classList.remove('box-selected'));
|
|
sendingList.querySelectorAll('li').forEach((li) => li.classList.remove('box-selected'));
|
|
const mainFileContainer = li.closest('.main-file-container');
|
|
mainFileContainer.classList.add('box-selected');
|
|
|
|
recipientList.querySelectorAll('.doc-list-item').forEach((item) => item.classList.remove('item-selected'));
|
|
sendingList.querySelectorAll('.doc-list-item').forEach((item) => item.classList.remove('item-selected'));
|
|
const listItem = li.querySelector('.doc-list-item');
|
|
listItem.classList.add('item-selected');
|
|
}
|
|
|
|
export async function renderDocViewer(resourcePath, docId) {
|
|
clearMetadata();
|
|
resetViewer();
|
|
|
|
await syncDocInfo(['official', 'attach', null]);
|
|
|
|
previewNotice.style.display = 'none';
|
|
previewContainer.style.display = 'flex';
|
|
viewerWrap.style.display = 'flex';
|
|
totalviewBtn.style.display = 'flex';
|
|
docVars.viewer = viewerWrap.querySelector('.viewer');
|
|
|
|
if (resourcePath.includes('__attach')) {
|
|
infoWrap.style.display = 'none';
|
|
} else {
|
|
infoWrap.style.display = 'flex';
|
|
await viewerMetadata(docVars.allDocData?.find((doc) => doc.doc_id === docId));
|
|
}
|
|
|
|
// fallback-pdf-btn 숨김
|
|
const docFallbackPdfBtn = document.getElementById('doc-fallback-pdf-btn');
|
|
if (docFallbackPdfBtn) {
|
|
docFallbackPdfBtn.style.display = 'none';
|
|
}
|
|
|
|
let ext = splitBaseAndExt(resourcePath).ext.toLowerCase();
|
|
|
|
let excelDirectArr = ['xls', 'xlsx', 'xlsm'];
|
|
let hwpDirectArr = ['hwp', 'hwpx'];
|
|
let wordDirectArr = ['docx'];
|
|
let pptxDirectArr = [];
|
|
let isDirectView = excelDirectArr.includes(ext) || hwpDirectArr.includes(ext) || wordDirectArr.includes(ext) || pptxDirectArr.includes(ext);
|
|
|
|
let selectedDoc = docVars.allDocData?.find((doc) => doc.doc_id === docId);
|
|
let previewKey = selectedDoc?.preview_key;
|
|
let objectKey = selectedDoc?.object_key;
|
|
|
|
let targetKey = isDirectView ? objectKey : previewKey;
|
|
|
|
//Presigned URL
|
|
let PresignedUrl = undefined;
|
|
|
|
if (targetKey == undefined || targetKey == `` || targetKey == null) {
|
|
let supportArr = ['hwp', 'hwpx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'doc', 'docx', 'dwg', 'dxf'];
|
|
|
|
if (!supportArr.includes(ext)) {
|
|
totalviewBtn.style.display = 'none';
|
|
viewerUnsupport(ext);
|
|
} else {
|
|
viewerConvert(resourcePath);
|
|
}
|
|
return;
|
|
}
|
|
|
|
let generateDownloadUrlParams = {
|
|
objectKey: targetKey,
|
|
resourcePath: resourcePath,
|
|
};
|
|
let generateDownloadUrlRes = await axios.post(`${docVars.path_name}/generateDownloadDocUrl`, generateDownloadUrlParams);
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadDocUrl_success') {
|
|
PresignedUrl = generateDownloadUrlRes.data.url;
|
|
}
|
|
//Presigned URL end
|
|
|
|
let pdfArr = ['pdf', 'hwp', 'hwpx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'doc', 'docx', 'dwg', 'dxf'];
|
|
let gsimArr = ['gsim'];
|
|
let ifcArr = ['ifc'];
|
|
let imageArr = ['png', 'jpg', 'jpeg'];
|
|
let videoArr = ['mp4', 'webm'];
|
|
let textArr = ['txt', 'log'];
|
|
let urlArr = ['url'];
|
|
let zipArr = ['zip'];
|
|
let threeArr = ['glb', 'gltf', 'obj', 'stl', 'fbx', '3dm'];
|
|
let allArr = [...pdfArr, ...gsimArr, ...ifcArr, ...imageArr, ...videoArr, ...textArr, ...urlArr, ...zipArr, ...threeArr];
|
|
if (allArr.includes(ext)) {
|
|
let pdfArrFiltered = pdfArr.filter(e => !excelDirectArr.includes(e) && !hwpDirectArr.includes(e) && !wordDirectArr.includes(e) && !pptxDirectArr.includes(e));
|
|
|
|
if (pdfArrFiltered.includes(ext)) viewerPdf(PresignedUrl);
|
|
if (excelDirectArr.includes(ext)) viewerExcel(PresignedUrl);
|
|
if (hwpDirectArr.includes(ext)) viewerHwp(PresignedUrl);
|
|
if (wordDirectArr.includes(ext)) viewerWord(PresignedUrl);
|
|
if (pptxDirectArr.includes(ext)) viewerPptx(PresignedUrl);
|
|
if (gsimArr.includes(ext)) viewerGsim(PresignedUrl);
|
|
if (ifcArr.includes(ext)) viewerIfc(PresignedUrl);
|
|
if (threeArr.includes(ext)) viewer3d(PresignedUrl);
|
|
if (imageArr.includes(ext)) viewerImage(PresignedUrl);
|
|
if (videoArr.includes(ext)) viewerVideo(PresignedUrl);
|
|
if (textArr.includes(ext)) viewerText(PresignedUrl);
|
|
if (urlArr.includes(ext)) viewerURL(PresignedUrl);
|
|
if (zipArr.includes(ext)) viewerZIP(PresignedUrl);
|
|
} else {
|
|
viewerUnsupport(ext);
|
|
}
|
|
|
|
function viewerUnsupport(ext) {
|
|
resetViewer();
|
|
|
|
let viewerUnsupportWrap = document.createElement('div');
|
|
viewerUnsupportWrap.classList.add('viewer-unsupport-wrap');
|
|
|
|
let text = document.createElement('div');
|
|
text.classList.add('text');
|
|
text.innerText = `${ext} 파일 형식은 현재 지원되지 않습니다.`;
|
|
|
|
viewerUnsupportWrap.appendChild(text);
|
|
docVars.viewer.appendChild(viewerUnsupportWrap);
|
|
docVars.viewer.dataset.viewerType = 'unsupport';
|
|
}
|
|
|
|
function viewerConvert() {
|
|
let viewerConvertWrap = document.createElement('div');
|
|
viewerConvertWrap.classList.add('viewer-convert-wrap');
|
|
|
|
let text = document.createElement('div');
|
|
text.classList.add('text');
|
|
text.innerText = `해당 파일은 변환 후 열람이 가능합니다.`;
|
|
|
|
viewerConvertWrap.appendChild(text);
|
|
docVars.viewer.appendChild(viewerConvertWrap);
|
|
docVars.viewer.dataset.viewerType = 'convert';
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// 오픈소스 문서 직접 뷰잉 및 PDF 폴백 함수 정의 (Doc Viewer)
|
|
// -----------------------------------------------------------------
|
|
function initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey) {
|
|
const btn = document.getElementById('doc-fallback-pdf-btn');
|
|
if (!btn) return;
|
|
|
|
// 이전 등록된 리스너 제거를 위해 복사 대체
|
|
const newBtn = btn.cloneNode(true);
|
|
btn.parentNode.replaceChild(newBtn, btn);
|
|
|
|
newBtn.style.display = 'flex';
|
|
newBtn.querySelector('.text').textContent = 'PDF로 보기';
|
|
newBtn.style.pointerEvents = 'auto';
|
|
|
|
newBtn.addEventListener('click', async () => {
|
|
newBtn.querySelector('.text').textContent = '로딩 중...';
|
|
newBtn.style.pointerEvents = 'none';
|
|
|
|
try {
|
|
// 1. 최신 메타데이터 (preview_key) 조회
|
|
if (!previewKey) {
|
|
await syncDocInfo(['official', 'attach', null]);
|
|
let selectedDoc = docVars.allDocData?.find((doc) => doc.doc_id === docId);
|
|
previewKey = selectedDoc?.preview_key;
|
|
objectKey = selectedDoc?.object_key;
|
|
}
|
|
|
|
// 2. 만약 PDF 변환본이 아직 없다면 백엔드 변환 요청
|
|
if (!previewKey) {
|
|
newBtn.querySelector('.text').textContent = 'PDF 변환 요청 중...';
|
|
await convertDocPdf(resourcePath, docId);
|
|
alert('서버 측 PDF 변환이 시작되었습니다. 잠시 후 다시 클릭해 주세요.');
|
|
newBtn.querySelector('.text').textContent = 'PDF로 보기';
|
|
newBtn.style.pointerEvents = 'auto';
|
|
return;
|
|
}
|
|
|
|
// 3. PDF용 Presigned URL 생성
|
|
let generateDownloadUrlParams = {
|
|
objectKey: previewKey,
|
|
resourcePath: resourcePath
|
|
}
|
|
let generateDownloadUrlRes = await axios.post(`${docVars.path_name}/generateDownloadDocUrl`, generateDownloadUrlParams);
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadDocUrl_success') {
|
|
let pdfUrl = generateDownloadUrlRes.data.url;
|
|
|
|
// 화면 초기화 및 PDF 뷰어 로드
|
|
docVars.viewer = viewerWrap.querySelector('.viewer');
|
|
docVars.viewer.innerHTML = '';
|
|
newBtn.style.display = 'none';
|
|
viewerPdf(pdfUrl);
|
|
} else {
|
|
alert('PDF 미리보기 주소 획득에 실패했습니다.');
|
|
newBtn.querySelector('.text').textContent = 'PDF로 보기';
|
|
newBtn.style.pointerEvents = 'auto';
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
alert('PDF 변환 및 조회 중 오류가 발생했습니다.');
|
|
newBtn.querySelector('.text').textContent = 'PDF로 보기';
|
|
newBtn.style.pointerEvents = 'auto';
|
|
}
|
|
});
|
|
}
|
|
|
|
function viewerExcel(presignedUrl) {
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">엑셀 데이터를 불러오는 중...</div>';
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
return res.arrayBuffer();
|
|
})
|
|
.then(arrayBuffer => {
|
|
docVars.viewer.innerHTML = '';
|
|
|
|
LuckyExcel.transformExcelToLucky(arrayBuffer, function(exportJson, luckysheetfile) {
|
|
if(exportJson.sheets == null || exportJson.sheets.length == 0) {
|
|
docVars.viewer.innerHTML = '<div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;color:#d9534f;font-size:1.1rem;background:#fff;">엑셀 데이터를 파싱하지 못했습니다. (xls 확장자는 지원하지 않습니다.)</div>';
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
return;
|
|
}
|
|
|
|
if (window.luckysheet) {
|
|
window.luckysheet.destroy();
|
|
}
|
|
|
|
docVars.viewer.style.position = 'relative';
|
|
const container = document.createElement('div');
|
|
container.id = 'luckysheet_inner_doc';
|
|
container.style.margin = '0px';
|
|
container.style.padding = '0px';
|
|
container.style.position = 'absolute';
|
|
container.style.width = '100%';
|
|
container.style.height = '100%';
|
|
container.style.left = '0px';
|
|
container.style.top = '0px';
|
|
docVars.viewer.appendChild(container);
|
|
|
|
try {
|
|
window.luckysheet.create({
|
|
container: 'luckysheet_inner_doc',
|
|
data: exportJson.sheets,
|
|
title: exportJson.info.name || 'Excel Viewer',
|
|
lang: 'en',
|
|
showinfobar: false,
|
|
myFolderUrl: 'javascript:void(0)'
|
|
});
|
|
} catch (createErr) {
|
|
console.error("Luckysheet create error: ", createErr);
|
|
docVars.viewer.innerHTML = `<div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">
|
|
<div>엑셀 시트 생성 중 오류가 발생했습니다.</div>
|
|
<div style="font-size:0.9rem;color:#999;margin-top:8px;">에러: ${createErr.message}</div>
|
|
</div>`;
|
|
}
|
|
}, function(err) {
|
|
console.error("Luckysheet transform error: ", err);
|
|
docVars.viewer.innerHTML = `<div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">
|
|
<div>엑셀 파일을 읽는 중 오류가 발생했습니다.</div>
|
|
<div style="font-size:0.9rem;color:#999;margin-top:8px;">상세: ${err.message || err}</div>
|
|
</div>`;
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
docVars.viewer.innerHTML = `<div style="display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">
|
|
<div>엑셀 파일을 불러오는데 실패했습니다.</div>
|
|
<div style="font-size:0.9rem;color:#999;margin-top:8px;">에러: ${err.message}</div>
|
|
</div>`;
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
});
|
|
|
|
docVars.viewer.dataset.viewerType = 'excel';
|
|
}
|
|
|
|
function viewerWord(presignedUrl) {
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">워드 문서를 불러오는 중...</div>';
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('Word fetch failed');
|
|
return res.arrayBuffer();
|
|
})
|
|
.then(arrayBuffer => {
|
|
docVars.viewer.innerHTML = '';
|
|
|
|
const container = document.createElement('div');
|
|
container.style.width = '100%';
|
|
container.style.height = '100%';
|
|
container.style.overflow = 'auto';
|
|
container.style.padding = '20px';
|
|
container.style.boxSizing = 'border-box';
|
|
container.style.background = '#f5f5f5';
|
|
|
|
const docxInner = document.createElement('div');
|
|
docxInner.style.background = '#ffffff';
|
|
docxInner.style.margin = '0 auto';
|
|
docxInner.style.maxWidth = '800px';
|
|
docxInner.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
|
docxInner.style.padding = '40px';
|
|
|
|
container.appendChild(docxInner);
|
|
docVars.viewer.appendChild(container);
|
|
|
|
docx.renderAsync(arrayBuffer, docxInner)
|
|
.then(() => console.log("docx rendered"))
|
|
.catch(err => {
|
|
console.error(err);
|
|
docxInner.innerHTML = '<div style="color:#d9534f;text-align:center;">워드 문서 파싱 중 오류가 발생했습니다. 상단의 "PDF로 보기" 버튼을 이용해 주세요.</div>';
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">워드 문서를 불러오는데 실패했습니다.</div>';
|
|
});
|
|
|
|
docVars.viewer.dataset.viewerType = 'word';
|
|
}
|
|
|
|
function viewerHwp(presignedUrl) {
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">한글 문서를 불러오는 중...</div>';
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('HWP fetch failed');
|
|
return res.blob();
|
|
})
|
|
.then(blob => {
|
|
docVars.viewer.innerHTML = '';
|
|
|
|
const container = document.createElement('div');
|
|
container.style.width = '100%';
|
|
container.style.height = '100%';
|
|
container.style.overflowX = 'auto';
|
|
container.style.overflowY = 'auto';
|
|
container.style.padding = '20px';
|
|
container.style.boxSizing = 'border-box';
|
|
container.style.background = '#f5f5f5';
|
|
|
|
const styleEl = document.createElement('style');
|
|
styleEl.textContent = `
|
|
.hwp-inner-container {
|
|
background: #ffffff;
|
|
margin: 0 auto;
|
|
width: max-content;
|
|
min-width: 800px;
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
|
padding: 0 !important;
|
|
box-sizing: border-box !important;
|
|
min-height: 100%;
|
|
}
|
|
.hwp-inner-container img {
|
|
max-width: 100% !important;
|
|
height: auto !important;
|
|
}
|
|
`;
|
|
container.appendChild(styleEl);
|
|
|
|
const hwpInner = document.createElement('div');
|
|
hwpInner.classList.add('hwp-inner-container');
|
|
|
|
container.appendChild(hwpInner);
|
|
docVars.viewer.appendChild(container);
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
const bstr = e.target.result;
|
|
try {
|
|
new hwp.Viewer(hwpInner, bstr);
|
|
} catch (err) {
|
|
console.error("hwp.js error: ", err);
|
|
hwpInner.innerHTML = '<div style="color:#d9534f;text-align:center;">한글 문서 파싱 중 오류가 발생했습니다. 상단의 "PDF로 보기" 버튼을 이용해 주세요.</div>';
|
|
}
|
|
};
|
|
reader.readAsBinaryString(blob);
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">한글 문서를 불러오는데 실패했습니다.</div>';
|
|
});
|
|
|
|
docVars.viewer.dataset.viewerType = 'hwp';
|
|
}
|
|
|
|
function viewerPptx(presignedUrl) {
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">PPTX 문서를 불러오는 중...</div>';
|
|
initDocFallbackPdfButton(docId, resourcePath, objectKey, previewKey);
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('PPTX fetch failed');
|
|
return res.arrayBuffer();
|
|
})
|
|
.then(async (arrayBuffer) => {
|
|
try {
|
|
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
|
|
// Read presentation.xml to get slide size
|
|
const presentationXmlText = await zip.file("ppt/presentation.xml").async("text");
|
|
const parser = new DOMParser();
|
|
const presDoc = parser.parseFromString(presentationXmlText, "text/xml");
|
|
const sldSz = presDoc.getElementsByTagName("p:sldSz")[0];
|
|
const cx = sldSz ? (parseInt(sldSz.getAttribute("cx"), 10) || 12192000) : 12192000;
|
|
const cy = sldSz ? (parseInt(sldSz.getAttribute("cy"), 10) || 6858000) : 6858000;
|
|
const ratio = (cy / cx) * 100;
|
|
|
|
// Get slide files
|
|
const slideFiles = Object.keys(zip.files).filter(name => name.startsWith("ppt/slides/slide") && name.endsWith(".xml"));
|
|
slideFiles.sort((a, b) => {
|
|
const numA = parseInt(a.replace("ppt/slides/slide", "").replace(".xml", ""), 10);
|
|
const numB = parseInt(b.replace("ppt/slides/slide", "").replace(".xml", ""), 10);
|
|
return numA - numB;
|
|
});
|
|
|
|
docVars.viewer.innerHTML = '';
|
|
|
|
const slidesContainer = document.createElement('div');
|
|
slidesContainer.style.display = 'flex';
|
|
slidesContainer.style.flexDirection = 'column';
|
|
slidesContainer.style.gap = '20px';
|
|
slidesContainer.style.alignItems = 'center';
|
|
slidesContainer.style.background = '#f0f0f0';
|
|
slidesContainer.style.padding = '20px';
|
|
slidesContainer.style.width = '100%';
|
|
slidesContainer.style.height = '100%';
|
|
slidesContainer.style.overflow = 'auto';
|
|
slidesContainer.style.boxSizing = 'border-box';
|
|
docVars.viewer.appendChild(slidesContainer);
|
|
|
|
for (let i = 0; i < slideFiles.length; i++) {
|
|
const slideXmlText = await zip.file(slideFiles[i]).async("text");
|
|
const slideDoc = parser.parseFromString(slideXmlText, "text/xml");
|
|
|
|
const slideCard = document.createElement('div');
|
|
slideCard.className = 'pptx-slide-card';
|
|
slideCard.style.position = 'relative';
|
|
slideCard.style.width = '100%';
|
|
slideCard.style.maxWidth = '800px';
|
|
slideCard.style.backgroundColor = '#ffffff';
|
|
slideCard.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
|
slideCard.style.height = '0';
|
|
slideCard.style.paddingTop = ratio + '%';
|
|
slideCard.style.overflow = 'hidden';
|
|
slideCard.style.flexShrink = '0';
|
|
|
|
const slideContent = document.createElement('div');
|
|
slideContent.style.position = 'absolute';
|
|
slideContent.style.top = '0';
|
|
slideContent.style.left = '0';
|
|
slideContent.style.width = '100%';
|
|
slideContent.style.height = '100%';
|
|
slideCard.appendChild(slideContent);
|
|
slidesContainer.appendChild(slideCard);
|
|
|
|
// Parse relationships for this slide
|
|
const relMap = {};
|
|
try {
|
|
const slideName = slideFiles[i].split('/').pop();
|
|
const relsFileName = `ppt/slides/_rels/${slideName}.rels`;
|
|
const relsFile = zip.file(relsFileName);
|
|
if (relsFile) {
|
|
const relsXmlText = await relsFile.async("text");
|
|
const relsDoc = parser.parseFromString(relsXmlText, "text/xml");
|
|
const relationships = relsDoc.getElementsByTagName("Relationship");
|
|
for (let r = 0; r < relationships.length; r++) {
|
|
const id = relationships[r].getAttribute("Id");
|
|
const target = relationships[r].getAttribute("Target");
|
|
relMap[id] = target;
|
|
}
|
|
}
|
|
} catch (relErr) {
|
|
console.warn("Failed to parse relationships for slide:", slideFiles[i], relErr);
|
|
}
|
|
|
|
const elements = slideDoc.querySelectorAll('p\\:sp, sp, p\\:pic, pic, p\\:graphicFrame, graphicFrame');
|
|
|
|
for (const elem of elements) {
|
|
const xfrm = elem.querySelector('a\\:xfrm, xfrm');
|
|
if (!xfrm) continue;
|
|
|
|
const off = xfrm.querySelector('a\\:off, off');
|
|
const ext = xfrm.querySelector('a\\:ext, ext');
|
|
if (!off || !ext) continue;
|
|
|
|
const x = parseInt(off.getAttribute('x'), 10);
|
|
const y = parseInt(off.getAttribute('y'), 10);
|
|
const w = parseInt(ext.getAttribute('cx'), 10);
|
|
const h = parseInt(ext.getAttribute('cy'), 10);
|
|
|
|
const leftPct = (x / cx) * 100;
|
|
const topPct = (y / cy) * 100;
|
|
const widthPct = (w / cx) * 100;
|
|
const heightPct = (h / cy) * 100;
|
|
|
|
const itemDiv = document.createElement('div');
|
|
itemDiv.style.position = 'absolute';
|
|
itemDiv.style.left = leftPct + '%';
|
|
itemDiv.style.top = topPct + '%';
|
|
itemDiv.style.width = widthPct + '%';
|
|
itemDiv.style.height = heightPct + '%';
|
|
itemDiv.style.boxSizing = 'border-box';
|
|
|
|
const nodeName = elem.nodeName.toLowerCase();
|
|
if (nodeName.includes('pic')) {
|
|
let imgUrl = null;
|
|
try {
|
|
const blip = elem.querySelector('a\\:blip, blip');
|
|
const rId = blip ? (blip.getAttribute('r:embed') || blip.getAttribute('embed')) : null;
|
|
if (rId && relMap[rId]) {
|
|
const targetPath = relMap[rId].replace('../', 'ppt/');
|
|
const imgFile = zip.file(targetPath);
|
|
if (imgFile) {
|
|
const imgBlob = await imgFile.async("blob");
|
|
imgUrl = URL.createObjectURL(imgBlob);
|
|
}
|
|
}
|
|
} catch (imgErr) {
|
|
console.warn("Failed to extract slide image:", imgErr);
|
|
}
|
|
|
|
if (imgUrl) {
|
|
itemDiv.style.backgroundImage = `url("${imgUrl}")`;
|
|
itemDiv.style.backgroundRepeat = 'no-repeat';
|
|
itemDiv.style.backgroundPosition = 'center';
|
|
itemDiv.style.backgroundSize = 'contain';
|
|
} else {
|
|
itemDiv.style.border = '1px dashed #cccccc';
|
|
itemDiv.style.backgroundColor = '#f9f9f9';
|
|
itemDiv.style.display = 'flex';
|
|
itemDiv.style.alignItems = 'center';
|
|
itemDiv.style.justifyContent = 'center';
|
|
|
|
const label = document.createElement('span');
|
|
label.style.color = '#999999';
|
|
label.style.fontSize = '10px';
|
|
label.style.fontWeight = 'bold';
|
|
label.textContent = '[그림 영역]';
|
|
itemDiv.appendChild(label);
|
|
}
|
|
} else if (nodeName.includes('graphicframe')) {
|
|
const tbl = elem.querySelector('a\\:tbl, tbl');
|
|
if (tbl) {
|
|
const htmlTable = document.createElement('table');
|
|
htmlTable.style.width = '100%';
|
|
htmlTable.style.height = '100%';
|
|
htmlTable.style.borderCollapse = 'collapse';
|
|
htmlTable.style.fontSize = 'calc(0.4vw + 5px)';
|
|
htmlTable.style.fontFamily = 'sans-serif';
|
|
htmlTable.style.backgroundColor = '#ffffff';
|
|
htmlTable.style.boxShadow = '0 1px 3px rgba(0,0,0,0.05)';
|
|
|
|
const rows = tbl.querySelectorAll('a\\:tr, tr');
|
|
rows.forEach((row, rIdx) => {
|
|
const trEl = document.createElement('tr');
|
|
if (rIdx === 0) {
|
|
trEl.style.backgroundColor = '#f8f9fa';
|
|
trEl.style.fontWeight = '600';
|
|
} else if (rIdx % 2 === 0) {
|
|
trEl.style.backgroundColor = '#fafafa';
|
|
}
|
|
|
|
const cells = row.querySelectorAll('a\\:tc, tc');
|
|
cells.forEach(cell => {
|
|
const tdEl = document.createElement('td');
|
|
tdEl.style.border = '1px solid #e0e0e0';
|
|
tdEl.style.padding = '4px 6px';
|
|
tdEl.style.wordBreak = 'break-all';
|
|
tdEl.style.verticalAlign = 'middle';
|
|
|
|
const gridSpan = cell.getAttribute('gridSpan');
|
|
if (gridSpan) tdEl.setAttribute('colspan', gridSpan);
|
|
const rowSpan = cell.getAttribute('rowSpan');
|
|
if (rowSpan) tdEl.setAttribute('rowspan', rowSpan);
|
|
|
|
const txBody = cell.querySelector('a\\:txBody, txBody');
|
|
if (txBody) {
|
|
const paragraphs = txBody.querySelectorAll('a\\:p, p');
|
|
paragraphs.forEach(p => {
|
|
const runs = p.querySelectorAll('a\\:r, r');
|
|
let cellText = '';
|
|
runs.forEach(r => {
|
|
const t = r.querySelector('a\\:t, t');
|
|
if (t) cellText += t.textContent;
|
|
});
|
|
if (cellText.trim()) {
|
|
const pEl = document.createElement('p');
|
|
pEl.style.margin = '0';
|
|
pEl.style.lineHeight = '1.2';
|
|
pEl.textContent = cellText;
|
|
tdEl.appendChild(pEl);
|
|
}
|
|
});
|
|
}
|
|
trEl.appendChild(tdEl);
|
|
});
|
|
htmlTable.appendChild(trEl);
|
|
});
|
|
itemDiv.appendChild(htmlTable);
|
|
} else {
|
|
itemDiv.style.border = '1px dashed #dddddd';
|
|
itemDiv.style.backgroundColor = '#fdfdfd';
|
|
itemDiv.style.display = 'flex';
|
|
itemDiv.style.alignItems = 'center';
|
|
itemDiv.style.justifyContent = 'center';
|
|
|
|
const label = document.createElement('span');
|
|
label.style.color = '#aaaaaa';
|
|
label.style.fontSize = '10px';
|
|
label.style.fontWeight = 'bold';
|
|
label.textContent = '[차트 영역]';
|
|
itemDiv.appendChild(label);
|
|
}
|
|
} else {
|
|
const txBody = elem.querySelector('p\\:txBody, txBody');
|
|
if (txBody) {
|
|
itemDiv.style.overflow = 'hidden';
|
|
itemDiv.style.wordBreak = 'break-all';
|
|
itemDiv.style.fontSize = 'calc(0.5vw + 5px)';
|
|
itemDiv.style.fontFamily = 'sans-serif';
|
|
itemDiv.style.color = '#333333';
|
|
|
|
const paragraphs = txBody.querySelectorAll('a\\:p, p');
|
|
paragraphs.forEach(p => {
|
|
const runs = p.querySelectorAll('a\\:r, r');
|
|
let paraText = '';
|
|
runs.forEach(r => {
|
|
const t = r.querySelector('a\\:t, t');
|
|
if (t) paraText += t.textContent;
|
|
});
|
|
|
|
if (paraText.trim()) {
|
|
const pEl = document.createElement('p');
|
|
pEl.style.margin = '0 0 2px 0';
|
|
pEl.style.lineHeight = '1.2';
|
|
pEl.textContent = paraText;
|
|
itemDiv.appendChild(pEl);
|
|
}
|
|
});
|
|
} else {
|
|
itemDiv.style.border = '1px solid #eeeeee';
|
|
itemDiv.style.backgroundColor = 'rgba(0,0,0,0.01)';
|
|
}
|
|
}
|
|
slideContent.appendChild(itemDiv);
|
|
}
|
|
}
|
|
} catch (parseErr) {
|
|
console.error("PPTX parse error:", parseErr);
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;padding:20px;text-align:center;">PPTX 파싱 중 에러가 발생했습니다. 상단의 "PDF로 보기" 버튼을 이용해 주세요.</div>';
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
docVars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">PPTX 문서를 불러오는데 실패했습니다.</div>';
|
|
});
|
|
|
|
docVars.viewer.dataset.viewerType = 'pptx';
|
|
}
|
|
|
|
async function viewerPdf(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
// iframe 초기화
|
|
const existingIframe = docVars.viewer.querySelector('iframe');
|
|
if (existingIframe) {
|
|
existingIframe.remove();
|
|
}
|
|
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = `/libs/pdfViewer/web/viewer.html`;
|
|
|
|
let pdfOptions = {
|
|
url: PresignedUrl,
|
|
initialPage: 1,
|
|
};
|
|
|
|
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('.official-doc-preview--container .viewer-wrap .viewer iframe').contentWindow.PDFViewerApplication;
|
|
app.pdfCursorTools._handTool.activate();
|
|
app.open(pdfOptions);
|
|
app.appConfig.mainContainer.classList.add('scrollbar');
|
|
app.appConfig.mainContainer.style.marginLeft = '4px';
|
|
});
|
|
docVars.viewer.appendChild(iframe);
|
|
docVars.viewer.dataset.viewerType = 'pdf';
|
|
}
|
|
|
|
function viewerImage(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerImageWrap = document.createElement('div');
|
|
viewerImageWrap.classList.add('viewer-image-wrap');
|
|
|
|
let viewerImage = document.createElement('img');
|
|
viewerImage.classList.add('viewer-image');
|
|
viewerImage.src = PresignedUrl;
|
|
|
|
viewerImageWrap.appendChild(viewerImage);
|
|
docVars.viewer.appendChild(viewerImageWrap);
|
|
docVars.viewer.dataset.viewerType = 'image';
|
|
|
|
viewerImage.addEventListener('load', function () {
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
for (let entry of entries) {
|
|
let { width, height } = entry.contentRect;
|
|
if (height > width) viewerImage.style.height = `${width}px`;
|
|
resizeObserver.disconnect(); // 측정 후 observer 해제
|
|
}
|
|
});
|
|
resizeObserver.observe(viewerImageWrap);
|
|
});
|
|
}
|
|
|
|
function viewerVideo(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerVideoWrap = document.createElement('div');
|
|
viewerVideoWrap.classList.add('viewer-video-wrap');
|
|
|
|
let viewerVideo = document.createElement('video');
|
|
viewerVideo.classList.add('viewer-video');
|
|
viewerVideo.autoplay = true;
|
|
viewerVideo.muted = true;
|
|
viewerVideo.playsInline = true;
|
|
viewerVideo.controls = true;
|
|
viewerVideo.crossOrigin = 'anonymous';
|
|
|
|
let sourceElement = document.createElement('source');
|
|
sourceElement.src = PresignedUrl;
|
|
sourceElement.type = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
|
|
|
|
viewerVideo.appendChild(sourceElement);
|
|
viewerVideoWrap.appendChild(viewerVideo);
|
|
docVars.viewer.appendChild(viewerVideoWrap);
|
|
docVars.viewer.dataset.viewerType = 'video';
|
|
}
|
|
|
|
function viewerText(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerTextWrap = document.createElement('div');
|
|
viewerTextWrap.classList.add('viewer-text-wrap');
|
|
viewerTextWrap.classList.add('scrollbar');
|
|
|
|
fetch(PresignedUrl)
|
|
.then((res) => res.text())
|
|
.then((data) => {
|
|
let viewerText = document.createElement('div');
|
|
viewerText.classList.add('viewer-text');
|
|
viewerText.textContent = data;
|
|
|
|
viewerTextWrap.appendChild(viewerText);
|
|
docVars.viewer.appendChild(viewerTextWrap);
|
|
docVars.viewer.dataset.viewerType = 'text';
|
|
});
|
|
}
|
|
|
|
function viewerGsim(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerGsim = document.createElement('div');
|
|
viewerGsim.classList.add('viewer-gsim');
|
|
|
|
let iframe = document.createElement('iframe');
|
|
iframe.onload = () => {
|
|
iframe.contentWindow.postMessage({ path: PresignedUrl }, '*'); // path 값을 iframe에 전달
|
|
};
|
|
iframe.src = `/libs/gsimViewer/gsimViewer.html?path=${encodeURIComponent(PresignedUrl)}`;
|
|
|
|
viewerGsim.appendChild(iframe);
|
|
docVars.viewer.appendChild(viewerGsim);
|
|
docVars.viewer.dataset.viewerType = 'gsim';
|
|
}
|
|
|
|
function viewerIfc(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerIfc = document.createElement('div');
|
|
viewerIfc.classList.add('viewer-ifc');
|
|
|
|
let iframe = document.createElement('iframe');
|
|
iframe.onload = () => {
|
|
iframe.contentWindow.postMessage({ path: PresignedUrl }, '*'); // path 값을 iframe에 전달
|
|
};
|
|
iframe.src = `/libs/ifcViewer/index.html`;
|
|
iframe.width = '100%';
|
|
iframe.height = '100%';
|
|
iframe.style.border = 'none';
|
|
|
|
viewerIfc.appendChild(iframe);
|
|
docVars.viewer.appendChild(viewerIfc);
|
|
docVars.viewer.dataset.viewerType = 'ifc';
|
|
}
|
|
|
|
function viewer3d(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewer3d = document.createElement('div');
|
|
viewer3d.classList.add('viewer-3d');
|
|
|
|
let iframe = document.createElement('iframe');
|
|
iframe.onload = () => {
|
|
iframe.contentWindow.postMessage({ path: PresignedUrl }, '*'); // path 값을 iframe에 전달
|
|
};
|
|
iframe.src = `/libs/3dViewer/index.html`;
|
|
iframe.width = '100%';
|
|
iframe.height = '100%';
|
|
iframe.style.border = 'none';
|
|
|
|
viewer3d.appendChild(iframe);
|
|
docVars.viewer.appendChild(viewer3d);
|
|
docVars.viewer.dataset.viewerType = '3d';
|
|
}
|
|
|
|
function viewerURL(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerURLWrap = document.createElement('div');
|
|
viewerURLWrap.classList.add('viewer-text-wrap');
|
|
|
|
fetch(PresignedUrl)
|
|
.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'; // 테두리 제거 (선택 사항)
|
|
|
|
viewerURLWrap.appendChild(iframe);
|
|
docVars.viewer.appendChild(viewerURLWrap);
|
|
docVars.viewer.dataset.viewerType = 'url';
|
|
});
|
|
}
|
|
|
|
function viewerZIP(PresignedUrl) {
|
|
resetViewer();
|
|
|
|
let viewerTextWrap = document.createElement('div');
|
|
viewerTextWrap.classList.add('viewer-text-wrap');
|
|
viewerTextWrap.classList.add('scrollbar');
|
|
|
|
fetch(PresignedUrl).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`;
|
|
}
|
|
}
|
|
});
|
|
|
|
let viewerText = document.createElement('div');
|
|
viewerText.classList.add('viewer-text');
|
|
viewerText.textContent = `${folderText}${folderText == `` ? '' : '\n'}${fileText}`;
|
|
|
|
viewerTextWrap.appendChild(viewerText);
|
|
docVars.viewer.appendChild(viewerTextWrap);
|
|
docVars.viewer.dataset.viewerType = 'text';
|
|
});
|
|
}
|
|
}
|
|
|
|
function resetViewer() {
|
|
if (!docVars.viewer) return;
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'unsupport') {
|
|
const unsupportWrap = docVars.viewer.querySelector('.viewer-unsupport-wrap');
|
|
if (unsupportWrap) {
|
|
unsupportWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'convert') {
|
|
const convertWrap = docVars.viewer.querySelector('.viewer-convert-wrap');
|
|
if (convertWrap) {
|
|
convertWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'pdf') {
|
|
const iframe = docVars.viewer.querySelector('.viewer iframe');;
|
|
if (iframe) {
|
|
const app = iframe.contentWindow?.PDFViewerApplication;
|
|
app?.close?.();
|
|
iframe.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'image') {
|
|
const imageWrap = docVars.viewer.querySelector('.viewer-image-wrap');
|
|
if (imageWrap) {
|
|
const image = imageWrap.querySelector('.viewer-image');
|
|
image.src = '';
|
|
image.remove();
|
|
imageWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'video') {
|
|
const videoWrap = docVars.viewer.querySelector('.viewer-video-wrap');
|
|
if (videoWrap) {
|
|
const video = docVars.viewer.querySelector('.viewer-video');
|
|
video.pause();
|
|
video.src = '';
|
|
video.load();
|
|
video.remove();
|
|
videoWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'text') {
|
|
const textWrap = docVars.viewer.querySelector('.viewer-text-wrap');
|
|
if (textWrap) {
|
|
textWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'ifc') {
|
|
const viewerIfc = docVars.viewer.querySelector('.viewer-ifc');
|
|
if (viewerIfc) {
|
|
viewerIfc.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'gsim') {
|
|
const viewerGsim = docVars.viewer.querySelector('.viewer-gsim');
|
|
if (viewerGsim) {
|
|
viewerGsim.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'url') {
|
|
const textWrap = docVars.viewer.querySelector('.viewer-text-wrap');
|
|
if (textWrap) {
|
|
textWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (docVars.viewer.dataset.viewerType == 'excel') {
|
|
if (window.luckysheet) {
|
|
try {
|
|
window.luckysheet.destroy();
|
|
} catch (e) {
|
|
console.error("Luckysheet destroy error: ", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
docVars.viewer.dataset.viewerType = '';
|
|
}
|
|
|
|
// 우측 미리보기 새 창으로 열기 버튼 클릭 이벤트
|
|
document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--container .viewer-header .wrap .btn')?.addEventListener('click', async () => {
|
|
let resourcePath = docVars.lastClickedListTarget.closest('li').dataset.resourcePath;
|
|
let docId = docVars.lastClickedListTarget.closest('li').dataset.docId;
|
|
let isLowerExt = true,
|
|
ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
|
|
|
|
//Presigned URL
|
|
let PresignedUrl;
|
|
await syncDocInfo(['official', 'attach', null]);
|
|
|
|
let directViewExtArr = ['xls', 'xlsx', 'xlsm', 'docx', 'hwp', 'hwpx'];
|
|
let selectedDoc = docVars.allDocData?.find((doc) => doc.doc_id === docId);
|
|
let objectKey = directViewExtArr.includes(ext) ? selectedDoc?.object_key : selectedDoc?.preview_key;
|
|
|
|
if (objectKey == undefined || objectKey == `` || objectKey == null) {
|
|
return;
|
|
}
|
|
|
|
let generateDownloadUrlParams = {
|
|
objectKey: objectKey,
|
|
resourcePath: resourcePath,
|
|
};
|
|
let generateDownloadUrlRes = await axios.post(`${docVars.path_name}/generateDownloadDocUrl`, generateDownloadUrlParams);
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadDocUrl_success') {
|
|
PresignedUrl = generateDownloadUrlRes.data.url;
|
|
}
|
|
//Presigned URL end
|
|
|
|
// 뷰어 기본 width값은 현재 화면 width값의 절반
|
|
let denominator = 2;
|
|
// url 뷰어는 현재 화면 width값과 동일하게 설정
|
|
if (ext == 'url') denominator = 1;
|
|
|
|
let width = screen.width / denominator;
|
|
let height = screen.height;
|
|
let left = screen.width * 2 + 10;
|
|
let fullPath = encodeURIComponent(PresignedUrl);
|
|
|
|
let open_ext = `pdf`;
|
|
switch (ext) {
|
|
case 'pdf':
|
|
case 'ppt':
|
|
case 'pptx':
|
|
case 'doc':
|
|
case 'dwg':
|
|
case 'dxf':
|
|
case 'grm':
|
|
open_ext = 'pdf';
|
|
break;
|
|
case 'hwp':
|
|
case 'hwpx':
|
|
open_ext = ext;
|
|
break;
|
|
case 'xls':
|
|
case 'xlsx':
|
|
case 'xlsm':
|
|
open_ext = ext;
|
|
break;
|
|
case 'docx':
|
|
open_ext = ext;
|
|
break;
|
|
case 'gsim':
|
|
open_ext = 'gsim';
|
|
break;
|
|
case 'ifc':
|
|
open_ext = 'ifc';
|
|
break;
|
|
case 'png':
|
|
case 'jpg':
|
|
case 'jpeg':
|
|
open_ext = 'png';
|
|
break;
|
|
case 'mp4':
|
|
open_ext = 'mp4';
|
|
break;
|
|
case 'log':
|
|
case 'txt':
|
|
case 'md':
|
|
open_ext = 'txt';
|
|
break;
|
|
case 'url':
|
|
open_ext = 'url';
|
|
break;
|
|
case 'zip':
|
|
open_ext = 'zip';
|
|
break;
|
|
}
|
|
|
|
//presigned url은 ext를 읽을수 없엉...
|
|
const jsonData = JSON.stringify({ $ext: open_ext });
|
|
const encodedData = btoa(jsonData);
|
|
|
|
let popup = window.open(`/popup?path=${fullPath}&data=${encodedData}`, '', `width=${width}, height=${height}, left=${left}`);
|
|
});
|
|
|
|
/*******************************************************************************************************************************/
|
|
/******************************************************************************************************************* 셀렉트 박스 */
|
|
/*******************************************************************************************************************************/
|
|
// depth1: 발주처/발주처외
|
|
// depth2: 기준(SY_JV, 삼안)
|
|
// depth3: 상대기관(DPWH, 서영, 진우 ...)
|
|
// depth1은 화면 메인 리스트 필터링에서 사용 + depth2, depth3
|
|
// depth2는 추가모달에서 사용 + depth3
|
|
// depth1 또는 depth2를 선택하면 뒤에 셀렉트박스의 첫번째 옵션값들이 자동으로 입력되게
|
|
|
|
/**
|
|
* 1. 셀렉트박스에서 발주처를 선택했을 떄 기준SY_JV 과 상대기관DPWH 가 나오는 함수 (화면메인리스트)
|
|
*
|
|
* @param {*} selectedDepth1
|
|
* @example const { 기준, 상대기관 } = getValuesFromDepth1('발주처');
|
|
* @returns - { 기준: ['SY_JV'], 상대기관: ['DPWH'] }
|
|
*/
|
|
function getValuesFromDepth1(selectedDepth1) {
|
|
const group = docVars.groupCompanyData[selectedDepth1];
|
|
if (!group) return { 기준: [], 상대기관: [] };
|
|
|
|
const extractNames = (list) => list.map((item) => item.name);
|
|
|
|
return {
|
|
기준: extractNames(group.기준),
|
|
상대기관: extractNames(group.상대기관),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 2. 셀렉트박스에서 depth2을 선택했을 때 depth1에 따라 depth3이 나오는 함수 (화면메인리스트, 추가모달)
|
|
*
|
|
* @param {*} selectedBase
|
|
* @example const { depth1, 상대기관 } = getValuesFromBase('삼안');
|
|
* @returns - { depth1: '발주처외', 상대기관: (4) ['서영', '진우', '경동', '한국수출입은행'] }
|
|
*/
|
|
function getValuesFromBase(selectedBase) {
|
|
for (const [depth1Key, group] of Object.entries(docVars.groupCompanyData)) {
|
|
if (group.기준.includes(selectedBase)) {
|
|
return {
|
|
depth1: depth1Key,
|
|
상대기관: group.상대기관,
|
|
};
|
|
}
|
|
}
|
|
|
|
return { depth1: null, 상대기관: [] };
|
|
}
|
|
|
|
/**
|
|
* 3. 셀렉트박스에서 depth3을 선택했을 때 depth1에 따라 depth2만 나오는 함수 (추가모달)
|
|
*
|
|
* @param {*} selectedTarget
|
|
* @example const 기준 = getBaseFromTarget('서영');
|
|
* @returns - { 기준: '삼안' }
|
|
*/
|
|
function getBaseFromTarget(selectedTarget) {
|
|
for (const group of Object.values(docVars.groupCompanyData)) {
|
|
if (group.상대기관.includes(selectedTarget)) {
|
|
return group.기준[0];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function initCustomSelectBoxes() {
|
|
if (checkProjectInactive()) return;
|
|
|
|
await getGroupCompanyData();
|
|
|
|
const selectDOM = {
|
|
type: document.querySelector('#doc-select-type'),
|
|
base: document.querySelector('#doc-select-base'),
|
|
target: document.querySelector('#doc-select-target'),
|
|
};
|
|
|
|
const updateSelectOptions = (wrapper, values, selectedValue, paramKey) => {
|
|
const optionsContainer = wrapper.querySelector('.options');
|
|
const selectedLabel = wrapper.querySelector('.selected');
|
|
optionsContainer.innerHTML = '';
|
|
|
|
// 기본 텍스트
|
|
const defaultLabelMap = {
|
|
typeOptions: '구분',
|
|
baseOptions: '기준',
|
|
targetOptions: '상대기관',
|
|
};
|
|
const labelText = selectedValue || defaultLabelMap[paramKey] || '선택';
|
|
|
|
selectedLabel.innerText = labelText;
|
|
docVars.selectParams[paramKey] = selectedValue || '';
|
|
// console.log(`[${paramKey}] 변경됨 →`, docVars.selectParams);
|
|
|
|
values.forEach((value) => {
|
|
const option = document.createElement('div');
|
|
option.classList.add('option');
|
|
|
|
// value가 오브젝트라면
|
|
const name = typeof value === 'object' ? value.name : value;
|
|
const id = typeof value === 'object' ? value.id : '';
|
|
|
|
option.dataset.value = name;
|
|
option.dataset.id = id;
|
|
option.innerText = name;
|
|
|
|
option.addEventListener('click', () => {
|
|
selectedLabel.innerText = name;
|
|
docVars.selectParams[paramKey] = name;
|
|
|
|
if (paramKey === 'typeOptions') {
|
|
const { 기준, 상대기관 } = getValuesFromDepth1(name);
|
|
updateSelectOptions(selectDOM.base, 기준, 기준[0], 'baseOptions');
|
|
updateSelectOptions(selectDOM.target, 상대기관, 상대기관[0], 'targetOptions');
|
|
} else if (paramKey === 'baseOptions') {
|
|
const { depth1, 상대기관 } = getValuesFromBase(name);
|
|
if (depth1) {
|
|
docVars.selectParams.typeOptions = depth1;
|
|
selectDOM.type.querySelector('.selected').innerText = depth1;
|
|
}
|
|
updateSelectOptions(selectDOM.target, 상대기관, 상대기관[0], 'targetOptions');
|
|
}
|
|
|
|
getDocDataBySelected();
|
|
});
|
|
|
|
optionsContainer.appendChild(option);
|
|
});
|
|
};
|
|
|
|
// 초기화: 첫 번째 항목 기준
|
|
const firstType = Object.keys(docVars.groupCompanyData)[0];
|
|
const { 기준: firstBaseList, 상대기관: firstTargetList } = getValuesFromDepth1(firstType);
|
|
|
|
updateSelectOptions(selectDOM.type, Object.keys(docVars.groupCompanyData), firstType, 'typeOptions');
|
|
updateSelectOptions(selectDOM.base, firstBaseList, firstBaseList[0], 'baseOptions');
|
|
updateSelectOptions(selectDOM.target, firstTargetList, firstTargetList[0], 'targetOptions');
|
|
}
|
|
|
|
// 셀렉트박스 클릭 열기/닫기 기능
|
|
export function activateSelectUI() {
|
|
document.querySelectorAll('.doc-custom-select').forEach((select) => {
|
|
select.addEventListener('click', function () {
|
|
this.classList.toggle('open');
|
|
});
|
|
});
|
|
|
|
document.addEventListener('click', (e) => {
|
|
document.querySelectorAll('.doc-custom-select').forEach((select) => {
|
|
if (!select.contains(e.target)) {
|
|
select.classList.remove('open');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 날짜 포맷
|
|
function formatDate(dateStr) {
|
|
const date = new Date(dateStr);
|
|
if (isNaN(date.getTime())) {
|
|
// 날짜 형식이 아닌 경우
|
|
return 'ㅡ';
|
|
}
|
|
const yy = String(date.getFullYear()).slice(2);
|
|
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
const dd = String(date.getDate()).padStart(2, '0');
|
|
return `${yy}. ${mm}. ${dd}.`;
|
|
}
|
|
|
|
function getFileNameFromKey(key) {
|
|
let keySplit1 = key?.split('/')[key.split('/').length - 1];
|
|
let keySplit2 = keySplit1?.split('__')[0];
|
|
let keySplit3 = keySplit2?.split('.');
|
|
keySplit3?.pop();
|
|
let fileName = keySplit3?.join('.');
|
|
return fileName;
|
|
}
|
|
|
|
/*******************************************************************************************************************************/
|
|
/****************************************************************************************************************** 컨텍스트메뉴 */
|
|
/*******************************************************************************************************************************/
|
|
// 컨텍스트메뉴 state false
|
|
main?.addEventListener('click', (e) => {
|
|
toggleDocContextmenu('.official-doc-contextmenu', false);
|
|
});
|
|
main?.addEventListener('wheel', (e) => {
|
|
toggleDocContextmenu('.official-doc-contextmenu', false);
|
|
});
|
|
document.querySelectorAll('.scroll-container')?.forEach((scrollContainer) => {
|
|
scrollContainer.addEventListener('scroll', async (e) => {
|
|
toggleDocContextmenu('.official-doc-contextmenu', false);
|
|
});
|
|
});
|
|
|
|
// 컨텍스트 메뉴 열기
|
|
export function toggleDocContextmenu(targetSelector, state, event) {
|
|
// targetSelector 공문(.main)과 첨부(.attach)를 구분하기 위함
|
|
// state는 t, f값
|
|
const allMenus = document.querySelectorAll('.official-doc-contextmenu');
|
|
const contextBox = document.querySelector(targetSelector);
|
|
if (!contextBox) return;
|
|
|
|
// 열려있는 컨텍스트메뉴 닫기
|
|
allMenus.forEach((menu) => {
|
|
menu.style.display = 'none';
|
|
});
|
|
|
|
if (state == false) {
|
|
contextBox.style.display = 'none';
|
|
}
|
|
|
|
if (state == true) {
|
|
let showContextMenu = true;
|
|
|
|
if (showContextMenu) {
|
|
contextBox.style.display = 'flex';
|
|
contextBox.style.left = event.clientX + 'px';
|
|
contextBox.style.top = event.clientY - 15 - document.querySelector('.banner-notice-area')?.clientHeight + 'px';
|
|
|
|
const contextMenuHeight = contextBox.offsetHeight;
|
|
const remainingSpaceBelow = window.innerHeight - event.clientY - 15 - document.querySelector('.banner-notice-area')?.clientHeight - document.querySelector('.footer')?.clientHeight;
|
|
|
|
// 아래에 공간이 부족하면 위로 표시
|
|
if (remainingSpaceBelow < contextMenuHeight) {
|
|
contextBox.style.top = event.clientY - contextMenuHeight - 15 - document.querySelector('.banner-notice-area')?.clientHeight + 'px';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************************************************************/
|
|
/****************************************************************************************************************** 사용자 설정 */
|
|
/*******************************************************************************************************************************/
|
|
// 도움말 닫기 버튼 클릭으롷 닫기
|
|
document.querySelector('.official-doc-main .xs-btn-instructions')?.addEventListener('click', async () => {
|
|
const instructionsEl = document.querySelector('.official-doc-instructions');
|
|
if (instructionsEl) {
|
|
instructionsEl.style.display = 'none';
|
|
}
|
|
|
|
// 도움말 보지않기 체크박스
|
|
const checkbox = document.querySelector('#doc-instructions-radio');
|
|
if (checkbox?.checked) { // 체크O 안보기
|
|
let isInstructionsChecked = false;
|
|
await sendUserDataToServer(isInstructionsChecked);
|
|
}
|
|
});
|
|
|
|
// 초기화면 도움말 보기
|
|
export async function showInstructions() {
|
|
const settings = await getUserSettingsInDB();
|
|
const checkInterval = setInterval(async () => {
|
|
const instructions = document.querySelector('.official-doc-main .official-doc-instructions');
|
|
|
|
if (instructions) {
|
|
clearInterval(checkInterval);
|
|
|
|
// 기본적으로 체크박스 상태에 따른 도움말 보이기/숨기기
|
|
const checkbox = document.querySelector('#doc-instructions');
|
|
if (checkbox && checkbox.checked) {
|
|
instructions.style.display = 'flex';
|
|
} else {
|
|
instructions.style.display = 'none';
|
|
}
|
|
|
|
try {
|
|
const instructionsOption = settings.doc_option_instructions ?? 'false';
|
|
if (instructionsOption === 'true') {
|
|
instructions.style.display = 'flex';
|
|
|
|
instructions.querySelector('.xs-btn-instructions')?.addEventListener('click', () => {
|
|
instructions.style.display = 'none';
|
|
});
|
|
}
|
|
} catch (err) {
|
|
// console.error('설정 불러오기 실패:', err);
|
|
console.log('공문 기본설정값 세팅');
|
|
}
|
|
}
|
|
}, 100); // 0.1초마다 확인
|
|
}
|
|
|
|
/*******************************************************************************************************************************/
|
|
/****************************************************************************************************************** 리스트 정렬 */
|
|
/*******************************************************************************************************************************/
|
|
// 리스트 헤더 클릭 이벤트 함수
|
|
function initHeaderSort(containerSelector, direction) {
|
|
const container = document.querySelector(containerSelector);
|
|
const headers = container.querySelectorAll('div');
|
|
|
|
headers.forEach((header) => {
|
|
header.addEventListener('click', (e) => {
|
|
|
|
docVars.curSortCol = header.classList[0];
|
|
docVars.direction = direction;
|
|
|
|
let target = e.target;
|
|
|
|
if (target.matches('.official-doc-list-header-item')) {
|
|
target = target.closest('.official-doc-list-header-item-wrap');
|
|
}
|
|
|
|
headers.forEach((h) => {
|
|
if (h === target) {
|
|
if (h.classList.contains('doc-sort-asc') && !h.classList.contains('doc-sort-desc')) {
|
|
h.classList.remove('doc-sort-asc');
|
|
h.classList.add('doc-sort-desc');
|
|
docVars.curSortOrder = 'desc';
|
|
} else if (!h.classList.contains('doc-sort-asc') && h.classList.contains('doc-sort-desc')) {
|
|
h.classList.add('doc-sort-asc');
|
|
h.classList.remove('doc-sort-desc');
|
|
docVars.curSortOrder = 'asc';
|
|
} else {
|
|
h.classList.add('doc-sort-asc');
|
|
docVars.curSortOrder = 'asc';
|
|
}
|
|
} else {
|
|
h.classList.remove('doc-sort-asc', 'doc-sort-desc');
|
|
}
|
|
});
|
|
|
|
getDocDataBySelected(docVars.direction);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 수신 헤더 이벤트
|
|
initHeaderSort(
|
|
'.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-top .official-doc-list',
|
|
'수신'
|
|
);
|
|
|
|
// 발신 헤더 이벤트
|
|
initHeaderSort(
|
|
'.official-doc-main .official-doc-list-container .official-doc-main-body .official-doc-bottom .official-doc-list',
|
|
'발신'
|
|
);
|
|
|
|
function sortDocData(data) {
|
|
let sortData = {};
|
|
data.forEach(d => {
|
|
const { doc_id, ...restData } = d;
|
|
sortData[doc_id] = restData;
|
|
});
|
|
|
|
const curSortCol = docVars.curSortCol;
|
|
const curSortOrder = docVars.curSortOrder;
|
|
|
|
let entries = Object.entries(sortData).sort((a, b) => {
|
|
const fileA = a[0];
|
|
const fileB = b[0];
|
|
|
|
let col = curSortCol;
|
|
let order = (curSortOrder == 'desc') ? -1 : 1;
|
|
|
|
const parse = (fileNumber, info) => {
|
|
// 리스트 헤더 명명 기준
|
|
return {
|
|
base: fileNumber,
|
|
rownum: info.rownum,
|
|
docNumber: info.doc_number,
|
|
docDate: info.doc_date,
|
|
docFrom: info.sender_name_abbr,
|
|
docTo: info.recipient_name_abbr,
|
|
docTitle: info.doc_title,
|
|
docState: document.querySelector(`.official-doc-main .main-file-container[data-doc-id="${fileNumber}"] .official-doc-file--stateText`)?.innerHTML || '',
|
|
};
|
|
};
|
|
|
|
const A = parse(fileA, a[1]);
|
|
const B = parse(fileB, b[1]);
|
|
|
|
switch(col) {
|
|
case 'number':
|
|
const numCmp = B.rownum.localeCompare(A.rownum, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(numCmp !== 0) return numCmp * order;
|
|
case 'recipient-number':
|
|
case 'sending-number':
|
|
const fileNumCmp = A.base.localeCompare(B.base, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(fileNumCmp !== 0) return fileNumCmp * order;
|
|
break;
|
|
case 'recipient-office-number':
|
|
case 'sending-office-number':
|
|
const officeNumCmp = A.docNumber.localeCompare(B.docNumber, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(officeNumCmp !== 0) return officeNumCmp * order;
|
|
break;
|
|
case 'recipient-date':
|
|
case 'sending-date':
|
|
const dateCmp = A.docDate.localeCompare(B.docDate, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(dateCmp !== 0) return dateCmp * order;
|
|
break;
|
|
case 'recipient-from':
|
|
case 'sending-from':
|
|
const fromCmp = A.docFrom.localeCompare(B.docFrom, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(fromCmp !== 0) return fromCmp * order;
|
|
break;
|
|
case 'recipient-to':
|
|
case 'sending-to':
|
|
const toCmp = A.docTo.localeCompare(B.docTo, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(toCmp !== 0) return toCmp * order;
|
|
break;
|
|
case 'recipient-title':
|
|
case 'sending-title':
|
|
const titleCmp = A.docTitle.localeCompare(B.docTitle, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(titleCmp !== 0) return titleCmp * order;
|
|
break;
|
|
case 'recipient-state':
|
|
case 'sending-state':
|
|
const stateCmp = A.docState.localeCompare(B.docState, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(stateCmp !== 0) return stateCmp * order;
|
|
break;
|
|
}
|
|
return 0;
|
|
})
|
|
|
|
return entries;
|
|
}
|