import { vars } from './variable.js'; import { checkProjectInactive } from '../main.js'; import { getList, drawUserPermissionList } from './userPermission.js'; import { getDepth, splitBaseAndExt, splitTopPathAndTargetName, getDataFromTreeObject, targetFocus, extractPathByLength, getMyDownloadList, pxToRem, openNewWindowViewer, initFileAreaUI, syncGroupStyle } from './common.js'; import { preparePageRendering, prepareRecycleBinRendering, processHeaderBtnOverflow, renderList, renderViewer, resetViewer, renderContextmenu, renderSizeBar, renderMemo, changeHeaderBtnStyle, changeTreeItemStyle, changeListItemStyle, renderCompositionData } from './pageRenderer.js'; import { createFolder, renameTarget, downloadTarget, openRenameModal, openEditAuthorModal, toggleRelocateCover, openRemoveModal, openDeleteModal, downloadTempFile, uploadMainTitleImage, updateAiButtonState, createVideoThumbnail, createTextThumbnail, getPdfData, createPdfThumbnail, resizeImage } from './dataManager.js' import { toggleRecycleBinModal, toggleModal, toggleDevMenuModal } from './modalManager.js' let archiveMain = document.querySelector('.archive-main'); let listContainer = archiveMain?.querySelector('.archive-main-center .list-container'); let listNotice = archiveMain?.querySelector('.archive-main-center .list-notice'); let viewerContainer = archiveMain?.querySelector('.archive-main-right .viewer-container'); let viewerNotice = archiveMain?.querySelector('.archive-main-right .viewer-notice'); let overviewMain = document.querySelector('.overview-main'); let officialDocMain = document.querySelector('.official-doc-main'); let mainNotice = document.querySelector('.main-notice'); //// 키보드 이벤트 // 트리영역 화살표 키 이벤트 방지 document.querySelector('.tree-container')?.addEventListener('keydown', (e) => { if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') e.preventDefault(); }) // 리스트영역 화살표 키 이벤트 방지 document.querySelector('.list-container .list-wrap.list-body')?.addEventListener('keydown', (e) => { // 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴 - 경도/위도 입력란에서 화살표 키 필요 if (vars.mapMode == 'edit') return; if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') e.preventDefault(); }) // 썸네일크기 슬라이더 화살표 키 이벤트 방지 document.querySelector('.slider-input .thumbnail-size-slider')?.addEventListener('keydown', (e) => { e.preventDefault(); }) // 실제 키보드 이벤트 코드 window.addEventListener('keydown', async (e) => { //// 모달창 단축키 // 모달 닫기 esc // 이름 변경 확인 enter //// 파일 관련 단축키 // 새 폴더 Ctrl A // 폴더 업로드 Ctrl Q // 파일 업로드 Ctrl E // 이름 변경 F2 // 이동 Ctrl X // 다운로드 Ctrl D // 삭제 Del // if (e.target.nodeName != 'INPUT') { // if (e.key == 'Backspace' || e.code == 'Backspace' || e.keyCode == 8) { // e.preventDefault(); // 기본 동작 막기 // e.stopPropagation(); // 이벤트 전파 중단하기 // } // } let archiveModal = document.querySelector('.archive-modal'); let devMenuModal = document.querySelector('.dev-menu-modal'); let binModal = document.querySelector('.recycle-bin-modal'); let compositionModal = document.querySelector('.composition-modal'); //// 아카이브/개발자메뉴/휴지통 모달창에서 esc, enter 키 이벤트 적용 if (archiveModal?.style.display == 'flex' || devMenuModal?.style.display == 'flex' || binModal.style.display == 'flex' || compositionModal.style.display == 'flex') { // 모달 닫기 if (e.key == 'Escape') { if (document.querySelector('.permission-modal').style.display == 'flex') { document.querySelector('.permission-modal').style.display = 'none'; return; } if (archiveModal.style.display == 'flex') { toggleModal(false); return; } if (devMenuModal?.style.display == 'flex') { toggleDevMenuModal(false); return; } if (binModal.style.display == 'flex') { toggleRecycleBinModal(false); toggleContextmenu(false); return; } if(compositionModal.style.display == 'flex') { compositionModal.style.display = 'none'; return; } } // 모달창에서 엔터, 스페이스바 누르면 버튼 클릭 처리 if (e.key == 'Enter') { // button 태그에 엔터키 적용하면 .click()으로 모달이 닫힌 후에도 기존 포커스된 버튼이 재활성화되며 다시 눌리는 현상 발생 // 브라우저 이벤트 흐름 상, keydown → click 순서 또는 이벤트 전파(bubbling) 문제 // e.preventDefault(); // e.stopPropagation(); if (devMenuModal?.style.display == 'flex') { // 모달 두개 이상 겹칠 수 있도록? // let toggleParams = { // text: '개발자 메뉴에서는 오작동 방지를 위해 엔터키 사용이 제한됩니다.', // type: 'alertModal' // }; // toggleModal(true, toggleParams); if (document.querySelector('.archive-modal .input-wrap textarea')) return; e.preventDefault(); e.stopPropagation(); alert('개발자 메뉴에서는 오작동 방지를 위해 엔터키 사용이 제한됩니다. (textarea 작성 경우는 제외)'); return; } if (archiveModal.querySelector('.input-wrap').getElementsByTagName('textarea')[0]) return; let convertBtn = document.querySelector('.archive-modal .convert-btn'); if (convertBtn) convertBtn.click(); let confirmBtn = document.querySelector('.archive-modal .confirm-btn'); if (confirmBtn && !confirmBtn.classList.contains('disabled')) confirmBtn.click(); } } //// 리스트/그리드 파일 목록 또는 휴지통 모달창에서 wasd/화살표, f2, delete 키 이벤트 적용 // 갤러리 폴더 지도 모드/위치 수정 모드 일 때 안내 표시 및 리턴 let mapContainer = listContainer?.querySelector('.map-container'); if (mapContainer && mapContainer.style.display == 'flex') { let notificatitonParams = { text: '지도모드에서는 키보드 기능이 제한됩니다.' } if (!mapContainer.classList.contains('edit-mode')) showNotification(notificatitonParams); return; } //// 리스트/그리드 파일 목록이 표시되어 있고, 모달창이 전부 꺼져 있을 때 if (archiveMain.style.display == 'flex' && listContainer.style.display == 'flex' && (archiveModal.style.display != 'flex' && devMenuModal?.style.display != 'flex' && binModal.style.display != 'flex')) { // 정보 영역 textarea 선택되어 있는 경우 리턴 if (document.activeElement == document.querySelector('.info-wrap textarea')) return; // 전체 선택 if (e.ctrlKey && e.key == 'a') { e.preventDefault(); // 기본 동작 막기 e.stopPropagation(); // 이벤트 전파 중단하기 resetViewer(); // 강조 해제 toggleContextFocusBox(false); // 컨텍스트 메뉴 닫기 toggleContextmenu(false, e); listContainer.querySelectorAll('.list-wrap.list-body .list-item').forEach(item => { item.classList.add('selected'); item.classList.add('group-style'); }); vars.multiSelectListItemArr = Array.from(listContainer.querySelectorAll('.list-wrap.list-body .list-item')); vars.lastSelectTarget = vars.multiSelectListItemArr[0]; } // 리스트/그리드 파일이 한개 이상이고, 특수키(컨트롤, 알트, 쉬프트, 맥os 메타키)를 누르지 않은 상태일 때 상하좌우/WSAD 키로 리스트/그리드 탐색 let arrowKeyArr = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']; let wasdKeyArr = ['w', 's', 'a', 'd']; let targetKeyArr = [...arrowKeyArr, ...wasdKeyArr]; if ( listContainer.querySelector('.list-body .list-item-wrap').children.length > 0 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && targetKeyArr.includes(e.key) ) { let isList = listContainer.classList.contains('list'); let isGrid = listContainer.classList.contains('grid'); let gridItemSize = -(document.querySelector('.slider-input .thumbnail-size-slider')?.value); let listItemArr = [...listContainer.querySelector('.list-body .list-item-wrap').children]; if (viewerContainer.dataset.resourcePath == '') { // 왼쪽 / A if (e.key == arrowKeyArr[2] || e.key == wasdKeyArr[2]) { if (isList) return; // 리스트 목록이면 리턴 (리스트 목록에서는 위아래로만 이동) } // 오른쪽 / D if (e.key == arrowKeyArr[3] || e.key == wasdKeyArr[3]) { if (isList) return; // 리스트 목록이면 리턴 (리스트 목록에서는 위아래로만 이동) } vars.lastListItem = listItemArr[0]; } else { let currentIdx = listItemArr.indexOf(vars.lastListItem); // 위 / W if (e.key == arrowKeyArr[0] || e.key == wasdKeyArr[0]) { if (currentIdx == 0) return; // 현재 선택된 아이템이 첫 번째면 리턴 if (isList) vars.lastListItem = listItemArr[(currentIdx-1 < 0) ? 0 : currentIdx-1]; if (isGrid) vars.lastListItem = listItemArr[(currentIdx-gridItemSize < 0) ? 0 : currentIdx-gridItemSize]; } // 아래 / S if (e.key == arrowKeyArr[1] || e.key == wasdKeyArr[1]) { if (currentIdx == listItemArr.length-1) return; // 현재 선택된 아이템이 마지막이면 리턴 if (isList) vars.lastListItem = listItemArr[currentIdx+1]; if (isGrid) vars.lastListItem = listItemArr[(currentIdx+gridItemSize > listItemArr.length-1) ? listItemArr.length-1 : currentIdx+gridItemSize]; } // 왼쪽 / A if (e.key == arrowKeyArr[2] || e.key == wasdKeyArr[2]) { if (currentIdx == 0 || isList) return; // 현재 선택된 아이템이 첫 번째이거나 리스트 목록이면 리턴 (리스트 목록에서는 위아래로만 이동) vars.lastListItem = listItemArr[(currentIdx < 0) ? 0 : currentIdx-1]; } // 오른쪽 / D if (e.key == arrowKeyArr[3] || e.key == wasdKeyArr[3]) { if (currentIdx == listItemArr.length-1 || isList) return; // 현재 선택된 아이템이 마지막이거나 리스트 목록이면 리턴 (리스트 목록에서는 위아래로만 이동) vars.lastListItem = listItemArr[currentIdx+1]; } } // 기존에 선택된 다중 선택된 아이템 배열 및 모든 아이템 선택상태 초기화 vars.multiSelectListItemArr = []; listContainer.querySelectorAll('.list-item').forEach((elem) => { elem.classList.remove('selected'); elem.classList.add('non-selected'); }); // 키보드로 선택된 아이템 뷰어에 표시 let resourcePath = vars.lastListItem.dataset.resourcePath; let dataId = vars.lastListItem.dataset.id; renderViewer(resourcePath, dataId); let res = await axios.get(`${vars.path_name}/getMemoInfo`, { params: { userInfoString: vars.userInfoString, resourcePath, dataId } }); let memo = res.data.result.memo; setTimeout(() => { renderMemo(memo, dataId); }, 100); // document.querySelectorAll('.archive-main .list-container .list-body .list-item').forEach((elem) => { // elem.classList.remove('selected'); // elem.classList.add('non-selected'); // }); // clickedListItem.classList.add('selected'); // clickedListItem.classList.remove('non-selected'); // 선택 상태로 스타일 변경 changeListItemStyle(vars.lastListItem); // 선택된 아이템으로 포커스 이동 targetFocus(vars.lastListItem); vars.lastSelectTarget = vars.lastListItem; await syncGroupStyle(); } } //// 휴지통 모달창 파일 목록이 표시되어 있을 때 if (binModal.style.display == 'flex') { let binModalListContainer = binModal.querySelector('.list-container'); // 전체 선택 if (e.ctrlKey && e.key == 'a') { e.preventDefault(); // 기본 동작 막기 e.stopPropagation(); // 이벤트 전파 중단하기 resetViewer(); // 강조 해제 toggleContextFocusBox(false); // 컨텍스트 메뉴 닫기 toggleContextmenu(false, e); binModalListContainer.querySelectorAll('.list-wrap.list-body .list-item').forEach(item => { item.classList.add('selected'); item.classList.add('group-style'); }); vars.multiSelectListItemArr_bin = Array.from(binModalListContainer.querySelectorAll('.list-wrap.list-body .list-item')); vars.lastSelectTarget_bin = vars.multiSelectListItemArr_bin[0]; } // 리스트/그리드 파일이 한개 이상이고, 특수키(컨트롤, 알트, 쉬프트, 맥os 메타키)를 누르지 않은 상태일 때 상하/WS 키로 리스트 탐색 let arrowKeyArr = ['ArrowUp', 'ArrowDown']; let wasdKeyArr = ['w', 's']; let targetKeyArr = [...arrowKeyArr, ...wasdKeyArr]; if ( binModalListContainer.querySelector('.list-body .list-item-wrap').children.length > 0 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && targetKeyArr.includes(e.key) ) { let listItemArr = [...binModalListContainer.querySelector('.list-body .list-item-wrap').children]; let currentIdx = listItemArr.indexOf(vars.lastListItem_bin); // 위 / W if (e.key == arrowKeyArr[0] || e.key == wasdKeyArr[0]) { if (currentIdx == 0) return; // 현재 선택된 아이템이 첫 번째면 리턴 vars.lastListItem_bin = listItemArr[(currentIdx-1 < 0) ? 0 : currentIdx-1]; } // 아래 / S if (e.key == arrowKeyArr[1] || e.key == wasdKeyArr[1]) { if (currentIdx == listItemArr.length-1) return; // 현재 선택된 아이템이 마지막이면 리턴 vars.lastListItem_bin = listItemArr[currentIdx+1]; } // 기존에 선택된 다중 선택된 아이템 배열 및 모든 아이템 선택상태 초기화 vars.multiSelectListItemArr_bin = []; binModalListContainer.querySelectorAll('.list-item').forEach((elem) => { elem.classList.remove('selected'); }); // 선택 상태로 스타일 변경 changeListItemStyle(vars.lastListItem_bin); // 선택된 아이템으로 포커스 이동 targetFocus(vars.lastListItem_bin); vars.lastSelectTarget_bin = vars.lastListItem_bin; await syncGroupStyle(); } } if (archiveModal?.style.display != 'flex' && devMenuModal?.style.display != 'flex') { let permission = vars.permission.permission; // 새 폴더 // if (e.ctrlKey && e.key == 'g') { // // if (document.querySelector('.archive-modal').style.display == 'flex') return; // e.preventDefault(); // 기본 동작 막기 // e.stopPropagation(); // 이벤트 전파 중단하기 // openCreateFolderModal(); // toggleContextmenu(false); // } // // 폴더 업로드 // if (e.ctrlKey && e.key == 'q') { // e.preventDefault(); // 기본 동작 막기 // e.stopPropagation(); // 이벤트 전파 중단하기 // document.getElementById('uploadFolderInput').click(); // toggleContextmenu(false); // } // // 파일 업로드 // if (e.ctrlKey && e.key == 'e') { // e.preventDefault(); // 기본 동작 막기 // e.stopPropagation(); // 이벤트 전파 중단하기 // document.getElementById('uploadFileInput').click(); // toggleContextmenu(false); // } // // 이동 // if (e.ctrlKey && e.key == 'b') { // if (vars.lastSelectTarget) { // openMoveModal(); // toggleContextmenu(false); // } // } // // 다운로드 // if (e.ctrlKey && e.key == 'd') { // e.preventDefault(); // 기본 동작 막기 // e.stopPropagation(); // 이벤트 전파 중단하기 // downloadTarget(); // toggleContextmenu(false); // } let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex'; let target; if (!isRecycleBinModal && vars.lastSelectTarget) { if (vars.lastSelectTarget.classList.contains('folder-btn')) target = vars.lastHeaderBtn; if (vars.lastSelectTarget.classList.contains('tree-item')) target = vars.lastMainTreeItem; if (vars.lastSelectTarget.classList.contains('list-item')) target = vars.lastListItem; } if (isRecycleBinModal && vars.lastSelectTarget_bin) { if (vars.lastSelectTarget_bin.classList.contains('list-item')) target = vars.lastListItem_bin; } let lastContextTarget = (isRecycleBinModal) ? vars.lastContextTarget_bin : vars.lastContextTarget; if (lastContextTarget) target = lastContextTarget; // .tree-item 우클릭 시 target을 .tree-item-wrap으로 설정 // if (target && target.matches('.tree-item')) target = target.parentElement; // 우선 리스트 아이템만 F2 단축키로 이름 변경하도록 제한 // if (target && !target.matches('.list-item')) return; // 이름 변경 if (e.key == 'F2') { if (!target) return; let depth = getDepth(target.dataset.resourcePath); if ((depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) return; if (isRecycleBinModal) return; if (target) openRenameModal(target.dataset.resourcePath, target); } // 삭제 if (e.key == 'Delete') { if (!target) return; let memoTextarea = document.querySelector('.info-wrap .textarea'); if (document.activeElement == memoTextarea) { return; } else { if (target) { let depth = getDepth(target.dataset.resourcePath); if ((depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) return; // 폴더는 Delete키로 삭제 못하도록 제한 if (!target.classList.contains('list-item')) return; if (isRecycleBinModal) openDeleteModal(target.dataset.resourcePath, target); else openRemoveModal(target.dataset.resourcePath, target); } else { if (isRecycleBinModal) { let depth = getDepth(vars.multiSelectListItemArr_bin[0].dataset.resourcePath); if ((depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) return; if (vars.multiSelectListItemArr_bin.length > 0) openDeleteModal(vars.multiSelectListItemArr_bin[0].dataset.resourcePath, vars.multiSelectListItemArr_bin[0]); } else { let depth = getDepth(vars.multiSelectListItemArr[0].dataset.resourcePath); if ((depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) return; if (vars.multiSelectListItemArr.length > 0) openRemoveModal(vars.multiSelectListItemArr[0].dataset.resourcePath); } } } } // 확대 / 축소 if (vars.lastFileAreaMode == 'grid') { if (e.key == '+' || e.key == '=') { let value = Number(gridSizeSlider.value); if (value == -1) return; if (value != -1) value = value + 1; gridSizeSlider.value = value; changeSliderTrackColor(gridSizeSlider, false); changeGridItemSize(value); } if (e.key == '-') { let value = Number(gridSizeSlider.value); if (value == -10) return; if (value != -10) value = value -1; gridSizeSlider.value = value; changeSliderTrackColor(gridSizeSlider, false); changeGridItemSize(value); } } } }); // let mediaQuery; // let orientationListenerAdded = false; // function handleOrientationChange(e) { // if (e.matches) { // alert('세로 모드 전환됨'); // } else { // alert('가로 모드 전환됨'); // } // } // function setupOrientationListener() { // if (mediaQuery && orientationListenerAdded) return; // 이미 등록됨 // matchMedia = css_mediaQuery를 동적으로 사용하게 해주는 이벤트 orientation : portrait(세로모드) 감시로 true/false를 반환한다 // mediaQuery = window.matchMedia("(orientation: portrait)"); // mediaQuery.addEventListener('change', handleOrientationChange); // orientationListenerAdded = true; // } // setupOrientationListener(); // window resize시 이벤트 -> 크기 조절할 때마다 연속적으로 함수가 많이 실행되는 현상 방지하기 위해 setTimeout 사용 let resizeTimer; window.addEventListener("resize", function(e) { clearTimeout(resizeTimer); // 리사이즈 이벤트 종료 후 지정한 시간이 지난 후 내부 함수들 실행 (200~500이 적당) resizeTimer = setTimeout(() => { // 강조 해제 toggleContextFocusBox(false); // 컨텍스트 메뉴 닫기 toggleContextmenu(false, e); // 헤더 버튼 overflow 처리 processHeaderBtnOverflow(); // 터치 스크린이 있는지 감지 const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 ; // 터치 스크린 (태블릿, 모바일)인 상황에서 분기 처리 if(!isTouchDevice){ // 모달 닫기 toggleModal(false); toggleDevMenuModal(false); if (document.querySelector('.permission-modal')?.style.display == 'flex') { document.querySelector('.permission-modal').style.display = 'none'; } } // 파일 이동 화면 닫기 toggleRelocateCover(false); // 저장공간 푸터 막대그래프 렌더링 renderSizeBar(); }, 200); }) // document 전체에서 클릭 시 이벤트 document.addEventListener('click', (e) => { // 강조 해제 toggleContextFocusBox(false, e.target); // 컨텍스트 메뉴 닫기 toggleContextmenu(false, e); if(e.target.id != 'model'){ // 위치기반 모델 임시창 닫기 document.getElementById('model-select-modal')?.remove(); } }) // document 전체에서 휠 조작 시 이벤트 document.addEventListener('wheel', (e) => { let headerCenterLeftWrap = document.querySelector('body > .header .center .left.wrap'); let menuTab = headerCenterLeftWrap.querySelector('.menu-tab'); if (headerCenterLeftWrap.contains(e.target)) { if (e.deltaY > 0) menuTab.scrollLeft += 25; else menuTab.scrollLeft -= 25; } // 강조 해제 toggleContextFocusBox(false); // 컨텍스트 메뉴 닫기 toggleContextmenu(false, e); }) // document 전체에서 우클릭 시 이벤트 document.addEventListener('contextmenu', (e) => { // 기본 컨텍스트 메뉴 표시 방지 e.preventDefault(); e.stopPropagation(); // 카운트다운 툴팁 표시되어 있는 경우 삭제 document.querySelector('.countdown-tooltip')?.remove(); // 컨텍스트 메뉴 열려야 될 영역 예외 처리 if (e.target.closest('.folder-btn')) return; if (e.target.closest('.recycle-bin-modal .list-container .list-body')) return; if (e.target.closest('.archive-main .archive-main-left')) return; if (e.target.closest('.archive-main .archive-main-center')) return; // 컨텍스트 메뉴가 열리지 않는 영역에서 우클릭 시 강조 해제 및 컨텍스트 메뉴 닫기 toggleContextFocusBox(false); toggleContextmenu(false); }) // 스크롤바 조작 시 이벤트 document.querySelectorAll('.scroll-container').forEach(scrollContainer => { scrollContainer.addEventListener('scroll', async (e) => { // 강조 해제 toggleContextFocusBox(false); // 컨텍스트 메뉴 닫기 toggleContextmenu(false, e); }) }) // 헤더 좌측 타이틀 부분 (PM / OOO) 클릭 시 프로젝트 리스트 화면으로 이동 document.querySelector('body > .header .left .title').addEventListener('click', (e) => { if (!e.isTrusted) { console.warn("Title click event bypassed due to programmatic trigger."); return; } // 데이터 불러오는 중 (로딩창)이 활성화되어 있는 동안에는 타이틀 클릭으로 인한 리다이렉트를 차단하여 루프 방지 const initProgress = document.querySelector('.init-progress'); if (initProgress && initProgress.style.display !== 'none') { console.warn("Title click event bypassed because data is still loading."); return; } // 페이지 로드 후 1.5초 이내의 타이틀 클릭 이벤트도 잔상 클릭으로 간주하여 차단 if (window.performance && window.performance.now() < 1500) { console.warn("Title click event bypassed due to immediate click after load."); return; } //////// pm-bcmf 연결용 테스트 코드 - bcmf계정인 경우 타이틀 클릭해서 프로젝트 리스트 화면으로 이동 불가능 if (JSON.parse(vars.userInfoString).user_id.includes('bcmf-')) return; // 프로젝트에 참여하지 않은 인원이 비정상적으로 프로젝트 페이지에 접속한 경우 타이틀 클릭 불가능 if (!vars.project) return; if (vars.storageType == 'ONPREMISE') { // 로컬 온프레미스 환경에서는 복잡한 카테고리 경로 대신 루트(/)로 직접 리다이렉트하여 리다이렉트 루프 방지 window.location.href = '/'; } if (vars.storageType == 'CLOUD') window.location.href = window.location.protocol + "//" + window.location.host; }) // 헤더 중간 좌측 영역 mouseover/mouserout 시 스크롤바 on/off let headerCenterLeftWrap = document.querySelector('body > .header .center .left.wrap'); headerCenterLeftWrap?.addEventListener('mouseover', (e) => { document.documentElement.style.setProperty('--header-scrollbar-thumb-color', '#e9eeed'); }) headerCenterLeftWrap?.addEventListener('mouseout', (e) => { document.documentElement.style.setProperty('--header-scrollbar-thumb-color', '#e9eeed00'); }) // 헤더 중간 좌측 스크롤 좌우 버튼 클릭 이벤트 let menuTab = headerCenterLeftWrap?.querySelector('.menu-tab'); document.querySelector('.scroll-left')?.addEventListener('click', (e) => { menuTab.scrollBy({ left: -100, behavior: 'smooth' }); }) document.querySelector('.scroll-right')?.addEventListener('click', (e) => { menuTab.scrollBy({ left: 100, behavior: 'smooth' }); }) // let scrollLeft = document.querySelector('.scroll-left'); // let scrollRight = document.querySelector('.scroll-left'); // if (scrollLeft) { // document.querySelector('.scroll-left').onclick = () => { // headerCenterLeftWrap.querySelector('.menu-tab').scrollBy({ left: -100, behavior: 'smooth' }); // } // } // if (scrollRight) { // document.querySelector('.scroll-right').onclick = () => { // headerCenterLeftWrap.querySelector('.menu-tab').scrollBy({ left: 100, behavior: 'smooth' }); // } // } // 헤더 우측 공문 버튼 document.querySelector('body > .header .center .right.wrap .official-doc-btn')?.addEventListener('click', async (e)=>{ document.querySelector('.archive-main').style.display = 'none'; if (overviewMain) overviewMain.style.display = 'none'; document.querySelector('.official-doc-main').style.display = 'flex'; // 공문 index.js 임포트 await import(`../officialDoc/index.js`); if (mainNotice.style.display == 'flex') mainNotice.style.display = 'none'; //// vars에서 마지막 선택 아이템을 저장한 속성들 초기화 - 헤더 우측 공문 버튼 vars.lastMainTreeItem = undefined; vars.lastListItem = undefined; vars.lastListGroupTarget = undefined; vars.lastContextTarget = undefined; vars.lastSelectTarget = undefined; vars.multiSelectListItemArr = []; let pageRanderingOption = { scope: 'headerBtn_page', resourcePath: '/공문' }; await preparePageRendering(pageRanderingOption); changeHeaderBtnStyle(e.target); // 컨트롤 박스 숨김 toggleControlBox(false); }); // 헤더 우측 위치기반모델 버튼 document.querySelector('body > .header .center .right.wrap .model-btn')?.addEventListener('click',async (e)=>{ ///////PresignedUrl // let PresignedUrl = undefined; // let generateDownloadUrlParams = { // objectKey: `gsim/model.gsim`, //=> 모든 모델 위치 버킷/gsim/model.gsim // resourcePath: `/model.gsim` // } // let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams); // if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') { // PresignedUrl = generateDownloadUrlRes.data.url; // } // ///////PresignedUrl end // let fullPath = encodeURIComponent(PresignedUrl); // window.open(`/popup?path=${fullPath}&data=eyIkZXh0IjoiZ3NpbSJ9`, '', `width=${screen.width}, height=${screen.height}`); //test if(vars.project.gsim_id.includes(',')){ let idArr = vars.project.gsim_id.split(','); let nameArr = vars.project.gsim_nm.split(','); let div = document.createElement('div'); div.className = 'model-select'; div.id = 'model-select-modal'; div.style.left = `${e.clientX}px`; div.style.top = `${e.clientY}px`; for(let i = 0; i < idArr.length; i++){ let item = document.createElement('h4'); item.className = 'model-item'; item.innerHTML = `${nameArr[i]}`; div.append(item); item.addEventListener('click', async (e)=>{ let uri = `https://www.gsimtech.com/${idArr[i]}?by=PM`; window.open(uri, '_blank'); div.remove(); }); } document.querySelector('body').append(div); }else{ let uri = `https://www.gsimtech.com/${(vars.project.gsim_id)?vars.project.gsim_id:''}?by=PM`; window.open(uri, '_blank'); } }); // 헤더 우측 접속 인원 클릭 이벤트 document.querySelector('body > .header .right .connected-users')?.addEventListener('click', (e) => { let toggleParams = { title: '접속 인원', type: 'connectedUsers', }; toggleModal(true, toggleParams); // 모달 열기 // 현재 사용자 정보 표시 }) // 접속 인원 모달 - 유저 권한 설정 버튼 클릭 이벤트 document.querySelector('.archive-modal > .wrap .modal-wrap .modal-header > .title .left-wrap .set-user-permission-btn')?.addEventListener('click', async () => { //list 받아오기 let list = await getList(); list.changePermission = {}; //체크박스 업데이트에 사용할 객체 list.deptUserCount = {}; list.permission.forEach(p => { list.changePermission[p.user_id] = {user_id : p.user_id, lev : p.lev}}) list.all.forEach(user => { const key = `${user.company}_${user.dept}`; if(!list.deptUserCount[key]) list.deptUserCount[key] = 0; list.deptUserCount[key]++; }); vars.permissionList = list; document.getElementById('sub-master').checked = true; drawUserPermissionList(vars.permissionList); let tab = document.querySelector('.permission-modal .select-list.select-list-sub-master'); [...tab.parentElement.children].forEach(element => { if (element.classList.contains('select-list')) element.style.display = 'none'; }); tab.style.display = 'block'; vars.permissionList.changed = []; document.querySelector('.permission-modal').style.display = 'flex'; }) // 접속 인원 모달 - 프로젝트 type, step 변경 이벤트 // document.getElementById('project-type-btn')?.addEventListener('click', (e) => { // e.stopPropagation(); // document.querySelector('.project-type__list').classList.toggle('--project-list__open'); // }) // document.getElementById('project-step-btn')?.addEventListener('click', (e) => { // e.stopPropagation(); // document.querySelector('.project-step__list').classList.toggle('--project-list__open'); // }) // document.getElementById('project-type-btn').closest('.modal-wrap').addEventListener('click', (e) => { // document.querySelector('.project-type__list').classList.remove('--project-list__open'); // document.querySelector('.project-step__list').classList.remove('--project-list__open'); // }) // document.querySelectorAll('.project-type__list_item').forEach(item => { // item.addEventListener('click', async(e) => { // let res = await axios.get(`/gsim/setProjectType?project_id=${vars.project_id}&type=${e.target.classList[1].split('__')[1]}`); // document.querySelector('.project-type__list').classList.remove('--project-list__open'); // let type = e.target.classList[1].split('__')[1]; // let btn = document.getElementById('project-type-btn'); // let kor = ''; // switch (type) { // case 'construction' : kor = '시공'; break; // case 'design' : kor = '설계'; break; // case 'surgest' : kor = '제안'; break; // case 'research' : kor = '연구'; break; // case 'support' : kor = '지원'; break; // case 'center' : kor = '센터'; break; // } // btn.innerHTML = ` //
${kor}
// `; // }); // }) // document.querySelectorAll('.project-step__list_item').forEach(item => { // item.addEventListener('click', async(e) => { // let res = await axios.get(`/gsim/setProjectStep?project_id=${vars.project_id}&step=${e.target.classList[1].split('__')[1]}`); // document.querySelector('.project-step__list').classList.remove('--project-list__open'); // let step = e.target.classList[1].split('__')[1]; // let btn = document.getElementById('project-step-btn'); // let kor = ''; // switch (step) { // case 'active' : kor = '진행'; break; // case 'done' : kor = '완료'; break; // case 'stop' : kor = '중지'; break; // case 'wait' : kor = '대기'; break; // } // btn.innerHTML = ` //
${kor}
// `; // }); // }) // 접속 인원 모달 - 로그아웃 버튼 클릭 이벤트 document.querySelector('.archive-modal > .wrap .modal-wrap .modal-body > .connected-users-wrap .btn-wrap .logout-btn')?.addEventListener('click', function() { window.location.href = '/oauth/logout'; }) // 좌측 트리 우클릭 시 강조 및 컨텍스트 메뉴 열기 // document.querySelector('.archive-main .archive-main-left .tree-container')?.addEventListener('contextmenu', (e) => { document.querySelector('.archive-main .archive-main-left')?.addEventListener('contextmenu', (e) => { let target, scope; if (e.target.classList.contains('countdown')) return; if (e.target.matches('.tree-item')) { target = e.target.parentElement; if (target.matches('.depth2')) scope = 'tree-item-wrap-depth2'; if (target.matches('.depth3')) scope = 'tree-item-wrap-depth3'; } else { // target = document.querySelector('.archive-main .archive-main-left .tree-container'); target = document.querySelector('.archive-main .archive-main-left'); scope = 'tree'; } toggleContextFocusBox(true, target, scope); toggleContextmenu(true, e, scope); }) // 좌측 트리 타이틀 클릭 document.querySelector('.archive-main .archive-main-left .tree-title')?.addEventListener('click', async (e) => { let listViewerCover = document.querySelector('.list-viewer-cover'); if (listViewerCover.style.display == 'flex') { let toggleParams = { text: '파일 이동 화면에서는 트리 타이틀 버튼의 좌클릭이 제한되며, 우클릭으로 새 폴더 생성만 가능합니다.', type: 'alertModal' }; toggleModal(true, toggleParams); return; } let treeTitle = e.target; changeTreeItemStyle(treeTitle); listContainer.querySelector('.list-body .list-item-wrap').innerHTML = ''; listContainer.style.display = 'none'; listNotice.style.display = 'flex'; resetViewer(); // 컨트롤 박스 숨김 toggleControlBox(false); // 251114 김아름, 브라우저 경로 수정 let pageRanderingOption = { resourcePath: vars.lastMainTreeItem.dataset.resourcePath, pushState: true, } await preparePageRendering(pageRanderingOption); // 251114 김아름, 주석처리 // // 아카이브 우측 영역 표시 // let option = { // from: '좌측 트리 타이틀 클릭' // } // toggleArchiveMainRight(true, option); toggleArchiveMainRight(false); }) /////////////////////////// 컨트롤 박스 // 컨트롤 박스 모드 선택 버튼 클릭 이벤트 document.querySelectorAll('.control-box .file-area-mode-btn').forEach(btn => { btn.addEventListener('click', async (e) => { // 초기화 document.querySelector('.control-box .file-area-mode-btn.selected')?.classList.remove('selected'); // 클릭한 버튼을 선택 상태로 변경 let newSelectedBtn = e.currentTarget; newSelectedBtn.classList.add('selected'); // 클릭한 버튼의 id를 value로 사용 let value = newSelectedBtn.id; if (vars.lastFileAreaMode == value) { // 이전에 선택된 모드와 동일하고 vars.mapMode가 normal이면 리턴 if (vars.mapMode == 'normal') return; // vars.mapMode가 edit인 경우 edit모드에서 빠져나와야 하기 때문에 이전에 선택된 모드와 동일한 지도 표시 모드여도 리턴하지 않고 이어서 진행 } // 사용자가 직접 컨트롤 박스 모드 선택 버튼을 클릭한 경우 value를 전역변수에 저장 // if (e.isTrusted) vars.lastFileAreaMode = value; vars.lastFileAreaMode = value; let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap'); clusterListWrap.style.display = 'none'; let mapContainer = document.querySelector('.list-container .list-body .map-container'); mapContainer.classList.remove('edit-mode'); vars.mapMode = 'normal'; if (value == 'list') { listContainer.classList.add('list'); listContainer.classList.remove('grid'); } else { listContainer.classList.add('grid'); listContainer.classList.remove('list'); } // 파일 영역 모드에 맞게 파일 영역 UI 초기화 initFileAreaUI(value); let renderOption = { resourcePath: vars.lastMainTreeItem.dataset.resourcePath, scope : 'list' } await preparePageRendering(renderOption); await syncGroupStyle(); }) }) //// 플로팅 버전 컨트롤 박스 관련 코드 // // 컨트롤 박스 드래그 관련 변수 // let isControlBoxDragging = false; // let controlBoxOffsetX = 0; // let controlBoxOffsetY = 0; // let currentDragElement = null; // // 컨트롤 박스 초기 위치 설정 // async function setControlBoxInitialPosition(controlBox) { // // DB에서 저장된 위치 정보 가져오기 (실제 구현 필요) // let savedPosition = await loadControlBoxPosition(); // 이 함수는 직접 구현해야 함 // if (savedPosition && savedPosition.leftPercent && savedPosition.topPercent) { // // 저장된 퍼센티지 위치가 있는 경우 // setControlBoxPositionFromPercent(controlBox, savedPosition.leftPercent, savedPosition.topPercent); // } else { // // 저장된 위치가 없는 경우 - 기본 위치 (중앙 하단) // setControlBoxPositionFromPercent(controlBox); // } // } // // 퍼센티지 값으로 컨트롤 박스 위치 설정 // function setControlBoxPositionFromPercent(controlBox, leftPercent, topPercent) { // let finalLeftPercent, finalTopPercent; // if (!leftPercent) { // let archiveMainLeftWidth = document.querySelector('.archive-main-left').getBoundingClientRect().width; // let controlBoxWidth = controlBox.getBoundingClientRect().width; // let left = (archiveMainLeftWidth - controlBoxWidth) / 2; // finalLeftPercent = `${(left / window.innerWidth) * 100}%`; // } // if (!topPercent) { // let controlBoxHeight = controlBox.getBoundingClientRect().height; // // finalTopPercent = '95%'; // finalTopPercent = `calc(100% - 2.25rem - ${pxToRem(controlBoxHeight)}rem)`; // } // if (leftPercent && topPercent) { // // 퍼센티지에서 % 제거하고 숫자로 변환 // let leftPercentValue = parseFloat(leftPercent.replace('%', '')); // let topPercentValue = parseFloat(topPercent.replace('%', '')); // // 요소 크기를 고려한 최대 퍼센티지 계산 // let boxRect = controlBox.getBoundingClientRect(); // let maxLeftPercent = 100 - (boxRect.width / window.innerWidth * 100); // let maxTopPercent = 100 - (boxRect.height / window.innerHeight * 100); // // 퍼센티지 값 경계 체크 // finalLeftPercent = `${Math.max(0, Math.min(leftPercentValue, maxLeftPercent))}%`; // finalTopPercent = `${Math.max(0, Math.min(topPercentValue, maxTopPercent))}%`; // } // // 퍼센티지로 직접 적용 // controlBox.style.left = finalLeftPercent; // controlBox.style.top = finalTopPercent; // // controlBox.style.left = leftPercent; // // controlBox.style.top = topPercent; // } // // 포인터 다운 (드래그 시작) // function handleControlBoxPointerDown(e) { // isControlBoxDragging = true; // currentDragElement = e.target.closest('.control-box'); // currentDragElement.querySelector('.drag-handle').style.cursor = 'grabbing'; // // 마우스와 요소의 상대 위치 계산 // let rect = currentDragElement.getBoundingClientRect(); // controlBoxOffsetX = e.clientX - rect.left; // controlBoxOffsetY = e.clientY - rect.top; // // 드래그 중 선택 방지 // e.preventDefault(); // // 드래그 중 애니메이션 제거 // currentDragElement.classList.add('dragging'); // } // // 포인터 이동 (드래그 중) // function handleControlBoxPointerMove(e) { // if (!isControlBoxDragging || !currentDragElement) return; // // 새로운 위치 계산 // let newX = e.clientX - controlBoxOffsetX; // let newY = e.clientY - controlBoxOffsetY; // // 화면 경계 체크 // let maxX = window.innerWidth - currentDragElement.offsetWidth; // let maxY = window.innerHeight - currentDragElement.offsetHeight; // newX = Math.max(0, Math.min(newX, maxX)); // newY = Math.max(0, Math.min(newY, maxY)); // // 위치 적용 // // currentDragElement.style.left = newX + 'px'; // // currentDragElement.style.top = newY + 'px'; // //// 퍼센티지로 적용 추가 // // 현재 화면 해상도 // const screenWidth = window.innerWidth; // const screenHeight = window.innerHeight; // // 퍼센티지 계산 (소수점 2자리까지) // const leftPercent = parseFloat((newX / screenWidth * 100).toFixed(2)); // const topPercent = parseFloat((newY / screenHeight * 100).toFixed(2)); // currentDragElement.style.left = leftPercent + '%'; // currentDragElement.style.top = topPercent + '%'; // } // // 포인터 업 (드래그 종료) // function handleControlBoxPointerUp(e) { // if (!isControlBoxDragging) return; // isControlBoxDragging = false; // if (currentDragElement) { // currentDragElement.querySelector('.drag-handle').style.cursor = 'grab'; // currentDragElement.classList.remove('dragging'); // // 드래그 종료 시 위치 저장 (console.log로 표시) // saveControlBoxPosition(currentDragElement); // currentDragElement = null; // } // } // // DB에서 저장된 위치 정보 가져오기 (구현 필요) // async function loadControlBoxPosition() { // let position = null; // let userId = JSON.parse(vars.userInfoString).user_id; // let getControlBoxPositionRes = await axios.get(`${vars.path_name}/getControlBoxPosition`, { params: { userId: userId } }); // if (getControlBoxPositionRes.data.message == 'getControlBoxPosition_success' && getControlBoxPositionRes.data.result.floating_box_position) { // position = getControlBoxPositionRes.data.result.floating_box_position; // } // return JSON.parse(position); // } // async function saveControlBoxPosition(element) { // // 현재 화면 해상도 // let screenWidth = window.innerWidth; // let screenHeight = window.innerHeight; // let positionData = { // // 퍼센티지 값 // leftPercent: element.style.left, // topPercent: element.style.top, // // 화면 해상도 정보 // screenWidth: screenWidth, // screenHeight: screenHeight // } // let params = { // userId: JSON.parse(vars.userInfoString).user_id, // positionData: JSON.stringify(positionData) // }; // await axios.post(`${vars.path_name}/setControlBoxPosition`, { params: params }); // } /////////////////////////// // 가운데 파일 영역 우클릭 시 강조 및 컨텍스트 메뉴 열기 document.querySelector('.archive-main .archive-main-center .list-container')?.addEventListener('contextmenu', (e) => { let target, scope; if (e.target.matches('.list-item-data')) { target = e.target.parentElement; scope = 'list-item'; } else if (e.target.matches('.convert-btn')) { target = e.target.parentElement.parentElement; scope = 'list-item'; } else if (e.target.matches('.grid-item') || e.target.closest('.grid-item')) { if (e.target.matches('.grid-item')) target = e.target; else target = e.target.closest('.grid-item'); scope = 'grid-item'; } else if (e.target.matches('.map-item')) { target = e.target; scope = 'map-item'; } else { target = document.querySelector('.archive-main .archive-main-center .list-container'); scope = 'list'; } // 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴 if (vars.mapMode == 'edit') return; toggleContextFocusBox(true, target, scope); toggleContextmenu(true, e, scope); }) // 휴지통 모달창 리스트 우클릭 시 강조 및 컨텍스트 메뉴 열기 document.querySelector('.recycle-bin-modal .list-container')?.addEventListener('contextmenu', (e) => { if (e.target.matches('.list-item-data')) { let target = e.target.parentElement; let scope = 'list-item'; toggleContextFocusBox(true, target, scope); toggleContextmenu(true, e, scope); } }) // // 가운데 리스트 드래그 - 지도 화면에서는 작동 안함 // // let listBody = document.querySelector('.archive-main-center .list-body'); // let listBody = document.querySelector('.list-container .list-body'); // let draggingStartTarget, draggingEndTarget; // let draggingStart = false, isDragging = false; // let isCtrl = false, isShift = false; // let processedItems = new Set(); // let originalSelection = new Set(); // let multiSelectBox = document.createElement('div'); // multiSelectBox.style.position = 'absolute'; // multiSelectBox.style.border = '1px solid rgba(0, 120, 215, 1)'; // multiSelectBox.style.background = 'rgba(0, 120, 215, 0.2)'; // multiSelectBox.style.zIndex = '9999'; // multiSelectBox.style.pointerEvents = 'none'; // multiSelectBox.style.display = 'none'; // listBody?.appendChild(multiSelectBox); // vars.multiSelectListItemArr = []; // vars.preventNextClick = false; // let startX = 0; // let startY = 0; // let prevClientX = 0; // let prevClientY = 0; // let autoScrollDirection = null; // let autoScrollFrameId = null; // const scrollSpeed = 8; // const scrollMargin = 36; // function autoScrollLoop() { // if (!autoScrollDirection) return; // // 더 작으면 느리고 부드러움 // if (autoScrollDirection === 'down') { // listBody.scrollTop += scrollSpeed; // } else if (autoScrollDirection === 'up') { // listBody.scrollTop -= scrollSpeed; // } // autoScrollFrameId = requestAnimationFrame(autoScrollLoop); // } // listBody?.addEventListener('pointerdown', (e) => { // // archive-main 화면이 아닌 경우 리턴 // if (document.querySelector('.archive-main').style.display !== 'flex') return; // // 마우스 좌클릭이 아닌 경우 리턴 // if (e.button !== 0) return; // // 갤러리 폴더 지도화면인 경우 리턴 // if (listBody.querySelector('.map-container').style.display === 'flex') return; // draggingStartTarget = e.target; // draggingStart = true; // isDragging = false; // isCtrl = e.ctrlKey; // isShift = e.shiftKey; // processedItems.clear(); // originalSelection = new Set(vars.multiSelectListItemArr); // vars.preventNextClick = false; // // if (isCtrl) { // // vars.lastListItem = e.target.closest('.list-item'); // // } else { // // listBody.querySelectorAll('.list-item').forEach(listItem => { // // listItem.classList.remove('selected'); // // listItem.classList.remove('group-style'); // // }) // // vars.multiSelectListItemArr = []; // // } // const dragStartListItem = e.target.closest('.list-item'); // if (dragStartListItem && !isShift) { // vars.lastListItem = dragStartListItem; // } // if (!isCtrl) { // // Ctrl이 아닐 경우 기존 선택 모두 해제 // listBody.querySelectorAll('.list-item').forEach(listItem => { // listItem.classList.remove('selected'); // listItem.classList.remove('group-style'); // }); // vars.multiSelectListItemArr = []; // } // const bodyRect = listBody.getBoundingClientRect(); // startX = e.clientX + listBody.scrollLeft - bodyRect.left; // startY = e.clientY + listBody.scrollTop - bodyRect.top; // prevClientX = e.clientX; // prevClientY = e.clientY; // Object.assign(multiSelectBox.style, { // left: `${startX}px`, // top: `${startY}px`, // width: `0px`, // height: `0px`, // display: 'none' // }); // }); // listBody?.addEventListener('pointermove', (e) => { // // archive-main 화면이 아닌 경우 리턴 // if (document.querySelector('.archive-main').style.display !== 'flex') return; // // 마우스 좌클릭이 아닌 경우 리턴 // if (e.buttons !== 1) return; // // list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴 // if (!draggingStart) return; // if (e.clientX === prevClientX && e.clientY === prevClientY) return; // prevClientX = e.clientX; // prevClientY = e.clientY; // const listBodyRect = listBody.getBoundingClientRect(); // const currX = e.clientX + listBody.scrollLeft - listBodyRect.left; // const currY = e.clientY + listBody.scrollTop - listBodyRect.top; // const dx = currX - startX; // const dy = currY - startY; // if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { // isDragging = true; // vars.preventNextClick = true; // const left = Math.min(startX, currX); // const top = Math.min(startY, currY); // const width = Math.abs(dx); // const height = Math.abs(dy); // Object.assign(multiSelectBox.style, { // left: `${left}px`, // top: `${top}px`, // width: `${width}px`, // height: `${height}px`, // display: 'block' // }); // const boxRect = multiSelectBox.getBoundingClientRect(); // document.querySelectorAll('.list-body .list-item .wrap').forEach(elem => { // const rect = elem.getBoundingClientRect(); // const overlaps = !( // rect.right < boxRect.left || // rect.left > boxRect.right || // rect.bottom < boxRect.top || // rect.top > boxRect.bottom // ); // let listItem = elem.parentElement; // if (isCtrl) { // if (overlaps && !processedItems.has(listItem)) { // processedItems.add(listItem); // // ✅ 토글: 원래 선택되어 있었으면 해제, 아니면 선택 // if (originalSelection.has(listItem)) { // listItem.classList.remove('selected'); // listItem.classList.remove('group-style'); // vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem); // } else { // listItem.classList.add('selected'); // listItem.classList.add('group-style'); // vars.multiSelectListItemArr.push(listItem); // } // } // // ✅ 박스에서 벗어난 항목 복원 // if (!overlaps && processedItems.has(listItem)) { // processedItems.delete(listItem); // if (originalSelection.has(listItem)) { // listItem.classList.add('selected'); // listItem.classList.add('group-style'); // if (!vars.multiSelectListItemArr.includes(listItem)) { // vars.multiSelectListItemArr.push(listItem); // } // } else { // listItem.classList.remove('selected'); // listItem.classList.remove('group-style'); // vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem); // } // } // } else { // listItem.classList.toggle('selected', overlaps); // listItem.classList.toggle('group-style', overlaps); // if (overlaps && !vars.multiSelectListItemArr.includes(listItem)) { // vars.multiSelectListItemArr.push(listItem); // } // } // }); // if (e.clientY > listBodyRect.bottom - scrollMargin) { // if (autoScrollDirection !== 'down') { // cancelAnimationFrame(autoScrollFrameId); // autoScrollDirection = 'down'; // autoScrollLoop(); // } // } else if (e.clientY < listBodyRect.top + scrollMargin) { // if (autoScrollDirection !== 'up') { // cancelAnimationFrame(autoScrollFrameId); // autoScrollDirection = 'up'; // autoScrollLoop(); // } // } else { // if (autoScrollDirection !== null) { // cancelAnimationFrame(autoScrollFrameId); // autoScrollDirection = null; // } // } // } // }); // document.addEventListener('pointerup', async (e) => { // // list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴 // if (!draggingStart) return; // if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn); // if (vars.lastMainTreeItem) targetFocus(vars.lastMainTreeItem); // if (isShift && isDragging) { // const dragStartListItem = draggingStartTarget.closest('.list-item'); // if (dragStartListItem) { // vars.lastListItem = dragStartListItem; // } // } // multiSelectBox.style.display = 'none'; // draggingEndTarget = e.target; // draggingStart = false; // isDragging = false; // isCtrl = false; // isShift = false; // // // 드래그가 끝나고 pointerup 이벤트가 발생하는 시점에 selected 클래스가 있는 listItem만 필터링 // // vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item.classList.contains('selected')); // // vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => !item.classList.contains('disabled')); // // vars.multiSelectListItemArr.forEach(item => { // // console.log(item); // // item.classList.remove('non-selected'); // // }) // // await syncGroupStyle(); // // 드래그가 아이템 바깥 영역에서만 진행되고, 드래그에 포함된 아이템이 하나도 없을 때 뷰어 초기화 // if (((draggingStartTarget.matches('.list-body') && draggingEndTarget.matches('.list-body')) // || (draggingStartTarget.matches('.list-item-wrap') && draggingEndTarget.matches('.list-item-wrap'))) // && vars.multiSelectListItemArr.length == 0) // { // // 모든 아이템에서 non-selected 클래스 삭제 // document.querySelectorAll('.grid-item').forEach(gridItem => { // gridItem.classList.remove('non-selected') // }) // // if (viewerContainer.style.display == 'flex') resetViewer(); // resetViewer(); // vars.lastListGroupTarget = undefined; // vars.lastListItem = undefined; // // vars.lastSelectTarget = undefined; // vars.lastContextTarget = undefined; // // userSelected 삭제 // let me = JSON.parse(vars.userInfoString); // me.selected = undefined; // vars.socket.emit('fileSelect',{me : me}); // } else { // // 모든 아이템에 non-selected 클래스 추가 // document.querySelectorAll('.grid-item').forEach(gridItem => { // gridItem.classList.add('non-selected') // }) // } // // 드래그 시작한 대상과 종료된 대상이 다르면 뷰어 초기화 // if (draggingStartTarget != draggingEndTarget) { // vars.lastListItem = undefined; // vars.lastListGroupTarget = undefined; // if (viewerContainer.style.display == 'flex') resetViewer(); // } // // 드래그가 끝나고 pointerup 이벤트가 발생하는 시점에 selected 클래스가 있는 listItem만 필터링 // vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item.classList.contains('selected')); // // vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => !item.classList.contains('disabled')); // // vars.multiSelectListItemArr에 포함된 아이템에서 non-selected 클래스 삭제 // vars.multiSelectListItemArr.forEach(item => { // item.classList.remove('non-selected'); // }) // await syncGroupStyle(); // // 드래그 시작한 대상의 부모요소와 종료된 대상의 부모요소가 다르면 뷰어 초기화 // // if (draggingStartTarget.parentElement != draggingEndTarget.parentElement) { // // if (viewerContainer.style.display == 'flex') resetViewer(); // // } // vars.lastSelectTarget = vars.multiSelectListItemArr[0]; // cancelAnimationFrame(autoScrollFrameId); // autoScrollDirection = null; // }); // 휴지통 모달창 헤더 - 정렬 클릭 이벤트 document.querySelector('.recycle-bin-modal .list-header-area')?.querySelectorAll('div').forEach(ele=>{ ele.addEventListener('click',async (e)=>{ let headers = document.querySelector('.recycle-bin-modal .list-header-area').querySelectorAll('div'); headers.forEach(h=>{ if (e.target == h) { if (h.classList.contains('sort-asc') && !h.classList.contains('sort-desc')) { h.classList.remove('sort-asc'); h.classList.add('sort-desc'); } else if (!h.classList.contains('sort-asc') && h.classList.contains('sort-desc')) { h.classList.add('sort-asc'); h.classList.remove('sort-desc'); } else if (!h.classList.contains('sort-asc') && !h.classList.contains('sort-desc')) { h.classList.add('sort-asc'); } } else { h.classList.remove('sort-asc', 'sort-desc'); } }) if(vars.curSortCol_bin != e.target.classList[0]){ vars.curSortCol_bin = `${e.target.classList[0]}`; vars.curSortOrder_bin = `desc`; } vars.curSortOrder_bin = (vars.curSortOrder_bin == 'asc') ? `desc` : `asc`; await prepareRecycleBinRendering(); }) }) // 일반 폴더 헤더 - 정렬 클릭 이벤트 document.querySelector('.archive-main-center .list-header-area')?.querySelectorAll('div').forEach(ele=>{ ele.addEventListener('click',async (e)=>{ let headers = document.querySelector('.archive-main-center .list-header-area').querySelectorAll('div'); headers.forEach(h=>{ if (e.target == h) { if (h.classList.contains('sort-asc') && !h.classList.contains('sort-desc')) { h.classList.remove('sort-asc'); h.classList.add('sort-desc'); } else if (!h.classList.contains('sort-asc') && h.classList.contains('sort-desc')) { h.classList.add('sort-asc'); h.classList.remove('sort-desc'); } else if (!h.classList.contains('sort-asc') && !h.classList.contains('sort-desc')) { h.classList.add('sort-asc'); } } else { h.classList.remove('sort-asc', 'sort-desc'); } }) if(vars.curSortCol != e.target.classList[0]){ vars.curSortCol = `${e.target.classList[0]}`; vars.curSortOrder = `desc`; } vars.curSortOrder = (vars.curSortOrder == 'asc') ? `desc` : `asc`; let renderOption = { resourcePath: vars.lastMainTreeItem.dataset.resourcePath, scope : 'list' } await preparePageRendering(renderOption); }) }) // 갤러리 폴더 헤더 - 분류 라디오버튼 클릭 이벤트 document.querySelector('.control-area .category .radio-btn-wrap')?.addEventListener('click', async function(e) { let clickedInput = e.target.querySelector('input'); // 라디오버튼이 이미 체크된 상태인 경우 리턴 if (clickedInput.checked == true) return; clickedInput.checked = true; let sortRadioBtns = document.querySelectorAll('.control-area .sort .radio-btn-wrap .radio-btn'); if (clickedInput.value == 'none') { //// 분류 없음 선택 // 정렬 라디오버튼 비활성화 sortRadioBtns.forEach(sortRadioBtn => { sortRadioBtn.classList.add('disabled'); }) document.querySelectorAll('.thumbnail-title').forEach(title => { title.style.display = 'none'; }); } else { //// 분류 파일명/등록자/등록일자/용량 선택 // 정렬 라디오버튼 활성화 sortRadioBtns.forEach(sortRadioBtn => { sortRadioBtn.classList.remove('disabled'); }) // 분류, 정렬 기준으로 그리드 리스트 다시 렌더링 vars.curSortCol = clickedInput.value; vars.curSortOrder = document.querySelector('input[type="radio"][name="sort"]:checked').value; //// 썸네일 모드에서 vars.curSortCol이 none(라벨 숨김)으로 저장된 채 리스트 모드로 바꾼 뒤 //// 바로 파일명 정렬을 하면 무시되는 문제가 생기기 때문에 vars.curSortCol이 none인 경우 name으로 강제 저장 if (vars.curSortCol == 'none') vars.curSortCol = 'name'; let renderOption = { resourcePath: vars.lastMainTreeItem.dataset.resourcePath, scope : 'list' } await preparePageRendering(renderOption); } }) // 갤러리 폴더 헤더 - 정렬 라디오버튼 클릭 이벤트 document.querySelector('.control-area .sort .radio-btn-wrap')?.addEventListener('click', async function(e) { if (e.target.classList.contains('disabled')) return; let clickedInput = e.target.querySelector('input'); // 라디오버튼이 이미 체크된 상태인 경우 리턴 if (clickedInput.checked == true) return; clickedInput.checked = true; vars.curSortOrder = clickedInput.value; vars.curSortCol = document.querySelector('input[type="radio"][name="category"]:checked').value; //// 썸네일 모드에서 vars.curSortCol이 none(라벨 숨김)으로 저장된 채 리스트 모드로 바꾼 뒤 //// 바로 파일명 정렬을 하면 무시되는 문제가 생기기 때문에 vars.curSortCol이 none인 경우 name으로 강제 저장 if (vars.curSortCol == 'none') vars.curSortCol = 'name'; let renderOption = { resourcePath: vars.lastMainTreeItem.dataset.resourcePath, scope : 'list' } await preparePageRendering(renderOption); }) // 갤러리 폴더 헤더 - 슬라이더 조절 이벤트 let gridSizeSlider = document.querySelector('.control-area .thumbnail .input-wrap .slider-input .thumbnail-size-slider'); gridSizeSlider?.addEventListener('input', function(e) { let value = Number(gridSizeSlider.value); changeSliderTrackColor(gridSizeSlider, gridSizeSlider.matches(':hover')); changeGridItemSize(value); }) // 갤러리 폴더 헤더 - 슬라이더 양 옆 + - 버튼 클릭 이벤트 document.querySelector('.control-area .thumbnail .input-wrap .btn.plus')?.addEventListener('click', function(e) { let value = Number(gridSizeSlider.value); if (value == -1) return; if (value != -1) value = value + 1; gridSizeSlider.value = value; changeSliderTrackColor(gridSizeSlider, false); changeGridItemSize(value); }) document.querySelector('.control-area .thumbnail .input-wrap .btn.minus')?.addEventListener('click', function(e) { let value = Number(gridSizeSlider.value); if (value == -10) return; if (value != -10) value = value - 1; gridSizeSlider.value = value; changeSliderTrackColor(gridSizeSlider, false); changeGridItemSize(value); }) // 갤러리 폴더 헤더 - 슬라이더 마우스호버 이벤트 gridSizeSlider?.addEventListener('mouseenter', () => changeSliderTrackColor(gridSizeSlider, true)); gridSizeSlider?.addEventListener('mouseleave', () => changeSliderTrackColor(gridSizeSlider, false)); // 갤러리 폴더 헤더 - 슬라이더 시작지점에서 커스텀thumb지점까지 색깔 채우는 함수 function changeSliderTrackColor(slider, isHover) { let value = slider.value; value = -(value); let min = -(slider.min); let max = -(slider.max); let percent = ((value - min) / (max - min)) * 100; let color = (isHover) ? '#1E5149' : '#a5b9b6'; slider.style.background = `linear-gradient(to right, ${color} ${percent}%, #ccc ${percent}%)`; } // 갤러리 폴더 헤더 - 슬라이더 조절 시 또는 양 옆 + - 버튼 클릭 시 그리드 크기 변경하는 함수 function changeGridItemSize(value) { value = -(value); // 썸네일 크기 클수록 썸네일 간격 넓게 적용 // if (value == 1) document.documentElement.style.setProperty('--thumbnail-gap', '1rem'); // if (value == 2) document.documentElement.style.setProperty('--thumbnail-gap', '0.75rem'); // if (value >= 3) document.documentElement.style.setProperty('--thumbnail-gap', '0.5rem'); let thumbnailSizeValue = `0 0 calc((100% - ${value-1} * var(--thumbnail-gap)) / ${value})`; document.documentElement.style.setProperty('--thumbnail-size', thumbnailSizeValue); document.documentElement.style.setProperty('--thumbnail-size-value', value); } // 갤러리 폴더 바디 - 기본지도 버튼 클릭 이벤트 document.querySelector('.map-container .base-map-btn')?.addEventListener('click', async function(e) { document.querySelector('.map-container .base-map-btn').classList.toggle('on'); document.querySelector('.map-container .base-map').classList.toggle('on'); }) // 갤러리 폴더 바디 - 기본지도 메뉴 닫기 클릭 이벤트 document.querySelector('.map-container .base-map .close')?.addEventListener('click', async function(e) { document.querySelector('.map-container .base-map-btn').classList.remove('on'); document.querySelector('.map-container .base-map').classList.remove('on'); }) // 갤러리 폴더 바디 - 기본지도 메뉴 라디오버튼 클릭 이벤트 document.querySelectorAll('.map-container .base-map .radio-btn-wrap .radio-btn').forEach(radioBtn => { radioBtn.addEventListener('click', async function(e) { let clickedInput = e.target.querySelector('input'); // 라디오버튼이 이미 체크된 상태인 경우 리턴 if (clickedInput.checked == true) return; clickedInput.checked = true; let value = document.querySelector('input[type="radio"][name="base-map"]:checked').value; // 지도 생성 시 일반 지도 1개만 등록 후 지도 url 변경 방식 // let source = vars.baseLayer.getSource(); // console.log(vars[value]); // source.setUrl(vars[value]); // source.refresh(); // 지도 생성 시 3가지 지도 모두 등록 후 교체 방식 vars.roadLayer.setVisible(value === 'road'); vars.satelliteLayer.setVisible(value === 'satellite'); vars.hybridLayer.setVisible(value === 'hybrid'); }) }) let infoWrap = document.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap'); let memo = infoWrap?.querySelector('.memo'); let apiBtn = infoWrap?.querySelector('.memo .header .wrap .api-btn'); let editBtn = infoWrap?.querySelector('.memo .header .wrap .edit-btn'); let aiBtn = infoWrap?.querySelector('.memo .header .wrap .ai-btn'); let editMessage = infoWrap?.querySelector('.memo .header .wrap .message'); let bodyWrap = infoWrap?.querySelector('.memo .body .wrap'); let bodyTextarea = infoWrap?.querySelector('.memo .body .wrap .textarea'); let bodyMessage = infoWrap?.querySelector('.memo .body .wrap .message'); // 우측 요약 textarea 입력 이벤트 bodyTextarea?.addEventListener('input', async (e) => { if (bodyTextarea.value == '') bodyMessage.style.color = '#777'; else bodyMessage.style.color = '#ddd'; }) // 우측 요약 수정/저장 버튼 클릭 이벤트 editBtn?.addEventListener('click', async(e) => { editBtn.classList.toggle('save'); if(editBtn.matches('.save')) { editBtn.querySelector('.text').innerText = '저장'; bodyWrap.style.border = '1px solid #000'; bodyTextarea.disabled = false; bodyMessage.innerText = '내용을 작성/수정한 후 저장 버튼을 눌러주세요.'; // text 입력 이벤트 bodyTextarea.addEventListener('input', () => { const resourcePath = vars.lastListItem.dataset.resourcePath; vars.tempMemo[resourcePath] = bodyTextarea.value; }) }else { editBtn.querySelector('.text').innerText = '수정'; bodyWrap.style.border = '1px solid #ddd'; bodyTextarea.disabled = true; bodyMessage.innerText = '수정 버튼을 눌러 내용을 작성/수정할 수 있습니다.'; if (bodyTextarea.value == '' ) bodyMessage.style.color = '#777'; else bodyMessage.style.color = '#ddd'; editMessage.style.transition = '1s'; editMessage.style.opacity = '1'; let timeoutId = setTimeout(async function() { editMessage.style.opacity = '0'; clearTimeout(timeoutId); }, 1500); let userInfoString = vars.userInfoString; let resourcePath = vars.lastListItem.dataset.resourcePath; let memo = bodyTextarea.value; let dataId = vars.lastListItem.dataset.dataId; let params = { resourcePath: resourcePath, memo: memo }; let res = await axios.post(`${vars.path_name}/updateMemoInfo`, { userInfoString, params }); if(res.data.message == 'updateMemo_success') { delete vars.tempMemo[resourcePath]; } } }) // ai 버튼 클릭 이벤트 // rpsm test 용 // aiBtn?.addEventListener('click', async event => { // const element = apiBtn.children[0]; // const container = element.closest('.viewer-container'); // const formData = new FormData(); // // 1. 선택한 파일의 과업명 추출 // for(let i=0; i { // 준비중 안내 모달 -> ai 기능 완료되면 제거 // let toggleParams = { // title: '안내', // text: 'AI 기능은 준비중입니다. 추후 업데이트 예정입니다.', // type: 'temp_ai', // }; // toggleModal(true, toggleParams); // const formData = new FormData(); // // 1. 프롬프트 파일 // const promptRes = await fetch('/main/jsm/archive/ai_prompt/prompt.txt'); // const promptBlob = await promptRes.blob(); // const promptFile = new File([promptBlob], 'prompt.txt', { type: 'text/plain' }); // formData.append('file', promptFile); // // 2. json 파일 // const jsonRes = await fetch('/main/jsm/archive/ai_prompt/prompt.json'); // const jsonBlob = await jsonRes.blob(); // const jsonFile = new File([jsonBlob], 'prompt.json', { type: 'application/json'}); // formData.append('file', jsonFile); // // 3. 선택한 파일 // const fileObj = vars.currentTreeObject.file; // const keys = Object.keys(fileObj); // for(let i=0; i key == fileName); // const resourcePath = fileObj[matchedKey].resourcePath; // let getDataInfoParams = { // userInfoString: vars.userInfoString, // storageType: vars.storageType, // dataIdArr: [dataId], // isRemoved: false, // debug: 'ai 변환 진행 중' // } // let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams }); // if(getDataInfoRes.data.message == 'getDataInfo_success') { // let result = getDataInfoRes.data.result; // let params = { // projectId: result.project_id, // objectKey: result.object_key, // storageType: result.storage_type, // userInfoString: vars.userInfoString, // resourcePath: resourcePath, // dataId: result.data_id, // depth1: extractPathByLength(resourcePath, 1), // depth2: extractPathByLength(resourcePath, 2), // depth3: extractPathByLength(resourcePath, 3), // type: 'gemini', // } // const aiRes = await axios.post(`${vars.path_name}/summarizeAI`, formData, { params: params }); // } // } // }) // 외부 API AI // apiBtn?.addEventListener('click', async event => { // const element = apiBtn.children[0]; // const container = element.closest('.viewer-container'); // const formData = new FormData(); // // 1. 프롬프트 파일 // const promptRes = await fetch('/main/jsm/archive/ai_prompt/prompt.txt'); // const promptBlob = await promptRes.blob(); // const promptFile = new File([promptBlob], 'prompt.txt', {type: 'text/plain'}); // formData.append('prompt_file', promptFile); // // 2. 선택한 파일 // for (let i=0; i { // element.addEventListener('click', async function(event) { const element = apiBtn.children[0]; const container = element.closest('.viewer-container'); const formData = new FormData(); // 1. 프롬프트 파일 const promptRes = await fetch('/main/jsm/archive/ai_prompt/prompt.txt'); const promptBlob = await promptRes.blob(); const promptFile = new File([promptBlob], 'prompt.txt', { type: 'text/plain'}); formData.append('prompt_file', promptFile); // 2. 선택한 파일 for(let i=0; i { infoWrap.classList.toggle('open'); infoWrap.classList.toggle('close'); }) // 우측 미리보기 10페이지 안내 버튼 클릭 이벤트 // document.querySelector('.archive-main .archive-main-right .viewer-container .viewer-wrap .thumb-alert')?.addEventListener('click', async (e) => { // e.target.classList.toggle('compact'); // }); // 우측 미리보기 새 창으로 열기 버튼 클릭 이벤트 document.querySelector('.archive-main .archive-main-right .viewer-container .viewer-header .btn')?.addEventListener('click', async () => { openNewWindowViewer(); }); // 우측 툴바 이전/다음 버튼 클릭 이벤트 // document.querySelectorAll('.archive-main .archive-main-right .viewer-container .toolbar .toolbar-container .btn').forEach(btn => { // btn.addEventListener('click', function(e) { // // disabled 클래스를 가지고 있으면 리턴 // if (e.target.classList.contains('disabled')) return; // // 현재 선택된 아이템의 인덱스를 기준으로 이전 버튼 클릭하면 이전 인덱스 아이템을, 다음 버튼을 클릭하면 다음 인덱스 아이템을 vars.lastListItem에 저장 // let listItemArr = [...listContainer.querySelector('.list-body .list-item-wrap').children]; // let currentIdx = listItemArr.indexOf(vars.lastListItem); // if (e.target.classList.contains('prev-btn')) vars.lastListItem = listItemArr[currentIdx-1]; // if (e.target.classList.contains('next-btn')) vars.lastListItem = listItemArr[currentIdx+1]; // // 모든 아이템 스타일 초기화 // document.querySelectorAll('.list-item').forEach((elem) => { // elem.classList.remove('selected'); // elem.classList.remove('group-style'); // }); // // 버튼 클릭으로 선택된 아이템 뷰어에 표시 // let resourcePath = vars.lastListItem.dataset.resourcePath; // let dataId = vars.lastListItem.dataset.id; // renderViewer(resourcePath, dataId); // // 선택 상태로 스타일 변경 // changeListItemStyle(vars.lastListItem); // // 선택된 아이템으로 포커스 이동 // targetFocus(vars.lastListItem); // }) // }); // 우측 메타데이터 영역 작성자 변경 버튼 클릭 이벤트 document.querySelector('.archive-main .archive-main-right .viewer-container .metadata .metadata-item-wrap .author .btn')?.addEventListener('click', ()=>{ let resourcePath = vars.lastListItem.dataset.resourcePath; openEditAuthorModal(resourcePath, vars.lastListItem); }); // 우측 메타데이터 영역 위치 수정 버튼 클릭 이벤트 document.querySelector('.archive-main .archive-main-right .viewer-container .metadata .metadata-item-wrap .gps-data .btn')?.addEventListener('click', ()=>{ let toggleParams = { title: '위치 수정', text: '확인 버튼을 누르면 위치 수정 모드로 전환됩니다.', type: 'editPosition', }; toggleModal(true, toggleParams); }); // 푸터 도움말 버튼 클릭 이벤트 document.querySelector('body > .footer .left .manual-wrap .text')?.addEventListener('click', ()=>{ let toggleParams = { title: '도움말', type: 'manual', }; toggleModal(true, toggleParams); }) // 푸터 문의사항 버튼 클릭 이벤트 document.querySelector('body > .footer .left .question-wrap .text')?.addEventListener('click', ()=>{ window.open('https://docs.google.com/spreadsheets/d/18XL_xJERh50Ls1n1Y7G_nSrDo1DKvJwPRaPuuiGw2uQ/edit?gid=0#gid=0','question'); }) // 푸터 패치노트 버튼 클릭 이벤트 document.querySelector('body > .footer .left .patch-note-wrap .text')?.addEventListener('click', ()=>{ window.open('https://docs.google.com/spreadsheets/d/1MgLs-_PZkpu8jb653A56hw6NQ9jl77OAiXOM4r45Ri0/edit?gid=1522357484#gid=1522357484','patch-note'); }) // 푸터 휴지통 버튼 클릭 이벤트 document.querySelector('body > .footer .left .recycle-bin-wrap .text')?.addEventListener('click', async ()=>{ toggleRecycleBinModal(true); }) // 푸터 저장공간 버튼 클릭 이벤트 document.querySelector('body > .footer .left .size-wrap .text')?.addEventListener('click', (e) => { let toggleParams = { title: '저장공간', type: 'size', }; toggleModal(true, toggleParams); // 모달 열기 // 현재 사용자 정보 표시 }) // 푸터 활동로그 버튼 클릭 이벤트 document.querySelector('body > .footer .left .log-wrap .text')?.addEventListener('click', (e) => { let toggleParams = { title: '활동로그', type: 'log', }; toggleModal(true, toggleParams); // 모달 열기 // 현재 사용자 정보 표시 }) // 개발자 계정일 때 푸터 로고 클릭 이벤트 if (vars.permission.checkPermission('dev-menu')) { // 클릭을 위해 이미지 태그에 기본으로 설정된 pointer-events 'none'을 'all'로 변경 document.querySelector('body > .footer .right').addEventListener('click', () => { toggleDevMenuModal(true); }) } // 모달창 이외 영역 클릭 시 모달창 닫기 // document.querySelector('.archive-modal')?.addEventListener('pointerdown', (e) => { // if (e.target.className.includes('archive-modal')) toggleModal(false); // }) // 모달창 X버튼 닫기 document.querySelector('.archive-modal .modal-header .close')?.addEventListener('click', () => { document.querySelector('.project-type__list')?.classList.remove('--project-list__open'); document.querySelector('.project-step__list')?.classList.remove('--project-list__open'); toggleModal(false); }) // 휴지통 모달창 X버튼 닫기 document.querySelector('.recycle-bin-modal .modal-header .close')?.addEventListener('click', () => { toggleRecycleBinModal(false); }) // 개발자 메뉴 모달창 이외 영역 클릭 시 모달창 닫기 // document.querySelector('.dev-menu-modal')?.addEventListener('pointerdown', (e) => { // if (e.target.className.includes('dev-menu-modal')) toggleDevMenuModal(false); // }) // 개발자 메뉴 모달창 X버튼 닫기 document.querySelector('.dev-menu-modal .modal-header .close')?.addEventListener('click', () => { toggleDevMenuModal(false); }) export function toggleContextFocusBox(state, target, scope) { let contextFocusBox = document.querySelector('.context-focus-box'); if (state == false) { // 이벤트 대상(target)이 컨텍스트메뉴에 포함된 dom(컨텍스트메뉴 아이템)이 아닌 경우 리턴 if (target && document.querySelector('.contextmenu.main-menu').contains(target)) return; if (target && document.querySelector('.archive-modal').contains(target)) return; // contextFocusBox 숨김 처리 contextFocusBox.style.display = 'none'; } if (state == true) { let targetWidth = target.offsetWidth; let targetHeight = target.offsetHeight; let top = 0; let left = 0; if (scope == 'tree') { let controlBox = document.querySelector('.archive-main .archive-main-left .control-box'); if (controlBox) targetHeight = targetHeight - controlBox.offsetHeight; } if (scope == 'list') { // 갤러리 폴더 지도모드일 때 컨텍스트 포커스 박스 생성 비활성화 let mapContainer = listContainer?.querySelector('.map-container'); if (mapContainer.style.display == 'flex') return; } // if (scope == 'headerBtn') { // targetWidth = target.querySelector('.wrap').offsetWidth; // targetHeight = target.querySelector('.wrap').offsetHeight; // } if (scope == 'tree' || scope == 'list') { targetWidth = targetWidth-8; targetHeight = targetHeight-8; top = 4; left = 4; } if (scope == 'list-item') { // 파일 이동 모드인 경우 리턴 if (document.querySelector('.list-viewer-cover').style.display == 'flex') return; // 추가 파일에 disabled 클래스가 포함되어 있으면 리턴 (파일 이동 모드) let listItem = target.closest('.list-item'); if (listItem.classList.contains('disabled')) return; targetWidth = target.parentElement.offsetWidth; targetHeight = target.parentElement.offsetHeight; } if (scope == 'grid-item') { // 파일 이동 모드인 경우 리턴 if (document.querySelector('.list-viewer-cover').style.display == 'flex') return; targetWidth = targetWidth; targetHeight = targetHeight; top = -1; left = -1; } if (scope == 'map-item') { return; // targetWidth = targetWidth-4; // targetHeight = targetHeight-4; // top = 1; // left = 1; } let width = `${targetWidth}px`; let height = `${targetHeight}px`; contextFocusBox.style.width = width; contextFocusBox.style.height = height; contextFocusBox.style.top = `${top}px`; contextFocusBox.style.left = `${left}px`; if (document.querySelector('.context-focus-box-clone')) { document.querySelector('.context-focus-box-clone').remove(); } let contextFocusBoxClone = contextFocusBox.cloneNode(true); contextFocusBoxClone.classList.add('context-focus-box-clone'); contextFocusBoxClone.style.display = 'block'; target.style.position = 'relative'; target.appendChild(contextFocusBoxClone); } } export function toggleContextmenu(state, event, scope) { let contextmenu = document.querySelector('.contextmenu'); if (state == false) { contextmenu.style.display = 'none'; // 컨텍스트메뉴 아이템을 제외한 영역에서 toggleContextmenu false 작동 시 컨텍스트메뉴 아이템 초기화 (업로드) if (event && event.target.closest && !event.target.closest('.contextmenu-item')) contextmenu.querySelector('.contextmenu-container').innerHTML = ''; // 이벤트 대상(event.target)이 컨텍스트메뉴에 포함된 dom(컨텍스트메뉴 아이템)이 아닌 경우 리턴 // if (event && document.querySelector('.contextmenu.main-menu')?.contains(event.target)) return; // if (event && document.querySelector('.archive-modal')?.contains(event.target)) return; if (event && typeof event.target.nodeType === 'number') { if ( ['.contextmenu.main-menu', '.archive-modal'].some(selector => document.querySelector(selector)?.contains(event.target) ) ) { return; // } else { } } // vars.lastContextTarget/vars.lastContextTarget_bin 초기화 vars.lastContextTarget = undefined; vars.lastContextTarget_bin = undefined; } if (state == true) { let target = event.target; if (!target.dataset.resourcePath) { if (target.matches('.tree-notice')) { // .tree-container > .tree-notice 우클릭 시 target을 .tree-container로 설정 target = target.parentElement; } else if (target.matches('.tree-wrap')) { // .tree-container > .tree-wrap 우클릭 시 target을 .tree-container로 설정 target = target.parentElement; } else if (target.matches('.tree-item')) { // .tree-item 우클릭 시 target을 .tree-item-wrap으로 설정 target = target.parentElement; } else if (target.closest('.list-wrap.list-header')) { // .list-wrap.list-header > .list-item-wrap > .list-item > .wrap > div 우클릭 시 target을 .list-wrap.list-header으로 설정 target = target.closest('.list-wrap.list-header'); } else if (target.closest('.list-wrap.list-body')) { // .list-wrap.list-body > .list-item-wrap > .list-item > .wrap > div 우클릭 시 target을 div를 포함하고 있는 .list-item으로 설정 target = target.closest('.list-item'); if (target && target.matches('.sub-list-item')) scope = 'sub-list-item'; } } let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex'; if (isRecycleBinModal) { vars.lastContextTarget_bin = target; if (vars.multiSelectListItemArr_bin.includes(target)) { if (vars.multiSelectListItemArr_bin.length > 1) { scope = 'multi-select-list' toggleContextFocusBox(false); } } else { document.querySelectorAll('.recycle-bin-modal .list-body .list-item').forEach(elem => { elem.classList.remove('selected'); elem.classList.remove('group-style'); }) vars.lastListGroupTarget_bin = undefined; vars.lastListItem_bin = undefined; vars.lastSelectTarget_bin = undefined; //userSelected 삭제 let me = JSON.parse(vars.userInfoString); me.selected = undefined; vars.socket.emit('fileSelect',{me : me}); vars.multiSelectListItemArr_bin = []; } } else { vars.lastContextTarget = target; if (vars.multiSelectListItemArr.includes(target)) { if (vars.multiSelectListItemArr.length > 1) { scope = 'multi-select-list' toggleContextFocusBox(false); } } else { document.querySelectorAll('.archive-main .list-body .list-item').forEach(elem => { if (viewerContainer.style.display == 'flex') resetViewer(); elem.classList.remove('selected'); elem.classList.remove('group-style'); }) vars.lastListGroupTarget = undefined; vars.lastListItem = undefined; vars.lastSelectTarget = undefined; //userSelected 삭제 let me = JSON.parse(vars.userInfoString); me.selected = undefined; vars.socket.emit('fileSelect',{me : me}); vars.multiSelectListItemArr = []; } } renderContextmenu(event, scope); } } // 컨트롤 박스 드래그 on/off 토글 export function toggleControlBox(state, fileAreaMode) { let controlBox = document.querySelector('.control-box'); if (state == false) { controlBox.style.display = 'none'; } //// 아카이브 좌측 영역 고정 버전 컨트롤 박스 관련 코드 if (state == true) { controlBox.style.display = 'flex'; controlBox.querySelectorAll('.file-area-mode-btn').forEach(btn => { btn.classList.remove('selected'); }) controlBox.querySelector(`.file-area-mode-btn.${fileAreaMode}`).classList.add('selected'); } //// 플로팅 버전 컨트롤 박스 관련 코드 // if (state == true) { // controlBox.style.display = 'flex'; // controlBox.querySelectorAll('.file-area-mode-btn').forEach(btn => { // btn.classList.remove('selected'); // }) // controlBox.querySelector(`.file-area-mode-btn.${fileAreaMode}`).classList.add('selected'); // // 모든 버튼 크기 동일하게 설정 // // let firstBtnWidth = Math.round(controlBox.querySelectorAll('.file-area-mode-btn')[0].getBoundingClientRect().width); // // controlBox.querySelectorAll('.file-area-mode-btn').forEach(btn => { // // btn.style.minWidth = `${pxToRem(firstBtnWidth)}rem`; // // }) // let dragHandle = controlBox.querySelector('.drag-handle'); // // 드래그 핸들에 이벤트 리스너 추가 // dragHandle.addEventListener('pointerdown', handleControlBoxPointerDown); // // 전역 이벤트 리스너 (한 번만 추가) // document.addEventListener('pointermove', handleControlBoxPointerMove); // document.addEventListener('pointerup', handleControlBoxPointerUp); // // 저장된 위치 확인 및 적용 // setControlBoxInitialPosition(controlBox); // } } export function toggleArchiveMainRight(state, option) { // let { from } = option; // console.log('@@@@@@@@@@@@@@@@'); // console.log(from); let archiveMainRight = document.querySelector('.archive-main-right'); if (state == false) { if (viewerContainer.style.display == 'flex') viewerContainer.style.display = 'none'; archiveMainRight.style.display = 'none'; // archiveMainRight.style.width = '0rem'; // archiveMainRight.style.opacity = '0'; } if (state == true) { if (vars.viewer && vars.viewer.childElementCount != 0) viewerContainer.style.display = 'flex'; archiveMainRight.style.display = 'flex'; // archiveMainRight.style.width = '41rem'; // archiveMainRight.style.opacity = '1'; if (document.querySelector('.viewer-container .viewer-wrap .viewer').children.length > 0) viewerNotice.style.display = 'none'; if (vars.lastListItem) { // 마지막으로 선택된 파일 아이템이 있는 경우 현재 파일 영역 모드에 따라서 뷰어 정보 영역 세팅 let value = document.querySelector('.control-box .file-area-mode-btn.selected')?.id; if (value) { let infoWrap = document.querySelector('.archive-main-right .info-wrap'); let toggleBtnText = infoWrap.querySelector('.toggle-btn .text'); let metadata = infoWrap.querySelector('.metadata'); // 뷰어 툴바는 그리드 모드 미리보기에서 사용하는데 그리드 모드에서 미리보기 표시 안하기로 했기 때문에 뷰어 툴바는 무조건 숨김 let viewerToolbar = infoWrap.querySelector('.toolbar'); viewerToolbar.style.display = 'none'; //// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 다르게 표시 --- 시작 // if (value == 'list') { // infoWrap.style.height = '15%'; // toggleBtnText.textContent = '메모 (AI 요약)'; // metadata.style.display = 'none'; // } else { // // 메타데이터 내용 채우기!!!!!!!!!!!!!!!!!!!!!!!!!!! 애초에 리스트 아이템 클릭할 때 메타데이터 숨겨져 있어도 내용은 그냥 채우는걸로? // infoWrap.style.height = '30%'; // toggleBtnText.textContent = '메타데이터'; // metadata.style.display = 'flex'; // } //// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 다르게 표시 --- 끝 // 메타데이터 수정 시 업데이트 된 메타데이터로 뷰어 재렌더링 setTimeout(async() => { if (vars.lastListItem) { let updateResourcePath = vars.lastListItem.dataset.resourcePath; let updatedDataId = vars.lastListItem.dataset.id; await renderViewer(updateResourcePath, updatedDataId, false); } }, 300); //// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 메타데이터 형태로 동일하게 표시 --- 시작 infoWrap.style.height = '30%'; toggleBtnText.textContent = '메타데이터'; metadata.style.display = 'flex'; //// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 메타데이터 형태로 동일하게 표시 --- 끝 } } else { // 마지막으로 선택된 파일 아이템이 없으면 뷰어 초기화 resetViewer(); } } } export function showNotification(params) { let fadeMs = 500; let holdMs = 1000; let notificationWrap = document.querySelector('.list-wrap.list-body .notification-wrap'); let text = notificationWrap.querySelector('.text'); text.innerHTML = params.text; // 1) 기존 hide 타이머 취소(연속 호출 동안 계속 표시) if (notificationWrap._hideTimer) { clearTimeout(notificationWrap._hideTimer); notificationWrap._hideTimer = null; } // 2) 현재 애니메이션 상태 확인 let animationName = getComputedStyle(notificationWrap).animationName || 'none'; // 2-1) fadeOut 중이면 즉시 중단하고 보이도록 스냅(깜빡임 방지) if (animationName !== 'none' && animationName.indexOf('fadeOut') !== -1) { resetNotificationAnimation(notificationWrap); notificationWrap.style.animation = 'fadeIn 1ms forwards'; notificationWrap._visible = true; } // 2-2) 처음 표시될 때만 fadeIn 실행 else if (!notificationWrap._visible) { notificationWrap.style.animation = `fadeIn ${fadeMs}ms forwards`; notificationWrap._visible = true; } // 이미 보이는/페이드인 중이면 건드리지 않음 → 깜빡임 없음 // 3) 마지막 호출 이후 holdMs 지나면 fadeOut notificationWrap._hideTimer = setTimeout(() => { resetNotificationAnimation(notificationWrap); notificationWrap.style.animation = `fadeOut ${fadeMs}ms forwards`; let onEnd = () => { notificationWrap.removeEventListener('animationend', onEnd); notificationWrap._visible = false; // 애니메이션 속성 정리 → 기본 CSS(opacity:0)로 복귀 resetNotificationAnimation(notificationWrap); }; notificationWrap.addEventListener('animationend', onEnd, { once: true }); }, holdMs); } function resetNotificationAnimation(el) { el.style.animation = 'none'; void el.offsetWidth; // 강제 리플로우 el.style.animation = ''; } // 템플릿 다운로드 document.querySelector('.list-notice-negative')?.addEventListener('click', () => { downloadTempFile(); }) document.querySelector('.list-notice-box-toggle-menu-list.download')?.addEventListener('click', () => { downloadTempFile(); }) // depth1 이미지 파일 업로드 setupDepth1Upload('fileInput'); setupDepth1Upload('fileInput2'); function setupDepth1Upload(inputId) { document.getElementById(inputId)?.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const newFileName = 'MAIN_TITLE_IMAGE___' + file.name; const renamedFile = new File([file], newFileName, { type: file.type, lastModified: file.lastModified }); document.querySelector('.list-notice-box-toggle-menu').style.display = 'none'; uploadMainTitleImage([renamedFile], inputId); }); } document.querySelector('.list-notice-box-toggle')?.addEventListener('click', () => { const toggleMenu = document.querySelector('.list-notice-box-toggle-menu'); updateToggleMenuPosition(); if (toggleMenu.style.display === 'none' || toggleMenu.style.display === '') { toggleMenu.style.display = 'flex'; }else { toggleMenu.style.display = 'none'; } }) function updateToggleMenuPosition() { if (checkProjectInactive()) return; const toggleBtn = document.querySelector('.list-notice-box-toggle'); const toggleMenu = document.querySelector('.list-notice-box-toggle-menu'); const wasHidden = toggleMenu.style.display === 'none'; // 일시적으로 보여주기 (위치 계산 위해) if (wasHidden) { toggleMenu.style.visibility = 'hidden'; toggleMenu.style.display = 'flex'; } if (toggleMenu.style.display !== 'none') { const menuWidth = toggleMenu.offsetWidth; toggleMenu.style.top = `${toggleBtn.offsetTop + toggleBtn.offsetHeight + 4}px`; toggleMenu.style.left = `${toggleBtn.offsetLeft - menuWidth + toggleBtn.offsetWidth}px`; } // 다시 숨김 (필요한 경우) if (wasHidden) { toggleMenu.style.display = 'none'; toggleMenu.style.visibility = 'visible'; } } // 창 크기 변경될 때 위치 다시 계산 window.addEventListener('resize', updateToggleMenuPosition); // main title image 삭제 이벤트 document.querySelector('.list-notice-box-toggle-menu-list.delete')?.addEventListener('click', async () =>{ let resourcePath = `${vars.lastHeaderBtn.dataset.resourcePath}`; let param = { userInfoString: vars.userInfoString, resourcePath: resourcePath }; let res = await axios.post(`${vars.path_name}/deleteMainTitleImage`, { params: param }); if(res.data.message == 'deleteMainTitleImage_success') { document.querySelector('.list-notice-top').style.display = 'flex'; document.querySelector('.list-notice-bottom').style.display = 'flex'; document.querySelector('.list-notice-viewer').style.display = 'none'; } }) // download btn 이벤트 document.querySelector(`.download-btn`)?.addEventListener('click', async () => { await getMyDownloadList(); document.querySelector('.download-modal').style.display = 'flex'; }) // download modal close 이벤트 document.querySelector(`.download-modal__head_close`)?.addEventListener('click', () => { document.querySelector('.download-modal').style.display = 'none'; }) document.querySelector(`.download-modal__foot .--button__medium`)?.addEventListener('click', () => { document.querySelector('.download-modal').style.display = 'none'; }) // (임시) 썸네일 생성 버튼 이벤트 const thumbBtn = document.querySelector(`.archive-main .archive-main-center .list-container .list-wrap .list-item-wrap .list-item .wrap .thumbnail .thumb-btn-wrap`); thumbBtn.addEventListener('click', async () => { await regenerateThumbnails(); }); async function regenerateThumbnails() { // 1) db에서 썸네일 없는 파일 목록 불러오기 let res = await axios.get(`${vars.path_name}/getNullThumbnailDataInfo`); if(res.data.message === 'getNullThumbnailDataInfo_success') { let lists = res.data.result; console.log('!!!!!!!!!!!!!'); console.log(lists); if(!lists || lists.length === 0) { console.log('처리할 파일이 없습니다.'); return; } // 2) 서버에 썸네일 presigned URL 요청 let imageExtArr = ['jpg', 'jpeg', 'png', 'webp', 'gif']; let videoExtArr = ['mp4', 'webm', 'mov']; let docExtArr = ['hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf', 'grm']; let textExtArr = ['txt', 'md']; let date = new Date(); let needsThumbnailExtArr = [...imageExtArr, ...videoExtArr, ...docExtArr, ...textExtArr,'pdf']; let isLowerExt = true; let coordArr = []; let uploadSuccessFileArr = []; for (let i=0; i img.onload = r); console.log(777); let maxSizeBytes = 100 * 1024; if (resizeTarget.size > maxSizeBytes) { thumbnailFile = await resizeImage(img, maxSizeBytes, thumbnailPath); }else { thumbnailFile = resizeTarget; } // 5) s3에 썸네일 업로드 await axios.put(thumbnailUrl, thumbnailFile, { headers: { 'Content-Type': thumbnailFile.type || 'application/octet-stream' } }); console.log(`@@@@@@@@@@@@@@ 썸네일 s3 업로드 완료_${i+1}/${lists.length} @@@@@@@@@@@@@@`); // 6) DB의 기존 데이터 썸네일 정보 업데이트 let updateParams = { data_id: list.data_id, thumbnail_key: thumbnailKey, thumbnail_size: thumbnailFile.size, lon, lat, height, user_id: vars.userInfoString.user_id, }; let updateRes = await axios.post(`${vars.path_name}/updateThumbnailInfo`, updateParams); if(updateRes.data.message === 'updateThumbnailInfo_success') { uploadSuccessFileArr.push(list.paht4); console.log(`@@@@@@@@@@@@@@ 썸네일 정보 DB 업데이트 완료_${i+1}/${lists.length} @@@@@@@@@@@@@@`); }else { console.warn(`${fileName} DB 업데이트 실패`) } } } } } } console.log('***************************************************************'); console.log('************************* 작업 종료 *************************'); console.log('***************************************************************'); } } // 구성 모달 ver // document.querySelector('.footer .left .wrap.site-map-wrap').addEventListener('click', () => { // // 1. 모달 표시 // const modal = document.querySelector('.composition-modal'); // modal.style.display = 'flex'; // // 2. 리스트 초기화 // const listWrap = modal.querySelector('.modal-wrap > ul'); // listWrap.innerHTML = ''; // // 3. 데이터 렌더링 // renderCompositionData(); // }); // // 구성 모달 닫기 이벤트 // document.querySelector('.composition-modal .modal-wrap .head i').addEventListener('click', () => { // const modal = document.querySelector('.composition-modal'); // modal.style.display = 'none'; // }); // 구성 팝업 ver // document.querySelector('.footer .left .wrap.site-map-wrap').addEventListener('click', () => { // const popupWidth = 1400; // const popupHeight = 800; // const left = (screen.width - popupWidth) / 2; // const top = (screen.height - popupHeight) / 2; // window.open( // `/main/composition-popup.html`, // 'composition', // `width=${popupWidth}, height=${popupHeight}, left=${left}, top=${top}, resizeable, scrollbars=yes` // ); // }) // 구성 탭 ver document.querySelector('.footer .left .wrap.site-map-wrap').addEventListener('click', () => { // 새 탭으로 열기 (_blank) const newTab = window.open( `/main/composition-tab.html`, '_blank' ); // 탭이 로드될 때까지 대기 후 데이터 전송 if (newTab) { const checkTabLoad = setInterval(() => { try { // 새 탭이 로드되었는지 확인 if (newTab.document.readyState === 'complete') { clearInterval(checkTabLoad); // 탭에 필요한 데이터 전송 newTab.postMessage({ type: 'INIT_DATA', data: { userInfoString: vars.userInfoString, storageType: vars.storageType, allTreeObject: vars.allTreeObject, path_name: vars.path_name, } }, window.location.origin); } }catch(e) { // Cross-origin 에러 무시 (탭이 완전히 로드되기 전) } }, 100); // 10초 후에도 로드되지 않으면 interval 정리 setTimeout(() => clearInterval(checkTabLoad), 10000); } }); const messageHandler = { 'NAVIGATE_TO_PATH': (path) => { // vars 대신 const resourcePath = `/${path}`; // 헤더 버튼 클릭 (탭 선택) const tabPath = resourcePath.split('/')[1]; // '모델뷰어테스트/모델/3dm' -> '모델뷰어테스트' const headerBtn = document.querySelector(`[data-resource-path="/${tabPath}"]`); if (headerBtn) headerBtn.click(); // 트리 아이템 클릭 (폴더 선택) setTimeout(() => { const treeItem = document.querySelector(`.tree-item-wrap[data-resource-path="${resourcePath}"] .tree-item`); if (treeItem) treeItem.click(); }, 500); } } window.addEventListener('message', (event) => { if(event.origin !== window.location.origin) return; const handler = messageHandler[event.data.type]; if (handler) handler(event.data.path); }); window.asd = async function() { regenerateThumbnails(); }