7842 lines
339 KiB
JavaScript
7842 lines
339 KiB
JavaScript
import { vars } from './variable.js';
|
|
import { throttle, checkProjectInactive } from '../main.js';
|
|
import {
|
|
formatBytes,
|
|
addCommasToNumber,
|
|
splitBaseAndExt,
|
|
splitTopPathAndTargetName,
|
|
extractPathByLength,
|
|
getDataFromTreeObject,
|
|
getCurrentTreeObject,
|
|
targetFocus,
|
|
pxToRem,
|
|
headerBtnForceClick,
|
|
treeBtnForceClick,
|
|
closeInitProgress,
|
|
openNewWindowViewer,
|
|
initFileAreaUI,
|
|
syncGroupStyle
|
|
} from './common.js'
|
|
import {
|
|
toggleContextFocusBox,
|
|
toggleContextmenu,
|
|
toggleControlBox,
|
|
toggleArchiveMainRight,
|
|
showNotification,
|
|
} from './eventManager.js'
|
|
import {
|
|
openCreateFolderModal,
|
|
uploadData,
|
|
openRenameModal,
|
|
openEditAuthorModal,
|
|
openDownloadModal,
|
|
toggleRelocateCover,
|
|
openRemoveModal,
|
|
removeTarget,
|
|
openDeleteModal,
|
|
setDataPermission,
|
|
convertPdf,
|
|
updateAiButtonState,
|
|
} from './dataManager.js';
|
|
import {
|
|
mgmtFunc_addClickLog,
|
|
mgmtFunc_resetConvert
|
|
} from './managementFunctions.js';
|
|
import { toggleModal } from './modalManager.js';
|
|
import { OLMultiLayerManager } from './olPmtiles.js';
|
|
|
|
let mainNotice = document.querySelector('.main-notice');
|
|
let treeNotice = document.querySelector('.archive-main .archive-main-left .tree-container .tree-notice');
|
|
let treeWrap = document.querySelector('.archive-main .archive-main-left .tree-container .tree-wrap');
|
|
let listContainer = document.querySelector('.archive-main .archive-main-center .list-container');
|
|
let listNotice = document.querySelector('.archive-main .archive-main-center .list-notice');
|
|
let viewerContainer = document.querySelector('.archive-main .archive-main-right .viewer-container');
|
|
let viewerNotice = document.querySelector('.archive-main .archive-main-right .viewer-notice');
|
|
let viewerWrap = document.querySelector('.viewer-wrap');
|
|
let viewerToolbar = viewerContainer?.querySelector('.toolbar');
|
|
let infoWrap = viewerContainer?.querySelector('.info-wrap');
|
|
let metadata = infoWrap?.querySelector('.metadata');
|
|
|
|
let subCategoryList = ['version', 'attachment'];
|
|
let subCategoryData = {
|
|
version: {
|
|
idx: 1,
|
|
korean: '버전'
|
|
},
|
|
attachment: {
|
|
idx: 2,
|
|
korean: '첨부'
|
|
},
|
|
};
|
|
|
|
// 💡 글로벌 자동 삭제/보관 정책 설정 동적 변수 (기본값)
|
|
let FOLDER_KEEP_FILE_THRESHOLD = 3;
|
|
let FOLDER_KEEP_DAYS_THRESHOLD = 15;
|
|
let FOLDER_KEEP_POLICY_ACTIVE = false;
|
|
let isPolicyLoaded = false;
|
|
|
|
async function loadSystemPolicy() {
|
|
if (isPolicyLoaded) return;
|
|
try {
|
|
const res = await axios.get('/common/system-policy');
|
|
if (res.data) {
|
|
FOLDER_KEEP_POLICY_ACTIVE = res.data.is_active ?? false;
|
|
// 글로벌 정책이 켜져 있을 때만 DB 값을 적용하고, 꺼져 있으면 배지 노출을 차단합니다.
|
|
if (FOLDER_KEEP_POLICY_ACTIVE) {
|
|
FOLDER_KEEP_FILE_THRESHOLD = Number(res.data.limit_file_count) || 3;
|
|
FOLDER_KEEP_DAYS_THRESHOLD = Number(res.data.limit_days) || 15;
|
|
}
|
|
}
|
|
isPolicyLoaded = true;
|
|
} catch (err) {
|
|
console.error("Failed to load system policy:", err);
|
|
isPolicyLoaded = true;
|
|
}
|
|
}
|
|
|
|
export function updateSystemPolicyCache(policy) {
|
|
if (policy) {
|
|
FOLDER_KEEP_POLICY_ACTIVE = policy.is_active ?? false;
|
|
if (FOLDER_KEEP_POLICY_ACTIVE) {
|
|
FOLDER_KEEP_FILE_THRESHOLD = Number(policy.limit_file_count) || 3;
|
|
FOLDER_KEEP_DAYS_THRESHOLD = Number(policy.limit_days) || 15;
|
|
} else {
|
|
FOLDER_KEEP_FILE_THRESHOLD = 3;
|
|
FOLDER_KEEP_DAYS_THRESHOLD = 15;
|
|
}
|
|
} else {
|
|
isPolicyLoaded = false;
|
|
}
|
|
}
|
|
|
|
|
|
// 브라우저 뒤로가기, 앞으로가기 이벤트
|
|
window.addEventListener('popstate', async (e)=>{
|
|
// console.log(e);
|
|
if(window.location.search.split('?path=')[1]){
|
|
// let resourcePath = atob(decodeURIComponent(window.location.search.split('?path=')[1]));
|
|
let resourcePath = unicodeAtob(window.location.search.split('?path=')[1]);
|
|
|
|
// console.log(getDataFromTreeObject(resourcePath, 'folder').data);
|
|
// if (!getDataFromTreeObject(resourcePath, 'folder').data) {
|
|
|
|
// }
|
|
|
|
let resourcePathSplit = resourcePath.split('/');
|
|
resourcePathSplit.shift();
|
|
let headerBtnPath = `/${resourcePath.split('/')[1]}`;
|
|
|
|
// 휴지통 뒤로가기 - 임시
|
|
if (resourcePath == '/휴지통') return;
|
|
|
|
let treeItem;
|
|
if (resourcePath == '/과업개요' || resourcePath == '/공문') {
|
|
document.querySelector('.archive-main').style.display = 'none';
|
|
if (resourcePath == '/과업개요') {
|
|
document.querySelector('.overview-main').style.display = 'flex';
|
|
document.querySelector('.official-doc-main').style.display = 'none';
|
|
} else {
|
|
document.querySelector('.overview-main').style.display = 'none';
|
|
document.querySelector('.official-doc-main').style.display = 'flex';
|
|
}
|
|
|
|
let pageRanderingOption = {
|
|
scope: 'headerBtn_page',
|
|
resourcePath: resourcePath,
|
|
pushState: false,
|
|
}
|
|
await preparePageRendering(pageRanderingOption);
|
|
} else {
|
|
document.querySelector('.archive-main').style.display = 'flex';
|
|
document.querySelector('.overview-main').style.display = 'none';
|
|
document.querySelector('.official-doc-main').style.display = 'none';
|
|
|
|
// 휴지통 뒤로가기 - 임시
|
|
// if (resourcePathSplit[0] == '휴지통') {
|
|
// document.querySelector('.archive-main-left').style.display = 'none';
|
|
// } else {
|
|
// document.querySelector('.archive-main-left').style.display = 'flex';
|
|
// }
|
|
// console.log(resourcePathSplit);
|
|
|
|
if (resourcePathSplit.length == 1) {
|
|
listNotice.style.display = 'flex';
|
|
listContainer.style.display = 'none';
|
|
// viewerNotice.style.display = 'flex';
|
|
// viewerContainer.style.display = 'none';
|
|
|
|
document.querySelector('.archive-main .archive-main-left .tree-title').classList.add('selected');
|
|
|
|
let pageRanderingOption = {
|
|
scope: 'tree',
|
|
resourcePath: resourcePath,
|
|
pushState: false,
|
|
}
|
|
await preparePageRendering(pageRanderingOption);
|
|
} else {
|
|
listNotice.style.display = 'none';
|
|
listContainer.style.display = 'flex';
|
|
// viewerNotice.style.display = 'flex';
|
|
// viewerContainer.style.display = 'none';
|
|
|
|
let pageRanderingOption = {
|
|
scope: 'tree',
|
|
resourcePath: headerBtnPath,
|
|
pushState: false,
|
|
}
|
|
await preparePageRendering(pageRanderingOption);
|
|
|
|
pageRanderingOption = {
|
|
scope: 'list',
|
|
resourcePath: resourcePath,
|
|
pushState: false,
|
|
}
|
|
await preparePageRendering(pageRanderingOption);
|
|
|
|
treeItem = document.querySelector(`.archive-main .archive-main-left .tree-container .tree-item-wrap[data-resource-path="${resourcePath}"]`);
|
|
await changeTreeItemStyle(treeItem);
|
|
|
|
if (treeItem) targetFocus(treeItem);
|
|
}
|
|
}
|
|
|
|
let headerBtn = document.querySelector(`body > .header .center .left.wrap .menu-tab .btn[data-resource-path="${headerBtnPath}"]`);
|
|
if (!headerBtn) headerBtn = document.querySelector('.official-doc-btn');
|
|
await changeHeaderBtnStyle(headerBtn);
|
|
|
|
if (headerBtn) targetFocus(headerBtn);
|
|
}else{
|
|
window.history.back();
|
|
}
|
|
})
|
|
|
|
// base64 인코딩된 문자열을 한글로 디코딩하는 함수
|
|
function unicodeAtob(str) {
|
|
// base64 디코딩 후 UTF-8로 디코딩
|
|
return decodeURIComponent(
|
|
Array.prototype.map.call(
|
|
atob(str),
|
|
function(c) {
|
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
}
|
|
).join('')
|
|
);
|
|
}
|
|
|
|
// 한글을 btoa로 인코딩하는 함수
|
|
function unicodeBtoa(str) {
|
|
// 문자열을 UTF-8 인코딩된 바이너리로 변환 후 base64 인코딩
|
|
return btoa(
|
|
encodeURIComponent(str).replace(
|
|
/%([0-9A-F]{2})/g,
|
|
function(match, p1) {
|
|
return String.fromCharCode('0x' + p1);
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
// 확장자가 정상인지 체크하는 함수
|
|
function isValidExt(ext) {
|
|
let result = false;
|
|
if (!/\s/.test(ext) || /^[A-Za-z0-9]+$/.test(ext)) result = true;
|
|
return result;
|
|
}
|
|
|
|
// 월, 일 한 자리수 일 때 앞에 0 추가
|
|
function changeDateFormat(date) {
|
|
let dataSplit = date.split('.');
|
|
dataSplit.pop();
|
|
|
|
let [year, month, day] = dataSplit;
|
|
|
|
year = year.substr(2, 4);
|
|
month = month.replace(' ', '');
|
|
day = day.replace(' ', '');
|
|
|
|
if (month.length == 1) month = '0' + month;
|
|
if (day.length == 1) day = '0' + day;
|
|
|
|
return [year, month, day].join('-');
|
|
}
|
|
|
|
// 시간 형식 변환 (시:분:초 -> 시:분)
|
|
function changeTimeFormat(time) {
|
|
let [hours, minutes, seconds] = time.split(':');
|
|
return `${hours}:${minutes}`;
|
|
}
|
|
|
|
function getExpiryDate(lastFolderActDate) {
|
|
let result = {};
|
|
|
|
// 마지막으로 폴더에 저장된 시간
|
|
lastFolderActDate = new Date(lastFolderActDate);
|
|
// lastFolderActDate로부터 FOLDER_KEEP_DAYS_THRESHOLD일 뒤의 시간
|
|
let expiryDate = new Date(lastFolderActDate.getTime() + FOLDER_KEEP_DAYS_THRESHOLD * 24 * 60 * 60 * 1000);
|
|
// 현재 시간
|
|
let nowDate = new Date();
|
|
// expiryDate와 nowDate의 차이 (밀리세컨드)
|
|
let diffMs = expiryDate - nowDate;
|
|
|
|
if (diffMs <= 0) {
|
|
|
|
} else {
|
|
let diffSec = Math.floor(diffMs / 1000);
|
|
let days = Math.floor(diffSec / (24 * 60 * 60));
|
|
let hours = Math.floor((diffSec % (24 * 60 * 60)) / 3600);
|
|
let minutes = Math.floor((diffSec % 3600) / 60);
|
|
let seconds = diffSec % 60;
|
|
|
|
// 두 자리 포맷
|
|
let pad = n => String(n).padStart(2, '0');
|
|
|
|
let formatted = `${pad(days)}일 ${pad(hours)}시간 ${pad(minutes)}분 ${pad(seconds)}초`;
|
|
result.days = days;
|
|
result.formatted = formatted;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// 만료 폴더 삭제 함수
|
|
function removeExpiredFolder(value) {
|
|
// useSocket true false ?
|
|
let params ={
|
|
resourcePathArr: [value.resourcePath],
|
|
dataIdArr: [value.dataId],
|
|
dataType: 'folder',
|
|
isExpiredFolder: true
|
|
}
|
|
removeTarget(params);
|
|
}
|
|
|
|
// 만료 기간 툴팁 및 타이머 삭제 함수
|
|
export function removeCountdownTooltip() {
|
|
let countdownTooltip = document.querySelector('.countdown-tooltip');
|
|
if (!countdownTooltip) return;
|
|
if (vars.countdownTimer) {
|
|
clearInterval(vars.countdownTimer);
|
|
vars.countdownTimer = null;
|
|
}
|
|
countdownTooltip.remove();
|
|
}
|
|
|
|
function sortData(data) {
|
|
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
|
let curSortCol = (isRecycleBinModal) ? vars.curSortCol_bin : vars.curSortCol;
|
|
let curSortOrder = (isRecycleBinModal) ? vars.curSortOrder_bin : vars.curSortOrder;
|
|
|
|
let entries = Object.entries(data).sort((a, b) => {
|
|
const fileA = a[0];
|
|
const fileB = b[0];
|
|
|
|
let col = curSortCol;
|
|
let order = (curSortOrder == 'desc')?-1:1;
|
|
if (a[1].type == 'folder') {
|
|
col = 'folder';
|
|
order = 1;
|
|
}
|
|
|
|
const parse = (filename, info) => {
|
|
const match = filename.match(/^(.+?)(?:_(\d{4}-\d{2}-\d{2} \d{2}-\d{2}-\d{2}-\d{3}))?\.([^.]+)$/);
|
|
|
|
// depth4 파일에는 메모가 없지만 이 파일의 추가파일에는 메모가 있는 경우,
|
|
// depth4 파일에도 메모가 있다고 가정한 후 메모 정렬에 포함
|
|
// 메모 data : 원래 => String, 메모가 있다고 가정 => boolean(true)
|
|
const result = getFilenameWithMemoInDepth5ByDepth4Path(info.resourcePath);
|
|
if (info.depth == 4 && info.memo == '') {
|
|
if (filename === result.memoFilename) info.memo = true;
|
|
}
|
|
|
|
return {
|
|
base: match?.[1] || filename,
|
|
timestamp: match?.[2] || null,
|
|
ext: match?.[3] || '',
|
|
originalLocation: splitTopPathAndTargetName(info.resourcePath).topPath,
|
|
removeUser: info.modUserNm,
|
|
removeDate: info.modDate,
|
|
memo: info.memo,
|
|
author: info.authorNm,
|
|
uploader: info.userNm,
|
|
createDate: info.createDate,
|
|
size: info.size,
|
|
state: (info.needConvert)?`${info.isConverted}` : `${info.isSupported}`,
|
|
depth: info.depth,
|
|
isSub: result.isSub,
|
|
};
|
|
};
|
|
|
|
const A = parse(fileA, a[1]);
|
|
const B = parse(fileB, b[1]);
|
|
|
|
switch(col){
|
|
case 'original-location':
|
|
if (isRecycleBinModal) {
|
|
const originalLocationCmp = A.originalLocation.localeCompare(B.originalLocation, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(originalLocationCmp !==0) return originalLocationCmp * order;
|
|
}
|
|
break;
|
|
case 'remove-user':
|
|
if (isRecycleBinModal) {
|
|
const removeUserCmp = A.removeUser.localeCompare(B.removeUser, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(removeUserCmp !==0) return removeUserCmp * order;
|
|
}
|
|
break;
|
|
case 'remove-date':
|
|
if (isRecycleBinModal) {
|
|
const removeDateCmp = A.removeDate.localeCompare(B.removeDate, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(removeDateCmp !==0) return removeDateCmp * order;
|
|
}
|
|
break;
|
|
case 'memo':
|
|
const getMemoPriority = (item) => { // 정렬 우선순위
|
|
if (item.depth == 4 && item.isSub == false && item.memo != '') return 0;
|
|
if (item.depth == 4 && item.isSub == true && item.memo != '' && item.memo != true) return 1;
|
|
if (item.depth == 4 && item.isSub == true && item.memo == true) return 2;
|
|
if (item.depth == 5 && item.memo != '') return 3;
|
|
return 99;
|
|
};
|
|
const memoA = getMemoPriority(A);
|
|
const memoB = getMemoPriority(B);
|
|
if (memoA !== memoB) return (memoA - memoB) * order;
|
|
case 'author':
|
|
if (A.author == '' || A.author == undefined || A.author == null) A.author = A.uploader;
|
|
if (B.author == '' || B.author == undefined || B.author == null) B.author = B.uploader;
|
|
const authorCmp = A.author.localeCompare(B.author, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(authorCmp !==0) return authorCmp * order;
|
|
break;
|
|
case 'uploader':
|
|
const uploaderCmp = A.uploader.localeCompare(B.uploader, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(uploaderCmp !==0) return uploaderCmp * order;
|
|
break;
|
|
case 'create-date':
|
|
const dateCmp = A.createDate.localeCompare(B.createDate, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(dateCmp !==0) return dateCmp * order;
|
|
break;
|
|
case 'size':
|
|
const sizeCmp = A.size.localeCompare(B.size, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(sizeCmp !==0) return sizeCmp * order;
|
|
break;
|
|
case 'state':
|
|
const stateCmp = A.state.localeCompare(B.state, undefined, { numeric: true, sensitivity: 'base' });
|
|
if(stateCmp !==0) return stateCmp * order;
|
|
break;
|
|
default :
|
|
// 1. base name 정렬
|
|
const baseCmp = A.base.localeCompare(B.base, undefined, { numeric: true, sensitivity: 'base' });
|
|
if (baseCmp !== 0) return baseCmp * order;
|
|
|
|
// 2. 확장자 정렬
|
|
const extCmp = A.ext.localeCompare(B.ext, undefined, { numeric: true, sensitivity: 'base' });
|
|
if (extCmp !== 0) return extCmp * order;
|
|
|
|
// 3. timestamp 없는 파일 먼저
|
|
if (A.timestamp && B.timestamp) {
|
|
return A.timestamp.localeCompare(B.timestamp) * order;
|
|
} else if (A.timestamp) {
|
|
return 1 * order; // A는 있음 → B가 먼저
|
|
} else if (B.timestamp) {
|
|
return -1 * order; // B는 있음 → A가 먼저
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
return entries;
|
|
}
|
|
|
|
// depth4 경로를 기준으로, 1. depth4 파일이 추가파일(depth5)을 가지고 있는지 판단 후 반환
|
|
// 2. depth4 자체에는 메모가 없고 하위 depth5 파일 중 메모가 하나라도 존재하면 그 depth4 파일의 이름을 반환
|
|
function getFilenameWithMemoInDepth5ByDepth4Path(depth4Path) {
|
|
let isSub = false;
|
|
let memoFilename = null;
|
|
|
|
for (const ele of subCategoryList) {
|
|
let sub = getDataFromTreeObject(`${depth4Path}_${ele}`, 'folder', vars.allTreeObject).data;
|
|
const subFiles = sub?.child?.file;
|
|
if (subFiles) {
|
|
isSub = true;
|
|
for (const key of Object.keys(subFiles)) {
|
|
if (subFiles[key]?.memo) {
|
|
memoFilename = subFiles[key].mainFileName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (memoFilename) break;
|
|
}
|
|
|
|
return { isSub: isSub, memoFilename };
|
|
}
|
|
|
|
export function resetSort() {
|
|
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
|
let listContainer, curSortCol, curSortOrder;
|
|
|
|
if (isRecycleBinModal) {
|
|
listContainer = document.querySelector('.recycle-bin-modal .list-container');
|
|
curSortCol = 'remove-date';
|
|
vars.curSortCol_bin = curSortCol;
|
|
vars.curSortOrder_bin = 'desc'
|
|
} else {
|
|
listContainer = document.querySelector('.archive-main-center .list-container');
|
|
curSortCol = 'name';
|
|
vars.curSortCol = curSortCol;
|
|
vars.curSortOrder = 'asc'
|
|
}
|
|
|
|
|
|
let headers = listContainer.querySelector('.list-header-area').querySelectorAll('div');
|
|
headers.forEach(h=>{
|
|
h.classList.remove('sort-asc', 'sort-desc');
|
|
if (h.classList.contains(curSortCol)) h.classList.add('sort-asc');
|
|
})
|
|
}
|
|
|
|
function translateActivity(activity) {
|
|
let result;
|
|
/*
|
|
< _ 분리 O >
|
|
uploadData_file
|
|
renameTarget_file
|
|
renameTarget_folder
|
|
downloadTarget_file
|
|
removeTarget_file
|
|
removeTarget_folder
|
|
setDataPermission_folder
|
|
addOn_version
|
|
addOn_attachment
|
|
< _ 분리 X >
|
|
createFolder
|
|
convertPdf
|
|
*/
|
|
|
|
let activitySplit = activity.split('_');
|
|
|
|
if (activitySplit[1]) {
|
|
let prefix, suffix;
|
|
|
|
if (activitySplit[1] == 'file') prefix = '파일';
|
|
if (activitySplit[1] == 'folder') prefix = '폴더';
|
|
if (activitySplit[1] == 'version') prefix = '버전파일';
|
|
if (activitySplit[1] == 'attachment') prefix = '첨부파일';
|
|
if (activitySplit[1] == 'subMaster') prefix = '부관리자';
|
|
if (activitySplit[1] == 'securityWorker') prefix = '보안참여자';
|
|
if (activitySplit[1] == 'worker') prefix = '일반참여자';
|
|
if (activitySplit[1] == 'viewer') prefix = '참관자';
|
|
|
|
if (activitySplit[0] == 'uploadData') suffix = '업로드';
|
|
if (activitySplit[0] == 'renameTarget') suffix = '이름 변경';
|
|
if (activitySplit[0] == 'relocateTarget') suffix = '이동';
|
|
if (activitySplit[0] == 'downloadTarget') suffix = '다운로드';
|
|
if (activitySplit[0] == 'removeTarget') {
|
|
suffix = (activitySplit[1] == 'file') ? '휴지통으로 이동' : '폴더 삭제';
|
|
if (activitySplit[2] && activitySplit[2] == 'expired') suffix = '폴더 자동 삭제 (파일 개수 미달)';
|
|
prefix = '';
|
|
}
|
|
if (activitySplit[0] == 'deleteTarget') suffix = '파일 삭제';
|
|
if (activitySplit[0] == 'setDataPermission') suffix = '권한 설정';
|
|
if (activitySplit[0] == 'addOn') suffix = '추가';
|
|
if (activitySplit[0] == 'addPermission') suffix = '권한 추가';
|
|
if (activitySplit[0] == 'deletePermission') suffix = '권한 삭제';
|
|
|
|
result = `${prefix} ${suffix}`;
|
|
} else {
|
|
if (activitySplit[0] == 'createFolder') result = '새 폴더 생성';
|
|
if (activitySplit[0] == 'convertPdf') result = 'PDF 변환';
|
|
if (activitySplit[0] == 'saveMemo') result = '메모 데이터 저장';
|
|
if (activitySplit[0] == 'editAuthor') result = '작성자 변경';
|
|
if (activitySplit[0] == 'summarizeAI') result = 'AI요약';
|
|
}
|
|
|
|
if (result) result = result.trim();
|
|
return result;
|
|
}
|
|
|
|
export async function preparePageRendering(pageRanderingOption) {
|
|
if (checkProjectInactive()) return;
|
|
|
|
// scope: headerBtn, tree(depth2, depth3), list
|
|
let headerBtn = false, tree = false, list = false;
|
|
let scope = pageRanderingOption.scope;
|
|
let resourcePath = pageRanderingOption.resourcePath;
|
|
let userCurPath = (pageRanderingOption.userCurPath) ? pageRanderingOption.userCurPath : resourcePath;
|
|
if (scope == 'headerBtn') headerBtn = true, resourcePath = '/';
|
|
if (scope == 'tree') tree = true;
|
|
if (scope == 'list') list = true;
|
|
if (scope == 'recycle_bin') list = true;
|
|
|
|
let isInit = (pageRanderingOption) ? pageRanderingOption.isInit : undefined;
|
|
if (isInit != true) isInit = false;
|
|
|
|
let getTreeObject = (pageRanderingOption) ? pageRanderingOption.getTreeObject : undefined;
|
|
if (getTreeObject != false) getTreeObject = true;
|
|
|
|
let pushState = (pageRanderingOption) ? pageRanderingOption.pushState : undefined;
|
|
if (pushState != false) pushState = true;
|
|
|
|
// 초기 실행 시
|
|
if (isInit) {
|
|
// // 데이터 로딩 프로그레스 시작
|
|
document.querySelector('.init-progress').style.display = 'flex';
|
|
|
|
// // 마지막 히스토리를 현재 경로로 대체
|
|
window.history.replaceState(window.history.length+1,'',window.location.pathname);
|
|
}
|
|
|
|
// 251114 김아름
|
|
// if (pushState && resourcePath != '/') window.history.pushState(window.history.length+1,'',window.location.pathname + '?path='+unicodeBtoa(resourcePath));
|
|
if (pushState) {
|
|
const isValidPath = resourcePath && resourcePath !== '/';
|
|
if (isValidPath) {
|
|
window.history.pushState(window.history.length+1,'',window.location.pathname + '?path='+unicodeBtoa(resourcePath));
|
|
} else {
|
|
window.history.pushState(window.history.length+1,'',window.location.pathname);
|
|
}
|
|
}
|
|
|
|
// 휴지통 리스트를 보고 있지 않는 경우에만 파일 리스트 화면으로 변경
|
|
if (vars.lastHeaderBtn && vars.lastHeaderBtn.dataset.resourcePath != '/휴지통') {
|
|
if (document.querySelector('.archive-main-left').style.display == 'none') {
|
|
// 좌측트리영역 표시
|
|
document.querySelector('.archive-main-left').style.display = 'flex';
|
|
// list-item-wrap에 추가했던 recycle-bin클래스 삭제
|
|
vars.listItemWrap.classList.remove('recycle-bin');
|
|
}
|
|
}
|
|
|
|
if (!scope) return;
|
|
|
|
if (scope == 'updateTreeObject') {
|
|
// TreeObject만 갱신하고 렌더링은 하지 않음
|
|
let userString = vars.userInfoString;
|
|
let user = JSON.parse(userString);
|
|
user.project_id = vars.project_id;
|
|
user.curPath = resourcePath;
|
|
if (userCurPath) user.curPath = userCurPath;
|
|
|
|
let getTreeObjectParams = {
|
|
userInfoString: JSON.stringify(user),
|
|
storageType: vars.storageType,
|
|
resourcePath: resourcePath
|
|
};
|
|
let getTreeObjectRes = await axios.get(`${vars.path_name}/getTreeObject`, {params: {params: getTreeObjectParams}});
|
|
|
|
if (getTreeObjectRes.data.message == 'getTreeObject_success') {
|
|
vars.currentTreeObject = getTreeObjectRes.data.currentTreeObject;
|
|
|
|
// allTreeObject 갱신
|
|
if (resourcePath != '/') {
|
|
let parentData = getDataFromTreeObject(resourcePath, 'folder', vars.allTreeObject).parentData;
|
|
let data = getDataFromTreeObject(resourcePath, 'folder', vars.allTreeObject).data;
|
|
|
|
if (data) {
|
|
data.child = vars.currentTreeObject;
|
|
let targetName = splitTopPathAndTargetName(resourcePath).targetName;
|
|
parentData.folder[targetName] = data;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (getTreeObject) {
|
|
let meString = vars.userInfoString;
|
|
let me = JSON.parse(meString);
|
|
me.project_id = vars.project_id;
|
|
me.curPath = resourcePath;
|
|
if (userCurPath) me.curPath = userCurPath;
|
|
vars.socket.emit('setUser',{me:me, stat:'no'});
|
|
vars.socket.emit('getMe');
|
|
|
|
if(me.bookmark && me.bookmark != '' && me.bookmark != 'null') me.bookmark = '';
|
|
|
|
let getTreeObjectParams = { userInfoString: JSON.stringify(me), storageType: vars.storageType, resourcePath: resourcePath };
|
|
try {
|
|
let getTreeObjectRes = await axios.get(`${vars.path_name}/getTreeObject`, { params: { params: getTreeObjectParams } });
|
|
if (getTreeObjectRes.data.message == 'getTreeObject_success') {
|
|
vars.convertingDataArr = getTreeObjectRes.data.convertingDataArr;
|
|
|
|
vars.currentTreeObject = getTreeObjectRes.data.currentTreeObject;
|
|
|
|
if (!vars.allTreeObject || resourcePath == '/') {
|
|
vars.allTreeObject = vars.currentTreeObject;
|
|
} else {
|
|
if (scope != 'recycle_bin') {
|
|
let parentData = getDataFromTreeObject(resourcePath, 'folder', vars.allTreeObject).parentData;
|
|
let data = getDataFromTreeObject(resourcePath, 'folder', vars.allTreeObject).data;
|
|
|
|
if (data) {
|
|
data.child = vars.currentTreeObject;
|
|
|
|
let targetName = splitTopPathAndTargetName(resourcePath).targetName;
|
|
parentData.folder[targetName] = data;
|
|
}
|
|
} else {
|
|
console.log(1234567890);
|
|
}
|
|
}
|
|
} else if (getTreeObjectRes.data.message == 'getTreeObject_failed') {
|
|
console.log('---------------- getTreeObject 실패');
|
|
closeInitProgress();
|
|
}
|
|
} catch (err) {
|
|
console.error('---------------- getTreeObject 에러 발생:', err);
|
|
closeInitProgress();
|
|
}
|
|
} else {
|
|
vars.allTreeObject = vars.allTreeObject;
|
|
vars.currentTreeObject = getCurrentTreeObject(resourcePath);
|
|
}
|
|
// if (!scope) return;
|
|
if (scope == 'headerBtn_page' || scope == 'updateTreeObject') return;
|
|
|
|
await (async function () {
|
|
// 과업개요 비활성화 된 경우 트리 오브젝트 생성 후에 시작경로가 없으면 초기 프로그레스 종료
|
|
// if (isInit && !vars.project.overview) {
|
|
// if (!vars.startPathDepth1 && !vars.startPathDepth2 && !vars.startPathDepth3) document.querySelector('.init-progress').style.display = 'none';
|
|
// }
|
|
|
|
let userInfo = JSON.parse(vars.userInfoString);
|
|
if (userInfo.permission != null && typeof userInfo.permission == 'number') {
|
|
if (headerBtn) {
|
|
//// header folder
|
|
let headerBtnParent = document.querySelector('body > .header .center .left.wrap .menu-tab');
|
|
await renderHeaderBtn(headerBtnParent, vars.currentTreeObject.folder, isInit);
|
|
|
|
// 헤더 버튼이 없으면 초기 프로그레스 종료 후 mainNotice 표시
|
|
if (document.querySelector('body > .header .center .left.wrap .menu-tab').children.length == 0) {
|
|
console.log('******** 헤더버튼 영역 렌더링 후 초기 프로그레스 종료 (과업개요버튼 없음, 헤더폴더버튼 없음)');
|
|
await closeInitProgress();
|
|
mainNotice.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
if (tree) {
|
|
let treeTitle = document.querySelector('.archive-main-left .tree-title');
|
|
let treeContainer = document.querySelector('.archive-main-left .tree-container');
|
|
let treeWrap = treeContainer.querySelector('.tree-wrap');
|
|
|
|
treeTitle.querySelector('.wrap .text').textContent = vars.lastHeaderBtn.querySelector('.name-text').textContent;
|
|
treeTitle.dataset.resourcePath = vars.lastHeaderBtn.dataset.resourcePath;
|
|
treeContainer.dataset.resourcePath = vars.lastHeaderBtn.dataset.resourcePath;
|
|
treeWrap.dataset.resourcePath = vars.lastHeaderBtn.dataset.resourcePath;
|
|
|
|
await renderTree(treeWrap, vars.currentTreeObject.folder);
|
|
|
|
//////// pm-bcmf 연결용 테스트 코드 - 파라미터로 전달받은 시작경로로 화면 전환 (depth3)
|
|
if (vars.startPathDepth3) {
|
|
let path = `/${vars.startPathDepth1}/${vars.startPathDepth2}/${vars.startPathDepth3}`;
|
|
let treeBtn = document.querySelector(`.tree-container .tree-wrap .tree-item-wrap[data-resource-path="${path}"] .tree-item`);
|
|
if (treeBtn) await treeBtnForceClick(treeBtn);
|
|
}
|
|
|
|
if (document.querySelector('.init-progress').style.display != 'none') {
|
|
console.log('******** 트리 영역 렌더링 후 초기 프로그레스 종료');
|
|
await closeInitProgress();
|
|
}
|
|
}
|
|
|
|
if (list) {
|
|
let listParent = document.querySelector('.archive-main-center .list-container .list-wrap.list-body .list-item-wrap');
|
|
renderList(listParent, vars.currentTreeObject.file);
|
|
}
|
|
}
|
|
})();
|
|
}
|
|
|
|
export async function prepareRecycleBinRendering() {
|
|
let resourcePath = '/휴지통';
|
|
|
|
let getRecycleBinObjectParams = { userInfoString: vars.userInfoString, storageType: vars.storageType, resourcePath: resourcePath };
|
|
let getRecycleBinObjectRes = await axios.get(`${vars.path_name}/getRecycleBinObject`, { params: { params: getRecycleBinObjectParams } });
|
|
if (getRecycleBinObjectRes.data.message == 'getRecycleBinObject_success') {
|
|
vars.recycleBinObject = getRecycleBinObjectRes.data.recycleBinObject;
|
|
|
|
let listParent = document.querySelector('.recycle-bin-modal .recycle-bin-wrap .list-container .list-body .list-item-wrap');
|
|
listParent.classList.add('recycle-bin');
|
|
|
|
renderRecycleBin(listParent, vars.recycleBinObject.recycleBin);
|
|
}
|
|
}
|
|
|
|
export async function renderHeaderBtn(parent, data, isInit) {
|
|
let headerCenter = document.querySelector('body > .header .center');
|
|
let rightWrap = headerCenter.querySelector('.right.wrap');
|
|
let leftWrap = headerCenter.querySelector('.left.wrap');
|
|
let menuTab = leftWrap.querySelector('.menu-tab');
|
|
leftWrap.style.width = `${headerCenter.offsetWidth - rightWrap.offsetWidth - 16}px`; // body > .header .center -> gap: 16px;
|
|
|
|
// let me = JSON.parse(vars.userInfoString);
|
|
|
|
//// 과업개요 버튼 사용하는 프로젝트인 경우 과업개요 버튼 복제
|
|
let overviewBtnClone;
|
|
if (vars.project.overview && menuTab.querySelector('.overview-btn')) {
|
|
overviewBtnClone = menuTab.querySelector('.overview-btn').cloneNode(true);
|
|
|
|
//// 복제한 과업개요 버튼에 클릭 이벤트 추가
|
|
overviewBtnClone.addEventListener('click', async () => {
|
|
if (document.querySelector('.archive-main')) document.querySelector('.archive-main').style.display = 'none';
|
|
if (document.querySelector('.overview-main')) document.querySelector('.overview-main').style.display = 'flex';
|
|
if (document.querySelector('.official-doc-main')) document.querySelector('.official-doc-main').style.display = 'none';
|
|
|
|
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(overviewBtnClone);
|
|
|
|
// 컨트롤 박스 숨김
|
|
toggleControlBox(false);
|
|
})
|
|
}
|
|
|
|
//// menuTab 전체 초기화 -> menuTab에 복제한 과업개요 버튼 추가
|
|
menuTab.innerHTML = '';
|
|
if (overviewBtnClone) menuTab.appendChild(overviewBtnClone);
|
|
|
|
let docFragment = document.createDocumentFragment();
|
|
let entries = sortData(data);
|
|
|
|
entries.forEach(entry => {
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
let childState;
|
|
if (value.filesCount == 0) {
|
|
childState = 'empty';
|
|
} else {
|
|
childState = 'filled';
|
|
}
|
|
|
|
let resourcePath = value.resourcePath;
|
|
|
|
let btn = document.createElement('div');
|
|
btn.classList.add('btn');
|
|
btn.classList.add('folder-btn');
|
|
btn.classList.add(`depth${value.depth}`);
|
|
btn.classList.add(childState);
|
|
btn.classList.add('folder');
|
|
btn.dataset.resourcePath = resourcePath;
|
|
btn.dataset.id = value.dataId;
|
|
btn.dataset.size = value.size;
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
let image = document.createElement('div');
|
|
image.classList.add('image');
|
|
|
|
let nameText = document.createElement('div');
|
|
nameText.classList.add('name-text');
|
|
nameText.innerHTML = key;
|
|
|
|
wrap.appendChild(image);
|
|
wrap.appendChild(nameText);
|
|
|
|
let addPermissionBadge = false, permissionEng = '', permissionKor = '';
|
|
if (value.permission == 0) {
|
|
addPermissionBadge = true;
|
|
permissionEng = 'sub-master';
|
|
permissionKor = '관리';
|
|
}
|
|
if (value.permission == 8) {
|
|
addPermissionBadge = true;
|
|
permissionEng = 'security-worker';
|
|
permissionKor = '보안';
|
|
}
|
|
if (value.permission == 4) {
|
|
addPermissionBadge = true;
|
|
permissionEng = 'worker';
|
|
permissionKor = '일반';
|
|
}
|
|
// if (value.permission == 2) {
|
|
// addPermissionBadge = true;
|
|
// permissionEng = 'uploader';
|
|
// permissionKor = '업로더';
|
|
// }
|
|
if (addPermissionBadge) {
|
|
let permissionBadge = document.createElement('div');
|
|
permissionBadge.classList.add(`user-permission-${permissionEng}`);
|
|
// permissionBadge.style.marginLeft = '8px';
|
|
|
|
let h6 = document.createElement('h6');
|
|
h6.innerText = permissionKor;
|
|
|
|
permissionBadge.appendChild(h6);
|
|
wrap.appendChild(permissionBadge);
|
|
}
|
|
|
|
btn.appendChild(wrap);
|
|
|
|
docFragment.appendChild(btn);
|
|
|
|
btn.addEventListener('click', async (e) => {
|
|
headerBtnClickFunc(btn);
|
|
});
|
|
|
|
// depth1 폴더 우클릭 시 강조 및 컨텍스트 메뉴 열기
|
|
btn.addEventListener('contextmenu', async (e) => {
|
|
headerBtnContextmenuFunc(e);
|
|
});
|
|
});
|
|
|
|
parent.appendChild(docFragment);
|
|
|
|
processHeaderBtnOverflow();
|
|
|
|
if (isInit) {
|
|
// 헤더 버튼 최초 렌더링 시
|
|
let headerBtn;
|
|
// 시작경로가 있고 과업개요를 사용 안하는 프로젝트인 경우 시작경로로 화면 전환
|
|
if (vars.startPathDepth1 && !vars.project.overview) {
|
|
//////// pm-bcmf 연결용 테스트 코드 - 파라미터로 전달받은 시작경로로 화면 전환 (depth1)
|
|
headerBtn = document.querySelector(`body > .header .center .left.wrap .menu-tab .btn[data-resource-path="/${vars.startPathDepth1}"]`);
|
|
} else {
|
|
// 시작경로 없는 경우 첫 번째 헤더버튼으로 화면 전환
|
|
headerBtn = document.querySelectorAll('body > .header .center .left.wrap .menu-tab .btn')[0];
|
|
}
|
|
if (headerBtn) await headerBtnForceClick(headerBtn);
|
|
} else {
|
|
if (!vars.lastHeaderBtn) return;
|
|
let headerBtnPath = vars.lastHeaderBtn.dataset.resourcePath;
|
|
let headerBtn = document.querySelector(`body > .header .center .left.wrap .menu-tab .btn[data-resource-path="${headerBtnPath}"]`);
|
|
if (headerBtn == null || headerBtn == undefined || !headerBtn) vars.lastHeaderBtn = undefined;
|
|
if (headerBtn) await changeHeaderBtnStyle(headerBtn);
|
|
}
|
|
}
|
|
|
|
async function headerBtnClickFunc(btn) {
|
|
vars.lastSelectTarget = btn;
|
|
|
|
if (btn) targetFocus(btn);
|
|
|
|
if (document.querySelector('.archive-main')) document.querySelector('.archive-main').style.display = 'flex';
|
|
if (document.querySelector('.overview-main')) document.querySelector('.overview-main').style.display = 'none';
|
|
if (document.querySelector('.official-doc-main')) document.querySelector('.official-doc-main').style.display = 'none';
|
|
|
|
if (mainNotice.style.display == 'flex') mainNotice.style.display = 'none';
|
|
|
|
listContainer.style.display = 'none';
|
|
listNotice.style.display = 'flex';
|
|
resetViewer();
|
|
|
|
changeHeaderBtnStyle(btn);
|
|
|
|
let resourcePath = btn.dataset.resourcePath;
|
|
document.querySelector('.archive-main-left .tree-title').dataset.resourcePath = resourcePath;
|
|
document.querySelector('.archive-main-left .tree-container').dataset.resourcePath = resourcePath;
|
|
|
|
let pageRanderingOption = {
|
|
scope: 'tree',
|
|
resourcePath: resourcePath
|
|
}
|
|
await preparePageRendering(pageRanderingOption);
|
|
|
|
document.querySelector('.archive-main-left .tree-title').classList.add('selected');
|
|
|
|
// 파일 이동 모드(listViewerCover display flex)일 때 헤더 버튼 클릭하면 선택 폴더 경로 초기화
|
|
let listViewerCover = document.querySelector('.list-viewer-cover');
|
|
if (listViewerCover.style.display == 'flex') {
|
|
listViewerCover.querySelector('.new-path .value').innerText = '-';
|
|
} else {
|
|
//// vars에서 마지막 선택 아이템을 저장한 속성들 초기화 - renderTree
|
|
// 파일 이동 모드에서 초기화하면 이동 전, 후 경로를 가져올 수 없어서 파일 이동 모드 아닐 때에만 초기화
|
|
vars.lastMainTreeItem = undefined;
|
|
vars.lastListItem = undefined;
|
|
vars.lastListGroupTarget = undefined;
|
|
vars.lastContextTarget = undefined;
|
|
vars.multiSelectListItemArr = [];
|
|
}
|
|
|
|
//// 리스트 영역 가이드 화면 표시
|
|
const viewer = document.querySelector('.list-notice-viewer');
|
|
viewer.dataset.resourcePath = resourcePath;
|
|
|
|
let param = { resourcePath };
|
|
|
|
let res = await axios.get(`${vars.path_name}/isMainTitleImage`, {params: param});
|
|
if(res.data.result || !res.data.result == undefined) {
|
|
let mainTitleImg = res.data.result.path2;
|
|
if(mainTitleImg.includes('MAIN_TITLE_IMAGE___')) {
|
|
let generateImageUrlParams = {
|
|
resourcePath: `${vars.lastHeaderBtn.dataset.resourcePath}`
|
|
};
|
|
let imgUrlRes = await axios.get(`${vars.path_name}/generateImageUrl`, { params: generateImageUrlParams });
|
|
let imageUrl = imgUrlRes.data.result.url;
|
|
|
|
document.querySelector('.list-notice-viewer').src = imageUrl;
|
|
document.querySelector('.list-notice-viewer').style.display = 'flex';
|
|
document.querySelector('.list-notice-top').style.display = 'none';
|
|
document.querySelector('.list-notice-bottom').style.display = 'none';
|
|
} else {
|
|
document.querySelector('.list-notice-viewer').style.display = 'none';
|
|
document.querySelector('.list-notice-top').style.display = 'flex';
|
|
document.querySelector('.list-notice-bottom').style.display = 'flex';
|
|
}
|
|
}else {
|
|
document.querySelector('.list-notice-viewer').style.display = 'none';
|
|
document.querySelector('.list-notice-top').style.display = 'flex';
|
|
document.querySelector('.list-notice-bottom').style.display = 'flex';
|
|
}
|
|
|
|
// 컨트롤 박스 숨김
|
|
toggleControlBox(false);
|
|
|
|
// // 아카이브 우측 영역 표시
|
|
// let option = {
|
|
// from: '헤더 버튼 클릭'
|
|
// }
|
|
// toggleArchiveMainRight(true, option);
|
|
toggleArchiveMainRight(false);
|
|
}
|
|
|
|
function headerBtnContextmenuFunc(event) {
|
|
let scope = 'headerBtn';
|
|
|
|
toggleContextFocusBox(true, event.target, scope);
|
|
toggleContextmenu(true, event, scope);
|
|
}
|
|
|
|
export function processHeaderBtnOverflow() {
|
|
if (checkProjectInactive()) return;
|
|
|
|
let headerCenter = document.querySelector('body > .header .center');
|
|
|
|
let leftWrap = headerCenter.querySelector('.left.wrap');
|
|
let leftWrapWidth = leftWrap.offsetWidth;
|
|
|
|
let menuTab = leftWrap.querySelector('.menu-tab');
|
|
|
|
let menuAdd = leftWrap.querySelector('.menu-add');
|
|
let menuAddWidth = 0;
|
|
if (menuAdd) menuAddWidth = menuAdd.offsetWidth;
|
|
|
|
let menuTabMaxWidth = leftWrapWidth - menuAddWidth - 16 - 16 - 16; // .left.wrap => padding-left: 16px / gap: 16px / padding-right: 16px;
|
|
|
|
let btnsWidth = 0;
|
|
Object.values(menuTab.children).forEach(btn => {
|
|
btn.style.display = 'flex';
|
|
btnsWidth += (btn.offsetWidth + 4); // 버튼에 margin: 0 2px 설정되어 있어서 좌우 마진값 4 추가
|
|
})
|
|
|
|
let scrollLeft = document.querySelector('.scroll-left');
|
|
let scrollRight = document.querySelector('.scroll-right');
|
|
if (btnsWidth > menuTabMaxWidth) {
|
|
leftWrap.style.paddingRight = '80px';
|
|
scrollLeft.style.display = 'flex';
|
|
scrollRight.style.display = 'flex';
|
|
} else {
|
|
leftWrap.style.paddingRight = '16px';
|
|
scrollLeft.style.display = 'none';
|
|
scrollRight.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
export async function renderTree(parent, data, isSub) {
|
|
await loadSystemPolicy();
|
|
|
|
if (parent.matches('.tree-wrap')) {
|
|
document.querySelectorAll('.tree-wrap').forEach(treeWrap => {
|
|
treeWrap.innerHTML = '';
|
|
})
|
|
}
|
|
|
|
let entries = sortData(data);
|
|
|
|
if (entries.length != 0) {
|
|
if (!isSub) {
|
|
treeNotice.style.display = 'none';
|
|
treeWrap.style.display = 'flex';
|
|
}
|
|
|
|
let docFragment = document.createDocumentFragment();
|
|
|
|
let tree = document.createElement('div');
|
|
tree.classList.add('tree');
|
|
if (isSub) tree.classList.add('sub');
|
|
|
|
docFragment.appendChild(tree);
|
|
|
|
// entries.forEach(async entry => {
|
|
entries.forEach(async (entry, idx) => {
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
let childState;
|
|
if (value.filesCount == 0) {
|
|
childState = 'empty';
|
|
} else {
|
|
childState = 'filled';
|
|
}
|
|
|
|
let treeItemWrap = document.createElement('div');
|
|
treeItemWrap.classList.add('tree-item-wrap');
|
|
treeItemWrap.classList.add(`depth${value.depth}`);
|
|
treeItemWrap.classList.add(childState);
|
|
treeItemWrap.classList.add('folder');
|
|
treeItemWrap.dataset.resourcePath = value.resourcePath;
|
|
treeItemWrap.dataset.id = value.dataId;
|
|
treeItemWrap.dataset.size = value.size;
|
|
tree.appendChild(treeItemWrap);
|
|
if (FOLDER_KEEP_POLICY_ACTIVE && value.depth == 3 && value.filesCount < FOLDER_KEEP_FILE_THRESHOLD) {
|
|
if (value.folderType == null || value.folderType == undefined) {
|
|
treeItemWrap.classList.add('default-folder');
|
|
} else {
|
|
treeItemWrap.classList.add(`${value.folderType}-folder`);
|
|
}
|
|
|
|
let lastFolderActDate = new Date(value.lastFolderActDate);
|
|
|
|
let expiryDate = getExpiryDate(lastFolderActDate);
|
|
if (expiryDate.days < 1) {
|
|
treeItemWrap.classList.add('expired-warn');
|
|
|
|
// 타이머 동작 부분
|
|
let tick = () => {
|
|
let expiryDate = getExpiryDate(lastFolderActDate);
|
|
if (!expiryDate.formatted) {
|
|
if (treeItemWrap.countdownTimer) {
|
|
clearInterval(treeItemWrap.countdownTimer);
|
|
treeItemWrap.countdownTimer = null;
|
|
}
|
|
// 폴더 삭제
|
|
removeExpiredFolder(value);
|
|
return;
|
|
}
|
|
};
|
|
// 즉시 1회 갱신 후 1초 간격으로 갱신
|
|
tick();
|
|
treeItemWrap.countdownTimer = setInterval(tick, 1000);
|
|
}
|
|
}
|
|
|
|
|
|
let treeItem = document.createElement('div');
|
|
treeItem.classList.add('tree-item');
|
|
|
|
let nameWrap = document.createElement('div');
|
|
nameWrap.classList.add('name-wrap');
|
|
|
|
let image = document.createElement('div');
|
|
image.classList.add('image');
|
|
|
|
let textWrap = document.createElement('div');
|
|
textWrap.classList.add('text-wrap');
|
|
|
|
let nameText = document.createElement('div');
|
|
nameText.classList.add('name-text');
|
|
nameText.innerHTML = key;
|
|
|
|
textWrap.appendChild(nameText);
|
|
|
|
if (value.depth == 3) nameWrap.appendChild(image);
|
|
nameWrap.appendChild(textWrap);
|
|
|
|
treeItem.appendChild(nameWrap);
|
|
|
|
let addPermissionBadge = false, permissionEng = '', permissionKor = '';
|
|
if (value.permission == 0) {
|
|
addPermissionBadge = true;
|
|
permissionEng = 'sub-master';
|
|
permissionKor = '관리';
|
|
}
|
|
if (value.permission == 8) {
|
|
addPermissionBadge = true;
|
|
permissionEng = 'security-worker';
|
|
permissionKor = '보안';
|
|
}
|
|
if (value.permission == 4) {
|
|
addPermissionBadge = true;
|
|
permissionEng = 'worker';
|
|
permissionKor = '일반';
|
|
}
|
|
// if (value.permission == 2) {
|
|
// addPermissionBadge = true;
|
|
// permissionEng = 'uploader';
|
|
// permissionKor = '업로더';
|
|
// }
|
|
if (addPermissionBadge) {
|
|
let permissionBadge = document.createElement('div');
|
|
permissionBadge.classList.add(`user-permission-${permissionEng}`);
|
|
// permissionBadge.style.marginLeft = '8px';
|
|
|
|
let h6 = document.createElement('h6');
|
|
h6.innerText = permissionKor;
|
|
|
|
permissionBadge.appendChild(h6);
|
|
nameWrap.appendChild(permissionBadge);
|
|
}
|
|
|
|
// depth3일 때 파일 개수 표시
|
|
if (isSub) {
|
|
let fileCount = document.createElement('div');
|
|
fileCount.classList.add('file-count');
|
|
|
|
// 파일 개수가 3개 미만이면, lastFolderActDate랑 현재 날짜 비교해서 만료 기간 계산
|
|
// 만료 기간 지나면 폴더 삭제 및 하위 파일 휴지통으로 이동
|
|
if (FOLDER_KEEP_POLICY_ACTIVE && value.filesCount < FOLDER_KEEP_FILE_THRESHOLD) {
|
|
// 트리 생성할 때 이미 만료 기간이 지난 폴더는 바로 삭제
|
|
let lastFolderActDate = new Date(value.lastFolderActDate);
|
|
let expiryDate = getExpiryDate(lastFolderActDate);
|
|
if (!expiryDate.formatted) {
|
|
// 폴더 삭제
|
|
removeExpiredFolder(value);
|
|
return;
|
|
}
|
|
|
|
let countdown = document.createElement('div');
|
|
countdown.classList.add('countdown');
|
|
let countdownText = document.createElement('div');
|
|
countdownText.classList.add('text');
|
|
countdownText.innerHTML = `D-${expiryDate.days}`;
|
|
countdown.appendChild(countdownText);
|
|
treeItem.appendChild(countdown);
|
|
|
|
let countdownTooltip;
|
|
treeItem.addEventListener('pointerenter', () => {
|
|
// 마우스가 treeItem에 진입하면 기존 countdownTooltip 제거
|
|
removeCountdownTooltip();
|
|
|
|
// treeItem이랑 countdownTooltip이 세로 기준으로 가운데 정렬 되도록 위치 조정
|
|
let treeItemRect = treeItemWrap.getBoundingClientRect();
|
|
let rectTop = treeItemRect.top;
|
|
let rectLeft = treeItemRect.left;
|
|
let rectWidth = treeItemRect.width;
|
|
let rectHeight = treeItemRect.height;
|
|
|
|
let top = rectTop + (rectHeight/2);
|
|
let left = rectLeft + rectWidth - 8;
|
|
|
|
countdownTooltip = document.createElement('div');
|
|
countdownTooltip.classList.add('countdown-tooltip');
|
|
countdownTooltip.style.top = `${pxToRem(top)}rem`;
|
|
countdownTooltip.style.left = `${pxToRem(left)}rem`;
|
|
|
|
// countdownTooltip 내용 구성 (메시지, 기간 연장 버튼)
|
|
let countdownTooltipWrap = document.createElement('div');
|
|
countdownTooltipWrap.classList.add('wrap');
|
|
|
|
let message1 = document.createElement('pre');
|
|
message1.classList.add('message');
|
|
message1.innerHTML = `해당 폴더의 파일 개수가 <span class="bold color">${FOLDER_KEEP_FILE_THRESHOLD}개</span> 미만이므로,`;
|
|
|
|
let message2 = document.createElement('pre');
|
|
message2.classList.add('message');
|
|
message2.innerHTML = `만료 기간이 지나면 자동으로 삭제되고,`;
|
|
|
|
let message3 = document.createElement('pre');
|
|
message3.classList.add('message');
|
|
message3.innerHTML = `폴더 내 파일은 휴지통으로 이동됩니다.`;
|
|
|
|
let message4 = document.createElement('pre');
|
|
message4.classList.add('message', 'expiry-date');
|
|
let countdownSpan = document.createElement('span');
|
|
countdownSpan.classList.add('bold', 'color', 'countdown');
|
|
countdownSpan.textContent = 'dd일 hh시간 mm분 ss초'; // 초기 표시
|
|
message4.innerHTML = `</div><span class="bold">만료 기간</span>`;
|
|
message4.appendChild(countdownSpan);
|
|
|
|
let renewExpiryDateBtn = document.createElement('div');
|
|
renewExpiryDateBtn.classList.add('renew-expiry-date-btn');
|
|
renewExpiryDateBtn.dataset.resourcePath = value.resourcePath;
|
|
renewExpiryDateBtn.dataset.dataId = value.dataId;
|
|
let renewExpiryDateBtnText = document.createElement('div');
|
|
renewExpiryDateBtnText.classList.add('text');
|
|
renewExpiryDateBtnText.classList.add('ft-12');
|
|
renewExpiryDateBtnText.innerHTML = '만료 기간 갱신';
|
|
renewExpiryDateBtn.appendChild(renewExpiryDateBtnText);
|
|
|
|
countdownTooltipWrap.appendChild(message1);
|
|
countdownTooltipWrap.appendChild(message2);
|
|
countdownTooltipWrap.appendChild(message3);
|
|
countdownTooltipWrap.appendChild(message4);
|
|
countdownTooltipWrap.appendChild(renewExpiryDateBtn);
|
|
|
|
let devRenewExpiryDateBtn;
|
|
if (vars.permission.checkPermission('dev-menu')) {
|
|
devRenewExpiryDateBtn = document.createElement('div');
|
|
devRenewExpiryDateBtn.classList.add('dev-renew-expiry-date-btn');
|
|
devRenewExpiryDateBtn.dataset.resourcePath = value.resourcePath;
|
|
devRenewExpiryDateBtn.dataset.dataId = value.dataId;
|
|
let devRenewExpiryDateBtnText = document.createElement('div');
|
|
devRenewExpiryDateBtnText.classList.add('text');
|
|
devRenewExpiryDateBtnText.classList.add('ft-12');
|
|
devRenewExpiryDateBtnText.innerHTML = '(개발자) 만료 기간 변경';
|
|
devRenewExpiryDateBtn.appendChild(devRenewExpiryDateBtnText);
|
|
countdownTooltipWrap.appendChild(devRenewExpiryDateBtn);
|
|
}
|
|
|
|
countdownTooltip.appendChild(countdownTooltipWrap);
|
|
document.querySelector('body').appendChild(countdownTooltip);
|
|
|
|
// 타이머 동작 부분
|
|
let lastFolderActDate = new Date(value.lastFolderActDate);
|
|
let tick = () => {
|
|
let expiryDate = getExpiryDate(lastFolderActDate);
|
|
if (!expiryDate.formatted) {
|
|
// 만료됨: 0초로 고정하거나, 툴팁을 닫을 수도 있습니다.
|
|
countdownSpan.textContent = '00일 00시간 00분 00초';
|
|
if (vars.countdownTimer) {
|
|
clearInterval(vars.countdownTimer);
|
|
vars.countdownTimer = null;
|
|
}
|
|
// 폴더 삭제
|
|
removeExpiredFolder(value);
|
|
|
|
return;
|
|
}
|
|
countdownSpan.textContent = expiryDate.formatted;
|
|
};
|
|
|
|
// 즉시 1회 갱신 후 1초 간격으로 갱신
|
|
tick();
|
|
vars.countdownTimer = setInterval(tick, 1000);
|
|
|
|
// 편의성 - countdownTooltip이 화면 영역 아래로 들어가지 않도록 위치 조정
|
|
if (countdownTooltip.offsetHeight + rectTop > window.innerHeight) {
|
|
countdownTooltip.style.top = `${pxToRem(window.innerHeight - (countdownTooltip.offsetHeight/2) - 1)}rem`;
|
|
}
|
|
|
|
// 마우스가 countdownTooltip을 벗어나면 countdownTooltip 제거
|
|
countdownTooltip.addEventListener('pointerleave', () => {
|
|
removeCountdownTooltip();
|
|
})
|
|
|
|
// renewExpiryDateBtn 클릭 이벤트 추가
|
|
renewExpiryDateBtn.addEventListener('click', async (e) => {
|
|
let renewExpiryDateParams = {
|
|
userInfoString: vars.userInfoString,
|
|
storageType: vars.storageType,
|
|
resourcePath: value.resourcePath,
|
|
dataId: value.dataId,
|
|
depth3DataIdArr: [e.target.dataset.dataId],
|
|
}
|
|
let renewExpiryDateRes = await axios.post(`${vars.path_name}/renewExpiryDate`, { params: renewExpiryDateParams });
|
|
if (renewExpiryDateRes.data.message == 'renewExpiryDate_success') {
|
|
console.log('renewExpiryDate_success');
|
|
|
|
// renewExpiryDateBtn을 클릭하면 countdownTooltip dom 제거 및 툴팁타이머, 트리아이템타이머 제거
|
|
removeCountdownTooltip();
|
|
clearInterval(document.querySelector(`.tree-item-wrap[data-resource-path="${value.resourcePath}"]`).countdownTimer);
|
|
document.querySelector(`.tree-item-wrap[data-resource-path="${value.resourcePath}"]`).countdownTimer = null;
|
|
|
|
let toggleParams = {
|
|
text: `'${key}' 폴더의 만료 기간이 ${FOLDER_KEEP_DAYS_THRESHOLD}일로 갱신되었습니다.`,
|
|
// text: `'${key}' 폴더의 만료 기간이 ${FOLDER_KEEP_DAYS_THRESHOLD-1}일로 갱신되었습니다.`,
|
|
type: 'alertModal'
|
|
};
|
|
toggleModal(true, toggleParams);
|
|
}
|
|
})
|
|
|
|
// 개발자용 devRenewExpiryDateBtn 클릭 이벤트 추가
|
|
if (devRenewExpiryDateBtn) {
|
|
devRenewExpiryDateBtn.addEventListener('click', async (e) => {
|
|
let toggleParams = {
|
|
text: `<span>입력한 초(sec)만큼 만료 기간이 변경됩니다.</span><span>(입력 안하고 확인을 누르면 15일로 변경됩니다.)</span>`,
|
|
type: 'devRenewExpiryDate',
|
|
resourcePath: value.resourcePath,
|
|
dataId: value.dataId,
|
|
depth3DataIdArr: [e.target.dataset.dataId],
|
|
folderName: key
|
|
};
|
|
toggleModal(true, toggleParams);
|
|
})
|
|
}
|
|
})
|
|
|
|
treeItem.addEventListener('pointerleave', (e) => {
|
|
// 마우스가 treeItem을 벗어난 직후 마우스 위치 대상이 countdownTooltip인 경우 리턴
|
|
let next = e.relatedTarget;
|
|
if (next && next.closest('.countdown-tooltip')) return;
|
|
|
|
// 마우스가 treeItem을 벗어나면 countdownTooltip 제거
|
|
removeCountdownTooltip();
|
|
})
|
|
} else {
|
|
let fileCountText = document.createElement('div');
|
|
fileCountText.classList.add('file-count-text');
|
|
fileCountText.innerHTML = addCommasToNumber(value.filesCount);
|
|
fileCount.appendChild(fileCountText);
|
|
treeItem.appendChild(fileCount);
|
|
}
|
|
}
|
|
|
|
treeItemWrap.appendChild(treeItem);
|
|
|
|
if (value.depth == 2) renderTree(treeItemWrap, value.child.folder, true);
|
|
|
|
treeItem.addEventListener('click', async (e) => {
|
|
vars.lastSelectTarget = treeItem;
|
|
|
|
let parent = treeItem.parentElement;
|
|
|
|
if (value.depth == 2) {
|
|
// console.log('depth2');
|
|
|
|
// let targetTree = 'main';
|
|
// changeTreeItemStyle(treeItem, targetTree);
|
|
}
|
|
|
|
if (value.depth == 3) {
|
|
if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn);
|
|
|
|
listContainer.querySelector('.list-body .list-item-wrap').innerHTML = '';
|
|
listContainer.style.display = 'flex';
|
|
listNotice.style.display = 'none';
|
|
resetViewer();
|
|
|
|
let isMainTree = e.target.closest('.archive-main-left .tree-container');
|
|
// let isModalTree = e.target.closest('.archive-main-center .list-container');
|
|
let targetTree;
|
|
if (isMainTree) targetTree = 'main';
|
|
// if (isModalTree) targetTree = 'modal';
|
|
|
|
let selectedDepth3TreeItemWrap = document.querySelector(`.archive-${targetTree} .tree-item-wrap.depth3.selected`);
|
|
if (selectedDepth3TreeItemWrap) selectedDepth3TreeItemWrap.classList.remove('selected');
|
|
treeItemWrap.classList.add('selected');
|
|
|
|
// document.querySelector('.control-box .file-area-mode-btn.selected')?.classList.remove('selected');
|
|
// document.querySelector(`.control-box .file-area-mode-btn.${fileAreaMode}`).classList.add('selected');
|
|
|
|
// let targetTree = 'main';
|
|
// if (option.modalTree) {
|
|
// targetTree = 'modal';
|
|
// compareMovePath(resourcePath);
|
|
// }
|
|
changeTreeItemStyle(treeItemWrap, targetTree);
|
|
|
|
// let childFileData = getDataFromTreeObject(resourcePath, 'folder', vars.allTreeObject).data.child.file;
|
|
// console.log(childFileData);
|
|
// let listParent = document.querySelector('.main-center .list-container .list-wrap.list-body .list-item-wrap');
|
|
// renderList(listParent, childFileData);
|
|
// let pageRanderingOption = {
|
|
// isInit: isInit,
|
|
// // favorite: 즐겨찾기 한 경로
|
|
// }
|
|
|
|
// .list-wrap.list-header, .list-wrap.list-body에 dataset.resourcePath 설정
|
|
document.querySelector('.archive-main-center .list-container .list-wrap.list-header').dataset.resourcePath = value.resourcePath;
|
|
document.querySelector('.archive-main-center .list-container .list-wrap.list-body').dataset.resourcePath = value.resourcePath;
|
|
|
|
// 트리 폴더 버튼 클릭할 때 리스트 생성 순서 뒤집히지 않도록 list sort 초기화
|
|
resetSort();
|
|
|
|
// 기존 리스트 영역 화면이 위치 수정 모드인 경우 초기화
|
|
if (vars.mapMode == 'edit') {
|
|
vars.mapMode = 'normal';
|
|
document.querySelector('.map-container').classList.remove('edit-mode');
|
|
}
|
|
|
|
// 다중 선택 아이템 배열 초기화
|
|
vars.multiSelectListItemArr = [];
|
|
|
|
let pageRanderingOption = {
|
|
scope: 'list',
|
|
resourcePath: value.resourcePath
|
|
}
|
|
await preparePageRendering(pageRanderingOption);
|
|
|
|
// 파일 이동 모드(listViewerCover display flex)일 때 트리 depth3 메뉴 클릭하면 선택 폴더 경로 변경
|
|
let listViewerCover = document.querySelector('.list-viewer-cover');
|
|
if (listViewerCover.style.display == 'flex') {
|
|
listViewerCover.querySelector('.new-path .value').textContent = value.resourcePath;
|
|
} else {
|
|
//// vars에서 마지막 선택 아이템을 저장한 속성들 초기화 - renderTree
|
|
// 파일 이동 모드에서 초기화하면 이동 전, 후 경로를 가져올 수 없어서 파일 이동 모드 아닐 때에만 초기화
|
|
vars.lastListItem = undefined;
|
|
vars.lastListGroupTarget = undefined;
|
|
vars.lastContextTarget = undefined;
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
parent.appendChild(docFragment);
|
|
|
|
document.querySelector('.archive-main-left .tree-title').classList.add('selected');
|
|
} else {
|
|
if (!isSub) {
|
|
treeNotice.style.display = 'flex';
|
|
treeWrap.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
|
|
async function renderRecycleBin(parent, data) {
|
|
let listItemWrap = document.querySelector('.recycle-bin-modal .list-container .list-wrap.list-body .list-item-wrap');
|
|
listItemWrap.innerHTML = '';
|
|
listItemWrap.style.display = 'flex';
|
|
|
|
initFileAreaUI('list');
|
|
|
|
await createRecycleBinItems(parent, data);
|
|
}
|
|
|
|
function createRecycleBinItems(parent, data) {
|
|
let docFragment = document.createDocumentFragment();
|
|
let entries = sortData(data);
|
|
|
|
entries.forEach(async (entry, idx) => {
|
|
let key = entry[0].split('___[recycle-bin]___')[0];
|
|
let value = entry[1];
|
|
|
|
let ext = (value.ext).toLowerCase();
|
|
|
|
// 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
let resourcePathSplit = value.resourcePath.split('/');
|
|
if (resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
let resourcePath = resourcePathSplit.join('/');
|
|
let dataId = value.dataId;
|
|
|
|
let listItem = document.createElement('div');
|
|
listItem.classList.add('list-item');
|
|
listItem.classList.add(`depth${value.depth}`);
|
|
listItem.classList.add('file');
|
|
listItem.classList.add('main-list-item');
|
|
listItem.dataset.resourcePath = resourcePath;
|
|
listItem.dataset.id = dataId;
|
|
listItem.dataset.size = value.size;
|
|
listItem.dataset.mainFileName = `${dataId}_${resourcePath}`;
|
|
|
|
// 확장자가 유효한 경우 클래스에 ext 추가
|
|
if (isValidExt(ext)) listItem.classList.add(ext);
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
// 파일 타입
|
|
// let type = document.createElement('div');
|
|
// type.classList.add('type');
|
|
// type.classList.add('list-item-data');
|
|
// let typeImage = document.createElement('div');
|
|
// typeImage.classList.add('type-image');
|
|
// type.appendChild(typeImage);
|
|
|
|
// 파일명
|
|
let name = document.createElement('div');
|
|
name.classList.add('name');
|
|
name.classList.add('list-item-data');
|
|
|
|
let typeImage = document.createElement('div');
|
|
typeImage.classList.add('type-image');
|
|
name.appendChild(typeImage);
|
|
|
|
// let typeImage = document.createElement('div');
|
|
// typeImage.classList.add('type-image');
|
|
// name.appendChild(typeImage);
|
|
|
|
let nameText = document.createElement('div');
|
|
nameText.classList.add('name-text');
|
|
nameText.classList.add('text');
|
|
nameText.classList.add('ft-14');
|
|
nameText.innerHTML = key;
|
|
name.appendChild(nameText);
|
|
|
|
// 원래 위치
|
|
let originalLocation = document.createElement('div');
|
|
originalLocation.classList.add('original-location');
|
|
originalLocation.classList.add('list-item-data');
|
|
let originalLocationText = document.createElement('div');
|
|
originalLocationText.classList.add('text');
|
|
originalLocationText.classList.add('ft-12');
|
|
originalLocationText.innerHTML = splitTopPathAndTargetName(resourcePath).topPath;
|
|
originalLocation.appendChild(originalLocationText);
|
|
|
|
// 삭제자
|
|
let removeUser = document.createElement('div');
|
|
removeUser.classList.add('remove-user');
|
|
removeUser.classList.add('list-item-data');
|
|
let removeUserText = document.createElement('div');
|
|
removeUserText.classList.add('text');
|
|
removeUserText.classList.add('ft-12');
|
|
removeUserText.innerHTML = value.modUserNm;
|
|
removeUser.appendChild(removeUserText);
|
|
|
|
// 삭제일자
|
|
let removeDate = document.createElement('div');
|
|
removeDate.classList.add('remove-date');
|
|
removeDate.classList.add('list-item-data');
|
|
let removeDateText = document.createElement('div');
|
|
removeDateText.classList.add('text');
|
|
removeDateText.classList.add('ft-12');
|
|
removeDateText.innerHTML = '-';
|
|
if (value.modDate) {
|
|
let date = new Date(value.modDate);
|
|
removeDateText.innerHTML = `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}`;
|
|
}
|
|
removeDate.appendChild(removeDateText);
|
|
|
|
// 작성자
|
|
let author = document.createElement('div');
|
|
author.classList.add('author');
|
|
author.classList.add('list-item-data');
|
|
let authorText = document.createElement('div');
|
|
authorText.classList.add('text');
|
|
authorText.classList.add('ft-12');
|
|
authorText.innerHTML = (value.authorNm) ? value.authorNm : value.userNm;
|
|
author.appendChild(authorText);
|
|
|
|
// 등록자
|
|
let uploader = document.createElement('div');
|
|
uploader.classList.add('uploader');
|
|
uploader.classList.add('list-item-data');
|
|
let uploaderText = document.createElement('div');
|
|
uploaderText.classList.add('text');
|
|
uploaderText.classList.add('ft-12');
|
|
uploaderText.innerHTML = '-';
|
|
if (value.userNm) uploaderText.innerHTML = value.userNm;
|
|
uploader.appendChild(uploaderText);
|
|
|
|
// 등록일자
|
|
let createDate = document.createElement('div');
|
|
createDate.classList.add('create-date');
|
|
createDate.classList.add('list-item-data');
|
|
let createDateText = document.createElement('div');
|
|
createDateText.classList.add('text');
|
|
createDateText.classList.add('ft-12');
|
|
createDateText.innerHTML = '-';
|
|
if (value.createDate) {
|
|
let date = new Date(value.createDate);
|
|
createDateText.innerHTML = `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}`;
|
|
}
|
|
createDate.appendChild(createDateText);
|
|
|
|
// 용량
|
|
let size = document.createElement('div');
|
|
size.classList.add('size');
|
|
size.classList.add('list-item-data');
|
|
let sizeText = document.createElement('div');
|
|
sizeText.classList.add('text');
|
|
sizeText.classList.add('ft-12');
|
|
sizeText.innerHTML = formatBytes(value.size);
|
|
if (value.size >= 1073741824) sizeText.classList.add('large');
|
|
size.appendChild(sizeText);
|
|
|
|
// 상태
|
|
let state = document.createElement('div');
|
|
state.classList.add('state');
|
|
state.classList.add('list-item-data');
|
|
let stateText = document.createElement('div');
|
|
stateText.classList.add('state-text');
|
|
stateText.classList.add('ft-12');
|
|
if (value.isSupported) {
|
|
// 지원
|
|
if (value.needConvert && !value.isConverted) {
|
|
state.classList.add('convert');
|
|
stateText.innerHTML = '변환필요';
|
|
} else {
|
|
// 열람가능 - 변환 된 파일 또는 변환 필요 없는 파일일 때
|
|
state.classList.add('viewable');
|
|
stateText.innerHTML = '열람가능';
|
|
}
|
|
} else {
|
|
// 미지원
|
|
state.classList.add('unsupport');
|
|
stateText.innerHTML = '미지원';
|
|
}
|
|
state.appendChild(stateText);
|
|
|
|
// wrap.appendChild(type);
|
|
wrap.appendChild(name);
|
|
wrap.appendChild(originalLocation);
|
|
wrap.appendChild(removeUser);
|
|
wrap.appendChild(removeDate);
|
|
wrap.appendChild(author);
|
|
wrap.appendChild(uploader);
|
|
wrap.appendChild(createDate);
|
|
wrap.appendChild(size);
|
|
wrap.appendChild(state);
|
|
|
|
listItem.appendChild(wrap);
|
|
|
|
docFragment.appendChild(listItem);
|
|
|
|
wrap.addEventListener('dblclick', async (e) => {
|
|
openNewWindowViewer();
|
|
})
|
|
|
|
wrap.addEventListener('click', async (e) => {
|
|
let isCtrl = e.ctrlKey;
|
|
let isShift = e.shiftKey;
|
|
let target = e.target;
|
|
|
|
// 좌클릭이 아닌 경우 무시
|
|
if (e.button !== 0) return;
|
|
|
|
let clickedListItem = target.closest('.list-item');
|
|
if (!clickedListItem) return;
|
|
|
|
let allItems = [...document.querySelectorAll('.recycle-bin-modal .list-container .list-body .list-item')];
|
|
let clickedIndex = allItems.indexOf(clickedListItem);
|
|
|
|
let isClickFinished = false;
|
|
|
|
// --- Ctrl + Shift ---
|
|
if (!isClickFinished && isCtrl && isShift) {
|
|
isClickFinished = true;
|
|
|
|
let anchorIndex = allItems.indexOf(vars.lastListItem_bin);
|
|
if (anchorIndex == -1) anchorIndex = 0;
|
|
|
|
let [start, end] = [anchorIndex, clickedIndex].sort((a, b) => a - b);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
let item = allItems[i];
|
|
if (!vars.multiSelectListItemArr_bin.includes(item)) {
|
|
vars.multiSelectListItemArr_bin.push(item);
|
|
}
|
|
item.classList.add('selected');
|
|
item.classList.add('group-style');
|
|
}
|
|
|
|
isClickFinished = true;
|
|
}
|
|
|
|
// --- Shift only ---
|
|
if (!isClickFinished && isShift) {
|
|
let anchorIndex = allItems.indexOf(vars.lastListItem_bin);
|
|
if (anchorIndex == -1) anchorIndex = 0;
|
|
|
|
// 기존 선택 해제
|
|
vars.multiSelectListItemArr_bin = [];
|
|
allItems.forEach(item => {
|
|
item.classList.remove('selected');
|
|
});
|
|
|
|
let [start, end] = [anchorIndex, clickedIndex].sort((a, b) => a - b);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
let item = allItems[i];
|
|
vars.multiSelectListItemArr_bin.push(item);
|
|
item.classList.add('selected');
|
|
}
|
|
|
|
//// shift 다중 선택 후 delete키로 휴지통으로 이동
|
|
vars.lastSelectTarget_bin = vars.multiSelectListItemArr_bin[0];
|
|
|
|
isClickFinished = true;
|
|
}
|
|
|
|
// --- Ctrl only ---
|
|
if (!isClickFinished && isCtrl) {
|
|
if (vars.multiSelectListItemArr_bin.includes(clickedListItem)) {
|
|
vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== clickedListItem);
|
|
clickedListItem.classList.remove('selected');
|
|
} else {
|
|
vars.multiSelectListItemArr_bin.push(clickedListItem);
|
|
clickedListItem.classList.add('selected');
|
|
}
|
|
|
|
vars.lastListItem_bin = clickedListItem;
|
|
|
|
isClickFinished = true;
|
|
}
|
|
|
|
if (!isClickFinished) {
|
|
// --- 일반 클릭 (기존 선택 제거 후 단일 선택 + 동작 실행) ---
|
|
vars.multiSelectListItemArr_bin = [];
|
|
document.querySelectorAll('.recycle-bin-modal .list-container .list-body .list-item').forEach((elem) => {
|
|
elem.classList.remove('selected');
|
|
});
|
|
|
|
clickedListItem.classList.add('selected');
|
|
|
|
vars.lastListItem_bin = clickedListItem;
|
|
|
|
if (!isCtrl && !isShift) {
|
|
vars.lastSelectTarget_bin = clickedListItem;
|
|
}
|
|
|
|
changeListItemStyle(clickedListItem, '리스트 아이템 클릭');
|
|
}
|
|
|
|
await syncGroupStyle();
|
|
});
|
|
})
|
|
|
|
parent.appendChild(docFragment);
|
|
}
|
|
|
|
export async function renderList(parent, data, isSub, subCategory) {
|
|
// 폴더타입 지정
|
|
// fileAreaMode = document.querySelector('.control-box .file-area-mode-btn.selected').id;
|
|
let fileAreaMode = vars.lastFileAreaMode;
|
|
|
|
// 컨트롤 박스 표시
|
|
toggleControlBox(true, fileAreaMode);
|
|
|
|
listContainer.classList.remove('list');
|
|
listContainer.classList.add(fileAreaMode);
|
|
|
|
let listItemWrap = listContainer.querySelector('.list-body .list-item-wrap');
|
|
listItemWrap.innerHTML = '';
|
|
|
|
// 파일 영역 모드에 맞게 파일 영역 UI 초기화
|
|
initFileAreaUI(fileAreaMode);
|
|
|
|
// 폴더타입에 따라 아이템 생성
|
|
if (fileAreaMode == 'list') {
|
|
if (!isSub) {
|
|
// 아카이브 우측 영역 표시
|
|
let option = {
|
|
from: '리스트 렌더링'
|
|
}
|
|
await toggleArchiveMainRight(true, option);
|
|
}
|
|
|
|
await createListItems(parent, data, isSub, subCategory);
|
|
}
|
|
|
|
if (fileAreaMode == 'grid') {
|
|
// 아카이브 우측 영역 숨김
|
|
let option = {
|
|
from: '그리드 렌더링'
|
|
}
|
|
await toggleArchiveMainRight(false, option);
|
|
|
|
// vars.listOrder 배열 초기화 후 createListOrder 함수에서 vars.listOrder 배열에 리스트 기준 순서로 파일명, 데이터id 담긴 객체 추가
|
|
vars.listOrder = [];
|
|
await createListOrder(data, isSub, subCategory);
|
|
|
|
// newData에 depth4, depth5 데이터 추가해서 레벨 평탄화
|
|
let entries = Object.entries(data);
|
|
let newData = {};
|
|
entries.forEach(async (entry) => {
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
newData[key] = value;
|
|
subCategoryList.forEach(async (ele) => {
|
|
let sub = getDataFromTreeObject(`${value.resourcePath}_${ele}`, 'folder', vars.allTreeObject).data;
|
|
if (sub) {
|
|
let subEntries = Object.entries(sub.child.file);
|
|
subEntries.forEach(async (subEntry) => {
|
|
let subKey = subEntry[0];
|
|
let subValue = subEntry[1];
|
|
subValue.name = `${key}___${subKey}`;
|
|
newData[`${key}___${subKey}`] = subValue;
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
// dataId 기준으로 newData 정렬한 map 생성
|
|
let newDataById = new Map(
|
|
Object.values(newData).map(item => [item.dataId, item])
|
|
);
|
|
|
|
|
|
// vars.listOrder 순서대로 dataId 사용해서 map에서 데이터 꺼내서 orderedData에 추가
|
|
let orderedData = {};
|
|
for (const item of vars.listOrder) {
|
|
const data = newDataById.get(item.dataId);
|
|
if (data) orderedData[data.name] = data;
|
|
}
|
|
|
|
// 평탄화, 리스트 기준 정렬 된 orderedData로 그리드 화면 생성
|
|
await createGridItems(parent, orderedData);
|
|
}
|
|
|
|
if (fileAreaMode == 'map') {
|
|
await createMapItems(data);
|
|
}
|
|
|
|
if (vars.multiSelectListItemArr.length > 0) {
|
|
let newMultiSelectListArr = [];
|
|
|
|
vars.multiSelectListItemArr.forEach(item => {
|
|
let dataId = item.dataset.id;
|
|
let newItem = listContainer.querySelector(`.list-item[data-id="${dataId}"]`);
|
|
if (newItem) {
|
|
newItem.classList.add('selected');
|
|
newItem.classList.add('group-style');
|
|
newMultiSelectListArr.push(newItem);
|
|
}
|
|
});
|
|
|
|
vars.multiSelectListItemArr = newMultiSelectListArr;
|
|
|
|
if (vars.lastListItem) await changeListItemStyle(vars.lastListItem, 'renderList 끝부분');
|
|
await syncGroupStyle();
|
|
}
|
|
}
|
|
|
|
function createListItems(parent, data, isSub, subCategory) {
|
|
let docFragment = document.createDocumentFragment();
|
|
let entries = sortData(data);
|
|
|
|
entries.forEach(async (entry, idx) => {
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
let ext = (value.ext).toLowerCase();
|
|
|
|
// 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
let resourcePathSplit = value.resourcePath.split('/');
|
|
if (resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
let resourcePath = resourcePathSplit.join('/');
|
|
let dataId = value.dataId;
|
|
|
|
let listItem = document.createElement('div');
|
|
listItem.classList.add('list-item');
|
|
listItem.classList.add(`depth${value.depth}`);
|
|
listItem.classList.add('file');
|
|
listItem.dataset.resourcePath = resourcePath;
|
|
listItem.dataset.id = dataId;
|
|
listItem.dataset.size = value.size;
|
|
listItem.dataset.mainFileName = value.mainFileName;
|
|
|
|
// 확장자가 유효한 경우 클래스에 ext 추가
|
|
if (isValidExt(ext)) listItem.classList.add(ext);
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
// 파일 타입
|
|
// let type = document.createElement('div');
|
|
// type.classList.add('type');
|
|
// type.classList.add('list-item-data');
|
|
// let typeImage = document.createElement('div');
|
|
// typeImage.classList.add('type-image');
|
|
// type.appendChild(typeImage);
|
|
|
|
// 파일명
|
|
let name = document.createElement('div');
|
|
name.classList.add('name');
|
|
name.classList.add('list-item-data');
|
|
|
|
if (isSub) {
|
|
listItem.classList.add('sub-list-item');
|
|
|
|
let subSign = document.createElement('div');
|
|
subSign.classList.add('sub-sign');
|
|
|
|
let symbol = document.createElement('div');
|
|
symbol.classList.add('symbol');
|
|
symbol.innerText = '└';
|
|
|
|
let subSignText = document.createElement('div');
|
|
subSignText.classList.add('text');
|
|
|
|
subSign.classList.add(subCategory);
|
|
subSignText.innerText = subCategoryData[subCategory].korean;
|
|
|
|
subSign.appendChild(symbol);
|
|
subSign.appendChild(subSignText);
|
|
name.appendChild(subSign);
|
|
} else {
|
|
listItem.classList.add('main-list-item');
|
|
|
|
let typeImage = document.createElement('div');
|
|
typeImage.classList.add('type-image');
|
|
name.appendChild(typeImage);
|
|
}
|
|
|
|
// let typeImage = document.createElement('div');
|
|
// typeImage.classList.add('type-image');
|
|
// name.appendChild(typeImage);
|
|
|
|
let nameText = document.createElement('div');
|
|
nameText.classList.add('name-text');
|
|
nameText.classList.add('text');
|
|
nameText.classList.add('ft-14');
|
|
nameText.innerHTML = key;
|
|
name.appendChild(nameText);
|
|
|
|
// 권한설정 - 파일에 권한설정?
|
|
// let addPermissionBadge = false, permissionEng = '', permissionKor = '';
|
|
// if (value.permission == 0) {
|
|
// nameText.classList.add('sub-master');
|
|
// addPermissionBadge = true;
|
|
// permissionEng = 'sub-master';
|
|
// permissionKor = '부관리자';
|
|
// }
|
|
// if (value.permission == 8) {
|
|
// nameText.classList.add('worker');
|
|
// addPermissionBadge = true;
|
|
// permissionEng = 'worker';
|
|
// permissionKor = '참여자';
|
|
// }
|
|
// if (addPermissionBadge) {
|
|
// let permissionBadge = document.createElement('div');
|
|
// permissionBadge.classList.add(`user-permission-${permissionEng}`);
|
|
// permissionBadge.style.marginLeft = '8px';
|
|
|
|
// // let image = document.createElement('img');
|
|
// // image.src = `/main/img/permission/${permissionEng}.svg`;
|
|
// // image.alt = permissionKor;
|
|
|
|
// let h6 = document.createElement('h6');
|
|
// h6.innerText = permissionKor;
|
|
|
|
// // permissionBadge.appendChild(image);
|
|
// permissionBadge.appendChild(h6);
|
|
// }
|
|
// name.appendChild(permissionBadge);
|
|
|
|
// 작성자
|
|
let author = document.createElement('div');
|
|
author.classList.add('author');
|
|
author.classList.add('list-item-data');
|
|
let authorText = document.createElement('div');
|
|
authorText.classList.add('text');
|
|
authorText.classList.add('ft-12');
|
|
authorText.innerHTML = (value.authorNm) ? value.authorNm : value.userNm;
|
|
author.appendChild(authorText);
|
|
|
|
// 등록자
|
|
let uploader = document.createElement('div');
|
|
uploader.classList.add('uploader');
|
|
uploader.classList.add('list-item-data');
|
|
let uploaderText = document.createElement('div');
|
|
uploaderText.classList.add('text');
|
|
uploaderText.classList.add('ft-12');
|
|
uploaderText.innerHTML = '-';
|
|
if (value.userNm) uploaderText.innerHTML = value.userNm;
|
|
uploader.appendChild(uploaderText);
|
|
|
|
// 등록일자
|
|
let createDate = document.createElement('div');
|
|
createDate.classList.add('create-date');
|
|
createDate.classList.add('list-item-data');
|
|
let createDateText = document.createElement('div');
|
|
createDateText.classList.add('text');
|
|
createDateText.classList.add('ft-12');
|
|
createDateText.innerHTML = '-';
|
|
if (value.createDate) {
|
|
let date = new Date(value.createDate);
|
|
createDateText.innerHTML = `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}`;
|
|
}
|
|
createDate.appendChild(createDateText);
|
|
|
|
// 용량
|
|
let size = document.createElement('div');
|
|
size.classList.add('size');
|
|
size.classList.add('list-item-data');
|
|
let sizeText = document.createElement('div');
|
|
sizeText.classList.add('text');
|
|
sizeText.classList.add('ft-12');
|
|
sizeText.innerHTML = formatBytes(value.size);
|
|
if (value.size >= 1073741824) sizeText.classList.add('large');
|
|
size.appendChild(sizeText);
|
|
|
|
// 상태
|
|
let state = document.createElement('div');
|
|
state.classList.add('state');
|
|
state.classList.add('list-item-data');
|
|
let stateText = document.createElement('div');
|
|
stateText.classList.add('state-text');
|
|
stateText.classList.add('ft-12');
|
|
let convertBtn = document.createElement('div');
|
|
convertBtn.classList.add('convert-btn');
|
|
let convertBtnImage = document.createElement('div');
|
|
let convertBtnText = document.createElement('div');
|
|
convertBtnImage.classList.add('convert-btn-image');
|
|
convertBtnText.classList.add('convert-btn-text');
|
|
convertBtnText.classList.add('ft-12');
|
|
convertBtn.appendChild(convertBtnImage);
|
|
convertBtn.appendChild(convertBtnText);
|
|
let addBtn = false;
|
|
if (value.isSupported) {
|
|
// 지원
|
|
if (vars.convertingDataArr.some(data => Number(data.dataId) === Number(dataId))) {
|
|
// 변환중
|
|
addBtn = true;
|
|
state.classList.add('working');
|
|
convertBtnText.innerHTML = '변환중';
|
|
} else {
|
|
if (value.needConvert && !value.isConverted) {
|
|
// ** 권한 관련
|
|
// 변환필요 - 변환 필요한 파일이면서 변환이 안 된 상태일 때
|
|
let permission = JSON.parse(vars.userInfoString).permission;
|
|
if (permission == 1) {
|
|
state.classList.add('convert');
|
|
stateText.innerHTML = '변환필요';
|
|
} else {
|
|
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);
|
|
|
|
// 메모유무 아이콘
|
|
let isMemo = document.createElement('div');
|
|
isMemo.classList.add('memo');
|
|
isMemo.classList.add('list-item-data');
|
|
let memoIconWrap = document.createElement('div');
|
|
memoIconWrap.classList.add('memo-item-wrap');
|
|
let memoIcon = document.createElement('img');
|
|
memoIcon.classList.add('memo-icon');
|
|
memoIcon.src = '/main/img/archive/icon__list-memo.svg';
|
|
if (!(value.memo == '' || value.memo == undefined || value.memo == null || value.memo == true)) memoIconWrap.appendChild(memoIcon);
|
|
isMemo.appendChild(memoIconWrap);
|
|
|
|
// 위치 텍스트 저장
|
|
let gpsData = document.createElement('div');
|
|
gpsData.classList.add('gps-data');
|
|
gpsData.style.display = 'none';
|
|
let gpsDataText = document.createElement('div');
|
|
gpsDataText.classList.add('text');
|
|
gpsDataText.innerHTML = `경도: ${(value.lon) ? value.lon : '없음'} / 위도: ${(value.lat) ? value.lat : '없음'}`;
|
|
gpsDataText.style.display = 'none';
|
|
gpsData.appendChild(gpsDataText);
|
|
|
|
// wrap.appendChild(type);
|
|
wrap.appendChild(name);
|
|
wrap.appendChild(author);
|
|
wrap.appendChild(uploader);
|
|
wrap.appendChild(createDate);
|
|
wrap.appendChild(size);
|
|
wrap.appendChild(state);
|
|
wrap.appendChild(isMemo);
|
|
wrap.appendChild(gpsData);
|
|
|
|
listItem.appendChild(wrap);
|
|
|
|
// 추가 파일이 있는 경우 추가 파일 생성
|
|
subCategoryList.forEach(async (ele) => {
|
|
let sub = getDataFromTreeObject(`${resourcePath}_${ele}`, 'folder', vars.allTreeObject).data;
|
|
if (sub) {
|
|
let subListItemWrap = document.createElement('div');
|
|
subListItemWrap.classList.add('list-item-wrap');
|
|
subListItemWrap.classList.add('sub-list-item-wrap');
|
|
listItem.appendChild(subListItemWrap);
|
|
renderList(subListItemWrap, sub.child.file, true, ele);
|
|
}
|
|
})
|
|
|
|
docFragment.appendChild(listItem);
|
|
|
|
if (vars.lastListItem?.dataset.resourcePath == resourcePath) {
|
|
// const memo = getDataFromTreeObject(value.resourcePath, 'file', vars.allTreeObject).data;
|
|
let res = await axios.get(`${vars.path_name}/getMemoInfo`,
|
|
{ params: { userInfoString: vars.userInfoString, resourcePath: resourcePath, dataId: dataId } }
|
|
);
|
|
let memo = res.data.result.memo;
|
|
|
|
setTimeout(() => {
|
|
renderMemo (memo, dataId);
|
|
}, 100);
|
|
}
|
|
|
|
wrap.addEventListener('dblclick', async (e) => {
|
|
openNewWindowViewer();
|
|
})
|
|
|
|
wrap.addEventListener('click', async (e) => {
|
|
let isCtrl = e.ctrlKey;
|
|
let isShift = e.shiftKey;
|
|
let target = e.target;
|
|
|
|
// 좌클릭이 아닌 경우 무시
|
|
if (e.button !== 0) return;
|
|
|
|
let clickedListItem = target.closest('.list-item');
|
|
if (!clickedListItem) return;
|
|
|
|
if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn);
|
|
if (vars.lastMainTreeItem) targetFocus(vars.lastMainTreeItem);
|
|
|
|
let allItems = [...document.querySelectorAll('.archive-main .list-container .list-body .list-item')];
|
|
let clickedIndex = allItems.indexOf(clickedListItem);
|
|
|
|
let isClickFinished = false;
|
|
|
|
// --- Ctrl + Shift ---
|
|
if (!isClickFinished && isCtrl && isShift) {
|
|
isClickFinished = true;
|
|
|
|
let anchorIndex = allItems.indexOf(vars.lastListItem);
|
|
if (anchorIndex == -1) anchorIndex = 0;
|
|
|
|
let [start, end] = [anchorIndex, clickedIndex].sort((a, b) => a - b);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
let item = allItems[i];
|
|
if (!vars.multiSelectListItemArr.includes(item)) {
|
|
vars.multiSelectListItemArr.push(item);
|
|
}
|
|
item.classList.add('selected');
|
|
item.classList.add('group-style');
|
|
}
|
|
|
|
isClickFinished = true;
|
|
|
|
resetViewer();
|
|
}
|
|
|
|
// --- Shift only ---
|
|
if (!isClickFinished && isShift) {
|
|
let anchorIndex = allItems.indexOf(vars.lastListItem);
|
|
if (anchorIndex == -1) anchorIndex = 0;
|
|
|
|
// 기존 선택 해제
|
|
vars.multiSelectListItemArr = [];
|
|
allItems.forEach(item => {
|
|
item.classList.remove('selected');
|
|
});
|
|
|
|
let [start, end] = [anchorIndex, clickedIndex].sort((a, b) => a - b);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
let item = allItems[i];
|
|
vars.multiSelectListItemArr.push(item);
|
|
item.classList.add('selected');
|
|
}
|
|
|
|
//// shift 다중 선택 후 delete키로 휴지통으로 이동
|
|
vars.lastSelectTarget = vars.multiSelectListItemArr[0];
|
|
|
|
isClickFinished = true;
|
|
|
|
resetViewer();
|
|
}
|
|
|
|
// --- Ctrl only ---
|
|
if (!isClickFinished && isCtrl) {
|
|
if (vars.multiSelectListItemArr.includes(clickedListItem)) {
|
|
vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== clickedListItem);
|
|
clickedListItem.classList.remove('selected');
|
|
} else {
|
|
vars.multiSelectListItemArr.push(clickedListItem);
|
|
clickedListItem.classList.add('selected');
|
|
}
|
|
|
|
vars.lastListItem = clickedListItem;
|
|
|
|
isClickFinished = true;
|
|
|
|
resetViewer();
|
|
}
|
|
|
|
if (!isClickFinished) {
|
|
// --- 일반 클릭 (기존 선택 제거 후 단일 선택 + 동작 실행) ---
|
|
vars.multiSelectListItemArr = [];
|
|
document.querySelectorAll('.archive-main .list-container .list-body .list-item').forEach((elem) => {
|
|
elem.classList.remove('selected');
|
|
});
|
|
|
|
clickedListItem.classList.add('selected');
|
|
|
|
vars.lastListItem = clickedListItem;
|
|
|
|
if (!isCtrl && !isShift) {
|
|
vars.lastSelectTarget = clickedListItem;
|
|
|
|
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.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.add('open');
|
|
// document.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.remove('close');
|
|
|
|
vars.viewerConnectingTime = vars.viewerConnectingTimeValue;
|
|
if (value.isSupported == false || (value.needConvert == true && value.isConverted == false)) {
|
|
// 미지원 파일인 경우 -> value.isSupported == false
|
|
// 변환 필요 파일인 경우 -> value.needConvert == true && value.isConverted == false (변환해야되지만 아직 변환 안한 상태)
|
|
// 뷰어 프로그레스 표시 시간 0으로 설정
|
|
vars.viewerConnectingTime = 0;
|
|
}
|
|
|
|
renderViewer(resourcePath, dataId);
|
|
|
|
if (target === convertBtn && state.classList.contains('convert')) {
|
|
convertPdf(resourcePath, dataId);
|
|
}
|
|
} else {
|
|
|
|
}
|
|
|
|
changeListItemStyle(clickedListItem, '리스트 아이템 클릭');
|
|
}
|
|
|
|
await syncGroupStyle();
|
|
});
|
|
})
|
|
|
|
parent.appendChild(docFragment);
|
|
|
|
if (vars.lastSelectTarget && vars.lastSelectTarget.classList.contains('list-item')) {
|
|
let dataId = vars.lastSelectTarget.dataset.id;
|
|
let item = document.querySelector(`.list-item[data-id="${dataId}"]`);
|
|
if (item) {
|
|
changeListItemStyle(item, '리스트 아이템 생성');
|
|
targetFocus(item, 'instant');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function createListOrder(data, isSub, subCategory) {
|
|
let entries = sortData(data);
|
|
|
|
entries.forEach(async (entry, idx) => {
|
|
let obj = {};
|
|
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
let dataId = value.dataId;
|
|
|
|
// 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
let resourcePathSplit = value.resourcePath.split('/');
|
|
if (resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
let resourcePath = resourcePathSplit.join('/');
|
|
|
|
obj.name = key;
|
|
if (isSub) obj.name = `${value.mainFileName}___${key}`;
|
|
|
|
obj.dataId = dataId;
|
|
|
|
// obj.author = (value.authorNm) ? value.authorNm : value.userNm;;
|
|
|
|
// obj.uploader = (value.userNm) ? value.userNm : '-';
|
|
|
|
// let date = new Date(value.createDate);
|
|
// obj.createDate = (value.createDate) ? `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}` : '-';
|
|
|
|
// obj.size = formatBytes(value.size);
|
|
|
|
// obj.isSub = isSub;
|
|
|
|
// obj.subCategory = subCategory;
|
|
|
|
vars.listOrder.push(obj);
|
|
|
|
subCategoryList.forEach(async (ele) => {
|
|
let sub = getDataFromTreeObject(`${resourcePath}_${ele}`, 'folder', vars.allTreeObject).data;
|
|
if (sub) {
|
|
await createListOrder(sub.child.file, true, ele);
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
async function createGridItems(parent, data) {
|
|
vars.curSortCol = document.querySelector('input[type="radio"][name="category"]:checked').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 docFragment = document.createDocumentFragment();
|
|
let entries = Object.entries(data);
|
|
|
|
// let imageExtArr = ['jpg', 'jpeg', 'png'];
|
|
let videoExtArr = ['mp4', 'mov', 'webm'];
|
|
|
|
let currentTreeItem = vars.lastMainTreeItem;
|
|
|
|
for (let i = 0; i < entries.length; i++) {
|
|
let entry = entries[i];
|
|
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
let ext = (value.ext).toLowerCase();
|
|
|
|
// 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
let resourcePathSplit = value.resourcePath.split('/');
|
|
if (resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
let resourcePath = resourcePathSplit.join('/');
|
|
|
|
let dataId = value.dataId;
|
|
|
|
let gridItem = document.createElement('div');
|
|
gridItem.classList.add('list-item');
|
|
gridItem.classList.add('grid-item');
|
|
gridItem.classList.add(`depth${value.depth}`);
|
|
gridItem.classList.add('file');
|
|
gridItem.dataset.resourcePath = resourcePath;
|
|
gridItem.dataset.id = dataId;
|
|
gridItem.dataset.size = value.size;
|
|
gridItem.dataset.mainFileName = value.mainFileName;
|
|
// 확장자가 유효한 경우 클래스에 ext 추가
|
|
if (isValidExt(ext)) gridItem.classList.add(ext);
|
|
// 마지막으로 선택한 리스트 아이템에 그룹상태 클래스 추가
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
// 동영상 표시 아이콘
|
|
let play = document.createElement('div');
|
|
play.classList.add('play');
|
|
let playImage = document.createElement('div');
|
|
playImage.classList.add('image');
|
|
play.appendChild(playImage);
|
|
|
|
// 그리드 아이템 선택됐을 때 강조표시용 div
|
|
let highlight = document.createElement('div');
|
|
highlight.classList.add('highlight');
|
|
|
|
// 추가 파일일 때(value.depth == 5) 추가 파일 종류 뱃지 추가
|
|
if (value.depth == 5) {
|
|
gridItem.classList.add('sub-list-item');
|
|
gridItem.classList.add(value.subCategory);
|
|
|
|
let subSign = document.createElement('div');
|
|
subSign.classList.add('sub-sign');
|
|
|
|
let subSignText = document.createElement('div');
|
|
subSignText.classList.add('text');
|
|
subSignText.classList.add('ft-14');
|
|
|
|
subSign.classList.add(value.subCategory);
|
|
subSignText.innerText = subCategoryData[value.subCategory].korean;
|
|
|
|
subSign.appendChild(subSignText);
|
|
wrap.appendChild(subSign);
|
|
} else {
|
|
gridItem.classList.add('main-list-item');
|
|
}
|
|
|
|
// 위치 유무 아이콘 및 위치 텍스트 저장
|
|
let gpsData = document.createElement('div');
|
|
gpsData.classList.add('gps-data');
|
|
gpsData.classList.add('text-wrap');
|
|
let gpsDataWrap = document.createElement('div');
|
|
gpsDataWrap.classList.add('gps-data-wrap');
|
|
let gpsDataTooltip = document.createElement('div');
|
|
gpsDataTooltip.classList.add('tooltip');
|
|
gpsDataTooltip.classList.add('ft-14');
|
|
if (value.lon && value.lat) {
|
|
gpsData.classList.add('gps-data-yes');
|
|
gpsDataTooltip.innerHTML = '위치있음';
|
|
} else {
|
|
gpsData.classList.add('gps-data-no');
|
|
gpsDataTooltip.innerHTML = '위치없음';
|
|
}
|
|
let gpsDataImage = document.createElement('div');
|
|
gpsDataImage.classList.add('image');
|
|
let gpsDataText = document.createElement('div');
|
|
gpsDataText.classList.add('text');
|
|
gpsDataText.innerHTML = `경도: ${(value.lon) ? value.lon : '없음'} / 위도: ${(value.lat) ? value.lat : '없음'}`;
|
|
gpsDataText.style.display = 'none';
|
|
gpsDataWrap.appendChild(gpsDataTooltip);
|
|
gpsDataWrap.appendChild(gpsDataImage);
|
|
gpsDataWrap.appendChild(gpsDataText);
|
|
gpsData.appendChild(gpsDataWrap);
|
|
|
|
// 썸네일
|
|
// let extType;
|
|
// if (imageExtArr.includes(ext)) extType = 'img';
|
|
// if (videoExtArr.includes(ext)) extType = 'video';
|
|
// let defaultThumbnail = `/main/img/archive/file_extension/${extType}.svg`;
|
|
|
|
// let defaultThumbnail = `/main/img/archive/file_extension/${ext}.svg`;
|
|
// let thumbnail = document.createElement('img');
|
|
// thumbnail.classList.add('thumbnail');
|
|
// thumbnail.src = defaultThumbnail;
|
|
// thumbnail.onerror = function() {
|
|
// this.onerror = null; // 무한루프 방지
|
|
// this.src = defaultThumbnail;
|
|
// };
|
|
|
|
let thumbnail = document.createElement('div');
|
|
thumbnail.classList.add('thumbnail');
|
|
let thumbnailImg = document.createElement('img');
|
|
thumbnail.appendChild(thumbnailImg);
|
|
|
|
// 파일명
|
|
let name = document.createElement('div');
|
|
name.classList.add('name');
|
|
name.classList.add('thumbnail-title');
|
|
name.classList.add('text-wrap');
|
|
let nameText = document.createElement('div');
|
|
nameText.classList.add('name-text');
|
|
nameText.classList.add('text');
|
|
nameText.classList.add('ft-14');
|
|
nameText.classList.add('ellipsis');
|
|
// 그리드 아이템 파일명 줄바꿈 적용
|
|
// nameText.classList.add('line-break');
|
|
nameText.innerHTML = key;
|
|
if (key.split('___')[1] && key.split('___')[0] == value.mainFileName) {
|
|
nameText.innerHTML = key.split('___')[1];
|
|
}
|
|
name.appendChild(nameText);
|
|
|
|
// 등록자
|
|
let uploader = document.createElement('div');
|
|
uploader.classList.add('uploader');
|
|
uploader.classList.add('thumbnail-title');
|
|
uploader.classList.add('text-wrap');
|
|
let uploaderText = document.createElement('div');
|
|
uploaderText.classList.add('text');
|
|
uploaderText.classList.add('ft-14');
|
|
uploaderText.innerHTML = '-';
|
|
if (value.userNm) uploaderText.innerHTML = value.userNm;
|
|
// if (value.userNm) uploaderText.innerHTML = value.userNm.split('___')[1];
|
|
uploader.appendChild(uploaderText);
|
|
|
|
// 등록일자
|
|
let createDate = document.createElement('div');
|
|
createDate.classList.add('create-date');
|
|
createDate.classList.add('thumbnail-title');
|
|
createDate.classList.add('text-wrap');
|
|
let createDateText = document.createElement('div');
|
|
createDateText.classList.add('text');
|
|
createDateText.classList.add('ft-14');
|
|
createDateText.innerHTML = '-';
|
|
if (value.createDate) {
|
|
let date = new Date(value.createDate);
|
|
// let date = new Date(value.createDate.split('___')[1]);
|
|
createDateText.innerHTML = `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}`;
|
|
}
|
|
createDate.appendChild(createDateText);
|
|
|
|
// 용량
|
|
let size = document.createElement('div');
|
|
size.classList.add('size');
|
|
size.classList.add('thumbnail-title');
|
|
size.classList.add('text-wrap');
|
|
let sizeText = document.createElement('div');
|
|
sizeText.classList.add('text');
|
|
sizeText.classList.add('ft-14');
|
|
sizeText.innerHTML = formatBytes(value.size);
|
|
// sizeText.innerHTML = formatBytes(value.size.split('___')[1]);
|
|
if (value.size >= 1073741824) sizeText.classList.add('large');
|
|
size.appendChild(sizeText);
|
|
|
|
// 작성자
|
|
let author = document.createElement('div');
|
|
author.classList.add('author');
|
|
author.classList.add('thumbnail-title');
|
|
author.classList.add('text-wrap');
|
|
let authorText = document.createElement('div');
|
|
authorText.classList.add('text');
|
|
authorText.classList.add('ft-14');
|
|
authorText.innerHTML = (value.authorNm) ? value.authorNm : value.userNm;
|
|
author.appendChild(authorText);
|
|
|
|
// name.style.display = 'none';
|
|
uploader.style.display = 'none';
|
|
createDate.style.display = 'none';
|
|
size.style.display = 'none';
|
|
author.style.display = 'none';
|
|
|
|
if (videoExtArr.includes(ext)) wrap.appendChild(play);
|
|
wrap.appendChild(highlight);
|
|
wrap.appendChild(gpsData);
|
|
wrap.appendChild(thumbnail);
|
|
wrap.appendChild(name);
|
|
wrap.appendChild(uploader);
|
|
wrap.appendChild(createDate);
|
|
wrap.appendChild(size);
|
|
wrap.appendChild(author);
|
|
|
|
gridItem.appendChild(wrap);
|
|
|
|
docFragment.appendChild(gridItem);
|
|
|
|
// 썸네일에서 위치있음/없음 버튼 클릭 시 위치 수정 모드 전환 확인 모달창 표시
|
|
gpsData.addEventListener('click', async function(e) {
|
|
vars.lastSelectTarget = gridItem;
|
|
let toggleParams = {
|
|
title: '위치 수정',
|
|
text: '확인 버튼을 누르면 위치 수정 모드로 전환됩니다.',
|
|
type: 'editPosition',
|
|
};
|
|
toggleModal(true, toggleParams);
|
|
|
|
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.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.remove('open');
|
|
// document.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.add('close');
|
|
|
|
vars.viewerConnectingTime = vars.viewerConnectingTimeValue;
|
|
if (value.isSupported == false || (value.needConvert == true && value.isConverted == false)) {
|
|
// 미지원 파일인 경우 -> value.isSupported == false
|
|
// 변환 필요 파일인 경우 -> value.needConvert == true && value.isConverted == false (변환해야되지만 아직 변환 안한 상태)
|
|
// 뷰어 프로그레스 표시 시간 0으로 설정
|
|
vars.viewerConnectingTime = 0;
|
|
}
|
|
|
|
renderViewer(resourcePath, dataId);
|
|
})
|
|
|
|
gridItem.addEventListener('dblclick', async (e) => {
|
|
if (e.target.classList.contains('gps-data')) return;
|
|
openNewWindowViewer();
|
|
})
|
|
|
|
gridItem.addEventListener('click', async (e) => {
|
|
// 위치있음/없음 버튼 클릭하면 위치 수정 모드 전환 확인 모달창 표시로 연결되어야 하므로 여기서는 리턴
|
|
if (e.target.classList.contains('gps-data')) return;
|
|
|
|
let isCtrl = e.ctrlKey;
|
|
let isShift = e.shiftKey;
|
|
let target = e.target;
|
|
// let resourcePath = value.resourcePath;
|
|
|
|
// 좌클릭이 아닌 경우 무시
|
|
if (e.button !== 0) return;
|
|
|
|
let clickedListItem = target.closest('.list-item');
|
|
if (!clickedListItem) return;
|
|
|
|
if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn);
|
|
if (vars.lastMainTreeItem) targetFocus(vars.lastMainTreeItem);
|
|
|
|
//// non-selected: 그리드 모드에서 선택되지 않은 아이템은 thumbnail의 opacity를 0.5로 설정
|
|
//// selected: 그리도 모드에서 선택된 아이템은 thumbnail의 opacity를 1로 설정
|
|
let allItems = [...document.querySelectorAll('.archive-main .list-container .list-body .list-item')];
|
|
allItems.forEach(item => {
|
|
if (!item.classList.contains('selected')) item.classList.add('non-selected');
|
|
});
|
|
let clickedIndex = allItems.indexOf(clickedListItem);
|
|
|
|
let isClickFinished = false;
|
|
|
|
// --- Ctrl + Shift ---
|
|
if (!isClickFinished && isCtrl && isShift) {
|
|
let anchorIndex = allItems.indexOf(vars.lastListItem);
|
|
if (anchorIndex == -1) anchorIndex = 0;
|
|
|
|
let [start, end] = [anchorIndex, clickedIndex].sort((a, b) => a - b);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
let item = allItems[i];
|
|
if (!vars.multiSelectListItemArr.includes(item)) {
|
|
vars.multiSelectListItemArr.push(item);
|
|
}
|
|
item.classList.add('group-style');
|
|
item.classList.add('selected');
|
|
item.classList.remove('non-selected');
|
|
}
|
|
|
|
isClickFinished = true;
|
|
}
|
|
|
|
// --- Shift only ---
|
|
if (!isClickFinished && isShift) {
|
|
let anchorIndex = allItems.indexOf(vars.lastListItem);
|
|
if (anchorIndex == -1) anchorIndex = 0;
|
|
|
|
// 기존 선택 해제
|
|
vars.multiSelectListItemArr = [];
|
|
allItems.forEach(item => {
|
|
item.classList.remove('selected');
|
|
item.classList.add('non-selected');
|
|
});
|
|
|
|
let [start, end] = [anchorIndex, clickedIndex].sort((a, b) => a - b);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
let item = allItems[i];
|
|
vars.multiSelectListItemArr.push(item);
|
|
item.classList.add('selected');
|
|
item.classList.remove('non-selected');
|
|
}
|
|
|
|
//// shift 다중 선택 후 delete키로 휴지통으로 이동
|
|
vars.lastSelectTarget = vars.multiSelectListItemArr[0];
|
|
|
|
isClickFinished = true;
|
|
}
|
|
|
|
// --- Ctrl only ---
|
|
if (!isClickFinished && isCtrl) {
|
|
if (vars.multiSelectListItemArr.includes(clickedListItem)) {
|
|
vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== clickedListItem);
|
|
clickedListItem.classList.remove('selected');
|
|
clickedListItem.classList.add('non-selected');
|
|
} else {
|
|
vars.multiSelectListItemArr.push(clickedListItem);
|
|
clickedListItem.classList.add('selected');
|
|
clickedListItem.classList.remove('non-selected');
|
|
}
|
|
|
|
vars.lastListItem = clickedListItem;
|
|
|
|
isClickFinished = true;
|
|
}
|
|
|
|
if (!isClickFinished) {
|
|
// --- 일반 클릭 (기존 선택 제거 후 단일 선택 + 동작 실행) ---
|
|
vars.multiSelectListItemArr = [];
|
|
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');
|
|
|
|
vars.lastListItem = clickedListItem;
|
|
|
|
if (!isCtrl && !isShift) {
|
|
vars.lastSelectTarget = clickedListItem;
|
|
|
|
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.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.remove('open');
|
|
// document.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.add('close');
|
|
|
|
vars.viewerConnectingTime = vars.viewerConnectingTimeValue;
|
|
if (value.isSupported == false || (value.needConvert == true && value.isConverted == false)) {
|
|
// 미지원 파일인 경우 -> value.isSupported == false
|
|
// 변환 필요 파일인 경우 -> value.needConvert == true && value.isConverted == false (변환해야되지만 아직 변환 안한 상태)
|
|
// 뷰어 프로그레스 표시 시간 0으로 설정
|
|
vars.viewerConnectingTime = 0;
|
|
}
|
|
|
|
renderViewer(resourcePath, dataId);
|
|
} else {
|
|
|
|
}
|
|
|
|
changeListItemStyle(clickedListItem, '그리드 아이템 클릭');
|
|
}
|
|
|
|
await syncGroupStyle();
|
|
});
|
|
}
|
|
|
|
parent.appendChild(docFragment);
|
|
|
|
if (vars.lastSelectTarget && vars.lastSelectTarget.classList.contains('list-item')) {
|
|
let dataId = vars.lastSelectTarget.dataset.id;
|
|
let item = document.querySelector(`.grid-item[data-id="${dataId}"]`);
|
|
if (item) {
|
|
changeListItemStyle(item, '그리드 아이템 생성');
|
|
targetFocus(item, 'instant');
|
|
}
|
|
}
|
|
|
|
// 갤러리 폴더 헤더에서 분류 라디오버튼 클릭 시 썸네일 타이틀에 클릭된 라디오버튼 value에 해당하는 텍스트 표시
|
|
if (vars.curSortCol) {
|
|
document.querySelectorAll('.list-container.grid .list-body .list-item .thumbnail-title').forEach(thumbnailTitle => {
|
|
thumbnailTitle.style.display = 'none';
|
|
})
|
|
document.querySelectorAll(`.list-container.grid .list-body .list-item .thumbnail-title.${vars.curSortCol}`).forEach(thumbnailTitle => {
|
|
thumbnailTitle.style.display = 'flex';
|
|
})
|
|
}
|
|
|
|
// 썸네일 오브젝트키로 이미지 가져와서 화면에 표시
|
|
for (let i = 0; i < entries.length; i++) {
|
|
let entry = entries[i];
|
|
let value = entry[1];
|
|
|
|
// 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
let resourcePathSplit = value.resourcePath.split('/');
|
|
if (resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
let resourcePath = resourcePathSplit.join('/');
|
|
|
|
let thumbnailKey = value.thumbnailKey;
|
|
|
|
if (thumbnailKey) {
|
|
// 썸네일키가 있으면 바로 썸네일에 표시
|
|
let generateDownloadUrlParams = {
|
|
objectKey: thumbnailKey,
|
|
resourcePath: resourcePath,
|
|
isThumbnail: true
|
|
}
|
|
|
|
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
|
|
|
|
// grid-item이 만들어지는 depth3 폴더와 현재 보고 있는 depth3 폴더가 다르면 리턴
|
|
if (currentTreeItem != vars.lastMainTreeItem) return;
|
|
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
|
|
let presignedUrl = generateDownloadUrlRes.data.url;
|
|
let thumbnailImg = document.querySelector(`.list-container.grid .grid-item[data-resource-path="${resourcePath}"] .thumbnail img`);
|
|
if (thumbnailImg) {
|
|
thumbnailImg.src = presignedUrl;
|
|
}
|
|
}
|
|
} else {
|
|
// console.log('그리드 아이템 생성 완료 / 썸네일키 없음');
|
|
|
|
let src = '/main/img/archive/file_extension/file.svg';
|
|
if (value.ext == 'pdf') src = '/main/img/archive/file_extension/pdf.svg';
|
|
if (value.ext == 'hwp') src = '/main/img/archive/file_extension/hwp.svg';
|
|
if (value.ext == 'hwpx') src = '/main/img/archive/file_extension/hwp.svg';
|
|
if (value.ext == 'doc') src = '/main/img/archive/file_extension/doc.svg';
|
|
if (value.ext == 'docx') src = '/main/img/archive/file_extension/doc.svg';
|
|
if (value.ext == 'xls') src = '/main/img/archive/file_extension/xls.svg';
|
|
if (value.ext == 'xlsx') src = '/main/img/archive/file_extension/xls.svg';
|
|
if (value.ext == 'xlsm') src = '/main/img/archive/file_extension/xls.svg';
|
|
if (value.ext == 'ppt') src = '/main/img/archive/file_extension/ppt.svg';
|
|
if (value.ext == 'pptx') src = '/main/img/archive/file_extension/ppt.svg';
|
|
if (value.ext == 'dwg') src = '/main/img/archive/file_extension/dwg.svg';
|
|
if (value.ext == 'dxf') src = '/main/img/archive/file_extension/dwg.svg';
|
|
if (value.ext == 'grm') src = '/main/img/archive/file_extension/dwg.svg';
|
|
if (value.ext == 'mp3') src = '/main/img/archive/file_extension/audio.svg';
|
|
if (value.ext == 'wav') src = '/main/img/archive/file_extension/audio.svg';
|
|
if (value.ext == 'jpg') src = '/main/img/archive/file_extension/img.svg';
|
|
if (value.ext == 'jpeg') src = '/main/img/archive/file_extension/img.svg';
|
|
if (value.ext == 'png') src = '/main/img/archive/file_extension/img.svg';
|
|
if (value.ext == 'mp4') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'webm') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'mov') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'avi') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'mkv') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'zip') src = '/main/img/archive/file_extension/zip.svg';
|
|
if (value.ext == 'txt') src = '/main/img/archive/file_extension/text.svg';
|
|
if (value.ext == 'gsim') src = '/main/img/archive/file_extension/gsim.svg';
|
|
if (value.ext == 'ifc') src = '/main/img/archive/file_extension/3d.svg';
|
|
|
|
let thumbnailImg = document.querySelector(`.list-container.grid .grid-item[data-resource-path="${resourcePath}"] .thumbnail img`);
|
|
if (thumbnailImg) {
|
|
thumbnailImg.src = src;
|
|
thumbnailImg.classList.add('default-image'); // default-image 클래스가 붙으면 css에서 width, height를 70%로 설정
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function createMapItems(data) {
|
|
|
|
// 지도 초기화
|
|
initOlMap();
|
|
|
|
// let imageExtArr = ['jpg', 'jpeg', 'png'];
|
|
let videoExtArr = ['mp4', 'mov', 'webm'];
|
|
|
|
let currentTreeItem = vars.lastMainTreeItem;
|
|
|
|
// 오버레이 및 피쳐를 담아두는 배열
|
|
let overlayArr = [];
|
|
let featureArr = [];
|
|
|
|
// 포인트 레이어 설정
|
|
let pointSource = new ol.source.Vector();
|
|
let pointStyle = new ol.style.Style({
|
|
image: new ol.style.Circle({
|
|
radius: 8, // 점 크기(px)
|
|
fill: new ol.style.Fill({ color: '#fff' }), // 내부색
|
|
stroke: new ol.style.Stroke({ color: '#000', width: 4 }) // 테두리
|
|
})
|
|
});
|
|
let pointLayer = new ol.layer.Vector({
|
|
source: pointSource,
|
|
style: pointStyle,
|
|
zIndex: 11, // 클러스터 레이어 zIndex를 더 크게 두면 위에 보임
|
|
});
|
|
pointLayer.setVisible(false);
|
|
// pointLayer.setVisible(true);
|
|
|
|
// 전역변수와 ol 지도에 포인트 레이어 저장 및 추가
|
|
vars.olPointLayer = pointLayer;
|
|
ol.map.addLayer(pointLayer);
|
|
|
|
let entries = sortData(data);
|
|
for (let i = 0; i < entries.length; i++) {
|
|
let entry = entries[i];
|
|
|
|
let key = entry[0];
|
|
let value = entry[1];
|
|
|
|
let ext = (value.ext).toLowerCase();
|
|
|
|
// 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
let resourcePathSplit = value.resourcePath.split('/');
|
|
if (resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
let resourcePath = resourcePathSplit.join('/');
|
|
|
|
let dataId = value.dataId;
|
|
|
|
let lon = value.lon;
|
|
let lat = value.lat;
|
|
// let height = value.height;
|
|
|
|
//// 경도/위도 정보가 있으면 지도에 오버레이 표시
|
|
if (lon && lat) {
|
|
|
|
// 지도 준비중 프로그레스
|
|
let progressData = {};
|
|
progressData.fileName = key;
|
|
progressData.count = entries.length;
|
|
progressData.idx = i+1;
|
|
await toggleMapProgress(true, progressData);
|
|
|
|
let mapItem = document.createElement('div');
|
|
mapItem.classList.add('list-item');
|
|
mapItem.classList.add('map-item');
|
|
mapItem.classList.add(`depth${value.depth}`);
|
|
mapItem.classList.add('file');
|
|
mapItem.dataset.resourcePath = resourcePath;
|
|
mapItem.dataset.id = dataId;
|
|
mapItem.dataset.size = value.size;
|
|
mapItem.classList.add('main-list-item');
|
|
// 확장자가 유효한 경우 클래스에 ext 추가
|
|
if (isValidExt(ext)) mapItem.classList.add(ext);
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
// 동영상 표시 아이콘
|
|
let play = document.createElement('div');
|
|
play.classList.add('play');
|
|
let playImage = document.createElement('div');
|
|
playImage.classList.add('image');
|
|
play.appendChild(playImage);
|
|
|
|
// 위치 텍스트 저장
|
|
let gpsData = document.createElement('div');
|
|
gpsData.classList.add('gps-data');
|
|
let gpsDataText = document.createElement('div');
|
|
gpsDataText.classList.add('text');
|
|
gpsDataText.innerHTML = `경도: ${(value.lon) ? value.lon : '없음'} / 위도: ${(value.lat) ? value.lat : '없음'}`;
|
|
gpsDataText.style.display = 'none';
|
|
gpsData.appendChild(gpsDataText);
|
|
|
|
// 썸네일
|
|
let thumbnail = document.createElement('img');
|
|
thumbnail.classList.add('thumbnail');
|
|
thumbnail.classList.add('cover');
|
|
|
|
// 파일명
|
|
let name = document.createElement('div');
|
|
name.classList.add('name');
|
|
name.classList.add('thumbnail-title');
|
|
let nameText = document.createElement('div');
|
|
nameText.classList.add('name-text');
|
|
nameText.classList.add('text');
|
|
nameText.classList.add('ft-14');
|
|
nameText.classList.add('ellipsis');
|
|
// nameText.classList.add('line-break');
|
|
nameText.innerHTML = key;
|
|
name.appendChild(nameText);
|
|
|
|
// 등록자
|
|
let uploader = document.createElement('div');
|
|
uploader.classList.add('uploader');
|
|
uploader.classList.add('thumbnail-title');
|
|
let uploaderText = document.createElement('div');
|
|
uploaderText.classList.add('text');
|
|
uploaderText.classList.add('ft-14');
|
|
uploaderText.innerHTML = '-';
|
|
if (value.userNm) uploaderText.innerHTML = value.userNm;
|
|
uploader.appendChild(uploaderText);
|
|
|
|
// 등록일자
|
|
let createDate = document.createElement('div');
|
|
createDate.classList.add('create-date');
|
|
createDate.classList.add('thumbnail-title');
|
|
let createDateText = document.createElement('div');
|
|
createDateText.classList.add('text');
|
|
createDateText.classList.add('ft-14');
|
|
createDateText.innerHTML = '-';
|
|
if (value.createDate) {
|
|
let date = new Date(value.createDate);
|
|
createDateText.innerHTML = `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}`;
|
|
}
|
|
createDate.appendChild(createDateText);
|
|
|
|
// 용량
|
|
let size = document.createElement('div');
|
|
size.classList.add('size');
|
|
size.classList.add('thumbnail-title');
|
|
let sizeText = document.createElement('div');
|
|
sizeText.classList.add('text');
|
|
sizeText.classList.add('ft-14');
|
|
sizeText.innerHTML = formatBytes(value.size);
|
|
if (value.size >= 1073741824) sizeText.classList.add('large');
|
|
size.appendChild(sizeText);
|
|
|
|
// 작성자
|
|
let author = document.createElement('div');
|
|
author.classList.add('author');
|
|
author.classList.add('thumbnail-title');
|
|
let authorText = document.createElement('div');
|
|
authorText.classList.add('text');
|
|
authorText.classList.add('ft-14');
|
|
authorText.innerHTML = (value.authorNm) ? value.authorNm : value.userNm;
|
|
author.appendChild(authorText);
|
|
|
|
name.style.display = 'none';
|
|
uploader.style.display = 'none';
|
|
createDate.style.display = 'none';
|
|
size.style.display = 'none';
|
|
author.style.display = 'none';
|
|
|
|
if (videoExtArr.includes(ext)) wrap.appendChild(play);
|
|
wrap.appendChild(gpsData);
|
|
wrap.appendChild(thumbnail);
|
|
wrap.appendChild(name);
|
|
wrap.appendChild(uploader);
|
|
wrap.appendChild(createDate);
|
|
wrap.appendChild(size);
|
|
wrap.appendChild(author);
|
|
|
|
mapItem.appendChild(wrap);
|
|
|
|
// mapItem 숨김 상태로 생성
|
|
mapItem.style.display = 'none';
|
|
|
|
mapItem.addEventListener('click', async function(e) {
|
|
// 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴
|
|
if (vars.mapMode == 'edit') return;
|
|
//// 컨트롤, 쉬프트 조합 클릭 이벤트 추가?
|
|
|
|
// 이전에 선택된 클러스터 선택 상태 해제
|
|
if (clusterLayer && vars.lastSelectCluster) {
|
|
vars.lastSelectCluster.set('selected', false);
|
|
vars.lastSelectCluster.set('clicked', false);
|
|
vars.lastSelectCluster = null;
|
|
clusterLayer.changed();
|
|
}
|
|
|
|
// clusterListWrap 숨김
|
|
clusterListWrap.style.display = 'none';
|
|
|
|
let option = {
|
|
from: '오버레이 클릭'
|
|
}
|
|
toggleArchiveMainRight(true, option);
|
|
|
|
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.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.remove('open');
|
|
// document.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.add('close');
|
|
|
|
vars.viewerConnectingTime = vars.viewerConnectingTimeValue;
|
|
if (value.isSupported == false || (value.needConvert == true && value.isConverted == false)) {
|
|
// 미지원 파일인 경우 -> value.isSupported == false
|
|
// 변환 필요 파일인 경우 -> value.needConvert == true && value.isConverted == false (변환해야되지만 아직 변환 안한 상태)
|
|
// 뷰어 프로그레스 표시 시간 0으로 설정
|
|
vars.viewerConnectingTime = 0;
|
|
}
|
|
|
|
renderViewer(resourcePath, dataId);
|
|
|
|
vars.lastSelectTarget = mapItem;
|
|
|
|
vars.multiSelectListItemArr = [];
|
|
|
|
changeListItemStyle(e.target);
|
|
})
|
|
|
|
let zoom = ol.map.getView().getZoom();
|
|
let scale = Math.max(0.5, Math.min(1.5, zoom / 5));
|
|
mapItem.style.setProperty('--overlay-scale-transform', `scale(${scale})`);
|
|
|
|
let coord3857 = ol.proj.fromLonLat([lon, lat]);
|
|
|
|
// 오버레이 생성
|
|
let overlay = new ol.Overlay({
|
|
element: mapItem,
|
|
position: coord3857,
|
|
positioning: 'bottom-center',
|
|
stopEvent: false
|
|
});
|
|
// 지도에 오버레이 추가
|
|
ol.map.addOverlay(overlay);
|
|
|
|
// 피쳐 생성
|
|
let feature = new ol.Feature({
|
|
geometry: new ol.geom.Point(coord3857),
|
|
dataId: dataId
|
|
});
|
|
// 피쳐에 오버레이 속성 추가
|
|
// -> 클러스터 클릭 시 해당 클러스터에 포함된 피쳐를 탐색하고,
|
|
// 각 피쳐에 포함된 오버레이 속성을 클러스터 리스트 렌더에 사용
|
|
feature.set('overlay', overlay);
|
|
// 포인트 소스에 각 피쳐 추가
|
|
pointSource.addFeature(feature);
|
|
|
|
// overlayArr/featureArr에 각각 오버레이/피쳐 추가
|
|
overlayArr.push(overlay);
|
|
featureArr.push(feature);
|
|
|
|
if (value.thumbnailKey) {
|
|
let objectKey = value.thumbnailKey;
|
|
let generateDownloadUrlParams = {
|
|
objectKey: objectKey,
|
|
resourcePath: resourcePath,
|
|
isThumbnail: true
|
|
}
|
|
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
|
|
|
|
// map-item이 만들어지는 depth3 폴더와 현재 보고 있는 depth3 폴더가 다르면 리턴
|
|
if (currentTreeItem != vars.lastMainTreeItem) return;
|
|
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
|
|
let presignedUrl = generateDownloadUrlRes.data.url;
|
|
thumbnail.src = presignedUrl;
|
|
}
|
|
} else {
|
|
let src = '/main/img/archive/file_extension/file.svg';
|
|
if (value.ext == 'pdf') src = '/main/img/archive/file_extension/pdf.svg';
|
|
if (value.ext == 'hwp') src = '/main/img/archive/file_extension/hwp.svg';
|
|
if (value.ext == 'hwpx') src = '/main/img/archive/file_extension/hwp.svg';
|
|
if (value.ext == 'doc') src = '/main/img/archive/file_extension/doc.svg';
|
|
if (value.ext == 'docx') src = '/main/img/archive/file_extension/doc.svg';
|
|
if (value.ext == 'xls') src = '/main/img/archive/file_extension/xls.svg';
|
|
if (value.ext == 'xlsx') src = '/main/img/archive/file_extension/xls.svg';
|
|
if (value.ext == 'xlsm') src = '/main/img/archive/file_extension/xls.svg';
|
|
if (value.ext == 'ppt') src = '/main/img/archive/file_extension/ppt.svg';
|
|
if (value.ext == 'pptx') src = '/main/img/archive/file_extension/ppt.svg';
|
|
if (value.ext == 'dwg') src = '/main/img/archive/file_extension/dwg.svg';
|
|
if (value.ext == 'dxf') src = '/main/img/archive/file_extension/dwg.svg';
|
|
if (value.ext == 'grm') src = '/main/img/archive/file_extension/dwg.svg';
|
|
if (value.ext == 'mp3') src = '/main/img/archive/file_extension/audio.svg';
|
|
if (value.ext == 'wav') src = '/main/img/archive/file_extension/audio.svg';
|
|
if (value.ext == 'jpg') src = '/main/img/archive/file_extension/img.svg';
|
|
if (value.ext == 'jpeg') src = '/main/img/archive/file_extension/img.svg';
|
|
if (value.ext == 'png') src = '/main/img/archive/file_extension/img.svg';
|
|
if (value.ext == 'mp4') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'webm') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'mov') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'avi') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'mkv') src = '/main/img/archive/file_extension/video.svg';
|
|
if (value.ext == 'zip') src = '/main/img/archive/file_extension/zip.svg';
|
|
if (value.ext == 'txt') src = '/main/img/archive/file_extension/text.svg';
|
|
if (value.ext == 'gsim') src = '/main/img/archive/file_extension/gsim.svg';
|
|
if (value.ext == 'ifc') src = '/main/img/archive/file_extension/3d.svg';
|
|
|
|
thumbnail.src = src;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 아카이브 우측 영역 표시/숨김
|
|
if (vars.lastListItem && document.querySelector(`.map-item[data-id="${vars.lastListItem.dataset.id}"]`) && vars.mapMode == 'normal') {
|
|
console.log(vars.mapMode);
|
|
let option = {
|
|
from: '지도 렌더링 - 미리보기 표시',
|
|
}
|
|
toggleArchiveMainRight(true, option);
|
|
} else {
|
|
let option = {
|
|
from: '지도 렌더링 - 미리보기 숨김',
|
|
}
|
|
toggleArchiveMainRight(false, option);
|
|
}
|
|
|
|
if (vars.lastSelectTarget && vars.lastSelectTarget.classList.contains('list-item')) {
|
|
let dataId = vars.lastSelectTarget.dataset.id;
|
|
let item = document.querySelector(`.map-item[data-id="${dataId}"]`);
|
|
if (item) {
|
|
changeListItemStyle(item);
|
|
// 갤러리 폴더 상단 라디오버튼 변경 시 뷰어 영역도 새로 그리려면 renderViewer 실행
|
|
// let resourcePath = vars.lastSelectTarget.dataset.resourcePath;
|
|
// renderViewer(resourcePath, dataId);
|
|
}
|
|
}
|
|
|
|
// 지도 모드 전환 시 모든 오버레이의 범위에 맞게 카메라 이동하도록 overlayPositionArr 변수 생성
|
|
let overlayPositionArr = overlayArr.map(overlay => overlay.getPosition());
|
|
let extent = new ol.extent.boundingExtent(overlayPositionArr);
|
|
vars.allOverlayExtent = extent;
|
|
|
|
if (vars.mapMode == 'edit') {
|
|
let validCoordResult = isValidCoord();
|
|
if (validCoordResult == true) {
|
|
let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
let lon = lonInput.value;
|
|
let lat = latInput.value;
|
|
|
|
let overlayPosition = ol.proj.fromLonLat([lon, lat]);
|
|
// 위치 수정 모드 실행 시 경도/위도 정보가 있으면 해당 위치로 카메라 이동하도록 overlayPositionArr 변수 갱신
|
|
overlayPositionArr = [overlayPosition];
|
|
// 마커 표시
|
|
if(vars.markerOverlay) {
|
|
vars.markerOverlay.setPosition(overlayPosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
let overlayFitBtn = document.querySelector('.map-container .control-btn-wrap .overlays-fit-btn');
|
|
if (overlayPositionArr.length > 0) {
|
|
// overlayPositionArr의 length가 1 이상이면 오버레이의 범위에 맞게 카메라 이동
|
|
await fitToOverlayExtent(overlayPositionArr, async () => {
|
|
// 다음 로직 실행 - 지도 프로그레스 종료
|
|
await toggleMapProgress(false);
|
|
});
|
|
|
|
let maxZoom;
|
|
if (overlayPositionArr.length != 1 && !vars.lastListItem) maxZoom = vars.allOverlayVisibleZoom - 1;
|
|
overlayFitBtn.style.display = 'flex';
|
|
overlayFitBtn.addEventListener('click', async() => {
|
|
let paddingValue = 100;
|
|
let maxZoom = vars.allOverlayVisibleZoom;
|
|
if (overlayPositionArr.length != 1 && !vars.lastListItem) maxZoom = vars.allOverlayVisibleZoom - 1;
|
|
ol.map.getView().fit(vars.allOverlayExtent, {
|
|
padding: [paddingValue, paddingValue, paddingValue, paddingValue],
|
|
// duration: 500,
|
|
// constrainResolution: true,
|
|
maxZoom: maxZoom
|
|
});
|
|
|
|
if (vars.lastSelectCluster) {
|
|
renderClusterList(vars.lastSelectCluster.get('features'), vars.olClusterLayer);
|
|
}
|
|
})
|
|
} else {
|
|
// overlayPositionArr의 length가 0이면 바로 지도 프로그레스 종료
|
|
await toggleMapProgress(false);
|
|
overlayFitBtn.style.display = 'none';
|
|
}
|
|
// await toggleMapProgress(false);
|
|
|
|
// 줌 레벨에 따라 오버레이 크기 조절
|
|
await scaleOverlaysByZoom();
|
|
|
|
//// 클러스터 코드
|
|
let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap');
|
|
let rawSource = new ol.source.Vector({ features: featureArr });
|
|
let clusterSource = new ol.source.Cluster({
|
|
distance: vars.clusterDistance,
|
|
minDistance: 0,
|
|
source: rawSource
|
|
});
|
|
|
|
// 최소 거리(minDistance) 계산 후 적용
|
|
let radius = 20; // circle radius (스타일에서 쓰는 값과 동일)
|
|
let strokeWidth = 4; // stroke width
|
|
let padding = 6; // 여유
|
|
let wantMin = 2 * radius + strokeWidth + padding; // 대략 50px
|
|
clusterSource.setDistance(Math.max(clusterSource.getDistance(), wantMin));
|
|
if (clusterSource.setMinDistance) {
|
|
clusterSource.setMinDistance(wantMin);
|
|
}
|
|
clusterSource.refresh();
|
|
|
|
let clusterStyleCache = { normal: {}, hover: {}, selected: {}, clicked: {} };
|
|
let clusterLayer = new ol.layer.Vector({
|
|
source: clusterSource,
|
|
declutter: false,
|
|
style: (clusterFeature) => {
|
|
const members = clusterFeature.get('features');
|
|
const size = members?.length || 0;
|
|
if (size <= 1) return null;
|
|
|
|
let selectedId = vars.lastSelectTarget?.dataset?.id;
|
|
let isSelectedCluster = selectedId && members.some(f => String(f.get('dataId')) === String(selectedId));
|
|
|
|
let state = 'normal';
|
|
if (isSelectedCluster) state = 'selected';
|
|
if (clusterFeature.get('hover')) state = 'hover';
|
|
if (clusterFeature.get('clicked')) state = 'clicked';
|
|
|
|
return clusterStyleFor(size, state, clusterStyleCache);
|
|
},
|
|
zIndex: 12,
|
|
});
|
|
|
|
// 전역변수와 ol 지도에 클러스터 레이어 저장 및 추가
|
|
vars.olClusterLayer = clusterLayer;
|
|
ol.map.addLayer(clusterLayer);
|
|
|
|
let lastHoveredCluster = null; // 마지막으로 hover된 클러스터
|
|
let hoverSeq = 0; // 비동기 응답 순서 보호용
|
|
ol.map.on('pointermove', (evt) => {
|
|
// 드래그 중인 경우 리턴 (성능/깜빡임 방지)
|
|
if (evt.dragging) return;
|
|
|
|
// 위치 수정 모드인 경우 리턴
|
|
if (document.querySelector('.list-container .list-body .map-container').classList.contains('edit-mode')) return;
|
|
|
|
// 클러스터 레이어가 숨겨진 상태인 경우 리턴
|
|
if (clusterLayer.getVisible() == false) return;
|
|
|
|
const seq = ++hoverSeq;
|
|
// 클러스터 레이어만 히트 검사 (비동기)
|
|
clusterLayer.getFeatures(evt.pixel).then((hits) => {
|
|
if (seq !== hoverSeq) return; // 이전 pointermove의 지연 응답 무시
|
|
|
|
let cf = null;
|
|
if (hits && hits.length) {
|
|
const cand = hits[0];
|
|
const members = cand.get('features');
|
|
if (members && members.length > 1) cf = cand; // 진짜 클러스터만 hover 대상
|
|
}
|
|
|
|
// 기존 hover 해제
|
|
if (lastHoveredCluster && lastHoveredCluster !== cf) {
|
|
lastHoveredCluster.set('hover', false);
|
|
lastHoveredCluster = null;
|
|
clusterLayer.changed(); // 재렌더
|
|
}
|
|
|
|
// 새 hover 설정
|
|
if (cf) {
|
|
if (lastHoveredCluster !== cf) {
|
|
cf.set('hover', true);
|
|
lastHoveredCluster = cf;
|
|
clusterLayer.changed();
|
|
}
|
|
ol.map.getTargetElement().style.cursor = 'pointer';
|
|
} else {
|
|
ol.map.getTargetElement().style.cursor = 'default';
|
|
}
|
|
});
|
|
});
|
|
|
|
// 맵 영역을 벗어났을 때 hover 리셋
|
|
ol.map.getTargetElement().addEventListener('mouseleave', () => {
|
|
if (lastHoveredCluster) {
|
|
lastHoveredCluster.set('hover', false);
|
|
lastHoveredCluster = null;
|
|
clusterLayer.changed();
|
|
}
|
|
ol.map.getTargetElement().style.cursor = 'default';
|
|
});
|
|
|
|
// 초기 1회 동기화 - clusterSource가 클러스터 피처를 만든 뒤 그 프레임이 그려진 다음에 실행
|
|
// clusterSource.refresh();
|
|
// ol.map.once('rendercomplete', () => {
|
|
// syncOverlaysByCluster(overlayArr, clusterSource);
|
|
// });
|
|
|
|
let startZoom, endZoom;
|
|
ol.map.on('movestart', () => {
|
|
startZoom = ol.map.getView().getZoom();
|
|
|
|
// vars.olPointLayer.setVisible(true);
|
|
|
|
// 지도 이동 및 줌 변경 시작할 때 map-container에 overlay-suspend 클래스 추가해서 오버레이 숨김
|
|
document.querySelector('.map-container').classList.add('overlay-suspend');
|
|
})
|
|
|
|
// 이동/줌 종료 시 동기화 - 렌더가 끝난 뒤에 한 번만 실행
|
|
ol.map.on('moveend', async (e) => {
|
|
if (!vars.lastSelectTarget || !vars.lastSelectTarget.classList.contains('map-item')) {
|
|
// vars.lastSelectTarget이 없거나 vars.lastSelectTarget의 클래스 리스트에 map-item이 없는 경우
|
|
vars.lastSelectCluster = undefined;
|
|
}
|
|
|
|
endZoom = ol.map.getView().getZoom();
|
|
|
|
if (endZoom >= vars.allOverlayVisibleZoom) {
|
|
// 줌 변경 종료 시점의 줌 레벨이 vars.allOverlayVisibleZoom 이상이면 클러스터레이어 숨기고 포인트 레이어 표시
|
|
vars.olClusterLayer.setVisible(false);
|
|
vars.olPointLayer.setVisible(true);
|
|
} else {
|
|
// 줌 변경 종료 시점의 줌 레벨이 vars.allOverlayVisibleZoom 보다 작으면 클러스터레이어 표시하고 포인트 레이어 숨김
|
|
vars.olClusterLayer.setVisible(true);
|
|
vars.olPointLayer.setVisible(false);
|
|
|
|
await requestAnimationFrame(() => {
|
|
reapplyClickedByLastOverlay(clusterSource, clusterLayer, startZoom, endZoom);
|
|
})
|
|
clusterLayer.changed();
|
|
}
|
|
|
|
ol.map.once('rendercomplete', async () => {
|
|
// vars.lastSelectTarget이 없으면 클러스터 아이템 리스트 숨김
|
|
// if (!vars.lastSelectTarget) clusterListWrap.style.display = 'none';
|
|
|
|
// 클러스터에 포함된 오버레이는 숨기고 포함되지 않은 오버레이는 표시
|
|
// bufferPx - 양수: 뷰포트 범위 바깥까지 오버레이 표시 영역 확장 / 음수: 뷰포트 범위 안쪽으로 오버레이 표시 영역 축소
|
|
let bufferPx = -50;
|
|
await syncOverlaysByCluster(overlayArr, clusterSource, bufferPx);
|
|
|
|
clusterLayer.changed();
|
|
|
|
await requestAnimationFrame(() => {
|
|
// 지도 이동 및 줌 변경 종료할 때 map-container에 overlay-suspend 클래스 제거해서 오버레이 표시
|
|
document.querySelector('.map-container').classList.remove('overlay-suspend');
|
|
})
|
|
});
|
|
});
|
|
|
|
ol.map.on('click', (e) => {
|
|
// 클릭한 대상이 오버레이인 경우 리턴
|
|
if (e.originalEvent?.target.classList.contains('list-item')) return;
|
|
|
|
// 위치 수정 모드인 경우 리턴
|
|
if (document.querySelector('.list-container .list-body .map-container').classList.contains('edit-mode')) return;
|
|
|
|
// 클러스터 레이어가 숨겨진 상태인 경우 리턴
|
|
if (clusterLayer.getVisible() == false) return;
|
|
|
|
// 이 레이어(clusteLayer)에서만 히트 검사
|
|
clusterLayer.getFeatures(e.pixel).then(async (clickedFeatures) => {
|
|
if (!clickedFeatures || !clickedFeatures.length) {
|
|
// 아무 것도 안 눌렀으면 이전에 선택된 클러스터 선택 상태 해제
|
|
if (vars.lastSelectCluster) {
|
|
// vars.lastSelectCluster.set('selected', false);
|
|
vars.lastSelectCluster.set('clicked', false);
|
|
vars.lastSelectCluster = null;
|
|
clusterLayer.changed();
|
|
}
|
|
|
|
// 클러스터 아이템 리스트 닫기
|
|
clusterListWrap.style.display = 'none';
|
|
|
|
return;
|
|
}
|
|
|
|
// 클릭된 건(보통 1개)의 내부 멤버(원본 피처들)
|
|
let clusterFeature = clickedFeatures[0];
|
|
let items = clusterFeature.get('features');
|
|
if (!items || items.length <= 1) {
|
|
// 단건이면 이전에 선택된 클러스터 선택 상태 해제
|
|
if (vars.lastSelectCluster) {
|
|
// vars.lastSelectCluster.set('selected', false);
|
|
vars.lastSelectCluster.set('clicked', false);
|
|
vars.lastSelectCluster = null;
|
|
clusterLayer.changed();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// 기존 선택된 클러스터 선택 상태 해제
|
|
for (let i = 0; i < clusterSource.getFeatures().length; i++) {
|
|
let feature = clusterSource.getFeatures()[i];
|
|
// feature.set('selected', false);
|
|
feature.set('clicked', false);
|
|
}
|
|
|
|
// 새로 선택한 클러스터에 선택 상태 설정
|
|
// clusterFeature.set('selected', true);
|
|
// clusterFeature.set('clicked', true);
|
|
vars.lastSelectCluster = clusterFeature;
|
|
|
|
// 스타일 갱신
|
|
clusterLayer.changed();
|
|
|
|
// 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴
|
|
if (vars.mapMode == 'edit') return;
|
|
|
|
// 클러스터 리스트 렌더링
|
|
await renderClusterList(items, clusterLayer);
|
|
|
|
// 새로 선택한 클러스터에 선택 상태 설정
|
|
// clusterFeature.set('selected', true);
|
|
clusterFeature.set('clicked', true);
|
|
vars.lastSelectCluster.set('clicked', true);
|
|
|
|
// 스타일 갱신
|
|
clusterLayer.changed();
|
|
});
|
|
|
|
let option = {
|
|
from: '지도 영역 클릭'
|
|
}
|
|
toggleArchiveMainRight(false, option);
|
|
});
|
|
}
|
|
|
|
async function initOlMap() {
|
|
// 경도/위도 입력란
|
|
let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
|
|
// 기존 지도 완전 삭제
|
|
if (ol.map) {
|
|
ol.map.getOverlays()?.clear();
|
|
ol.map.getLayers()?.forEach(l => {
|
|
const src = l.getSource?.();
|
|
// 벡터라면 데이터까지 비우기(하드 클리어)
|
|
if (src && src instanceof ol.source.Vector) src.clear(true);
|
|
});
|
|
ol.map.getLayers()?.clear();
|
|
|
|
// 3) 타깃 해제(맵이 DOM과 이벤트 연결을 끊음)
|
|
ol.map.setTarget(null);
|
|
|
|
// 4) 가능하다면 dispose()로 내부 렌더러/리스너 완전 정리
|
|
if (typeof ol.map.dispose === 'function') {
|
|
ol.map.dispose();
|
|
}
|
|
|
|
document.querySelector('.map-container .ol-viewport')?.remove();
|
|
|
|
ol.map = undefined;
|
|
}
|
|
|
|
ol.map = new ol.Map({
|
|
target: 'map-container',
|
|
|
|
// 지도 생성 시 일반 지도 1개만 등록 후 지도 url 변경 방식
|
|
// layers: [vars.baseLayer],
|
|
|
|
// 지도 생성 시 3가지 지도 모두 등록 후 교체 방식
|
|
layers: [vars.roadLayer, vars.satelliteLayer, vars.hybridLayer],
|
|
|
|
view: new ol.View({
|
|
projection: 'EPSG:3857',
|
|
center: ol.proj.fromLonLat([127.8, 35.9]),
|
|
zoom: 7,
|
|
maxZoom: 22,
|
|
constrainResolution: true,
|
|
smoothResolutionConstraint: true
|
|
})
|
|
});
|
|
|
|
let olZoom = document.querySelector('.ol-zoom');
|
|
let controlBtnWrap = document.querySelector('.list-container.grid .list-wrap.list-body .map-container .control-btn-wrap');
|
|
controlBtnWrap.appendChild(olZoom);
|
|
|
|
proj4.defs([
|
|
['EPSG:5185', '+proj=tmerc +lat_0=38 +lon_0=125 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
|
['EPSG:5186', '+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
|
['EPSG:5187', '+proj=tmerc +lat_0=38 +lon_0=129 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
|
['EPSG:5188', '+proj=tmerc +lat_0=38 +lon_0=131 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
|
['EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'],
|
|
['EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs'],
|
|
])
|
|
ol.proj.proj4.register(proj4);
|
|
|
|
//// pmtiles 표시용 테스트 코드
|
|
vars.layerManager = new OLMultiLayerManager(ol.map);
|
|
|
|
await vars.layerManager.toggleLayer(vars.project_id, 'pmtiles');
|
|
|
|
let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap');
|
|
clusterListWrap.style.display = 'none';
|
|
|
|
// 클릭 지점 마커 생성
|
|
let marker = document.createElement('div');
|
|
marker.classList.add('marker');
|
|
vars.markerOverlay = new ol.Overlay({
|
|
element: marker,
|
|
positioning: 'center-center',
|
|
stopEvent: false,
|
|
zIndex: 9999
|
|
});
|
|
ol.map.addOverlay(vars.markerOverlay);
|
|
|
|
// 오픈레이어스 지도 단일 클릭 이벤트 - 위치 수정 모드에서 지도 클릭 시 경도/위도 입력란에 해당 위치 좌표 입력
|
|
ol.map.on('singleclick', function (e) {
|
|
// 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴
|
|
if (vars.mapMode == 'normal') return;
|
|
|
|
// 클릭 위치 경도/위도로 변환
|
|
let coord3857 = e.coordinate;
|
|
let coord4326 = ol.proj.toLonLat(coord3857);
|
|
let lon = coord4326[0];
|
|
let lat = coord4326[1];
|
|
|
|
lonInput.value = lon;
|
|
latInput.value = lat;
|
|
|
|
// 클릭 지점 마커 위치 업데이트
|
|
ol.map.addOverlay(vars.markerOverlay);
|
|
vars.markerOverlay.setPosition(coord3857);
|
|
})
|
|
|
|
// js 지도 영역 클릭 이벤트 - 지도 영역 클릭 시 선택된 리스트 아이템 및 뷰어 초기화
|
|
ol.map.addEventListener('click', function(e) {
|
|
if (vars.mapMode == 'edit') return;
|
|
|
|
vars.lastSelectTarget = undefined;
|
|
vars.lastListItem = undefined;
|
|
vars.multiSelectListItemArr = [];
|
|
document.querySelectorAll('.archive-main .list-container .list-body .list-item').forEach((elem) => {
|
|
elem.classList.remove('selected');
|
|
elem.classList.remove('group-style');
|
|
});
|
|
resetViewer();
|
|
toggleArchiveMainRight(false);
|
|
})
|
|
|
|
// 오픈레이어스 지도 viewport에서 휠 이벤트 발생할 때
|
|
let viewport = ol.map.getViewport();
|
|
viewport.addEventListener('wheel', function() {
|
|
// 선택된 map-item이 있는 경우 리턴
|
|
if (vars.lastListItem) return;
|
|
|
|
// 선택된 map-item이 없는 경우 클러스터 리스트 숨김
|
|
document.querySelector('.map-container .cluster-list-wrap').style.display = 'none';
|
|
})
|
|
|
|
// 경도/위도 입력란에 '없음' 텍스트로 표시된 경우 입력란 클릭하면 초기화
|
|
lonInput.addEventListener('click', function(e) {
|
|
if (e.target.value == '없음') e.target.value = '';
|
|
})
|
|
latInput.addEventListener('click', function(e) {
|
|
if (e.target.value == '없음') e.target.value = '';
|
|
})
|
|
// 경도/위도 입력란 수정하면 마커 위치 업데이트 및 마커 위치로 화면 이동
|
|
lonInput.addEventListener('input', function(e) {
|
|
let result = checkCoordInputValue(e.target.value, 'lon');
|
|
if (e.target.value !== result) e.target.value = result;
|
|
if (result == '') e.target.value = '없음';
|
|
ol.map.removeOverlay(vars.markerOverlay);
|
|
updateMarker();
|
|
})
|
|
latInput.addEventListener('input', function(e) {
|
|
let result = checkCoordInputValue(e.target.value, 'lat');
|
|
if (e.target.value !== result) e.target.value = result;
|
|
if (result == '') e.target.value = '없음';
|
|
ol.map.removeOverlay(vars.markerOverlay);
|
|
updateMarker();
|
|
})
|
|
|
|
// 위치 보기 버튼을 누를 때 경도/위도 값이 제대로 입력되어 있으면 마커 위치로 화면 이동
|
|
let focusBtn = document.querySelector('.edit-mode-ui .focus-btn');
|
|
focusBtn.addEventListener('click', function() {
|
|
let validCoordResult = isValidCoord();
|
|
if (validCoordResult == true) {
|
|
updateMarker();
|
|
} else {
|
|
let toggleParams = {
|
|
text: '경도/위도 값을 확인해주세요.<br><span style="text-align: left;">경도 범위: -180 ~ 180<br>위도 범위: -90 ~ 90</span>',
|
|
type: 'alertModal',
|
|
};
|
|
toggleModal(true, toggleParams);
|
|
}
|
|
})
|
|
|
|
// 저장 버튼을 누를 때 경도/위도 값이 제대로 입력되어 있으면 db에 경도/위도 값 저장
|
|
let saveBtn = document.querySelector('.edit-mode-ui .save-btn');
|
|
saveBtn.addEventListener('click', async function() {
|
|
let validCoordResult = isValidCoord();
|
|
if (validCoordResult == true) {
|
|
let dataId = document.querySelector('.edit-mode-ui .info-wrap .wrap').dataset.id;
|
|
let editPositionParams = {
|
|
dataId: dataId,
|
|
lon: lonInput.value,
|
|
lat: latInput.value,
|
|
};
|
|
let editPositionResult = await axios.post(`${vars.path_name}/editPosition`, { params: editPositionParams });
|
|
if (editPositionResult.data.message == 'editPosition_success') {
|
|
// TreeObject 업데이트
|
|
let resourcePath = document.querySelector(`.edit-mode-ui .info-wrap .wrap`).dataset.resourcePath;
|
|
await preparePageRendering({
|
|
scope: 'updateTreeObject',
|
|
resourcePath: resourcePath.substring(0, resourcePath.lastIndexOf('/')),
|
|
pushState: false
|
|
});
|
|
|
|
// 파일 영역 재렌더링
|
|
document.querySelector(`.control-box .file-area-mode-btn.${vars.lastFileAreaMode}`).click();
|
|
|
|
// 뷰어 재렌더링하여 메타데이터 업데이트
|
|
setTimeout(async() => {
|
|
if (vars.lastListItem) {
|
|
let updateResourcePath = vars.lastListItem.dataset.resourcePath;
|
|
let updatedDataId = vars.lastListItem.dataset.id;
|
|
await renderViewer(updateResourcePath, updatedDataId, false);
|
|
}
|
|
}, 300);
|
|
} else {
|
|
// 경도/위도 값에 '없음'이 입력되어 있으면 db에 null로 저장
|
|
if (lonInput.value == '없음' && latInput.value == '없음') {
|
|
let editPositionParams = {
|
|
dataId: document.querySelector('.edit-mode-ui .info-wrap .wrap').dataset.id,
|
|
lon: null,
|
|
lat: null,
|
|
height: null,
|
|
};
|
|
let editPositionResult = await axios.post(`${vars.path_name}/editPosition`, { params: editPositionParams });
|
|
if (editPositionResult.data.message == 'editPosition_success') {
|
|
document.querySelector(`.control-box .file-area-mode-btn.${vars.lastFileAreaMode}`).click();
|
|
}
|
|
} else {
|
|
let toggleParams = {
|
|
text: '경도/위도 값을 확인해주세요.<br><span style="text-align: left;">경도 범위: -180 ~ 180<br>위도 범위: -90 ~ 90</span>',
|
|
type: 'alertModal',
|
|
};
|
|
toggleModal(true, toggleParams);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
// 위치 삭제 버튼을 누르면 모달 표시
|
|
let deleteBtn = document.querySelector('.edit-mode-ui .delete-btn');
|
|
deleteBtn.addEventListener('click', function() {
|
|
let toggleParams = {
|
|
title: '위치 삭제',
|
|
text: '확인 버튼을 누르면 위치가 삭제됩니다.',
|
|
type: 'deletePosition',
|
|
};
|
|
toggleModal(true, toggleParams);
|
|
})
|
|
|
|
// 취소 버튼을 누르면 그리드 모드로 전환
|
|
let cancelBtn = document.querySelector('.edit-mode-ui .cancel-btn');
|
|
cancelBtn.addEventListener('click', function() {
|
|
document.querySelector(`.control-box .file-area-mode-btn.${vars.lastFileAreaMode}`).click();
|
|
})
|
|
}
|
|
|
|
/// 251104 박지은 ol 소켓처리
|
|
// // fileAreaMode가 map 일때는 새 파일만 지도에 추가
|
|
// export async function createNewMapItems(newResourcePathArr) {
|
|
|
|
// let videoExtArr = ['mp4', 'mov', 'webm'];
|
|
|
|
// let currentTreeItem = vars.lastMainTreeItem;
|
|
|
|
// // 오버레이 및 피쳐를 담아두는 배열
|
|
// let overlayArr = [];
|
|
// let featureArr = [];
|
|
|
|
// //// 기존 포인트 레이어 가져오기 또는 생성
|
|
// let pointSource, pointLayer;
|
|
// if (vars.olPointLayer) {
|
|
// // 기존 레이어 재사용
|
|
// pointLayer = vars.olPointLayer;
|
|
// pointSource = pointLayer.getSource();
|
|
// } else {
|
|
// // 새로 생성
|
|
// pointSource = new ol.source.Vector();
|
|
// let pointStyle = new ol.style.Style({
|
|
// image: new ol.style.Circle({
|
|
// radius: 8, // 점 크기(px)
|
|
// fill: new ol.style.Fill({ color: '#fff' }), // 내부색
|
|
// stroke: new ol.style.Stroke({ color: '#000', width: 4 }) // 테두리
|
|
// })
|
|
// });
|
|
// pointLayer = new ol.layer.Vector({
|
|
// source: pointSource,
|
|
// style: pointStyle,
|
|
// zIndex: 11, // 클러스터 레이어 zIndex를 더 크게 두면 위에 보임
|
|
// });
|
|
// pointLayer.setVisible(false);
|
|
|
|
// // 전역변수와 ol 지도에 포인트 레이어 저장 및 추가
|
|
// vars.olPointLayer = pointLayer;
|
|
// ol.map.addLayer(pointLayer);
|
|
// }
|
|
|
|
// // 새로 업로드된 파일 데이터만 가져오기
|
|
// let newFiles = [];
|
|
// if (newResourcePathArr) {
|
|
// for (let path of newResourcePathArr) {
|
|
|
|
// // let fileName = path.split('/').pop();
|
|
|
|
// let fileName;
|
|
// if (typeof path === 'object') {
|
|
// fileName = path.to.split('/').pop();
|
|
// } else {
|
|
// fileName = path.split('/').pop();
|
|
// }
|
|
|
|
// // 마지막이 빈 문자열이면 제거
|
|
// if (fileName === '') {
|
|
// let pathSplit = path.split('/');
|
|
// pathSplit.pop();
|
|
// fileName = pathSplit.pop();
|
|
// }
|
|
|
|
// // if (vars.currentTreeObject.file && vars.currentTreeObject.file[fileName]) {
|
|
// // newFiles[fileName] = vars.currentTreeObject.file[fileName];
|
|
// // }
|
|
|
|
// // newFiles[fileName] = vars.currentTreeObject.file[fileName];
|
|
|
|
// // let fileName;
|
|
// // if (typeof path === 'object') {
|
|
// // fileName = path.to.split('/').pop();
|
|
// // } else {
|
|
// // fileName = path.split('/').pop();
|
|
// // }
|
|
|
|
// // if (fileName === '') {
|
|
// // let pathSplit = path.split('/');
|
|
// // pathSplit.pop();
|
|
// // fileName = pathSplit.pop();
|
|
// // }
|
|
|
|
// // 현재 폴더에서 먼저 찾고, 없으면 전체 트리에서 찾기 (파일업로드/파일이동)
|
|
// let fileObject = vars.currentTreeObject.file?.[fileName] || findFileInTree(vars.currentTreeObject, fileName);
|
|
|
|
// if (fileObject) newFiles[fileName] = fileObject;
|
|
|
|
// }
|
|
// }
|
|
|
|
// // 새 파일이 없으면 리턴
|
|
// if (Object.keys(newFiles).length === 0) return;
|
|
|
|
// // 새로 추가된 dataId 추적
|
|
// let newlyAddedIds = [];
|
|
|
|
// // 새 파일에 대해서만 map item 생성
|
|
// let entries = sortData(newFiles);
|
|
// for (let i = 0; i<entries.length; i++) {
|
|
// let entry = entries[i];
|
|
|
|
// let key = entry[0];
|
|
// let value = entry[1];
|
|
|
|
// let ext = (value.ext).toLowerCase();
|
|
|
|
// // 파일 resourcePath 맨 마지막에 / 붙어있으면 제거
|
|
// let resourcePathSplit = value.resourcePath.split('/');
|
|
// if(resourcePathSplit[resourcePathSplit.length-1] == '') resourcePathSplit.pop();
|
|
// let resourcePath = resourcePathSplit.join('/');
|
|
|
|
// let dataId = value.dataId;
|
|
|
|
// let lon = value.lon;
|
|
// let lat = value.lat;
|
|
|
|
// //// 경도/위도 정보가 있으면 지도에 오버레이 표시
|
|
// if (lon && lat) {
|
|
// let mapItem = document.createElement('div');
|
|
// mapItem.classList.add('list-item');
|
|
// mapItem.classList.add('map-item');
|
|
// mapItem.classList.add(`depth${value.depth}`);
|
|
// mapItem.classList.add('file');
|
|
// mapItem.dataset.resourcePath = resourcePath;
|
|
// mapItem.dataset.id = dataId;
|
|
// mapItem.dataset.size = value.size;
|
|
// mapItem.classList.add('main-list-item');
|
|
// // 확장자가 유효한 경우 클래스에 ext 추가
|
|
// if (isValidExt(ext)) mapItem.classList.add(ext);
|
|
|
|
// let wrap = document.createElement('div');
|
|
// wrap.classList.add('wrap');
|
|
|
|
// // 동영상 표시 아이콘
|
|
// let play = document.createElement('div');
|
|
// play.classList.add('play');
|
|
// let playImage = document.createElement('div');
|
|
// playImage.classList.add('image');
|
|
// play.appendChild(playImage);
|
|
|
|
// // 위치 텍스트 저장
|
|
// let gpsData = document.createElement('div');
|
|
// gpsData.classList.add('gps-data');
|
|
// let gpsDataText = document.createElement('div');
|
|
// gpsDataText.classList.add('text');
|
|
// gpsDataText.innerHTML = `경도: ${(value.lon) ? value.lon : '없음'} / 위도: ${(value.lat) ? value.lat : '없음'}`;
|
|
// gpsDataText.style.display = 'none';
|
|
// gpsData.appendChild(gpsDataText);
|
|
|
|
// // 썸네일
|
|
// let thumbnail = document.createElement('img');
|
|
// thumbnail.classList.add('thumbnail');
|
|
// thumbnail.classList.add('cover');
|
|
|
|
// // 파일명
|
|
// let name = document.createElement('div');
|
|
// name.classList.add('name');
|
|
// name.classList.add('thumbnail-title');
|
|
// let nameText = document.createElement('div');
|
|
// nameText.classList.add('name-text');
|
|
// nameText.classList.add('text');
|
|
// nameText.classList.add('ft-14');
|
|
// nameText.classList.add('ellipsis');
|
|
// nameText.innerHTML = key;
|
|
// name.appendChild(nameText);
|
|
|
|
// // 등록자
|
|
// let uploader = document.createElement('div');
|
|
// uploader.classList.add('uploader');
|
|
// uploader.classList.add('thumbnail-title');
|
|
// let uploaderText = document.createElement('div');
|
|
// uploaderText.classList.add('text');
|
|
// uploaderText.classList.add('ft-14');
|
|
// uploaderText.innerHTML = '-';
|
|
// if (value.userNm) uploaderText.innerHTML = value.userNm;
|
|
// uploader.appendChild(uploaderText);
|
|
|
|
// // 등록일자
|
|
// let createDate = document.createElement('div');
|
|
// createDate.classList.add('create-date');
|
|
// createDate.classList.add('thumbnail-title');
|
|
// let createDateText = document.createElement('div');
|
|
// createDateText.classList.add('text');
|
|
// createDateText.classList.add('ft-14');
|
|
// createDateText.innerHTML = '-';
|
|
// if (value.createDate) {
|
|
// let date = new Date(value.createDate);
|
|
// createDateText.innerHTML = `${changeDateFormat(date.toLocaleDateString('ko-KR'))} ${changeTimeFormat(date.toLocaleTimeString('en-US', {hour12: false}))}`;
|
|
// }
|
|
// createDate.appendChild(createDateText);
|
|
|
|
// // 용량
|
|
// let size = document.createElement('div');
|
|
// size.classList.add('size');
|
|
// size.classList.add('thumbnail-title');
|
|
// let sizeText = document.createElement('div');
|
|
// sizeText.classList.add('text');
|
|
// sizeText.classList.add('ft-14');
|
|
// sizeText.innerHTML = formatBytes(value.size);
|
|
// if (value.size >= 1073741824) sizeText.classList.add('large');
|
|
// size.appendChild(sizeText);
|
|
|
|
// // 작성자
|
|
// let author = document.createElement('div');
|
|
// author.classList.add('author');
|
|
// author.classList.add('thumbnail-title');
|
|
// let authorText = document.createElement('div');
|
|
// authorText.classList.add('text');
|
|
// authorText.classList.add('ft-14');
|
|
// authorText.innerHTML = (value.authorNm) ? value.authorNm : value.userNm;
|
|
// author.appendChild(authorText);
|
|
|
|
// name.style.display = 'none';
|
|
// uploader.style.display = 'none';
|
|
// createDate.style.display = 'none';
|
|
// size.style.display = 'none';
|
|
// author.style.display = 'none';
|
|
|
|
// if (videoExtArr.includes(ext)) wrap.appendChild(play);
|
|
// wrap.appendChild(gpsData);
|
|
// wrap.appendChild(thumbnail);
|
|
// wrap.appendChild(name);
|
|
// wrap.appendChild(uploader);
|
|
// wrap.appendChild(createDate);
|
|
// wrap.appendChild(size);
|
|
// wrap.appendChild(author);
|
|
|
|
// mapItem.appendChild(wrap);
|
|
|
|
// // mapItem 숨김 상태로 생성
|
|
// mapItem.style.display = 'none';
|
|
|
|
// mapItem.addEventListener('click', async function(e) {
|
|
// // 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴
|
|
// if (vars.mapMode == 'edit') return;
|
|
|
|
// // 이전에 선택된 클러스터 선택 상태 해제
|
|
// if (vars.olClusterLayer && vars.lastSelectCluster) {
|
|
// vars.lastSelectCluster.set('selected', false);
|
|
// vars.lastSelectCluster.set('clicked', false);
|
|
// vars.lastSelectCluster = null;
|
|
// vars.olClusterLayer.changed();
|
|
// }
|
|
|
|
// // clusterListWrap 숨김
|
|
// let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap');
|
|
// if (clusterListWrap) clusterListWrap.style.display = 'none';
|
|
|
|
// let option = {
|
|
// from: '오버레이 클릭'
|
|
// }
|
|
// toggleArchiveMainRight(true, option);
|
|
|
|
// 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);
|
|
|
|
// vars.viewerConnectingTime = vars.viewerConnectingTimeValue;
|
|
// if (value.isSupported == false || (value.needConvert == true && value.isConverted == false)) {
|
|
// // 미지원 파일인 경우 -> value.isSupported == false
|
|
// // 변환 필요 파일인 경우 -> value.needConvert == true && value.isConverted == false (변환해야되지만 아직 변환 안한 상태)
|
|
// // 뷰어 프로그레스 표시 시간 0으로 설정
|
|
// vars.viewerConnectingTime = 0;
|
|
// }
|
|
|
|
// renderViewer(resourcePath, dataId);
|
|
|
|
// vars.lastSelectTarget = mapItem;
|
|
|
|
// vars.multiSelectListItemArr = [];
|
|
|
|
// changeListItemStyle(e.target);
|
|
// })
|
|
|
|
// let zoom = ol.map.getView().getZoom();
|
|
// let scale = Math.max(0.5, Math.min(2, zoom / 10));
|
|
// mapItem.style.setProperty('--overlay-scale-transform', `scale(${scale})`);
|
|
|
|
// let coord3857 = ol.proj.fromLonLat([lon, lat]);
|
|
|
|
// // 오버레이 생성
|
|
// let overlay = new ol.Overlay({
|
|
// element: mapItem,
|
|
// position: coord3857,
|
|
// positioning: 'bottom-center',
|
|
// stopEvent: false
|
|
// });
|
|
// // 지도에 오버레이 추가
|
|
// ol.map.addOverlay(overlay);
|
|
|
|
// // 피쳐 생성
|
|
// let feature = new ol.Feature({
|
|
// geometry: new ol.geom.Point(coord3857),
|
|
// dataId: dataId
|
|
// });
|
|
// // 피쳐에 오버레이 속성 추가
|
|
// // -> 클러스터 클릭 시 해당 클러스터에 포함된 피쳐를 탐색하고,
|
|
// // 각 피쳐에 포함된 오버레이 속성을 클러스터 리스트 렌더에 사용
|
|
// feature.set('overlay', overlay);
|
|
// // 포인트 소스에 피쳐 추가
|
|
// pointSource.addFeature(feature);
|
|
|
|
// // overlayArr/featureArr에 각각 오버레이/피쳐 추가
|
|
// overlayArr.push(overlay);
|
|
// featureArr.push(feature);
|
|
// newlyAddedIds.push(dataId); // 새로 추가된 ID 기록
|
|
|
|
// if (value.thumbnailKey) {
|
|
// let objectKey = value.thumbnailKey;
|
|
// let generateDownloadUrlParams = {
|
|
// objectKey: objectKey,
|
|
// resourcePath: resourcePath,
|
|
// isThumbail: true
|
|
// };
|
|
// let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
|
|
|
|
// // map-item이 만들어지는 depth3 폴더와 현재 보고 있는 depth3 폴더가 다르면 continue
|
|
// if (currentTreeItem != vars.lastMainTreeItem) continue;
|
|
|
|
// if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
|
|
// let presigendUrl = generateDownloadUrlRes.data.url;
|
|
// thumbnail.src = presigendUrl;
|
|
// } else {
|
|
// let src = '/main/img/archive/file_extension/file.svg';
|
|
// if (value.ext == 'pdf') src = '/main/img/archive/file_extension/pdf.svg';
|
|
// if (value.ext == 'hwp') src = '/main/img/archive/file_extension/hwp.svg';
|
|
// if (value.ext == 'hwpx') src = '/main/img/archive/file_extension/hwp.svg';
|
|
// if (value.ext == 'doc') src = '/main/img/archive/file_extension/doc.svg';
|
|
// if (value.ext == 'docx') src = '/main/img/archive/file_extension/doc.svg';
|
|
// if (value.ext == 'xls') src = '/main/img/archive/file_extension/xls.svg';
|
|
// if (value.ext == 'xlsx') src = '/main/img/archive/file_extension/xls.svg';
|
|
// if (value.ext == 'xlsm') src = '/main/img/archive/file_extension/xls.svg';
|
|
// if (value.ext == 'ppt') src = '/main/img/archive/file_extension/ppt.svg';
|
|
// if (value.ext == 'pptx') src = '/main/img/archive/file_extension/ppt.svg';
|
|
// if (value.ext == 'dwg') src = '/main/img/archive/file_extension/dwg.svg';
|
|
// if (value.ext == 'dxf') src = '/main/img/archive/file_extension/dwg.svg';
|
|
// if (value.ext == 'grm') src = '/main/img/archive/file_extension/dwg.svg';
|
|
// if (value.ext == 'mp3') src = '/main/img/archive/file_extension/audio.svg';
|
|
// if (value.ext == 'wav') src = '/main/img/archive/file_extension/audio.svg';
|
|
// if (value.ext == 'jpg') src = '/main/img/archive/file_extension/img.svg';
|
|
// if (value.ext == 'jpeg') src = '/main/img/archive/file_extension/img.svg';
|
|
// if (value.ext == 'png') src = '/main/img/archive/file_extension/img.svg';
|
|
// if (value.ext == 'mp4') src = '/main/img/archive/file_extension/video.svg';
|
|
// if (value.ext == 'webm') src = '/main/img/archive/file_extension/video.svg';
|
|
// if (value.ext == 'mov') src = '/main/img/archive/file_extension/video.svg';
|
|
// if (value.ext == 'avi') src = '/main/img/archive/file_extension/video.svg';
|
|
// if (value.ext == 'mkv') src = '/main/img/archive/file_extension/video.svg';
|
|
// if (value.ext == 'zip') src = '/main/img/archive/file_extension/zip.svg';
|
|
// if (value.ext == 'txt') src = '/main/img/archive/file_extension/text.svg';
|
|
// if (value.ext == 'gsim') src = '/main/img/archive/file_extension/gsim.svg';
|
|
// if (value.ext == 'ifc') src = '/main/img/archive/file_extension/3d.svg';
|
|
|
|
// thumbnail.src = src;
|
|
// }
|
|
// } else {
|
|
// // thumbnailKey가 없는 경우 기본 아이콘
|
|
// let src = '/main/img/archive/file_extension/file.svg';
|
|
// if (value.ext == 'pdf') src = '/main/img/archive/file_extension/pdf.svg';
|
|
// if (value.ext == 'hwp' || value.ext == 'hwpx') src = '/main/img/archive/file_extension/hwp.svg';
|
|
// if (value.ext == 'doc' || value.ext == 'docx') src = '/main/img/archive/file_extension/doc.svg';
|
|
// if (value.ext == 'xls' || value.ext == 'xlsx' || value.ext == 'xlsm') src = '/main/img/archive/file_extension/xls.svg';
|
|
// if (value.ext == 'ppt' || value.ext == 'pptx') src = '/main/img/archive/file_extension/ppt.svg';
|
|
// if (value.ext == 'dwg' || value.ext == 'dxf' || value.ext == 'grm') src = '/main/img/archive/file_extension/dwg.svg';
|
|
// if (value.ext == 'mp3' || value.ext == 'wav') src = '/main/img/archive/file_extension/audio.svg';
|
|
// if (value.ext == 'jpg' || value.ext == 'jpeg' || value.ext == 'png') src = '/main/img/archive/file_extension/img.svg';
|
|
// if (value.ext == 'mp4' || value.ext == 'webm' || value.ext == 'mov' || value.ext == 'avi' || value.ext == 'mkv') src = '/main/img/archive/file_extension/video.svg';
|
|
// if (value.ext == 'zip') src = '/main/img/archive/file_extension/zip.svg';
|
|
// if (value.ext == 'txt') src = '/main/img/archive/file_extension/text.svg';
|
|
// if (value.ext == 'gsim') src = '/main/img/archive/file_extension/gsim.svg';
|
|
// if (value.ext == 'ifc') src = '/main/img/archive/file_extension/3d.svg';
|
|
// thumbnail.src = src;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// //// 클러스터 레이어 재생성
|
|
// let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap');
|
|
|
|
// // 1. 기존 클러스터 레이어 제거
|
|
// if (vars.olClusterLayer) {
|
|
// ol.map.removeLayer(vars.olClusterLayer);
|
|
// }
|
|
|
|
// // 2. 전체 피처로 클러스터 소스 생성 (기존 + 새로운 피처)
|
|
// let allFeatures = pointSource.getFeatures(); // 포인트 소스의 모든 피처 가져오기
|
|
// let rawSource = new ol.source.Vector({ features: allFeatures });
|
|
// let clusterSource = new ol.source.Cluster({
|
|
// distance: vars.clusterDistance,
|
|
// minDistance: 0,
|
|
// source: rawSource
|
|
// });
|
|
|
|
// // 최소 거리(minDistance) 계산 후 적용
|
|
// let radius = 20; // circle radius (스타일에서 쓰는 값과 동일)
|
|
// let strokeWidth = 4; // stroke width
|
|
// let padding = 6; // 여유
|
|
// let wantMin = 2 * radius + strokeWidth + padding; // 대략 50px
|
|
// clusterSource.setDistance(Math.max(clusterSource.getDistance(), wantMin));
|
|
// if (clusterSource.setMinDistance) {
|
|
// clusterSource.setMinDistance(wantMin);
|
|
// }
|
|
|
|
// // 클러스터 소스 강제 갱신 및 완료 대기
|
|
// clusterSource.refresh();
|
|
|
|
// // 클러스터링이 완료될 때까지 명시적으로 대기
|
|
// await new Promise(resolve => {
|
|
// // 클러스터 소스의 변경 이벤트를 감지
|
|
// const onClusterChange = () => {
|
|
// clusterSource.un('change', onClusterChange);
|
|
// resolve();
|
|
// };
|
|
// clusterSource.on('change', onClusterChange);
|
|
|
|
// // 타임아웃 설정 (최대 100ms 대기)
|
|
// setTimeout(resolve, 100);
|
|
// });
|
|
|
|
// let clusterStyleCache = { normal: {}, hover: {}, selected: {}, clicked: {} };
|
|
// let clusterLayer = new ol.layer.Vector({
|
|
// source: clusterSource,
|
|
// declutter: false,
|
|
// style: (clusterFeature) => {
|
|
// const members = clusterFeature.get('features');
|
|
// const size = members?.length || 0;
|
|
// if (size <= 1) return null;
|
|
|
|
// let selectedId = vars.lastSelectTarget?.dataset?.id;
|
|
// let isSelectedCluster = selectedId && members.some(f => String(f.get('dataId')) === String(selectedId));
|
|
|
|
// let state = 'normal';
|
|
// if (isSelectedCluster) state = 'selected';
|
|
// if (clusterFeature.get('hover')) state = 'hover';
|
|
// if (clusterFeature.get('clicked')) state = 'clicked';
|
|
|
|
// return clusterStyleFor(size, state, clusterStyleCache);
|
|
// }
|
|
// });
|
|
|
|
// // 3. 새 클러스터 레이어 추가
|
|
// vars.olClusterLayer = clusterLayer;
|
|
// ol.map.addLayer(clusterLayer);
|
|
|
|
// let lastHoveredCluster = null; // 마지막으로 hover된 클러스터
|
|
// let hoverSeq = 0; // 비동기 응답 순서 보호용
|
|
// ol.map.on('pointermove', (evt) => {
|
|
// // 드래그 중인 경우 리턴 (성능/깜빡임 방지)
|
|
// if (evt.dragging) return;
|
|
|
|
// // 위치 수정 모드인 경우 리턴
|
|
// if (document.querySelector('.list-container .list-body .map-container').classList.contains('edit-mode')) return;
|
|
|
|
// // 클러스터 레이어가 숨겨진 상태인 경우 리턴
|
|
// if (clusterLayer.getVisible() == false) return;
|
|
|
|
// const seq = ++hoverSeq;
|
|
// // 클러스터 레이어만 히트 검사 (비동기)
|
|
// clusterLayer.getFeatures(evt.pixel).then((hits) => {
|
|
// if (seq !== hoverSeq) return; // 이전 pointermove의 지연 응답 무시
|
|
|
|
// let cf = null;
|
|
// if (hits && hits.length) {
|
|
// const cand = hits[0];
|
|
// const members = cand.get('features');
|
|
// if (members && members.length > 1) cf = cand; // 진짜 클러스터만 hover 대상
|
|
// }
|
|
|
|
// // 기존 hover 해제
|
|
// if (lastHoveredCluster && lastHoveredCluster !== cf) {
|
|
// lastHoveredCluster.set('hover', false);
|
|
// lastHoveredCluster = null;
|
|
// clusterLayer.changed(); // 재렌더
|
|
// }
|
|
|
|
// // 새 hover 설정
|
|
// if (cf) {
|
|
// if (lastHoveredCluster !== cf) {
|
|
// cf.set('hover', true);
|
|
// lastHoveredCluster = cf;
|
|
// clusterLayer.changed();
|
|
// }
|
|
// ol.map.getTargetElement().style.cursor = 'pointer';
|
|
// } else {
|
|
// ol.map.getTargetElement().style.cursor = 'default';
|
|
// }
|
|
// });
|
|
// });
|
|
|
|
// // 맵 영역을 벗어났을 때 hover 리셋
|
|
// ol.map.getTargetElement().addEventListener('mouseleave', () => {
|
|
// if (lastHoveredCluster) {
|
|
// lastHoveredCluster.set('hover', false);
|
|
// lastHoveredCluster = null;
|
|
// clusterLayer.changed();
|
|
// }
|
|
// ol.map.getTargetElement().style.cursor = 'default';
|
|
// });
|
|
|
|
// let startZoom, endZoom;
|
|
// ol.map.on('movestart', () => {
|
|
// startZoom = ol.map.getView().getZoom();
|
|
|
|
// // 지도 이동 및 줌 변경 시작할 때 map-container에 overlay-suspend 클래스 추가해서 오버레이 숨김
|
|
// document.querySelector('.map-container').classList.add('overlay-suspend');
|
|
// })
|
|
|
|
// // 이동/줌 종료 시 동기화 - 렌더가 끝난 뒤에 한 번만 실행
|
|
// ol.map.on('moveend', async (e) => {
|
|
// endZoom = ol.map.getView().getZoom();
|
|
|
|
// if (endZoom >= vars.allOverlayVisibleZoom) {
|
|
// // 줌 변경 종료 시점의 줌 레벨이 vars.allOverlayVisibleZoom 이상이면 클러스터레이어 숨기고 포인트 레이어 표시
|
|
// vars.olClusterLayer.setVisible(false);
|
|
// vars.olPointLayer.setVisible(true);
|
|
// } else {
|
|
// // 줌 변경 종료 시점의 줌 레벨이 vars.allOverlayVisibleZoom 보다 작으면 클러스터레이어 표시하고 포인트 레이어 숨김
|
|
// vars.olClusterLayer.setVisible(true);
|
|
// vars.olPointLayer.setVisible(false);
|
|
|
|
// await requestAnimationFrame(() => {
|
|
// reapplyClickedByLastOverlay(clusterSource, clusterLayer, startZoom, endZoom);
|
|
// })
|
|
// clusterLayer.changed();
|
|
// }
|
|
|
|
// ol.map.once('rendercomplete', async() => {
|
|
// // 클러스터에 포함된 오버레이는 숨기고 포함되지 않은 오버레이는 표시
|
|
// // bufferPx - 양수: 뷰포트 범위 바깥까지 오버레이 표시 영역 확장 / 음수: 뷰포트 범위 안쪽으로 오버레이 표시 영역 축소
|
|
// let bufferPx = -50;
|
|
// let allOverlays = ol.map.getOverlays().getArray();
|
|
// // moveend에서는 newlyAddedIds를 빈 배열로 전달 (모든 오버레이를 일반적으로 처리)
|
|
// await syncOverlaysByCluster(allOverlays, clusterSource, bufferPx, []);
|
|
|
|
// clusterLayer.changed();
|
|
|
|
// await requestAnimationFrame(() => {
|
|
// // 지도 이동 및 줌 변경 종료할 때 map-container에 overlay-suspend 클래스 제거해서 오버레이 표시
|
|
// document.querySelector('.map-container').classList.remove('overlay-suspend');
|
|
// })
|
|
// })
|
|
// })
|
|
|
|
// ol.map.on('click', (e) => {
|
|
// // 클릭한 대상이 오버레이인 경우 리턴
|
|
// if (e.originalEvent?.target.classList.contains('list-item')) return;
|
|
|
|
// // 위치 수정 모드인 경우 리턴
|
|
// if (document.querySelector('.list-container .list-body .map-container').classList.contains('edit-mode')) return;
|
|
|
|
// // 클러스터 레이어가 숨겨진 상태인 경우 리턴
|
|
// if (clusterLayer.getVisible() == false) return;
|
|
|
|
// // 이 레이어(clusteLayer)에서만 히트 검사
|
|
// clusterLayer.getFeatures(e.pixel).then(async (clickedFeatures) => {
|
|
// if (!clickedFeatures || !clickedFeatures.length) {
|
|
// // 아무 것도 안 눌렀으면 이전에 선택된 클러스터 선택 상태 해제
|
|
// if (vars.lastSelectCluster) {
|
|
// // vars.lastSelectCluster.set('selected', false);
|
|
// vars.lastSelectCluster.set('clicked', false);
|
|
// vars.lastSelectCluster = null;
|
|
// clusterLayer.changed();
|
|
// }
|
|
|
|
// // 클러스터 아이템 리스트 닫기
|
|
// clusterListWrap.style.display = 'none';
|
|
|
|
// return;
|
|
// }
|
|
|
|
// // 클릭된 건(보통 1개)의 내부 멤버(원본 피처들)
|
|
// let clusterFeature = clickedFeatures[0];
|
|
// let items = clusterFeature.get('features');
|
|
// if (!items || items.length <= 1) {
|
|
// // 단건이면 이전에 선택된 클러스터 선택 상태 해제
|
|
// if (vars.lastSelectCluster) {
|
|
// // vars.lastSelectCluster.set('selected', false);
|
|
// vars.lastSelectCluster.set('clicked', false);
|
|
// vars.lastSelectCluster = null;
|
|
// clusterLayer.changed();
|
|
// }
|
|
|
|
// return;
|
|
// }
|
|
|
|
// // 기존 선택된 클러스터 선택 상태 해제
|
|
// for (let i = 0; i < clusterSource.getFeatures().length; i++) {
|
|
// let feature = clusterSource.getFeatures()[i];
|
|
// feature.set('clicked', false);
|
|
// }
|
|
|
|
// // 새로 선택한 클러스터에 선택 상태 설정
|
|
// vars.lastSelectCluster = clusterFeature;
|
|
|
|
// // 스타일 갱신
|
|
// clusterLayer.changed();
|
|
|
|
// // 갤러리 폴더에서 위치 수정 모드가 실행중인 경우 리턴
|
|
// if (vars.mapMode == 'edit') return;
|
|
|
|
// // 클러스터 리스트 렌더링
|
|
// await renderClusterList(items, clusterLayer);
|
|
|
|
// // 새로 선택한 클러스터에 선택 상태 설정
|
|
// clusterFeature.set('clicked', true);
|
|
// vars.lastSelectCluster.set('clicked', true);
|
|
|
|
// // 스타일 갱신
|
|
// clusterLayer.changed();
|
|
// });
|
|
|
|
// let option = {
|
|
// from: '지도 영역 클릭'
|
|
// }
|
|
// toggleArchiveMainRight(false, option);
|
|
// });
|
|
|
|
// // 아카이브 우측 영역 표시/숨김
|
|
// if (vars.lastListItem && document.querySelector(`.map-item[data-id="${vars.lastListItem.dataset.id}"]`) && vars.mapMode == 'normal') {
|
|
// let option = {
|
|
// from: '지도 렌더링 -미리보기 표시',
|
|
// }
|
|
// toggleArchiveMainRight(true, option);
|
|
// } else {
|
|
// let option = {
|
|
// from: '지도 렌더링 - 미리보기 숨김',
|
|
// }
|
|
// toggleArchiveMainRight(false, option);
|
|
// }
|
|
|
|
// if (vars.lastSelectTarget && vars.lastSelectTarget.classList.contains('list-item')) {
|
|
// let dataId = vars.lastSelectTarget.dataset.id;
|
|
// let item = document.querySelector(`.map-item[data-id="${dataId}"]`);
|
|
// if (item) {
|
|
// changeListItemStyle(item);
|
|
// }
|
|
// }
|
|
|
|
// // 지도 모드 전환 시 모든 오버레이의 범위에 맞게 카메라 이동하도록 overlayPositionArr 변수 생성
|
|
// let overlayPositionArr = overlayArr.map(overlay => overlay.getPosition());
|
|
|
|
// if (vars.mapMode == 'edit') {
|
|
// let validCoordResult = isValidCoord();
|
|
// if (validCoordResult == true) {
|
|
// let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
// let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
// let lon = lonInput.value;
|
|
// let lat = latInput.value;
|
|
|
|
// let overlayPosition = ol.proj.fromLonLat([lon, lat]);
|
|
// // 위치 수정 모드 실행 시 경도/위도 정보가 있으면 해당 위치로 카메라 이동하도록 overlayPositionArr 변수 갱신
|
|
// overlayPositionArr = [overlayPosition];
|
|
// // 마커 표시
|
|
// vars.markerOverlay.setPosition(overlayPosition);
|
|
// }
|
|
// }
|
|
|
|
// // 줌 레벨에 따라 오버레이 크기 조절
|
|
// await scaleOverlaysByZoom();
|
|
|
|
// //// 오버레이 동기화 시 새로 추가된 ID 전달
|
|
// let bufferPx = -50;
|
|
|
|
// // 모든 오버레이 배열 가져오기 (기존 + 신규)
|
|
// let allOverlays = ol.map.getOverlays().getArray();
|
|
|
|
// // 클러스터 소스가 완전히 준비될 때까지 추가 대기
|
|
// await new Promise(resolve => setTimeout(resolve, 50));
|
|
|
|
// // **즉시 클러스터 상태 반영하여 오버레이 동기화**
|
|
// await syncOverlaysByCluster(allOverlays, clusterSource, bufferPx, newlyAddedIds);
|
|
|
|
// // 클러스터 레이어 강제 갱신
|
|
// clusterLayer.changed();
|
|
|
|
// // 포인트 레이어도 갱신
|
|
// pointLayer.changed();
|
|
|
|
// // requestAnimationFrame을 사용하여 DOM 업데이트 보장
|
|
// await new Promise(resolve => {
|
|
// requestAnimationFrame(() => {
|
|
// // 지도 렌더링 강제 트리거
|
|
// ol.map.render();
|
|
|
|
// // 한 프레임 더 대기 후 완료
|
|
// requestAnimationFrame(() => {
|
|
// document.querySelector('.map-container')?.classList.remove('overlay-suspend');
|
|
// resolve();
|
|
// });
|
|
// });
|
|
// });
|
|
// }
|
|
|
|
// 전체 트리에서 파일 찾는 함수
|
|
// function findFileInTree(obj, fileName) {
|
|
// if (obj.file && obj.file[fileName]) {
|
|
// return obj.file[fileName]
|
|
// };
|
|
|
|
// if (obj.folder) {
|
|
// for (let key in obj.folder) {
|
|
// const result = findFileInTree(obj.folder[key], fileName);
|
|
// if (result) return result;
|
|
// }
|
|
// };
|
|
|
|
// if (obj.child) {
|
|
// const result = findFileInTree(obj.child, fileName);
|
|
// if (result) return result;
|
|
// }
|
|
|
|
// return null;
|
|
// }
|
|
|
|
function isValidCoord() {
|
|
let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
let lon = lonInput.value;
|
|
let lat = latInput.value;
|
|
|
|
let re = /^\s*[+-]?(?:\d+(?:\.\d+)?|\.\d+)\s*$/;
|
|
let lonCheck = re.test(lon);
|
|
let latCheck = re.test(lat);
|
|
if (lonCheck == false || latCheck == false) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function updateMarker() {
|
|
let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
let lon = lonInput.value;
|
|
let lat = latInput.value;
|
|
|
|
if (lon == '없음' || lat == '없음') {
|
|
ol.map.removeOverlay(vars.markerOverlay);
|
|
return;
|
|
}
|
|
|
|
let validCoordResult = isValidCoord();
|
|
if (validCoordResult == false) return;
|
|
|
|
let coord4326 = ol.proj.fromLonLat([lon, lat]);
|
|
vars.markerOverlay.setPosition(coord4326);
|
|
ol.map.addOverlay(vars.markerOverlay);
|
|
|
|
let extent = new ol.extent.boundingExtent([coord4326]);
|
|
let paddingValue = 100;
|
|
ol.map.getView().fit(extent, {
|
|
padding: [paddingValue, paddingValue, paddingValue, paddingValue],
|
|
constrainResolution: true,
|
|
// maxZoom: 15
|
|
maxZoom: ol.map.getView().getZoom()
|
|
});
|
|
}
|
|
|
|
function checkCoordInputValue(value, type) {
|
|
// 공백 제거
|
|
let v = value.replace(/\s+/g, '');
|
|
|
|
// 허용 문자만 남기기 (숫자/.-)
|
|
v = v.replace(/[^\d\.\-]/g, '');
|
|
|
|
// 맨 앞의 '-'만 허용
|
|
v = v.replace(/(?!^)-/g, '');
|
|
if (v.indexOf('-') > 0) v = v.replace(/-/g, '');
|
|
|
|
// 점 하나만 허용
|
|
const firstDot = v.indexOf('.');
|
|
if (firstDot !== -1) {
|
|
v = v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, '');
|
|
}
|
|
|
|
// ".12" → "0.12", "-.12" → "-0.12"
|
|
if (v.startsWith('.')) v = '0' + v;
|
|
if (v.startsWith('-.')) v = v.replace('-.', '-0.');
|
|
|
|
|
|
// 입력 중 과도기 상태는 그대로 허용 (삭제/추가 편의)
|
|
if (v === '' || v === '-' || v === '0.' || v === '-0.') return v;
|
|
|
|
// 타입별 범위 계산
|
|
const maxAbs = type === 'lon' ? 180 : type === 'lat' ? 90 : null;
|
|
|
|
// 숫자로 해석되면 범위 클램프
|
|
const n = Number(v);
|
|
if (Number.isFinite(n) && maxAbs != null) {
|
|
const sign = n < 0 ? -1 : 1;
|
|
const abs = Math.abs(n);
|
|
|
|
if (abs > maxAbs) {
|
|
v = (sign < 0 ? '-' : '') + String(maxAbs); // 경계값으로 고정
|
|
// 필요시 소수 0채우기 등 추가 처리 가능
|
|
}
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
function fitToOverlayExtent(overlayPositionArr, onFitEnd) {
|
|
let extent = new ol.extent.boundingExtent(overlayPositionArr);
|
|
let paddingValue = 100;
|
|
|
|
function handlePostRender() {
|
|
ol.map.un('postrender', handlePostRender); // 한 번만 실행되도록 제거
|
|
if (typeof onFitEnd === 'function') {
|
|
onFitEnd();
|
|
}
|
|
}
|
|
|
|
ol.map.on('postrender', handlePostRender);
|
|
|
|
// maxZoom 기본값은 모든 오버레이를 표시하는 줌 레벨(vars.allOverlayVisibleZoom)로 설정
|
|
let maxZoom = vars.allOverlayVisibleZoom;
|
|
// overlayPositionArr의 length가 1이 아니고, 선택되어 있는 파일 아이템이 없는 경우
|
|
// 모든 오버레이를 표시하는 줌 레벨(vars.allOverlayVisibleZoom)보다 하나 아래 레벨로 설정해서 클러스터 표시
|
|
if (overlayPositionArr.length != 1 && !vars.lastListItem) maxZoom = vars.allOverlayVisibleZoom - 1;
|
|
|
|
ol.map.getView().fit(extent, {
|
|
padding: [paddingValue, paddingValue, paddingValue, paddingValue],
|
|
// duration: 500,
|
|
// constrainResolution: true,
|
|
maxZoom: maxZoom
|
|
});
|
|
}
|
|
|
|
function toggleMapProgress(state, progressData) {
|
|
let mapProgress = document.querySelector('.map-progress');
|
|
let mapCountname = document.querySelector('.map-progress .count-progress .fileName span');
|
|
let mapCountIdx = document.querySelector('.map-progress .count-progress .count .index span');
|
|
let mapCountTotal = document.querySelector('.map-progress .count-progress .count .total span');
|
|
|
|
if (state == false) mapProgress.style.display = 'none';
|
|
|
|
if (state == true) {
|
|
mapProgress.style.display = 'flex';
|
|
mapCountname.textContent = `${progressData.fileName}`;
|
|
mapCountIdx.textContent = `${progressData.idx}`;
|
|
mapCountTotal.textContent = `${progressData.count}`;
|
|
}
|
|
}
|
|
|
|
function scaleOverlaysByZoom() {
|
|
ol.map.getView().on('change:resolution', () => {
|
|
const zoom = ol.map.getView().getZoom();
|
|
const scale = Math.max(0.5, Math.min(2, zoom / 10));
|
|
|
|
document.querySelectorAll('.map-item').forEach(overlay => {
|
|
overlay.style.setProperty('--overlay-scale-transform', `scale(${scale})`);
|
|
overlay.style.transformOrigin = 'bottom center';
|
|
});
|
|
});
|
|
}
|
|
|
|
// size > 1 클러스터에 속한 feature id 집합 만들기
|
|
function getClusteredIds(clusterSource) {
|
|
const ids = new Set();
|
|
clusterSource.getFeatures().forEach(cf => {
|
|
const list = cf.get('features');
|
|
if (!list || list.length <= 1) return;
|
|
list.forEach(f => {
|
|
// 위에서 feature 생성 시 dataId를 넣었으므로 그 값을 사용
|
|
const id = f.get('dataId');
|
|
if (id != null) ids.add(String(id));
|
|
});
|
|
});
|
|
return ids;
|
|
}
|
|
|
|
/// 251104 박지은 ol 소켓처리
|
|
// async function syncOverlaysByCluster(overlayArr, clusterSource, bufferPx = 0, newlyAddedIds = []) {
|
|
// ID 매칭으로 오버레이 표시/숨김 동기화
|
|
async function syncOverlaysByCluster(overlayArr, clusterSource, bufferPx = 0) {
|
|
// 뷰포트(+버퍼) extent
|
|
const extent = getBufferedViewExtent(bufferPx);
|
|
const inView = (coord) => !extent || (coord && ol.extent.containsCoordinate(extent, coord));
|
|
|
|
// 현재 클러스터 멤버셋
|
|
const clustered = getClusteredIds(clusterSource); // Set<string>
|
|
|
|
/// 251104 박지은 ol 소켓처리
|
|
// 새로 추가된 ID들을 Set으로 변환 (빠른 검색)
|
|
// const newlyAddedSet = new Set(newlyAddedIds.map(id => String(id)));
|
|
|
|
// 줌 임계
|
|
const zoom = ol.map.getView().getZoom();
|
|
|
|
// 오버레이 DOM 토글
|
|
for (let ov of overlayArr) {
|
|
const el = ov.getElement();
|
|
if (!el) continue;
|
|
|
|
// map-item이 아니면 스킵
|
|
if (!el.classList.contains('map-item')) continue;
|
|
|
|
const id = el.dataset.id;
|
|
const pos = ov.getPosition();
|
|
const inside = inView(pos);
|
|
|
|
/// 251104 박지은 ol 소켓처리
|
|
// 새로 추가된 오버레이인지 확인
|
|
// const isNewlyAdded = newlyAddedSet.has(String(id));
|
|
|
|
// 클러스터에 포함되었는지 확인
|
|
const isInCluster = clustered.has(String(id));
|
|
|
|
let show;
|
|
if (zoom >= vars.allOverlayVisibleZoom) {
|
|
// 고배율: 항상 뷰포트(+버퍼) 안만 표시
|
|
show = inside;
|
|
} else {
|
|
// 저배율: 클러스터 멤버면 숨김 + 뷰포트 밖이면 숨김
|
|
show = !clustered.has(String(id)) && inside;
|
|
}
|
|
/// 251104 박지은 ol 소켓처리
|
|
// if (zoom >= vars.allOverlayVisibleZoom) {
|
|
// // 고배율: 클러스터 상관없이 뷰포트 안만 표시
|
|
// show = inside;
|
|
// } else {
|
|
// // 저배율: 클러스터에 포함되면 무조건 숨김
|
|
// if (isInCluster) {
|
|
// show = false;
|
|
// } else {
|
|
// // 클러스터에 없으면 뷰포트 확인
|
|
// // 새로 추가된 것은 뷰포트 체크 무시
|
|
// if (isNewlyAdded) {
|
|
// show = true;
|
|
// } else {
|
|
// show = inside;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
const currentDisplay = el.style.display;
|
|
const hidden = currentDisplay === 'none';
|
|
|
|
if (show && hidden) {
|
|
el.style.display = 'flex';
|
|
} else if (!show && !hidden) {
|
|
el.style.display = 'none';
|
|
}
|
|
|
|
// 실제 표시 중일 때만 리스트 닫고 하이라이트
|
|
if (show && vars.lastSelectTarget && vars.lastSelectTarget.dataset.id == id) {
|
|
let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap');
|
|
if (clusterListWrap) {
|
|
clusterListWrap.innerHTML = '';
|
|
clusterListWrap.style.display = 'none';
|
|
}
|
|
changeListItemStyle(el);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getBufferedViewExtent(bufferPx = 0) {
|
|
const map = ol.map;
|
|
const view = map.getView();
|
|
const size = map.getSize();
|
|
if (!view || !size) return null;
|
|
|
|
let extent = view.calculateExtent(size);
|
|
|
|
if (bufferPx !== 0) {
|
|
const res = view.getResolution(); // map-units/px
|
|
if (!res) return extent;
|
|
|
|
const b = bufferPx * res; // px → map-units
|
|
const w = extent[2] - extent[0];
|
|
const h = extent[3] - extent[1];
|
|
|
|
// 음수(축소)일 때 영역이 뒤집히지 않도록 클램프
|
|
const maxInset = Math.min(w, h) / 2 - 1e-6; // 남기는 최소 여유
|
|
const clamped = Math.max(-maxInset, b); // 너무 큰 음수 방지
|
|
|
|
extent = ol.extent.buffer(extent, clamped);
|
|
}
|
|
|
|
return extent;
|
|
}
|
|
|
|
// function clusterStyleFor(size, state, clusterStyleCache) {
|
|
// let cache = clusterStyleCache[state];
|
|
// if (!cache[size]) {
|
|
// let scale =
|
|
// state === 'selected' ? 1.2 :
|
|
// state === 'hover' ? 1.2 : 1.1;
|
|
|
|
// let strokeWidth =
|
|
// state === 'selected' ? 4.5 :
|
|
// state === 'hover' ? 4.5 : 4;
|
|
|
|
// let radius = 20 * scale;
|
|
|
|
// cache[size] = new ol.style.Style({
|
|
// image: new ol.style.Circle({
|
|
// radius,
|
|
// fill: new ol.style.Fill({ color: '#fff' }),
|
|
// stroke: new ol.style.Stroke({ color: state === 'selected' ? '#f9613b' : '#000', width: strokeWidth })
|
|
// }),
|
|
// // image: new ol.style.RegularShape({
|
|
// // points: 4, // 꼭짓점 4개
|
|
// // radius: radius * Math.SQRT2, // 원과 같은 가로폭(지름)을 맞추고 싶으면 √2 배
|
|
// // angle: Math.PI / 4, // 45도 회전 (각 변이 수평/수직)
|
|
// // fill: new ol.style.Fill({ color: '#fff' }),
|
|
// // stroke: new ol.style.Stroke({ color: state === 'selected' ? '#f9613b' : '#000', width: strokeWidth })
|
|
// // }),
|
|
// text: new ol.style.Text({
|
|
// font:
|
|
// state === 'selected' ? '14px sans-serif' :
|
|
// state === 'hover' ? '14px sans-serif' : '12px sans-serif',
|
|
// text: String(size),
|
|
// fill: new ol.style.Fill({ color: '#fff' }),
|
|
// stroke: new ol.style.Stroke({ color: '#000', width: 6 })
|
|
// })
|
|
// });
|
|
// }
|
|
// return cache[size];
|
|
// }
|
|
|
|
function clusterStyleFor(size, state, clusterStyleCache) {
|
|
let cache = clusterStyleCache[state];
|
|
if (!cache[size]) {
|
|
let scale, strokeWidth, strokeColor, font;
|
|
if (state == 'selected') {
|
|
scale = 1.2;
|
|
strokeWidth = 4.5;
|
|
strokeColor = '#f9613b';
|
|
font = '14px sans-serif';
|
|
}
|
|
if (state == 'hover') {
|
|
scale = 1.2;
|
|
strokeWidth = 4.5;
|
|
strokeColor = '#000';
|
|
font = '14px sans-serif';
|
|
}
|
|
if (state == 'clicked') {
|
|
scale = 1.2;
|
|
strokeWidth = 4.5;
|
|
strokeColor = '#3bb5f9';
|
|
font = '14px sans-serif';
|
|
}
|
|
if (state == 'normal') {
|
|
scale = 1.1;
|
|
strokeWidth = 4;
|
|
strokeColor = '#000';
|
|
font = '12px sans-serif';
|
|
}
|
|
|
|
let radius = 20 * scale;
|
|
|
|
cache[size] = new ol.style.Style({
|
|
image: new ol.style.Circle({
|
|
radius,
|
|
fill: new ol.style.Fill({ color: '#fff' }),
|
|
stroke: new ol.style.Stroke({ color: strokeColor, width: strokeWidth })
|
|
}),
|
|
text: new ol.style.Text({
|
|
font: font,
|
|
text: String(size),
|
|
fill: new ol.style.Fill({ color: '#fff' }),
|
|
stroke: new ol.style.Stroke({ color: '#000', width: 6 })
|
|
})
|
|
});
|
|
}
|
|
return cache[size];
|
|
}
|
|
|
|
function reapplyClickedByLastOverlay(clusterSource, clusterLayer, startZoom, endZoom) {
|
|
const el = vars.lastSelectTarget;
|
|
if (!el) return;
|
|
|
|
const id = el.dataset?.id;
|
|
if (!id) return;
|
|
|
|
// 현재 프레임의 클러스터들 중에서 해당 id를 포함하는 클러스터 찾기
|
|
const feats = clusterSource.getFeatures();
|
|
let target = null;
|
|
|
|
for (const cf of feats) {
|
|
const members = cf.get('features');
|
|
if (!members || members.length <= 1) continue;
|
|
if (members.some(f => String(f.get('dataId')) === String(id))) {
|
|
target = cf; break;
|
|
}
|
|
}
|
|
|
|
// 줌 변경 시에만(그리고 normal 모드일 때만) 리스트 다시 그림
|
|
if (startZoom && endZoom) {
|
|
if (target && startZoom != endZoom && vars.mapMode == 'normal') {
|
|
renderClusterList(target.get('features'), clusterLayer);
|
|
}
|
|
}
|
|
|
|
// 선택된 id를 기준으로 스타일이 다시 평가되도록 강제 리렌더
|
|
clusterLayer.changed();
|
|
}
|
|
|
|
function renderClusterList(items, clusterLayer) {
|
|
let clusterListWrap = document.querySelector('.map-container .cluster-list-wrap');
|
|
clusterListWrap.innerHTML = '';
|
|
clusterListWrap.style.display = 'flex';
|
|
|
|
for(let i = 0; i < items.length; i++) {
|
|
let item = items[i];
|
|
let element = item.values_.overlay.values_.element;
|
|
|
|
let thumbnailWrapClone = element.querySelector('.wrap').cloneNode(true);
|
|
thumbnailWrapClone.querySelector('.gps-data').remove();
|
|
thumbnailWrapClone.querySelector('.name').remove();
|
|
thumbnailWrapClone.querySelector('.uploader').remove();
|
|
thumbnailWrapClone.querySelector('.create-date').remove();
|
|
thumbnailWrapClone.querySelector('.size').remove();
|
|
|
|
let nameClone = element.querySelector('.name').cloneNode(true);
|
|
// nameClone.querySelector('.name-text').classList.remove('ft-14')
|
|
// nameClone.querySelector('.name-text').classList.add('ft-12')
|
|
nameClone.style.display = 'flex';
|
|
|
|
let resourcePath = element.dataset.resourcePath;
|
|
let dataId = element.dataset.id;
|
|
let clusterList = document.createElement('div');
|
|
clusterList.classList.add('cluster-list');
|
|
clusterList.dataset.id = dataId;
|
|
clusterList.dataset.resourcePath = resourcePath;
|
|
clusterList.appendChild(thumbnailWrapClone);
|
|
clusterList.appendChild(nameClone);
|
|
|
|
// vars.lastSelectTarget의 dataId와 동일한 dataId를 가진 clusterList에 selected 클래스 추가해서 선택 상태로 표시하고 포커스
|
|
if (vars.lastSelectTarget && vars.lastSelectTarget.dataset.id == dataId) {
|
|
clusterList.classList.add('selected');
|
|
|
|
// requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
// clusterList.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
|
|
targetFocus(clusterList, 'instant');
|
|
});
|
|
// });
|
|
}
|
|
|
|
clusterListWrap.appendChild(clusterList);
|
|
|
|
clusterList.addEventListener('click', async function(e) {
|
|
let option = {
|
|
from: '클러스터 리스트 클릭'
|
|
}
|
|
toggleArchiveMainRight(true, option);
|
|
|
|
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.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.remove('open');
|
|
// document.querySelector('.archive-main .archive-main-right .viewer-container .info-wrap').classList.add('close');
|
|
|
|
vars.viewerConnectingTime = vars.viewerConnectingTimeValue;
|
|
|
|
renderViewer(resourcePath, dataId);
|
|
|
|
vars.lastSelectTarget = document.querySelector(`.map-container .list-item[data-resource-path="${resourcePath}"]`);
|
|
changeListItemStyle(vars.lastSelectTarget);
|
|
vars.lastListItem = vars.lastSelectTarget;
|
|
|
|
vars.multiSelectListItemArr = [];
|
|
|
|
document.querySelectorAll('.cluster-list-wrap .cluster-list').forEach(clusterList => {
|
|
clusterList.classList.remove('selected');
|
|
})
|
|
e.target.classList.add('selected');
|
|
|
|
clusterLayer.changed();
|
|
|
|
// 클러스터 아이템 리스트에서 클릭한 대상의 위치로 지도 이동
|
|
let data = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject).data;
|
|
let overlayPosition = ol.proj.fromLonLat([data.lon, data.lat]);
|
|
let overlayPositionArr = [overlayPosition];
|
|
await fitToOverlayExtent(overlayPositionArr, async () => {});
|
|
})
|
|
}
|
|
}
|
|
|
|
export async function initEditPositionMode() {
|
|
document.querySelector('.control-box .file-area-mode-btn.map').click();
|
|
toggleArchiveMainRight(false);
|
|
|
|
vars.mapMode = 'edit';
|
|
document.querySelector('.map-container').classList.add('edit-mode');
|
|
|
|
// 위치 입력 부분 높이값
|
|
let coordWrapHeightRem = pxToRem(document.querySelector('.coord-wrap').getBoundingClientRect().height);
|
|
|
|
// 썸네일 wrap 복제
|
|
let thumbnailWrapClone = vars.lastListItem.querySelector('.wrap').cloneNode(true);
|
|
thumbnailWrapClone.querySelector('.highlight')?.remove();
|
|
thumbnailWrapClone.querySelector('.gps-data')?.remove();
|
|
thumbnailWrapClone.querySelector('.name')?.remove();
|
|
thumbnailWrapClone.querySelector('.uploader')?.remove();
|
|
thumbnailWrapClone.querySelector('.create-date')?.remove();
|
|
thumbnailWrapClone.querySelector('.size')?.remove();
|
|
thumbnailWrapClone.style.width = `${coordWrapHeightRem}rem`;
|
|
thumbnailWrapClone.style.minWidth = `${coordWrapHeightRem}rem`;
|
|
thumbnailWrapClone.style.maxWidth = `${coordWrapHeightRem}rem`;
|
|
thumbnailWrapClone.style.height = `${coordWrapHeightRem}rem`;
|
|
thumbnailWrapClone.style.minHeight = `${coordWrapHeightRem}rem`;
|
|
thumbnailWrapClone.style.maxHeight = `${coordWrapHeightRem}rem`;
|
|
thumbnailWrapClone.dataset.id = vars.lastListItem.dataset.id;
|
|
thumbnailWrapClone.dataset.resourcePath = vars.lastListItem.dataset.resourcePath;
|
|
|
|
// 파일명 복제
|
|
let nameClone = vars.lastListItem.querySelector('.name').cloneNode(true);
|
|
nameClone.style.display = 'flex';
|
|
|
|
// 현재 위치 수정중인 파일 정보 wrap에 복제한 썸네일 wrap, 파일명 추가
|
|
let editModeUi = document.querySelector('.edit-mode-ui');
|
|
let infoWrap = editModeUi.querySelector('.info-wrap');
|
|
infoWrap.style.height = `${coordWrapHeightRem}rem`;
|
|
infoWrap.innerHTML = '';
|
|
infoWrap.appendChild(thumbnailWrapClone);
|
|
infoWrap.appendChild(nameClone);
|
|
|
|
// 썸네일 wrap에 호버 이벤트 추가
|
|
thumbnailWrapClone.addEventListener('pointerover', function() {
|
|
if (document.querySelector('.popup-thumbnail')) document.querySelector('.popup-thumbnail').remove();
|
|
|
|
let popupThumbnailWrapClone = thumbnailWrapClone.cloneNode(true);
|
|
popupThumbnailWrapClone.classList.add('popup-thumbnail');
|
|
document.querySelector('.map-container .edit-mode-ui').appendChild(popupThumbnailWrapClone);
|
|
|
|
let editModeUiWidth = editModeUi.getBoundingClientRect().width;
|
|
let editModeUiHeight = editModeUi.getBoundingClientRect().height;
|
|
let rem;
|
|
if (editModeUiWidth >= editModeUiHeight) rem = pxToRem(editModeUiHeight);
|
|
if (editModeUiWidth < editModeUiHeight) rem = pxToRem(editModeUiWidth);
|
|
|
|
popupThumbnailWrapClone.style.width = `${rem*0.5}rem`;
|
|
popupThumbnailWrapClone.style.minWidth = `${rem*0.5}rem`;
|
|
popupThumbnailWrapClone.style.maxWidth = `${rem*0.5}rem`;
|
|
popupThumbnailWrapClone.style.height = `${rem*0.5}rem`;
|
|
popupThumbnailWrapClone.style.minHeight = `${rem*0.5}rem`;
|
|
popupThumbnailWrapClone.style.maxHeight = `${rem*0.5}rem`;
|
|
popupThumbnailWrapClone.style.background = '#aaa';
|
|
popupThumbnailWrapClone.style.border = '0.0625rem solid #ccc';
|
|
popupThumbnailWrapClone.style.borderRadius = '0.25rem';
|
|
popupThumbnailWrapClone.style.position = 'absolute';
|
|
popupThumbnailWrapClone.style.bottom = `${coordWrapHeightRem + 4}rem`;
|
|
popupThumbnailWrapClone.style.left = '50%';
|
|
popupThumbnailWrapClone.style.transform = 'translate(-50%, 0)';
|
|
popupThumbnailWrapClone.style.pointerEvents = 'none';
|
|
popupThumbnailWrapClone.style.zIndex = '9999';
|
|
})
|
|
thumbnailWrapClone.addEventListener('pointerleave', function() {
|
|
if (document.querySelector('.popup-thumbnail')) document.querySelector('.popup-thumbnail').remove();
|
|
})
|
|
|
|
let resourcePath = vars.lastListItem.dataset.resourcePath;
|
|
let data = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject).data;
|
|
let lon = (data.lon) ? data.lon : '없음';
|
|
let lat = (data.lat) ? data.lat : '없음';
|
|
let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
lonInput.value = lon;
|
|
latInput.value = lat;
|
|
}
|
|
|
|
export function deletePosition() {
|
|
let lonInput = document.querySelector('.edit-mode-ui .coord-input.lon');
|
|
let latInput = document.querySelector('.edit-mode-ui .coord-input.lat');
|
|
|
|
lonInput.value = '없음';
|
|
latInput.value = '없음';
|
|
updateMarker();
|
|
document.querySelector('.edit-mode-ui .save-btn').click();
|
|
}
|
|
|
|
export function renderMemo(fileData, dataId) {
|
|
// let infoWrap = document.querySelector('.archive-main-right .viewer-container .info-wrap');
|
|
const container = dataId
|
|
? document.querySelector(`.archive-main-right .viewer-container[data-data-id="${dataId}"]`)
|
|
: document.querySelector('.archive-main-right .viewer-container');
|
|
if (!container) return
|
|
|
|
let infoWrap = container.querySelector('.info-wrap'); // ★ scope 한정
|
|
if (!infoWrap) return;
|
|
|
|
let editBtn = infoWrap.querySelector('.memo .header .wrap .edit-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');
|
|
|
|
// editBtn 클래스에 edit 없으면 추가, save 있으면 삭제
|
|
// if (!editBtn.matches('.edit')) editBtn.classList.add('edit');
|
|
// if (editBtn.matches('.save')) editBtn.classList.remove('save');
|
|
|
|
// editBtn 텍스트 '수정'으로 변경
|
|
// if (editBtn) editBtn.querySelector('.text').innerText = '수정';
|
|
|
|
// 수정 상태 강제 초기화
|
|
if (editBtn?.classList.contains('save')) {
|
|
editBtn.classList.remove('save');
|
|
}
|
|
if (editBtn) editBtn.querySelector('.text').innerText = '수정';
|
|
|
|
// haederMessage 표시 상태인 경우 바로 숨김
|
|
editMessage.style.transition = '0s';
|
|
editMessage.style.opacity = '0';
|
|
|
|
// bodyWrap 테두리 강조 해제
|
|
bodyWrap.style.border = '1px solid #ddd';
|
|
bodyTextarea.disabled = true;
|
|
|
|
// bodyTextarea disabled 상태로 설정
|
|
bodyTextarea.disabled = true;
|
|
|
|
if(fileData == undefined || fileData == '') {
|
|
bodyTextarea.value = '';
|
|
}else{
|
|
bodyTextarea.value = fileData;
|
|
}
|
|
|
|
// 상태 초기화
|
|
// ** 권한 관련
|
|
let permission = JSON.parse(vars.userInfoString).permission;
|
|
if (!vars.permission.checkPermission('memo-text')) {
|
|
bodyMessage.innerText = '참관자 권한은 열람만 가능합니다.';
|
|
} else {
|
|
if(editBtn?.querySelector('.text').innerText == '수정') {
|
|
if (!editBtn.matches('.edit')) editBtn.classList.add ('edit');
|
|
bodyWrap.style.border = '1px solid #ddd';
|
|
bodyTextarea.disabled = true;
|
|
bodyMessage.innerText = '수정 버튼을 눌러 내용을 작성/수정할 수 있습니다.';
|
|
|
|
if (bodyTextarea.value == '') bodyMessage.style.color = '#777';
|
|
else bodyMessage.style.color = '#ddd';
|
|
|
|
}else {
|
|
if (editBtn?.matches('.save')) editBtn.classList.remove('save');
|
|
if (editBtn) editBtn.querySelector('.text').innerText = '저장';
|
|
bodyWrap.style.border = '1px solid #000'
|
|
bodyTextarea.disabled = false;
|
|
bodyMessage.innerText = '내용을 작성한 후 저장 버튼을 눌러주세요.';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
let renderTimer = null;
|
|
let progressFrame = null;
|
|
let progressStart = null;
|
|
export async function renderViewer(resourcePath, dataId, shouldAddClickLog = true) {
|
|
viewerContainer.style.display = 'flex';
|
|
viewerNotice.style.display = 'none';
|
|
infoWrap.style.height = '15%';
|
|
viewerToolbar.style.display = 'none';
|
|
metadata.style.display = 'none';
|
|
|
|
viewerContainer.dataset.resourcePath = resourcePath;
|
|
viewerContainer.dataset.dataId = dataId;
|
|
|
|
// let memo = infoWrap.querySelector('.memo');
|
|
// memo.style.height = '100%';
|
|
let toggleBtnText = infoWrap.querySelector('.separator .toggle-btn .text');
|
|
|
|
//// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 다르게 표시 --- 시작
|
|
// if (listContainer.classList.contains('grid')) {
|
|
// infoWrap.style.height = '30%';
|
|
// // // 그리드 파일 목록에서 뷰어 실행할 때 메타데이터영역, 뷰어툴바 표시
|
|
// // viewerWrap.style.display = 'flex';
|
|
// // metadata.style.display = 'flex';
|
|
// // viewerToolbar.style.display = 'flex';
|
|
|
|
// // 그리드 파일 목록에서 뷰어 실행할 때 메타데이터영역 표시
|
|
// metadata.style.display = 'flex';
|
|
|
|
// // memo.style.height = 'auto';
|
|
|
|
// // 그리드 아이템의 하위 dom에 담겨있는 정보를 가져와서 메타데이터 영역 내용 추가
|
|
// let currentItem = listContainer.querySelector(`.list-body .list-item[data-resource-path="${resourcePath}"]`);
|
|
// let arr = ['name', 'uploader', 'create-date', 'author', 'size', 'gps-data'];
|
|
// arr.map(a => {
|
|
// metadata.querySelector(`.${a} .value`).innerHTML = currentItem.querySelector(`.${a} .text`).innerHTML;
|
|
// })
|
|
|
|
// if (listContainer.querySelector('.list-body .list-item-wrap').style.display == 'flex') {
|
|
// // 그리드 파일 목록에서 뷰어 실행할 때 뷰어툴바 표시
|
|
// viewerToolbar.style.display = 'flex';
|
|
|
|
// // 메타데이터 영역 위(뷰어 하단)로 뷰어툴바 위치 조정
|
|
// // viewerToolbar.style.bottom = `${pxToRem(infoWrap.offsetHeight + 16)}rem`;
|
|
|
|
// // 뷰어툴바 이전/다음 버튼에 disabled 클래스 삭제
|
|
// let prevBtn = viewerToolbar.querySelector('.prev-btn');
|
|
// let nextBtn = viewerToolbar.querySelector('.next-btn');
|
|
// prevBtn.classList.remove('disabled');
|
|
// nextBtn.classList.remove('disabled');
|
|
|
|
// // 현재 선택된 아이템이 첫번째 아이템이면 이전버튼에, 마지막 아이템이면 다음버튼에 disabled 클래스 추가
|
|
// let listItemArr = [...listContainer.querySelector('.list-body .list-item-wrap').children];
|
|
// let currentIdx = listItemArr.indexOf(vars.lastListItem);
|
|
// if (currentIdx == 0) viewerToolbar.querySelector('.prev-btn').classList.add('disabled');
|
|
// if (currentIdx == listItemArr.length-1) viewerToolbar.querySelector('.next-btn').classList.add('disabled');
|
|
// }
|
|
|
|
// toggleBtnText.textContent = '메타데이터';
|
|
// } else {
|
|
// toggleBtnText.textContent = '메모 (AI 요약)';
|
|
// }
|
|
//// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 다르게 표시 --- 끝
|
|
|
|
//// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 메타데이터 형태로 동일하게 표시 --- 시작
|
|
infoWrap.style.height = '30%';
|
|
// // 그리드 파일 목록에서 뷰어 실행할 때 메타데이터영역, 뷰어툴바 표시
|
|
// viewerWrap.style.display = 'flex';
|
|
// metadata.style.display = 'flex';
|
|
// viewerToolbar.style.display = 'flex';
|
|
|
|
// 그리드 파일 목록에서 뷰어 실행할 때 메타데이터영역 표시
|
|
metadata.style.display = 'flex';
|
|
|
|
// memo.style.height = 'auto';
|
|
|
|
// 그리드 아이템의 하위 dom에 담겨있는 정보를 가져와서 메타데이터 영역 내용 추가
|
|
let parts = resourcePath.split('/').filter(Boolean);
|
|
let fileType = parts[3] ?? "";
|
|
let currentItem;
|
|
|
|
// 버전/첨부파일이 아닐 경우 (현재 버전/첨부파일은 지도표시에 표시안됨)
|
|
if(!fileType.includes('_version') && !fileType.includes('__attachment')) {
|
|
currentItem = listContainer.querySelector(`.list-body .list-item[data-resource-path="${resourcePath}"]`);
|
|
} else {
|
|
currentItem = vars.lastSelectTarget;
|
|
}
|
|
let arr = ['name', 'uploader', 'create-date', 'author', 'size', 'gps-data'];
|
|
arr.map(a => {
|
|
metadata.querySelector(`.${a} .value`).innerHTML = currentItem.querySelector(`.${a} .text`).innerHTML;
|
|
})
|
|
|
|
|
|
// if (listContainer.querySelector('.list-body .list-item-wrap').style.display == 'flex') {
|
|
// // 그리드 파일 목록에서 뷰어 실행할 때 뷰어툴바 표시
|
|
// viewerToolbar.style.display = 'flex';
|
|
|
|
// // 메타데이터 영역 위(뷰어 하단)로 뷰어툴바 위치 조정
|
|
// // viewerToolbar.style.bottom = `${pxToRem(infoWrap.offsetHeight + 16)}rem`;
|
|
|
|
// // 뷰어툴바 이전/다음 버튼에 disabled 클래스 삭제
|
|
// let prevBtn = viewerToolbar.querySelector('.prev-btn');
|
|
// let nextBtn = viewerToolbar.querySelector('.next-btn');
|
|
// prevBtn.classList.remove('disabled');
|
|
// nextBtn.classList.remove('disabled');
|
|
|
|
// // 현재 선택된 아이템이 첫번째 아이템이면 이전버튼에, 마지막 아이템이면 다음버튼에 disabled 클래스 추가
|
|
// let listItemArr = [...listContainer.querySelector('.list-body .list-item-wrap').children];
|
|
// let currentIdx = listItemArr.indexOf(vars.lastListItem);
|
|
// if (currentIdx == 0) viewerToolbar.querySelector('.prev-btn').classList.add('disabled');
|
|
// if (currentIdx == listItemArr.length-1) viewerToolbar.querySelector('.next-btn').classList.add('disabled');
|
|
// }
|
|
|
|
toggleBtnText.textContent = '메타데이터';
|
|
//// 리스트 모드와 지도 표시 모드에서 미리보기 하단 영역 메타데이터 형태로 동일하게 표시 --- 끝
|
|
|
|
// console.log('========= renderViewer');
|
|
// console.log(resourcePath);
|
|
// console.log(dataId);
|
|
|
|
let pdfArr = ['hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf', 'grm', 'pdf'];
|
|
let gsimArr = ['gsim'];
|
|
let ifcArr = ['ifc'];
|
|
let imageArr = ['png', 'jpg', 'jpeg', 'webp', 'gif'];
|
|
let videoArr = ['mp4', 'mov', 'webm'];
|
|
let textArr = ['txt', 'log', 'md'];
|
|
let urlArr = ['url'];
|
|
let zipArr = ['zip'];
|
|
let threeArr = ['glb', 'gltf', 'obj', 'stl', 'fbx', '3dm'];
|
|
let htmlArr = ['html'];
|
|
let allArr = [...pdfArr, ...videoArr, ...imageArr, ...ifcArr, ...gsimArr, ...textArr, ...urlArr, ...zipArr, ...threeArr, ...htmlArr];
|
|
|
|
let isLowerExt = true;
|
|
let ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
|
|
|
|
let excelDirectArr = ['xls', 'xlsx', 'xlsm'];
|
|
let hwpDirectArr = ['hwp', 'hwpx'];
|
|
let wordDirectArr = ['docx'];
|
|
let isDirectView = excelDirectArr.includes(ext) || hwpDirectArr.includes(ext) || wordDirectArr.includes(ext);
|
|
|
|
let treeData = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject)?.data || {};
|
|
let previewKey = treeData.previewKey;
|
|
let objectKey = treeData.objectKey || treeData.object_key;
|
|
|
|
// 문서 뷰어 실행 시 미리보기 10장 제한 문구 표시
|
|
let thumbAlert = document.querySelector('.archive-main-right .viewer-container .viewer-header .thumb-alert');
|
|
if (pdfArr.includes(ext)) {
|
|
thumbAlert.style.display = 'flex';
|
|
} else {
|
|
thumbAlert.style.display = 'none';
|
|
}
|
|
|
|
// fallback-pdf-btn 숨김
|
|
const mainFallbackPdfBtn = document.getElementById('main-fallback-pdf-btn');
|
|
if (mainFallbackPdfBtn) {
|
|
mainFallbackPdfBtn.style.display = 'none';
|
|
}
|
|
|
|
// 지원 파일인 경우 뷰어 프로그레스 표시, 대기 시간 700ms로 설정, 전체보기 버튼 표시
|
|
let originViewBtn = document.querySelector('.archive-main-right .viewer-container .viewer-header .btn');
|
|
if (allArr.includes(ext) && (previewKey || isDirectView)) {
|
|
toggleViewerProgress(true);
|
|
vars.viewerConnectingTime = 700;
|
|
originViewBtn.style.display = 'flex';
|
|
} else {
|
|
toggleViewerProgress(false);
|
|
vars.viewerConnectingTime = 0;
|
|
originViewBtn.style.display = 'none';
|
|
}
|
|
|
|
if (!shouldAddClickLog) vars.viewerConnectingTime = 0;
|
|
|
|
clearTimeout(renderTimer);
|
|
|
|
renderTimer = setTimeout(async () => {
|
|
toggleViewerProgress(false);
|
|
|
|
// 뷰어 초기화
|
|
vars.viewer = viewerWrap.querySelector('.viewer');
|
|
vars.viewer.innerHTML = '';
|
|
vars.viewer.style.display = 'flex';
|
|
|
|
// Presigned URL
|
|
let PresignedUrl = undefined;
|
|
let openFileViewer = true;
|
|
|
|
if (!previewKey || !objectKey) {
|
|
let getDataInfoParams = {
|
|
userInfoString: vars.userInfoString,
|
|
storageType: vars.storageType,
|
|
dataIdArr: [dataId],
|
|
isRemoved: false,
|
|
debug: "'renderViewer'에서 실행"
|
|
}
|
|
|
|
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams } );
|
|
if (getDataInfoRes.data.message == 'getDataInfo_success') {
|
|
let result = getDataInfoRes.data.result;
|
|
if (result) {
|
|
previewKey = result.preview_key;
|
|
objectKey = result.object_key;
|
|
}
|
|
}
|
|
}
|
|
|
|
let targetKey = isDirectView ? objectKey : previewKey;
|
|
|
|
if (allArr.includes(ext)) {
|
|
if (targetKey == undefined || targetKey == `` || targetKey == null) {
|
|
viewerConvert();
|
|
openFileViewer = false;
|
|
shouldAddClickLog = false;
|
|
} else {
|
|
let generateDownloadUrlParams = {
|
|
objectKey: targetKey,
|
|
resourcePath: resourcePath
|
|
}
|
|
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
|
|
PresignedUrl = generateDownloadUrlRes.data.url;
|
|
}
|
|
}
|
|
} else {
|
|
viewerUnsupport(ext);
|
|
openFileViewer = false;
|
|
shouldAddClickLog = false;
|
|
}
|
|
|
|
if (openFileViewer) {
|
|
let ext = (splitBaseAndExt(resourcePath).ext).toLowerCase();
|
|
let pdfArrFiltered = pdfArr.filter(e => !excelDirectArr.includes(e) && !hwpDirectArr.includes(e) && !wordDirectArr.includes(e));
|
|
|
|
// 3D뷰어 썸네일 변수
|
|
const thumbnail_key = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject).data?.thumbnailKey;
|
|
|
|
if (allArr.includes(ext)) {
|
|
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 (gsimArr.includes(ext)) viewerGsim(PresignedUrl);
|
|
if (ifcArr.includes(ext)) viewerIfc(PresignedUrl, thumbnail_key, resourcePath, dataId, vars.path_name);
|
|
if (threeArr.includes(ext)) viewer3d(PresignedUrl, thumbnail_key, resourcePath, dataId, vars.path_name);
|
|
if (imageArr.includes(ext)) viewerImage(PresignedUrl);
|
|
if (videoArr.includes(ext)) viewerVideo(PresignedUrl);
|
|
if (textArr.includes(ext)) viewerText(PresignedUrl, ext);
|
|
if (urlArr.includes(ext)) viewerURL(PresignedUrl);
|
|
if (zipArr.includes(ext)) viewerZIP(PresignedUrl);
|
|
if (htmlArr.includes(ext)) viewerHTML(PresignedUrl);
|
|
} else {
|
|
viewerUnsupport(ext);
|
|
shouldAddClickLog = false;
|
|
}
|
|
}
|
|
|
|
renderTimer = null;
|
|
|
|
// 클릭 로그 추가 (renderViewer)
|
|
if (shouldAddClickLog) {
|
|
// console.log('클릭 로그 추가 (renderViewer)');
|
|
let params = {
|
|
userInfoString: vars.userInfoString,
|
|
dataIdArr: [String(dataId)],
|
|
resourcePathArr: [resourcePath],
|
|
activity: 'click_file'
|
|
}
|
|
mgmtFunc_addClickLog(params);
|
|
}
|
|
|
|
// AI 버튼 상태 복원 (뷰어 렌더링 완료 후 실행)
|
|
// restoreAiButtonState(resourcePath, viewerContainer.dataset.dataId);
|
|
|
|
}, vars.viewerConnectingTime);
|
|
|
|
function toggleViewerProgress(state) {
|
|
let viewerProgress = document.querySelector('.viewer-progress');
|
|
let viewerProgressFill = viewerProgress.querySelector('.wrap .bar .fill');
|
|
|
|
if (state == false) {
|
|
viewerProgress.style.display = 'none';
|
|
|
|
if (progressFrame) {
|
|
cancelAnimationFrame(progressFrame);
|
|
progressFrame = null;
|
|
}
|
|
|
|
if (viewerProgressFill) viewerProgressFill.style.transform = 'scaleX(0)';
|
|
return;
|
|
}
|
|
|
|
if (state == true) {
|
|
viewerProgress.style.display = 'flex';
|
|
|
|
if (progressFrame) cancelAnimationFrame(progressFrame);
|
|
|
|
progressStart = performance.now();
|
|
|
|
function animate(now) {
|
|
let elapsed = now - progressStart;
|
|
let t = Math.min(elapsed / vars.viewerConnectingTime, 1);
|
|
let eased = 1 - (1 - t) ** 5;
|
|
|
|
if (viewerProgressFill) viewerProgressFill.style.transform = `scaleX(${eased})`;
|
|
|
|
if (elapsed < vars.viewerConnectingTime) progressFrame = requestAnimationFrame(animate);
|
|
}
|
|
|
|
progressFrame = requestAnimationFrame(animate);
|
|
}
|
|
}
|
|
|
|
function viewerUnsupport(ext) {
|
|
let viewerUnsupportWrap = document.createElement('div');
|
|
viewerUnsupportWrap.classList.add('viewer-unsupport-wrap');
|
|
|
|
let text = document.createElement('div');
|
|
text.classList.add('text');
|
|
if (isValidExt(ext)) {
|
|
// 확장자가 유효한 경우
|
|
text.innerText = `${ext} 파일 형식은 현재 지원되지 않습니다.`;
|
|
} else {
|
|
// 확장자가 유효하지 않은 경우
|
|
text.innerText = `확장자가 유효하지 않은 파일은 지원되지 않습니다.`;
|
|
}
|
|
|
|
viewerUnsupportWrap.appendChild(text);
|
|
vars.viewer.appendChild(viewerUnsupportWrap);
|
|
vars.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 = `해당 파일은 변환 후 열람이 가능합니다.`;
|
|
|
|
// let convertBtn = document.createElement('div');
|
|
// convertBtn.textContent = '변환';
|
|
// convertBtn.classList.add('convert-btn');
|
|
// convertBtn.addEventListener('click', () => {
|
|
// convertPdf(resourcePath);
|
|
// })
|
|
|
|
viewerConvertWrap.appendChild(text);
|
|
// viewerConvertWrap.appendChild(convertBtn);
|
|
vars.viewer.appendChild(viewerConvertWrap);
|
|
vars.viewer.dataset.viewerType = 'convert';
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// 오픈소스 문서 직접 뷰잉 및 PDF 폴백 함수 정의 (Main Viewer)
|
|
// -----------------------------------------------------------------
|
|
function initMainFallbackPdfButton(dataId, resourcePath, objectKey, previewKey) {
|
|
const btn = document.getElementById('main-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) {
|
|
let getDataInfoParams = {
|
|
userInfoString: vars.userInfoString,
|
|
storageType: vars.storageType,
|
|
dataIdArr: [dataId],
|
|
isRemoved: false,
|
|
debug: "main fallback"
|
|
}
|
|
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams } );
|
|
if (getDataInfoRes.data.message == 'getDataInfo_success') {
|
|
let result = getDataInfoRes.data.result;
|
|
if (result) {
|
|
previewKey = result.preview_key;
|
|
objectKey = result.object_key;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. 만약 PDF 변환본이 아직 없다면 백엔드 변환 요청
|
|
if (!previewKey) {
|
|
newBtn.querySelector('.text').textContent = 'PDF 변환 요청 중...';
|
|
await convertPdf(resourcePath, dataId);
|
|
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(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
|
|
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
|
|
let pdfUrl = generateDownloadUrlRes.data.url;
|
|
|
|
// 화면 초기화 및 PDF 뷰어 로드
|
|
vars.viewer = viewerWrap.querySelector('.viewer');
|
|
vars.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) {
|
|
vars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">엑셀 데이터를 불러오는 중...</div>';
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
return res.arrayBuffer();
|
|
})
|
|
.then(arrayBuffer => {
|
|
vars.viewer.innerHTML = '';
|
|
|
|
LuckyExcel.transformExcelToLucky(arrayBuffer, function(exportJson, luckysheetfile) {
|
|
if(exportJson.sheets == null || exportJson.sheets.length == 0) {
|
|
vars.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>';
|
|
initMainFallbackPdfButton(dataId, resourcePath, objectKey, previewKey);
|
|
return;
|
|
}
|
|
|
|
if (window.luckysheet) {
|
|
window.luckysheet.destroy();
|
|
}
|
|
|
|
vars.viewer.style.position = 'relative';
|
|
const container = document.createElement('div');
|
|
container.id = 'luckysheet_inner';
|
|
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';
|
|
vars.viewer.appendChild(container);
|
|
|
|
try {
|
|
window.luckysheet.create({
|
|
container: 'luckysheet_inner',
|
|
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);
|
|
vars.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);
|
|
vars.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>`;
|
|
initMainFallbackPdfButton(dataId, resourcePath, objectKey, previewKey);
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
vars.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>`;
|
|
initMainFallbackPdfButton(dataId, resourcePath, objectKey, previewKey);
|
|
});
|
|
|
|
vars.viewer.dataset.viewerType = 'excel';
|
|
}
|
|
|
|
function viewerWord(presignedUrl) {
|
|
vars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">워드 문서를 불러오는 중...</div>';
|
|
initMainFallbackPdfButton(dataId, resourcePath, objectKey, previewKey);
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('Word fetch failed');
|
|
return res.arrayBuffer();
|
|
})
|
|
.then(arrayBuffer => {
|
|
vars.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);
|
|
vars.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);
|
|
vars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">워드 문서를 불러오는데 실패했습니다.</div>';
|
|
});
|
|
|
|
vars.viewer.dataset.viewerType = 'word';
|
|
}
|
|
|
|
function viewerHwp(presignedUrl) {
|
|
vars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;font-size:1.2rem;color:#666;background:#fff;">한글 문서를 불러오는 중...</div>';
|
|
initMainFallbackPdfButton(dataId, resourcePath, objectKey, previewKey);
|
|
|
|
fetch(presignedUrl)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error('HWP fetch failed');
|
|
return res.blob();
|
|
})
|
|
.then(blob => {
|
|
vars.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 hwpInner = document.createElement('div');
|
|
hwpInner.style.background = '#ffffff';
|
|
hwpInner.style.margin = '0 auto';
|
|
hwpInner.style.maxWidth = '800px';
|
|
hwpInner.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
|
|
hwpInner.style.padding = '40px';
|
|
hwpInner.style.minHeight = '100%';
|
|
|
|
container.appendChild(hwpInner);
|
|
vars.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);
|
|
vars.viewer.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100%;color:#d9534f;background:#fff;">한글 문서를 불러오는데 실패했습니다.</div>';
|
|
});
|
|
|
|
vars.viewer.dataset.viewerType = 'hwp';
|
|
}
|
|
|
|
//// 원본 viewer
|
|
async function viewerPdf(presignedUrl) {
|
|
if(presignedUrl == undefined || presignedUrl == ``){
|
|
viewerConvert();
|
|
return;
|
|
}
|
|
|
|
let pdfOptions = {
|
|
url : presignedUrl,
|
|
initialPage: 1,
|
|
}
|
|
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = `/libs/pdfViewer/web/viewer.html`;
|
|
iframe.addEventListener('load', async () => {
|
|
// 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('.viewer-container .viewer-wrap .viewer iframe').contentWindow.PDFViewerApplication;
|
|
app.pdfCursorTools._handTool.activate();
|
|
app.open(pdfOptions);
|
|
// 미리보기 10페이지 표시 제한
|
|
app.appConfig.mainContainer.classList.add('scrollbar');
|
|
app.appConfig.mainContainer.style.marginLeft = '4px';
|
|
iframe.contentWindow.document.querySelector('body').style.backgroundColor = '#383838';
|
|
|
|
// 미리보기 10페이지 표시 제한
|
|
app.eventBus.on('pagesloaded', async function onPagesLoaded() {
|
|
// PDF가 완전히 로드된 후에 실행
|
|
|
|
// 미리보기 PDF 페이지 수가 10장 이하면 리턴
|
|
if (app.pagesCount <= 10) return;
|
|
|
|
let mainContainer = iframe.contentWindow.document.querySelector('#mainContainer');
|
|
let viewerContainer = mainContainer.querySelector('#viewerContainer');
|
|
let viewer = viewerContainer.querySelector('#viewer');
|
|
let pages = viewer.querySelectorAll('.page');
|
|
|
|
let numPages = mainContainer.querySelector('#toolbarContainer #numPages');
|
|
// 페이지가 10장 초과인 경우 툴바에 최대 페이지 수 10페이지로 고정
|
|
if (app.pagesCount > 10) numPages.innerText = '/ 10';
|
|
// 최대 페이지 수 표시
|
|
numPages.style.display = 'flex';
|
|
|
|
// 페이지 숫자 입력 input disabled 처리
|
|
let pageNumberInput = mainContainer.querySelector('#toolbarContainer #pageNumber');
|
|
pageNumberInput.disabled = true;
|
|
pageNumberInput.style.background = '#ddd';
|
|
pageNumberInput.style.color = '#999';
|
|
|
|
// 다음 페이지 버튼 클릭 시 10페이지 넘어가면 10 페이지로 고정
|
|
let nextBtn = mainContainer.querySelector('#toolbarContainer #next.toolbarButton');
|
|
nextBtn.addEventListener('click', function() {
|
|
if (pageNumberInput.value > 10) {
|
|
pageNumberInput.value = 10;
|
|
app.page = 10;
|
|
}
|
|
})
|
|
|
|
// 10페이지 높이값 설정
|
|
// -> 한 페이지 높이에 10을 곱한 값에 iframe 높이를 빼야 스크롤이 제일 아래로 내려갔을 때 마지막 페이지 끝에 맞음
|
|
let pageHeightLimit = (pages[0].offsetHeight * 10) - iframe.offsetHeight;
|
|
|
|
// pdf뷰어 실행 시 항상 스크롤 처음으로 고정
|
|
// viewerContainer.scrollTop = 0;
|
|
|
|
viewerContainer.addEventListener('scroll', function() {
|
|
if (viewerContainer.scrollTop >= pageHeightLimit) {
|
|
// 스크롤이 10페이지 높이값에 도달하면 스크롤위치를 10페이지 끝(높이값)으로 고정
|
|
viewerContainer.scrollTop = pageHeightLimit;
|
|
|
|
// 스크롤이 10페이지 높이값에 도달하면 11페이지부터 숨김처리
|
|
for (let i = 0; i < pages.length; i++) {
|
|
let page = pages[i];
|
|
if (i >= 10) {
|
|
if (page.querySelector('.canvasWrapper')) page.querySelector('.canvasWrapper').remove(); // 또는 숨김
|
|
page.style.backgroundColor = '#383838';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 가상 스크롤바 생성
|
|
// 마우스 다운 상태에서 가상 스크롤바 영역 벗어났을 때 이벤트 처리 필요
|
|
await setupFakeScrollbar(iframe, 10);
|
|
|
|
// 이벤트 리스너 제거 (중복방지)
|
|
app.eventBus.off('pagesloaded', onPagesLoaded);
|
|
});
|
|
})
|
|
vars.viewer.appendChild(iframe);
|
|
vars.viewer.dataset.viewerType = 'pdf';
|
|
// 미리보기 10페이지 표시 제한
|
|
function setupFakeScrollbar(iframe, visiblePages) {
|
|
const doc = iframe.contentWindow.document;
|
|
const container = doc.querySelector('#viewerContainer');
|
|
if (!container) return;
|
|
|
|
// fakeScrollbar div 생성
|
|
let fakeScrollbar = document.createElement('div');
|
|
fakeScrollbar.className = 'fake-scrollbar';
|
|
// thumb 생성
|
|
let fakeThumb = document.createElement('div');
|
|
fakeThumb.className = 'fake-thumb';
|
|
fakeScrollbar.appendChild(fakeThumb);
|
|
|
|
// iframe 위에 fakeScrollbar 추가
|
|
iframe.parentNode.appendChild(fakeScrollbar);
|
|
|
|
// 10페이지 높이 합산
|
|
let pageDivs = doc.querySelectorAll('.page');
|
|
let totalHeight = 0;
|
|
for (let i = 0; i < visiblePages && i < pageDivs.length; i++) {
|
|
totalHeight += pageDivs[i].offsetHeight;
|
|
}
|
|
|
|
// 실제 컨테이너 전체 높이
|
|
let contentHeight = doc.querySelector('#viewer').offsetHeight;
|
|
let viewportHeight = container.clientHeight;
|
|
|
|
// fakeThumb의 높이와 이동가능 범위 계산
|
|
let thumbHeight = Math.max(
|
|
(viewportHeight / totalHeight) * viewportHeight,
|
|
40
|
|
);
|
|
fakeThumb.style.height = thumbHeight + 'px';
|
|
|
|
// fakeThumb 위치 업데이트 함수
|
|
function updateFakeThumb() {
|
|
let maxScroll = totalHeight - viewportHeight;
|
|
let scroll = Math.min(container.scrollTop, maxScroll);
|
|
let top = (scroll / maxScroll) * (viewportHeight - thumbHeight);
|
|
fakeThumb.style.top = (isNaN(top) ? 0 : top) + 'px';
|
|
}
|
|
// 스크롤 이벤트 연결
|
|
container.addEventListener('scroll', (e) => {
|
|
updateFakeThumb();
|
|
});
|
|
// container.addEventListener('wheel', (e) => {
|
|
// console.log(e);
|
|
// updateFakeThumb();
|
|
// });
|
|
|
|
// fakeThumb 드래그로 실제 스크롤 조정
|
|
let dragging = false, dragStartY = 0, startScroll = 0;
|
|
fakeThumb.addEventListener('mousedown', (e) => {
|
|
dragging = true;
|
|
dragStartY = e.clientY;
|
|
startScroll = container.scrollTop;
|
|
document.body.style.userSelect = 'none';
|
|
});
|
|
window.addEventListener('mousemove', (e) => {
|
|
if (!dragging) return;
|
|
let delta = e.clientY - dragStartY;
|
|
let maxScroll = totalHeight - viewportHeight;
|
|
let trackLen = viewportHeight - thumbHeight;
|
|
let scroll = startScroll + (delta / trackLen) * maxScroll;
|
|
scroll = Math.max(0, Math.min(maxScroll, scroll));
|
|
container.scrollTop = scroll;
|
|
});
|
|
window.addEventListener('mouseup', () => {
|
|
dragging = false;
|
|
document.body.style.userSelect = '';
|
|
});
|
|
|
|
// fakeThumb와 컨테이너 위치 초기 동기화
|
|
updateFakeThumb();
|
|
|
|
fakeScrollbar.style.pointerEvents = 'auto';
|
|
}
|
|
}
|
|
|
|
function viewerImage(presignedUrl) {
|
|
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);
|
|
vars.viewer.appendChild(viewerImageWrap);
|
|
vars.viewer.dataset.viewerType = 'image';
|
|
}
|
|
|
|
function viewerVideo(presignedUrl) {
|
|
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);
|
|
vars.viewer.appendChild(viewerVideoWrap);
|
|
vars.viewer.dataset.viewerType = 'video';
|
|
}
|
|
|
|
function viewerText(presignedUrl, ext) {
|
|
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');
|
|
if(ext === 'md'){
|
|
viewerText.style.whiteSpace = 'normal';
|
|
viewerText.classList.add('markdown-body');
|
|
// mermaid 시퀀스 다이어그램 변환
|
|
const renderer = new marked.Renderer();
|
|
const originalCode = renderer.code.bind(renderer);
|
|
// 머메이드만 변환하고 나머지는 기존 렌더러 사용
|
|
renderer.code = (code) => {
|
|
if(code.lang === 'mermaid') return `<pre class="mermaid">${code.text}</pre>`;
|
|
|
|
return originalCode(code);
|
|
}
|
|
//md파일 파싱 후 삽입
|
|
marked.setOptions({ gfm: true, breaks: true, renderer});
|
|
viewerText.innerHTML = marked.parse(data);
|
|
|
|
// mermaid 초기화 및 생성
|
|
const mermaidEls = viewerText.querySelectorAll('.mermaid')
|
|
if(mermaidEls.length > 0){
|
|
mermaid.initialize({ startOnLoad: false });
|
|
mermaid.init(undefined, mermaidEls);
|
|
}
|
|
} else {
|
|
viewerText.textContent = data;
|
|
}
|
|
viewerTextWrap.appendChild(viewerText);
|
|
vars.viewer.appendChild(viewerTextWrap);
|
|
if(ext === 'md')hljs.highlightAll();
|
|
vars.viewer.dataset.viewerType = 'text';
|
|
})
|
|
}
|
|
|
|
function viewerGsim(presignedUrl) {
|
|
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);
|
|
vars.viewer.appendChild(viewerGsim);
|
|
vars.viewer.dataset.viewerType = 'gsim';
|
|
}
|
|
|
|
function viewerIfc(presignedUrl, thumbnail_key, resourcePath, dataId, path_name) {
|
|
let viewerIfc = document.createElement('div');
|
|
viewerIfc.classList.add('viewer-ifc');
|
|
|
|
let iframe = document.createElement('iframe');
|
|
iframe.onload = () => {
|
|
iframe.contentWindow.postMessage({ path: presignedUrl, thumbnail_key, resourcePath, dataId, path_name }, '*'); // presignedUrl을 iframe에 전달
|
|
};
|
|
iframe.src = `/libs/ifcViewer/index.html`;
|
|
iframe.width = '100%';
|
|
iframe.height = '100%';
|
|
iframe.style.border = 'none';
|
|
|
|
viewerIfc.appendChild(iframe);
|
|
vars.viewer.appendChild(viewerIfc);
|
|
vars.viewer.dataset.viewerType = 'ifc';
|
|
}
|
|
|
|
function viewer3d(presignedUrl, thumbnail_key, resourcePath, dataId, path_name) {
|
|
let viewer3d = document.createElement('div');
|
|
viewer3d.classList.add('viewer-3d');
|
|
|
|
let iframe = document.createElement('iframe');
|
|
// 데이터(payload)를 보냄
|
|
iframe.onload = () => {
|
|
iframe.contentWindow.postMessage({ path: presignedUrl, thumbnail_key, resourcePath, dataId, path_name}, '*');
|
|
};
|
|
iframe.src = `/libs/3dViewer/index.html`;
|
|
iframe.width = '100%';
|
|
iframe.height = '100%';
|
|
iframe.style.border = 'none';
|
|
|
|
viewer3d.appendChild(iframe);
|
|
vars.viewer.appendChild(viewer3d);
|
|
vars.viewer.dataset.viewerType = '3d';
|
|
}
|
|
|
|
function viewerURL(presignedUrl) {
|
|
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);
|
|
vars.viewer.appendChild(viewerURLWrap);
|
|
vars.viewer.dataset.viewerType = 'url';
|
|
})
|
|
}
|
|
|
|
function viewerHTML(presignedUrl){
|
|
let viewerHTMLWrap = document.createElement('div');
|
|
viewerHTMLWrap.classList.add('viewer-text-wrap');
|
|
|
|
fetch(presignedUrl).then(res => res.text()).then(data => {
|
|
let iframe = document.createElement('iframe');
|
|
iframe.srcdoc = data;
|
|
iframe.style.width = '100%'; // 컨테이너에 맞게 너비 설정
|
|
iframe.style.height = '100%'; // 컨테이너에 맞게 높이 설정
|
|
iframe.style.border = 'none'; // 테두리 제거 (선택 사항)
|
|
|
|
viewerHTMLWrap.appendChild(iframe);
|
|
vars.viewer.appendChild(viewerHTMLWrap);
|
|
vars.viewer.dataset.viewerType = 'url';
|
|
})
|
|
}
|
|
|
|
function viewerZIP(presignedUrl) {
|
|
let viewerTextWrap = document.createElement('div');
|
|
viewerTextWrap.classList.add('viewer-text-wrap');
|
|
viewerTextWrap.classList.add('scrollbar');
|
|
|
|
let selectItem = vars.lastListItem.dataset.resourcePath;
|
|
|
|
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`
|
|
}
|
|
}
|
|
});
|
|
|
|
if(!vars.lastListItem.dataset.resourcePath || vars.lastListItem.dataset.resourcePath != selectItem) return;
|
|
|
|
let viewerText = document.createElement('div');
|
|
viewerText.classList.add('viewer-text');
|
|
viewerText.textContent = `${folderText}${(folderText == ``)?'':'\n'}${fileText}`;
|
|
|
|
viewerTextWrap.appendChild(viewerText);
|
|
vars.viewer.appendChild(viewerTextWrap);
|
|
vars.viewer.dataset.viewerType = 'text';
|
|
})
|
|
}
|
|
|
|
// 뷰 렌더 후, 서버 상태를 한번 동기화
|
|
await syncAiStateRestore(resourcePath, dataId);
|
|
|
|
async function syncAiStateRestore(resourcePath, dataId) {
|
|
if (!dataId) return;
|
|
|
|
try {
|
|
const res = await axios.get(`${vars.path_name}/summarizeState`, {
|
|
params: { resourcePath, dataId }
|
|
});
|
|
|
|
if (res.data.message === 'summarizeState_success') {
|
|
const dataInfo = res.data.dataInfo;
|
|
|
|
if (dataInfo) {
|
|
updateAiButtonState(dataId, 'loading');
|
|
} else {
|
|
updateAiButtonState(dataId, 'initial');
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.warn(`서버 상태 동기화 실패 (dataId: ${dataId}):`, e);
|
|
updateAiButtonState(dataId, 'initial');
|
|
}
|
|
}
|
|
}
|
|
|
|
export function resetViewer() {
|
|
viewerContainer.style.display = 'none';
|
|
viewerContainer.dataset.resourcePath = '';
|
|
viewerContainer.dataset.dataId = '';
|
|
viewerNotice.style.display = 'flex';
|
|
|
|
if (!vars.viewer) return;
|
|
|
|
if (vars.viewer.dataset.viewerType == 'pdf') {
|
|
const iframe = vars.viewer.querySelector('.viewer iframe');
|
|
if (iframe) {
|
|
const app = iframe.contentWindow?.PDFViewerApplication;
|
|
app?.close?.();
|
|
iframe.remove();
|
|
}
|
|
}
|
|
|
|
if (vars.viewer.dataset.viewerType == 'image') {
|
|
const imageWrap = vars.viewer.querySelector('.viewer-image-wrap');
|
|
if (imageWrap) {
|
|
const image = imageWrap.querySelector('.viewer-image');
|
|
image.src = '';
|
|
image.remove();
|
|
imageWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (vars.viewer.dataset.viewerType == 'video') {
|
|
const videoWrap = vars.viewer.querySelector('.viewer-video-wrap');
|
|
if (videoWrap) {
|
|
const video = vars.viewer.querySelector('.viewer-video');
|
|
video.pause();
|
|
video.src = '';
|
|
video.load();
|
|
video.remove();
|
|
videoWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (vars.viewer.dataset.viewerType == 'text') {
|
|
const textWrap = vars.viewer.querySelector('.viewer-text-wrap');
|
|
if (textWrap) {
|
|
textWrap.remove();
|
|
}
|
|
}
|
|
|
|
if (vars.viewer.dataset.viewerType == '3d') {
|
|
const viewer3d = vars.viewer.querySelector('.viewer-3d');
|
|
if (viewer3d) {
|
|
viewer3d.remove();
|
|
}
|
|
}
|
|
|
|
if (vars.viewer.dataset.viewerType == 'excel') {
|
|
if (window.luckysheet) {
|
|
try {
|
|
window.luckysheet.destroy();
|
|
} catch (e) {
|
|
console.error("Luckysheet destroy error: ", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
vars.viewer.dataset.viewerType = '';
|
|
}
|
|
|
|
export function renderContextmenu(event, scope) {
|
|
/*
|
|
depth1 폴더 우클릭 메뉴 트리 우클릭 메뉴 리스트 우클릭 메뉴
|
|
- 새 폴더 - 파일 업로드
|
|
- 이름 변경 - 이름 변경 - 이름 변경
|
|
- 다운로드 - 다운로드 - 다운로드
|
|
- 이동
|
|
- 삭제 - 삭제 - 삭제
|
|
- 권한설정(?) - 권한설정 - 담당자 변경
|
|
|
|
* 트리 이동 : depth3 폴더는 depth2 폴더 선택해서 이동, depth2 폴더는 depth1 폴더 선택해서 이동
|
|
* 리스트 이동 : depth3 폴더 선택해서 이동
|
|
* 다른 depth1 폴더 내의 폴더로도 이동하도록 해야될까?
|
|
|
|
* depth1 폴더 폴더 업로드 : depth1 - depth2 - depth3 - files 순서로 구성된 폴더 업로드 하도록 가이드 표시
|
|
가이드 지켜지지 않는 경우 depth1, depth2에 포함된 파일 및 depth3에 포함된 폴더 무시
|
|
* 트리 폴더 업로드 (depth2) : depth2 - depth3 - files 순서로 구성된 폴더 업로드 하도록 가이드 표시
|
|
가이드 지켜지지 않는 경우 depth2에 포함된 파일 및 depth3에 포함된 폴더 무시
|
|
* 트리 폴더 업로드 (depth3) : depth3 - files 순서로 구성된 폴더 업로드 하도록 가이드 표시
|
|
가이드 지켜지지 않는 경우 depth3에 포함된 폴더 무시
|
|
*/
|
|
|
|
// addContextmenuItem('createFolder');
|
|
// addContextmenuItem('uploadFolder', true);
|
|
// addContextmenuItem('uploadFile', true);
|
|
// addContextmenuItem('renameTarget');
|
|
// addContextmenuItem('downloadTarget');
|
|
// addContextmenuItem('deleteTarget');
|
|
// addContextmenuItem('relocateTarget');
|
|
// addContextmenuItem('setPermission');
|
|
// addContextmenuItem('editAuthor');
|
|
// addContextmenuItem('lev_1');
|
|
// addContextmenuItem('lev_2');
|
|
// addContextmenuItem('lev_4');
|
|
// addContextmenuItem('lev_8');
|
|
// addContextmenuItem('lev_0');
|
|
|
|
let contextmenu = document.querySelector('.contextmenu.main-menu');
|
|
contextmenu.style.display = "none";
|
|
|
|
let contextmenuContainer = contextmenu.querySelector('.contextmenu-container');
|
|
contextmenuContainer.innerHTML = '';
|
|
|
|
let userInfo = JSON.parse(vars.userInfoString);
|
|
|
|
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
|
|
|
// ** 권한 관련
|
|
if (!vars.permission.checkPermission('context-menu-viewer')) {
|
|
addContextmenuWarn('viewer');
|
|
} else {
|
|
if (scope == 'headerBtn') {
|
|
// ** 권한 관련
|
|
if (!vars.permission.checkPermission('context-menu-sub-master')) {
|
|
addContextmenuWarn('min-sub-master');
|
|
} else {
|
|
// addContextmenuItem('createFolder');
|
|
addContextmenuItem('renameTarget');
|
|
// addContextmenuItem('deleteTarget');
|
|
addContextmenuItem('removeTarget');
|
|
addContextmenuItem('setPermission');
|
|
if(vars.permission.checkPermission('download-folder')) addContextmenuItem('downloadTarget-folder');
|
|
}
|
|
}
|
|
|
|
if (scope == 'tree') {
|
|
addContextmenuItem('createFolder');
|
|
}
|
|
|
|
if (scope == 'tree-item-wrap-depth2') {
|
|
addContextmenuItem('createFolder');
|
|
// addContextmenuItem('createFolder-drawing');
|
|
// addContextmenuItem('createFolder-gallery');
|
|
addContextmenuItem('renameTarget');
|
|
// addContextmenuItem('deleteTarget');
|
|
addContextmenuItem('removeTarget');
|
|
// ** 권한 관련
|
|
if (vars.permission.checkPermission('context-menu-sub-master')) addContextmenuItem('setPermission');
|
|
if(vars.permission.checkPermission('download-folder')) addContextmenuItem('downloadTarget-folder');
|
|
}
|
|
|
|
if (scope == 'tree-item-wrap-depth3') {
|
|
addContextmenuItem('renameTarget');
|
|
// addContextmenuItem('deleteTarget');
|
|
addContextmenuItem('removeTarget');
|
|
// ** 권한 관련
|
|
if (vars.permission.checkPermission('context-menu-sub-master')) addContextmenuItem('setPermission');
|
|
if(vars.permission.checkPermission('download-folder')) addContextmenuItem('downloadTarget-folder');
|
|
}
|
|
|
|
if (scope == 'list') {
|
|
// 갤러리 폴더 지도모드일 때 안내 표시 및 리턴
|
|
let mapContainer = listContainer?.querySelector('.map-container');
|
|
if (mapContainer.style.display == 'flex') {
|
|
let notificatitonParams = { text: '지도모드에서는 우클릭 기능이 제한됩니다.' }
|
|
showNotification(notificatitonParams);
|
|
return;
|
|
}
|
|
|
|
addContextmenuItem('uploadFile', true, event);
|
|
}
|
|
|
|
if (scope == 'list-item') {
|
|
// 파일 이동 모드인 경우 리턴
|
|
if (document.querySelector('.list-viewer-cover').style.display == 'flex') return;
|
|
|
|
if (isRecycleBinModal) {
|
|
addContextmenuItem('downloadTarget');
|
|
addContextmenuItem('deleteTarget');
|
|
} else {
|
|
let listItem = event.target.closest('.list-item');
|
|
let state = listItem.getElementsByClassName('state')[0];
|
|
if (state && state.classList.contains('working')) {
|
|
// 우클릭한 파일이 작업중인 경우 경고 표시
|
|
addContextmenuItem('uploadFile', true);
|
|
addContextmenuWarn('working');
|
|
} else {
|
|
let existsWorkingListItem;
|
|
Array.from(listItem.getElementsByClassName('state')).forEach(state => {
|
|
if (state.classList.contains('working')) existsWorkingListItem = true;
|
|
})
|
|
if (existsWorkingListItem) {
|
|
// 우클릭한 파일의 추가 파일중에 작업중인 파일이 있는 경우 경고 표시
|
|
addContextmenuItem('uploadFile', true);
|
|
addContextmenuWarn('includesWorking');
|
|
} else {
|
|
addContextmenuItem('uploadFile', true);
|
|
addContextmenuItem('addOn_version', true);
|
|
addContextmenuItem('addOn_attachment', true);
|
|
if (listItem.classList.contains('dwg') || listItem.classList.contains('dxf')) {
|
|
//// 도면 ctb xref 관련 코드
|
|
// addContextmenuItem('addOn_ctb', true);
|
|
// addContextmenuItem('addOn_xref', true);
|
|
}
|
|
addContextmenuItem('renameTarget');
|
|
addContextmenuItem('editAuthor');
|
|
addContextmenuItem('downloadTarget');
|
|
addContextmenuItem('relocateTarget'); // 파일이동
|
|
addContextmenuItem('removeTarget');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isRecycleBinModal && userInfo.group == 'dev') {
|
|
addContextmenuItem('mgmtFunc_resetConvert');
|
|
}
|
|
|
|
// 다중삭제, 다중이동 시 컨텍스트메뉴 추가
|
|
}
|
|
|
|
if (scope == 'grid-item') {
|
|
let gridItem = event.target.closest('.grid-item');
|
|
|
|
// 파일 이동 모드인 경우 리턴
|
|
if (document.querySelector('.list-viewer-cover').style.display == 'flex') return;
|
|
|
|
addContextmenuItem('uploadFile', true, event);
|
|
if (gridItem.classList.contains('dwg') || gridItem.classList.contains('dxf')) {
|
|
//// 도면 ctb xref 관련 코드
|
|
// addContextmenuItem('addOn_ctb', true);
|
|
// addContextmenuItem('addOn_xref', true);
|
|
}
|
|
addContextmenuItem('renameTarget');
|
|
addContextmenuItem('editAuthor');
|
|
addContextmenuItem('downloadTarget');
|
|
addContextmenuItem('relocateTarget'); // 파일이동
|
|
addContextmenuItem('removeTarget');
|
|
}
|
|
|
|
if (scope == 'map-item') {
|
|
// 갤러리 폴더 지도모드일 때 안내 표시 및 리턴
|
|
let mapContainer = listContainer?.querySelector('.map-container');
|
|
if (mapContainer.style.display == 'flex') {
|
|
let notificatitonParams = { text: '지도모드에서는 우클릭 기능이 제한됩니다.' }
|
|
showNotification(notificatitonParams);
|
|
return;
|
|
}
|
|
|
|
// addContextmenuItem('uploadFile', true, event);
|
|
// addContextmenuItem('renameTarget');
|
|
// addContextmenuItem('downloadTarget');
|
|
// addContextmenuItem('relocateTarget'); // 파일이동
|
|
// addContextmenuItem('removeTarget');
|
|
}
|
|
|
|
if (scope == 'sub-list-item') {
|
|
let listItem = event.target.closest('.list-item');
|
|
|
|
// 추가 파일에 disabled 클래스가 포함되어 있으면 리턴 (파일 이동 모드)
|
|
if (listItem.classList.contains('disabled')) return;
|
|
|
|
let state = listItem.getElementsByClassName('state')[0];
|
|
if (state && state.classList.contains('working')) {
|
|
// 우클릭한 추가 파일이 작업중인 경우 경고 표시
|
|
addContextmenuWarn('working');
|
|
} else {
|
|
addContextmenuItem('renameTarget');
|
|
addContextmenuItem('editAuthor');
|
|
addContextmenuItem('downloadTarget');
|
|
addContextmenuItem('relocateTarget');
|
|
addContextmenuItem('removeTarget');
|
|
// addContextmenuItem('deleteTarget');
|
|
}
|
|
|
|
if (userInfo.group == 'dev') addContextmenuItem('mgmtFunc_resetConvert');
|
|
|
|
// 다중삭제, 다중이동 시 컨텍스트메뉴 추가
|
|
}
|
|
|
|
if (scope == 'multi-select-list') {
|
|
if (isRecycleBinModal) {
|
|
addContextmenuItem('downloadTarget');
|
|
addContextmenuItem('deleteTarget');
|
|
} else {
|
|
let existsWorkingListItem;
|
|
let existsMainListItem;
|
|
let extArr = [];
|
|
vars.multiSelectListItemArr.map(listItem => {
|
|
if (listItem.classList.contains('main-list-item')) {
|
|
existsMainListItem = true;
|
|
|
|
Array.from(listItem.getElementsByClassName('state')).forEach(state => {
|
|
if (state.classList.contains('working')) existsWorkingListItem = true;
|
|
})
|
|
|
|
let resourcePath = listItem.dataset.resourcePath;
|
|
let isLowerExt = true;
|
|
let ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
|
|
extArr.push(ext);
|
|
} else {
|
|
let state = listItem.getElementsByClassName('state')[0];
|
|
if (state && state.classList.contains('working')) existsWorkingListItem = true;
|
|
}
|
|
})
|
|
|
|
// 확장자 배열 내에서 중복요소 제거
|
|
extArr = extArr.filter((element, index) => {
|
|
return extArr.indexOf(element) === index;
|
|
});
|
|
|
|
if (existsWorkingListItem) {
|
|
// vars.multiSelectListItemArr에 포함된 파일 혹은 파일의 추가 파일중에 작업중인 파일이 있는 경우 경고 표시
|
|
addContextmenuWarn('includesWorking');
|
|
} else {
|
|
// 도면 확장자
|
|
let drawingExtArr = ['dwg', 'dxf'];
|
|
// 확장자 배열에 도면 확장자들만 들어있는지 확인
|
|
let isDrawingExtMatch = (extArr, drawingExtArr) => {
|
|
let extArrSet = new Set(extArr);
|
|
|
|
// 최소 1개 이상 있어야 함
|
|
if (extArrSet.size === 0) return false;
|
|
|
|
// extArrSet이 drawingExtArr의 부분집합인지 확인
|
|
for (let ext of extArrSet) {
|
|
if (!drawingExtArr.includes(ext)) return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// 우클릭 한 대상이 main-list-item이고, 확장자 배열에 도면 확장자들만 들어있으면 'CTB파일 일괄 추가', 'XREF파일 일괄 추가' 메뉴 추가
|
|
let listItem = event.target.closest('.list-item');
|
|
if (listItem.classList.contains('main-list-item') && isDrawingExtMatch(extArr, drawingExtArr)) {
|
|
//// 도면 ctb xref 관련 코드
|
|
// addContextmenuItem('addOn_ctb_bulk', true);
|
|
// addContextmenuItem('addOn_xref_bulk', true);
|
|
}
|
|
addContextmenuItem('editAuthor');
|
|
addContextmenuItem('downloadTarget');
|
|
addContextmenuItem('removeTarget');
|
|
addContextmenuItem('relocateTarget');
|
|
}
|
|
|
|
// addContextmenuItem('deleteTarget');
|
|
}
|
|
}
|
|
}
|
|
|
|
contextmenu.style.display = "flex";
|
|
contextmenu.style.left = event.clientX + 5 + "px";
|
|
contextmenu.style.top = event.clientY + 10 + "px";
|
|
|
|
// 편의성 - contextmenu가 화면 영역 아래/옆으로 들어가지 않도록 위치 조정
|
|
if (contextmenu.offsetHeight + event.clientY > window.innerHeight) {
|
|
contextmenu.style.top = (event.clientY - contextmenu.offsetHeight) + "px";
|
|
}
|
|
if (contextmenu.offsetWidth + event.clientX > window.innerWidth) {
|
|
contextmenu.style.left = (event.clientX - contextmenu.offsetWidth) + "px";
|
|
}
|
|
|
|
function addContextmenuWarn(target) {
|
|
// target : min-sub-master (부관리자 미만) / viewer (참관자)
|
|
let html;
|
|
if (target == 'min-sub-master') html = `상단 메뉴의 생성/편집/삭제는 부관리자 권한 이상에서만 가능합니다.`;
|
|
if (target == 'viewer') html = `참관자 권한은 열람만 가능합니다.`;
|
|
if (target == 'working') html = `변환중인 파일은 편집/삭제가 불가능합니다.`;
|
|
if (target == 'includesWorking') html = `선택한 파일 또는 관련된 버전/첨부 파일 중 변환중인 항목이 있어 편집/삭제가 불가능합니다.`;
|
|
|
|
let contextmenuWarn = document.createElement('div');
|
|
contextmenuWarn.classList.add('contextmenu-warn');
|
|
contextmenuWarn.classList.add('contextmenu-padding');
|
|
contextmenuContainer.appendChild(contextmenuWarn);
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
let text = document.createElement('div');
|
|
text.classList.add('text');
|
|
text.innerHTML = html;
|
|
|
|
wrap.appendChild(text);
|
|
contextmenuWarn.appendChild(wrap);
|
|
}
|
|
|
|
function addContextmenuItem(id, useInputFile, event) {
|
|
let menuData = getMenuData(id);
|
|
|
|
let contextmenuItem = document.createElement('div');
|
|
contextmenuItem.id = id;
|
|
contextmenuItem.classList.add('contextmenu-item');
|
|
|
|
let img = document.createElement('img');
|
|
let imgFileName = id.replace('_bulk', '');
|
|
img.src = `/main/img/archive/contextmenu/${imgFileName}.svg`;
|
|
img.alt = `${imgFileName}_icon`;
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
let name = document.createElement('div');
|
|
name.classList.add('name');
|
|
name.innerText = menuData.menuNameKor;
|
|
|
|
wrap.appendChild(name);
|
|
// let shortcut;
|
|
// if (menuData.shortcut) {
|
|
// shortcut = document.createElement('div');
|
|
// shortcut.classList.add('shortcut');
|
|
// shortcut.innerText = menuData.shortcut;
|
|
// wrap.appendChild(shortcut);
|
|
// }
|
|
|
|
if (id == 'removeTarget' && vars.lastContextTarget.matches('.folder')) {
|
|
img.src = `/main/img/archive/contextmenu/deleteTarget.svg`;
|
|
img.alt = `deleteTarget_icon`;
|
|
name.innerText = '삭제';
|
|
}
|
|
|
|
if (useInputFile) {
|
|
let isGalleryFolder = false, isDrawingFolder = false;
|
|
if (vars.lastFolderType == 'gallery') isGalleryFolder = true;
|
|
if (vars.lastFolderType == 'drawing') isDrawingFolder = true;
|
|
|
|
let form = document.createElement('form');
|
|
form.classList.add('btn');
|
|
form.enctype = 'multipart/form-data';
|
|
|
|
let input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.id = `${id}Input`;
|
|
|
|
if (id.includes('File') || id.includes('addOn')) {
|
|
input.setAttribute('multiple', '');
|
|
input.setAttribute('onclick', 'this.value=null;');
|
|
}
|
|
if (id.includes('Folder')) {
|
|
input.setAttribute('webkitdirectory', '');
|
|
input.setAttribute('directory', '');
|
|
input.setAttribute('onclick', 'this.value=null;');
|
|
}
|
|
|
|
let label = document.createElement('label');
|
|
label.id = `${id}Label`;
|
|
label.classList.add('contextmenu-padding');
|
|
label.setAttribute('for', `${id}Input`);
|
|
|
|
label.appendChild(img);
|
|
label.appendChild(wrap);
|
|
|
|
form.appendChild(input);
|
|
form.appendChild(label);
|
|
|
|
contextmenuItem.appendChild(form);
|
|
} else {
|
|
contextmenuItem.classList.add('contextmenu-padding');
|
|
|
|
contextmenuItem.appendChild(img);
|
|
contextmenuItem.appendChild(wrap);
|
|
|
|
if (id == 'createFolder') {
|
|
// 새 폴더 메뉴에 마우스오버 시 폴더 만들어지는 경로 툴팁 표시?
|
|
contextmenuItem.addEventListener('mouseover', (e) => {
|
|
// console.log(e.target);
|
|
// console.log('새 폴더');
|
|
})
|
|
}
|
|
|
|
if (id == 'relocateTarget') {
|
|
}
|
|
|
|
if (id == 'setPermission') {
|
|
contextmenuItem.classList.add('mouseover');
|
|
let toggle = document.createElement('div');
|
|
toggle.classList.add('image');
|
|
toggle.classList.add('toggle');
|
|
wrap.appendChild(toggle);
|
|
|
|
let subContextmenu = document.createElement('div');
|
|
subContextmenu.classList.add('contextmenu');
|
|
subContextmenu.classList.add('sub-menu');
|
|
|
|
let subContextmenuContainer = document.createElement('div');
|
|
subContextmenuContainer.classList.add('contextmenu-container');
|
|
|
|
let resourcePath = vars.lastContextTarget.dataset.resourcePath;
|
|
let data = getDataFromTreeObject(resourcePath, 'folder').data;
|
|
let dataPermission = data.permission;
|
|
|
|
let lev1 = getSubContextmenuItem('lev_1', dataPermission);
|
|
// let lev2 = getSubContextmenuItem('lev_2', dataPermission);
|
|
let lev4 = getSubContextmenuItem('lev_4', dataPermission);
|
|
let lev8 = getSubContextmenuItem('lev_8', dataPermission);
|
|
let lev0 = getSubContextmenuItem('lev_0', dataPermission);
|
|
|
|
subContextmenuContainer.appendChild(lev1);
|
|
// subContextmenuContainer.appendChild(lev2);
|
|
subContextmenuContainer.appendChild(lev4);
|
|
subContextmenuContainer.appendChild(lev8);
|
|
subContextmenuContainer.appendChild(lev0);
|
|
|
|
subContextmenu.appendChild(subContextmenuContainer);
|
|
|
|
contextmenuItem.appendChild(subContextmenu);
|
|
|
|
// 권한 설정 메뉴 마우스오버 시 하위 메뉴 표시
|
|
document.addEventListener('mousemove', throttle((e) => {
|
|
if (document.querySelector('.contextmenu.main-menu').style.display == 'flex') {
|
|
let subMenu = document.querySelector('.contextmenu.sub-menu');
|
|
if (!subMenu) return;
|
|
|
|
if (e.target.id == 'setPermission' || subMenu.contains(e.target)) {
|
|
subMenu.style.display = 'flex';
|
|
let parentHeight = subMenu.parentElement.offsetHeight;
|
|
//편의성 - subMenu가 화면 영역 아래로 들어가지 않도록 위치 조정
|
|
if (subMenu.offsetHeight + e.clientY > window.innerHeight) {
|
|
subMenu.style.top = `-${subMenu.offsetHeight - parentHeight - 4}px`;
|
|
}
|
|
} else {
|
|
subMenu.style.display = 'none';
|
|
}
|
|
}
|
|
}, 100))
|
|
}
|
|
}
|
|
|
|
contextmenuContainer.appendChild(contextmenuItem);
|
|
|
|
contextmenuItem.addEventListener('click', (e) => {
|
|
let targetId = e.target.id;
|
|
let resourcePath, dataId;
|
|
|
|
let lastContextTarget;
|
|
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
|
if (isRecycleBinModal) {
|
|
lastContextTarget = vars.lastContextTarget_bin;
|
|
} else {
|
|
lastContextTarget = vars.lastContextTarget;
|
|
}
|
|
|
|
if (lastContextTarget) resourcePath = lastContextTarget.dataset.resourcePath;
|
|
if (lastContextTarget) dataId = lastContextTarget.dataset.id;
|
|
|
|
if (targetId == 'createFolder') openCreateFolderModal(resourcePath);
|
|
if (targetId == 'renameTarget') openRenameModal(resourcePath);
|
|
// if (targetId == 'downloadTarget') downloadTarget();
|
|
if (targetId == 'downloadTarget') openDownloadModal(resourcePath);
|
|
if (targetId == 'downloadTarget-folder') openDownloadModal(resourcePath);
|
|
if (targetId == 'relocateTarget') toggleRelocateCover(true, resourcePath);
|
|
if (targetId == 'removeTarget') openRemoveModal(resourcePath);
|
|
if (targetId == 'deleteTarget') openDeleteModal(resourcePath);
|
|
// if (targetId == 'setPermission')
|
|
if (targetId == 'editAuthor') openEditAuthorModal(resourcePath);
|
|
if (targetId == 'lev_1') setDataPermission(resourcePath, targetId);
|
|
// if (targetId == 'lev_2')
|
|
if (targetId == 'lev_4') setDataPermission(resourcePath, targetId);
|
|
if (targetId == 'lev_8') setDataPermission(resourcePath, targetId);
|
|
if (targetId == 'lev_0') setDataPermission(resourcePath, targetId);
|
|
if (targetId == 'mgmtFunc_resetConvert') mgmtFunc_resetConvert(resourcePath, dataId);
|
|
})
|
|
|
|
contextmenuItem.addEventListener('change', (e) => {
|
|
let uploadDataOption = {
|
|
functionId: id,
|
|
dataType: 'file',
|
|
}
|
|
|
|
if (id == 'uploadFile') {
|
|
// console.log(111);
|
|
let files = e.target.files;
|
|
uploadData(files, uploadDataOption);
|
|
// 컨텍스트메뉴 초기화
|
|
contextmenu.querySelector('.contextmenu-container').innerHTML = '';
|
|
}
|
|
|
|
if (id == 'addOn_version' || id == 'addOn_attachment') {
|
|
// console.log(222);
|
|
let files = e.target.files;
|
|
let uploadDataOption = {
|
|
functionId: id,
|
|
dataType: 'file',
|
|
addOnTarget: vars.lastContextTarget.dataset.resourcePath
|
|
}
|
|
uploadData(files, uploadDataOption);
|
|
// 컨텍스트메뉴 초기화
|
|
contextmenu.querySelector('.contextmenu-container').innerHTML = '';
|
|
}
|
|
|
|
//// 도면 ctb xref 관련 코드
|
|
if (id.includes('addOn_ctb') || id.includes('addOn_xref')) {
|
|
// console.log(333);
|
|
// vars.multiSelectListItemArr 으로 배열 돌려서 아래 uploadData를 여러번 실행?
|
|
|
|
// let files = e.target.files
|
|
// let uploadDataOption = {
|
|
// functionId: id,
|
|
// dataType: 'file',
|
|
// addOnTarget: vars.lastContextTarget.dataset.resourcePath
|
|
// }
|
|
// uploadData(files, uploadDataOption);
|
|
// // 컨텍스트메뉴 초기화
|
|
// contextmenu.querySelector('.contextmenu-container').innerHTML = '';
|
|
}
|
|
})
|
|
}
|
|
|
|
function getSubContextmenuItem(id, dataPermission) {
|
|
let menuData = getMenuData(id);
|
|
|
|
let subContextmenuItem = document.createElement('div');
|
|
subContextmenuItem.id = id;
|
|
subContextmenuItem.classList.add('contextmenu-item');
|
|
subContextmenuItem.classList.add('contextmenu-padding');
|
|
subContextmenuItem.classList.add('set-permission-item');
|
|
// ** 권한 관련 (클래스에는 포함 안될 예정)
|
|
// id : lev_1, lev_2, lev_4, lev_8, lev_0
|
|
if (id.split('_')[1] == dataPermission) subContextmenuItem.classList.add('check');
|
|
|
|
let img = document.createElement('img');
|
|
img.src = `/main/img/permission/${menuData.menuNameEng}.svg`;
|
|
img.alt = `${id}_icon`;
|
|
|
|
let wrap = document.createElement('div');
|
|
wrap.classList.add('wrap');
|
|
|
|
let name = document.createElement('div');
|
|
name.classList.add('name');
|
|
name.innerText = menuData.menuNameKor;
|
|
|
|
let image = document.createElement('div');
|
|
image.classList.add('image');
|
|
|
|
wrap.appendChild(name);
|
|
wrap.appendChild(image);
|
|
|
|
subContextmenuItem.appendChild(img);
|
|
subContextmenuItem.appendChild(wrap);
|
|
|
|
return subContextmenuItem;
|
|
}
|
|
|
|
function getMenuData(id) {
|
|
let result = { menuNameKor: undefined, shortcut: undefined };
|
|
if (id == 'createFolder') result.menuNameKor = '새 폴더', result.shortcut = 'Ctrl ';
|
|
if (id == 'uploadFolder') result.menuNameKor = '폴더 업로드', result.shortcut = 'Ctrl ';
|
|
if (id == 'uploadFile') result.menuNameKor = '파일 업로드', result.shortcut = 'Ctrl ';
|
|
// if (id == 'uploadFile') result.menuNameKor = '현재 폴더에 파일 업로드', result.shortcut = 'Ctrl ';
|
|
if (id == 'addOn_version') result.menuNameKor = '버전파일 추가', result.shortcut = 'Ctrl ';
|
|
if (id == 'addOn_attachment') result.menuNameKor = '첨부파일 추가', result.shortcut = 'Ctrl ';
|
|
if (id == 'addOn_ctb') result.menuNameKor = 'CTB파일 추가', result.shortcut = 'Ctrl ';
|
|
if (id == 'addOn_xref') result.menuNameKor = 'XREF파일 추가', result.shortcut = 'Ctrl ';
|
|
if (id == 'addOn_ctb_bulk') result.menuNameKor = 'CTB파일 일괄 추가', result.shortcut = 'Ctrl ';
|
|
if (id == 'addOn_xref_bulk') result.menuNameKor = 'XREF파일 일괄 추가', result.shortcut = 'Ctrl ';
|
|
if (id == 'renameTarget') result.menuNameKor = '이름 변경', result.shortcut = 'F2';
|
|
if (id == 'relocateTarget') result.menuNameKor = '위치 이동', result.shortcut = 'Ctrl ';
|
|
if (id == 'downloadTarget') result.menuNameKor = '다운로드', result.shortcut = 'Ctrl ';
|
|
if (id == 'downloadTarget-folder') result.menuNameKor = '폴더다운로드(관리자)', result.shortcut = 'Ctrl ';
|
|
if (id == 'removeTarget') result.menuNameKor = '휴지통으로 이동', result.shortcut = 'Del';
|
|
if (id == 'deleteTarget') result.menuNameKor = '삭제', result.shortcut = 'Del';
|
|
if (id == 'editAuthor') result.menuNameKor = '작성자 변경', result.shortcut = 'Ctrl ';
|
|
if (id == 'setPermission') result.menuNameKor = '권한 설정';
|
|
if (id == 'lev_1') result.menuNameKor = '참관자', result.menuNameEng = 'viewer';
|
|
// if (id == 'lev_2') result.menuNameKor = '업로더';
|
|
if (id == 'lev_4') result.menuNameKor = '일반참여자', result.menuNameEng = 'worker';
|
|
if (id == 'lev_8') result.menuNameKor = '보안참여자', result.menuNameEng = 'security-worker';
|
|
if (id == 'lev_0') result.menuNameKor = '부관리자', result.menuNameEng = 'sub-master';
|
|
if (id == 'mgmtFunc_resetConvert') result.menuNameKor = '(개발자) 변환 초기화';
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export async function renderSizeBar() {
|
|
if (checkProjectInactive()) return;
|
|
|
|
// return;
|
|
let getFolderSizeParams = { storageType: vars.storageType };
|
|
let getFolderSizeRes = await axios.get(`${vars.path_name}/getFolderSize`, { params: { params: getFolderSizeParams } });
|
|
if (getFolderSizeRes.data.message == 'getFolderSize_success') {
|
|
let result = getFolderSizeRes.data.result;
|
|
|
|
let totalSize = 0, totalCount = 0;
|
|
|
|
for(let i = 0; i < result.length; i++) {
|
|
let item = result[i];
|
|
let key = item.key;
|
|
let size = Number(item.size);
|
|
let count = Number(item.count);
|
|
|
|
item.text = getSizeText(key);
|
|
|
|
totalSize += size, totalCount += count;
|
|
}
|
|
|
|
vars.bucketSize = totalSize;
|
|
vars.remainingSize = vars.bucketMaxSize - vars.bucketSize;
|
|
vars.foldersSize = result;
|
|
|
|
//// 용량 / 최대용량, 용량 막대, 퍼센트 적용 버전
|
|
// let footerSizeWrap = document.querySelector('body > .footer .left .size-wrap');
|
|
// let sizeTextNumerator = footerSizeWrap.querySelector('.size-text .numerator');
|
|
// let sizeTextDenominator = footerSizeWrap.querySelector('.size-text .denominator');
|
|
// let sizeBarBase = footerSizeWrap.querySelector('.size-bar .base');
|
|
// let sizeBarValue = footerSizeWrap.querySelector('.size-bar .value');
|
|
// let sizePercent = footerSizeWrap.querySelector('.size-percent');
|
|
|
|
// // 사용중인 용량 / 최대 용량 텍스트
|
|
// sizeTextNumerator.innerHTML = formatBytes(vars.bucketSize);
|
|
// sizeTextDenominator.innerHTML = formatBytes(vars.bucketMaxSize);
|
|
|
|
// // 최대 용량 대비 사용중인 용량 비율
|
|
// let usageRatio = (vars.project) ? vars.bucketSize / vars.bucketMaxSize : 0;
|
|
|
|
// // 최대 용량 막대 길이
|
|
// let sizeBarBaseLength = sizeBarBase.offsetWidth;
|
|
|
|
// // 사용중인 용량 막대 길이
|
|
// let sizeBarValueLength = sizeBarBaseLength * usageRatio;
|
|
// if (sizeBarValueLength > sizeBarBaseLength) sizeBarValueLength = sizeBarBaseLength;
|
|
// sizeBarValue.style.width = `${sizeBarValueLength}px`;
|
|
|
|
// // 최대 용량 대비 사용중인 용량 퍼센트
|
|
// let usagePercent = usageRatio * 100;
|
|
// sizePercent.innerText = `${formatPercent(usagePercent)} %`;
|
|
|
|
// if (usagePercent > 90) {
|
|
// sizeTextNumerator.classList.add('over');
|
|
// sizeBarValue.classList.add('over');
|
|
// sizePercent.classList.add('over');
|
|
// } else {
|
|
// sizeTextNumerator.classList.remove('over');
|
|
// sizeBarValue.classList.remove('over');
|
|
// sizePercent.classList.remove('over');
|
|
// }
|
|
|
|
//// 퍼센트 적용 버전
|
|
let footerSizeWrap = document.querySelector('body > .footer .left .size-wrap');
|
|
let sizePercent = footerSizeWrap.querySelector('.size-percent');
|
|
|
|
// 최대 용량 대비 사용중인 용량 비율
|
|
let usageRatio = (vars.project) ? vars.bucketSize / vars.bucketMaxSize : 0;
|
|
|
|
// 최대 용량 대비 사용중인 용량 퍼센트
|
|
let usagePercent = usageRatio * 100;
|
|
sizePercent.innerText = `${formatPercent(usagePercent)} %`;
|
|
|
|
if (usagePercent > 90) {
|
|
sizePercent.classList.add('over');
|
|
} else {
|
|
sizePercent.classList.remove('over');
|
|
}
|
|
|
|
//// 모달 저장공간 파이그래프
|
|
let dom = document.querySelector('.archive-modal .modal-body .size-wrap .chart');
|
|
|
|
let chart = echarts.init(dom, null, {
|
|
renderer: 'canvas',
|
|
useDirtyRect: false
|
|
});
|
|
|
|
let chartColor = {
|
|
'여유공간': '#ccc',
|
|
'휴지통': '#777',
|
|
'아카이브 원본': '#03AEFC',
|
|
'아카이브 변환PDF': '#4DB251',
|
|
'아카이브 미리보기PDF': '#FF3D00',
|
|
'공문 원본': '#FFBF00',
|
|
'공문 변환PDF': '#B92ED1',
|
|
'과업개요': '#4255BD'
|
|
};
|
|
|
|
// rawData 배열을 생성하고, rowData를 다시 용도별 데이터 배열로 가공
|
|
let rawData = [
|
|
{
|
|
value: (vars.remainingSize < 0) ? 0 : vars.remainingSize, // 여유공간이 0보다 작으면 0으로 표시
|
|
name: `여유공간 (${formatBytes(vars.remainingSize)})`,
|
|
itemStyle: { color: chartColor['여유공간'] }
|
|
}
|
|
];
|
|
for (let i = 0; i < vars.foldersSize.length; i++) {
|
|
let item = vars.foldersSize[i];
|
|
let obj = {};
|
|
|
|
if (item.size == 0) continue;
|
|
|
|
obj.value = item.size;
|
|
obj.name = `${item.text} (${formatBytes(item.size)} / ${item.count} 개)`;
|
|
obj.itemStyle = { color: chartColor[item.text] };
|
|
rawData.push(obj);
|
|
}
|
|
|
|
// 파이그래프용 실제 데이터
|
|
let mainData = rawData.filter(d => d.value > 0);
|
|
|
|
// 파이그래프용 더미 데이터
|
|
let dummyData = rawData.filter(d => d.value <= 0).map(d => ({
|
|
...d,
|
|
name: `{over|${d.name}}`,
|
|
value: 1
|
|
}));
|
|
|
|
// 범례용 데이터 (여유공간이 0 이하인 경우 rich 옵션 사용을 위해 {style_name|} 추가)
|
|
let legendData = rawData.map(d => ({
|
|
name: (d.value <= 0) ? `{over|${d.name}}` : d.name,
|
|
icon: 'circle',
|
|
}));
|
|
|
|
// 타이틀용 텍스트 (여유공간이 0 이하인 경우 rich 옵션 사용을 위해 {style_name|} 추가)
|
|
// let titleText = (vars.remainingSize <= 0)
|
|
// ? `{normal|전체공간 (${formatBytes(vars.bucketMaxSize)})} │ {normal|사용공간 (${formatBytes(vars.bucketSize)})} │ {over|여유공간 (${formatBytes(vars.remainingSize)})}`
|
|
// : `전체공간 (${formatBytes(vars.bucketMaxSize)}) │ 사용공간 (${formatBytes(vars.bucketSize)}) │ 여유공간 (${formatBytes(vars.remainingSize)})`
|
|
let titleText = (usagePercent > 90)
|
|
? `{normal|전체공간} {bold|${formatBytes(vars.bucketMaxSize)}}\n{normal|사용공간} {bold|${formatBytes(vars.bucketSize)}}\n{normal_over|여유공간} {bold_over|${formatBytes(vars.remainingSize)} (${formatPercent(usagePercent)} %)}`
|
|
: `{normal|전체공간} {bold|${formatBytes(vars.bucketMaxSize)}}\n{normal|사용공간} {bold|${formatBytes(vars.bucketSize)}}\n{normal|여유공간} {bold|${formatBytes(vars.remainingSize)} (${formatPercent(usagePercent)} %)}`
|
|
|
|
let option;
|
|
let fontFamily = 'Pretendard Variable, Pretendard';
|
|
let fontSize = 12;
|
|
let fontWeight = 600;
|
|
option = {
|
|
title: {
|
|
text: titleText,
|
|
// left: 'center',
|
|
// top: 10,
|
|
left: 20,
|
|
top: 12,
|
|
textStyle: {
|
|
lineHeight: 30,
|
|
rich: {
|
|
// rich 옵션
|
|
normal: {
|
|
fontFamily: fontFamily,
|
|
fontSize: fontSize,
|
|
color: '#000000'
|
|
},
|
|
normal_over: {
|
|
fontFamily: fontFamily,
|
|
fontSize: fontSize,
|
|
color: '#ff0000'
|
|
},
|
|
bold: {
|
|
fontFamily: fontFamily,
|
|
fontSize: fontSize,
|
|
color: '#000000',
|
|
fontWeight: fontWeight
|
|
},
|
|
bold_over: {
|
|
fontFamily: fontFamily,
|
|
fontSize: fontSize,
|
|
color: '#ff0000',
|
|
fontWeight: fontWeight
|
|
},
|
|
}
|
|
},
|
|
},
|
|
textStyle: {
|
|
fontFamily: 'Pretendard Variable, Pretendard',
|
|
fontSize: 12,
|
|
},
|
|
tooltip: {
|
|
trigger: 'item',
|
|
formatter: function (params) {
|
|
return `${params.marker} ${params.name}`;
|
|
}
|
|
},
|
|
legend: {
|
|
orient: 'vertical',
|
|
left: 20,
|
|
bottom: 20,
|
|
// top: 'middle',
|
|
itemWidth: 16,
|
|
itemHeight: 16,
|
|
data: legendData,
|
|
textStyle: {
|
|
fontFamily: 'Pretendard Variable, Pretendard',
|
|
fontSize: 12,
|
|
rich: {
|
|
// rich 옵션
|
|
over: { color: '#ccc' }
|
|
}
|
|
}
|
|
},
|
|
series: [
|
|
{
|
|
// 실제 파이 그래프
|
|
type: 'pie',
|
|
radius: '60%',
|
|
center: ['65%', '50%'],
|
|
data: mainData,
|
|
label: {
|
|
formatter: function (params) {
|
|
return `${params.name.split(' (')[0]}`;
|
|
},
|
|
minMargin: 5,
|
|
position: 'outer',
|
|
alignTo: 'labelLine',
|
|
bleedMargin: 20,
|
|
},
|
|
labelLine: {
|
|
show: true,
|
|
length: 80,
|
|
length2: 0,
|
|
},
|
|
minAngle: 2, // 최소 각도 설정
|
|
itemStyle: {
|
|
borderRadius: 4,
|
|
borderColor: '#ffffff',
|
|
borderWidth: 1
|
|
},
|
|
emphasis: {
|
|
// 마우스 hover 강조 시 색상 유지
|
|
itemStyle: {
|
|
color: 'inherit',
|
|
shadowBlur: 10,
|
|
shadowOffsetX: 0,
|
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
// 더미 파이그래프 (파이그래프는 표시하지 않고 범례만 표시)
|
|
type: 'pie',
|
|
radius: ['0%', '0%'], // 보이지 않도록 반지름 0%
|
|
center: ['70%', '50%'],
|
|
label: { show: false }, // 파이그래프 주변 라벨 표시 안함
|
|
labelLine: { show: false }, // 파이그래프와 파이그래프 주변 라벨을 연결하는 선 표시 안함
|
|
data: dummyData.map(d => ({
|
|
...d,
|
|
value: 1 // 범례 표시를 위해 value 설정
|
|
})),
|
|
emphasis: {
|
|
disabled: true // 마우스 hover 강조 비활성화
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
if (option && typeof option === 'object') {
|
|
chart.setOption(option);
|
|
}
|
|
|
|
chart.resize();
|
|
|
|
function formatPercent(percent) {
|
|
let result = percent.toFixed(2);
|
|
let decimalPointNum = result.split('.')[1];
|
|
// 소수점 아래 숫자가 00인 경우
|
|
if (decimalPointNum == '00') result = result.split('.')[0];
|
|
// 소수점 아래 두번째 숫자가 0인 경우
|
|
else if (decimalPointNum[1] == '0') result = `${result.split('.')[0]}.${decimalPointNum[0]}`;
|
|
return result;
|
|
}
|
|
|
|
function getSizeText(key) {
|
|
let result;
|
|
let keySplit = key.split('/');
|
|
|
|
if (keySplit[1]) {
|
|
let prefix, suffix;
|
|
if (keySplit[0] == 'archive') prefix = '아카이브';
|
|
if (keySplit[0] == 'official_doc') prefix = '공문';
|
|
|
|
if (keySplit[1] == 'origin') suffix = '원본';
|
|
if (keySplit[1] == 'pdf') suffix = '변환PDF';
|
|
if (keySplit[1] == 'pdf_thumb') suffix = '미리보기PDF';
|
|
|
|
result = `${prefix} ${suffix}`;
|
|
} else {
|
|
if (keySplit[0] == 'recycle_bin') result = '휴지통';
|
|
if (keySplit[0] == 'overview') result = '과업개요';
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function renderLog(isInit, addFooterLog, filterValues) {
|
|
if (checkProjectInactive()) return;
|
|
|
|
// addFooterLog : 푸터 로그 추가 여부
|
|
// 기본은 true, 활동 로그 모달 열 때 실행하는 renderLog에서는 false
|
|
if (addFooterLog == undefined) addFooterLog = true;
|
|
|
|
let footerLatestLog = document.querySelector('body > .footer .left .log-wrap .latest-log');
|
|
|
|
let modalLogItemWrap = document.querySelector('.archive-modal .modal-body .log-wrap .log-item-wrap.log-body');
|
|
|
|
// 로그 데이터 가져와서 전역 변수에 저장
|
|
if (filterValues != undefined) {
|
|
vars.logData = filterValues;
|
|
}else {
|
|
let logRes = await axios.get(`${vars.path_name}/getLog`);
|
|
vars.logData = logRes.data.logData;
|
|
}
|
|
modalLogItemWrap.innerHTML = '';
|
|
|
|
// 새로 로그 렌더하기 전에 마지막으로 추가된 log-item 애니메이션 초기화
|
|
vars.newLogItemArr.forEach(newLogItem => {
|
|
newLogItem.style.setProperty('--log-highlight', 'none');
|
|
})
|
|
vars.newLogItemArr = [];
|
|
|
|
Object.values(vars.logData).forEach((data, idx) => {
|
|
// 로그 id (시퀀스 번호)
|
|
let logId = data.log_id;
|
|
|
|
// let existingLogItem = document.querySelector(`.log-${logId}`);
|
|
// if (existingLogItem) {
|
|
// // 애니메이션 초기화
|
|
// // existingLogItem.style.setProperty('--log-highlight', 'none');
|
|
// } else {
|
|
// 로그 시간 (yy. MM. dd. hh:mm:ss)
|
|
let logDate = new Date(data.log_date);
|
|
logDate = `${changeDateFormat(logDate.toLocaleDateString('ko-KR'))} ${logDate.toLocaleTimeString('en-US', {hour12: false})}`;
|
|
|
|
// 영어로 된 활동 id를 한글로 번역
|
|
let activity = data.activity;
|
|
let activityKor = translateActivity(activity);
|
|
let activityType = activity.split('_')[0];
|
|
|
|
// data에서 유저 관련 변수들 저장 후 조합해서 유저 정보 변수 생성
|
|
// let userId = data.user_id;
|
|
// let userCompany = data.user_company;
|
|
// let userDept = data.user_dept;
|
|
let userNm = data.user_nm;
|
|
let userPosition = '';
|
|
if (data.user_position) userPosition = data.user_position;
|
|
// let user = `${userCompany} ${userDept} ${userNm} ${userPosition}`;
|
|
let user = (`${userNm} ${userPosition}`).trim();
|
|
if (activity == 'removeTarget_folder_expired') user = '-';
|
|
|
|
// 활동 별 log 및 id 생성해서 각각 배열에 추가
|
|
let pathArr;
|
|
try {
|
|
pathArr = typeof data.path_arr === 'string' ? JSON.parse(data.path_arr) : data.path_arr;
|
|
} catch (e) {
|
|
pathArr = Array.isArray(data.path_arr) ? data.path_arr : [data.path_arr];
|
|
}
|
|
|
|
let idArr = [], dateArr = [], userArr = [], activityArr = [], logArr = [];
|
|
if (activity == 'uploadData_file') {
|
|
// 파일 업로드 경우 다중 업로드가 가능하므로 업로드 된 파일마다 로그 생성
|
|
for (let i = 0; i < pathArr.length; i++) {
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${pathArr[i]}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText = document.createElement('div');
|
|
logText.classList.add('text');
|
|
logText.innerHTML = `${pathArr[i]}`;
|
|
log.appendChild(logText);
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
}
|
|
} else if (activity.startsWith('renameTarget')) {
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${pathArr[0]}`;
|
|
if (pathArr[1]) id += `_${pathArr[1]}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText1 = document.createElement('div');
|
|
logText1.classList.add('text');
|
|
logText1.innerHTML = `변경 전 : ${pathArr[0]}`;
|
|
let logText2 = document.createElement('div');
|
|
logText2.classList.add('text');
|
|
logText2.innerHTML = `변경 후 : ${pathArr[1]}`;
|
|
log.appendChild(logText1);
|
|
log.appendChild(logText2);
|
|
|
|
let width1 = getTextPixelWidth(`변경 전 : `);
|
|
logText1.style.textIndent = `-${pxToRem(width1-2)}rem`;
|
|
logText1.style.paddingLeft = `${pxToRem(width1-2)}rem`;
|
|
let width2 = getTextPixelWidth(`변경 후 : `);
|
|
logText2.style.textIndent = `-${pxToRem(width2-2)}rem`;
|
|
logText2.style.paddingLeft = `${pxToRem(width2-2)}rem`;
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
} else if (activity.startsWith('relocateTarget')) {
|
|
for (let i = 0; i < pathArr.length; i++) {
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${pathArr[i].from}_${pathArr[i].to}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText1 = document.createElement('div');
|
|
logText1.classList.add('text');
|
|
logText1.innerHTML = `이동 전 : ${pathArr[i].from}`;
|
|
let logText2 = document.createElement('div');
|
|
logText2.classList.add('text');
|
|
logText2.innerHTML = `이동 후 : ${pathArr[i].to}`;
|
|
log.appendChild(logText1);
|
|
log.appendChild(logText2);
|
|
|
|
let width1 = getTextPixelWidth(`변경 전 : `);
|
|
logText1.style.textIndent = `-${pxToRem(width1-2)}rem`;
|
|
logText1.style.paddingLeft = `${pxToRem(width1-2)}rem`;
|
|
let width2 = getTextPixelWidth(`변경 후 : `);
|
|
logText2.style.textIndent = `-${pxToRem(width2-2)}rem`;
|
|
logText2.style.paddingLeft = `${pxToRem(width2-2)}rem`;
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
}
|
|
} else if (activity.startsWith('setDataPermission')) {
|
|
let path = pathArr[0].resourcePath;
|
|
let beforePermission = pathArr[0].beforePermission;
|
|
let newPermission = pathArr[0].newPermission;
|
|
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${path}_${beforePermission}_${newPermission}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText1 = document.createElement('div');
|
|
logText1.classList.add('text');
|
|
logText1.innerHTML = `${path}`;
|
|
let logText2 = document.createElement('div');
|
|
logText2.classList.add('text');
|
|
logText2.innerHTML = `기존 권한 : ${beforePermission} → 신규 권한 : ${newPermission}`;
|
|
log.appendChild(logText1);
|
|
log.appendChild(logText2);
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
} else if (activity.startsWith('addPermission') || activity.startsWith('deletePermission')){
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${pathArr[0]}`;
|
|
if (pathArr[1]) id += `_${pathArr[1]}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText = document.createElement('div');
|
|
logText.classList.add('text');
|
|
// 유저권한 로그는 한명 초과시 누구 외 O명으로 표현
|
|
logText.innerHTML = pathArr.length === 1 ? pathArr[0] : `${pathArr[0]} 외 ${pathArr.length - 1}명`;
|
|
log.appendChild(logText);
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
} else if (activity == 'editAuthor') {
|
|
for (let i = 0; i < pathArr.length; i++) {
|
|
let path = pathArr[i].resourcePath;
|
|
let prevAuthorNm = pathArr[i].prevAuthorNm;
|
|
let newAuthorNm = pathArr[i].newAuthorNm;
|
|
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${path}_${prevAuthorNm}_${newAuthorNm}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText1 = document.createElement('div');
|
|
logText1.classList.add('text');
|
|
logText1.innerHTML = `${path}`;
|
|
let logText2 = document.createElement('div');
|
|
logText2.classList.add('text');
|
|
logText2.innerHTML = `기존 작성자 : ${prevAuthorNm} → 신규 작성자 : ${newAuthorNm}`;
|
|
log.appendChild(logText1);
|
|
log.appendChild(logText2);
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
}
|
|
} else {
|
|
for (let i = 0; i < pathArr.length; i++) {
|
|
let id = `${logId}_${logDate}_${user}_${activityKor}_${pathArr[0]}`;
|
|
if (pathArr[1]) id += `_${pathArr[1]}`;
|
|
|
|
let log = document.createElement('div');
|
|
log.classList.add('log');
|
|
log.classList.add(activityType);
|
|
let logText = document.createElement('div');
|
|
logText.classList.add('text');
|
|
logText.innerHTML = `${pathArr[i]}`;
|
|
log.appendChild(logText);
|
|
|
|
idArr.push(id);
|
|
dateArr.push(logDate);
|
|
userArr.push(user);
|
|
activityArr.push(activityKor);
|
|
logArr.push(log);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < logArr.length; i++) {
|
|
let id = idArr[i];
|
|
let date = dateArr[i];
|
|
let user = userArr[i];
|
|
let activity = activityArr[i];
|
|
let log = logArr[i];
|
|
|
|
let dateDiv = document.createElement('div');
|
|
dateDiv.classList.add('date');
|
|
let dateText = document.createElement('div');
|
|
dateText.classList.add('text');
|
|
dateText.innerHTML = date;
|
|
dateDiv.appendChild(dateText);
|
|
|
|
let userDiv = document.createElement('div');
|
|
userDiv.classList.add('user');
|
|
let userText = document.createElement('div');
|
|
userText.classList.add('text');
|
|
userText.innerHTML = user;
|
|
userDiv.appendChild(userText);
|
|
|
|
let activityDiv = document.createElement('div');
|
|
activityDiv.classList.add('activity');
|
|
let activityText = document.createElement('div');
|
|
activityText.classList.add('text');
|
|
activityText.innerHTML = activity;
|
|
activityDiv.appendChild(activityText);
|
|
|
|
let logItem = document.createElement('div');
|
|
logItem.id = id;
|
|
logItem.classList.add('log-item');
|
|
logItem.classList.add(`log-${logId}`);
|
|
logItem.appendChild(dateDiv);
|
|
logItem.appendChild(userDiv);
|
|
logItem.appendChild(activityDiv);
|
|
logItem.appendChild(log);
|
|
|
|
// modalLogItemWrap.prepend(logItem);
|
|
modalLogItemWrap.prepend(logItem);
|
|
|
|
// vars.newLogItemArr 배열에 새로운 로그 아이템 추가
|
|
vars.newLogItemArr.push(logItem);
|
|
}
|
|
// }
|
|
})
|
|
|
|
// vars.newLogItemArr 배열에 있는 로그 아이템에 애니메이션 추가
|
|
for (let i = 0; i < vars.newLogItemArr.length; i++) {
|
|
let newLogItem = vars.newLogItemArr[i];
|
|
if (!isInit) {
|
|
newLogItem.style.setProperty('--log-highlight', 'log-highlight 1s linear(0 0%, 1 50%, 0 100%) 0s 2 normal none running');
|
|
}
|
|
}
|
|
|
|
// 모달 로그 아이템 중 최신 로그 복제
|
|
if (addFooterLog) {
|
|
let modalLatestLog = modalLogItemWrap.children[0];
|
|
if (modalLatestLog) {
|
|
let modalLatestLogClone = modalLatestLog.cloneNode(true);
|
|
|
|
// 푸터 로그 영역 비우고 복제한 로그 추가
|
|
if (footerLatestLog) {
|
|
let dateClone = modalLatestLogClone.querySelector('.date').cloneNode(true);
|
|
let userClone = modalLatestLogClone.querySelector('.user').cloneNode(true);
|
|
let activityClone = modalLatestLogClone.querySelector('.activity').cloneNode(true);
|
|
let logClone = modalLatestLogClone.querySelector('.log').cloneNode(true);
|
|
|
|
let separator1 = document.createElement('div');
|
|
separator1.classList.add('log-separator');
|
|
let separator2 = document.createElement('div');
|
|
separator2.classList.add('log-separator');
|
|
let separator3 = document.createElement('div');
|
|
separator3.classList.add('log-separator');
|
|
|
|
footerLatestLog.innerHTML = '';
|
|
footerLatestLog.appendChild(modalLatestLogClone);
|
|
|
|
modalLatestLogClone.innerHTML = '';
|
|
modalLatestLogClone.appendChild(dateClone);
|
|
modalLatestLogClone.appendChild(separator1);
|
|
modalLatestLogClone.appendChild(userClone);
|
|
modalLatestLogClone.appendChild(separator2);
|
|
modalLatestLogClone.appendChild(activityClone);
|
|
modalLatestLogClone.appendChild(separator3);
|
|
modalLatestLogClone.appendChild(logClone);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//// 활동로그 모달에서 시간, 유저, 활동종류 각각 제일 긴 항목 기준으로 width 조정
|
|
if (document.querySelector('.archive-modal .modal-body .log-wrap').style.display == 'flex') {
|
|
// 활동로그가 열려있을 때에만 적용
|
|
let dateDivs = modalLogItemWrap.querySelectorAll('.log-item .date');
|
|
let userDivs = modalLogItemWrap.querySelectorAll('.log-item .user');
|
|
let activityDivs = modalLogItemWrap.querySelectorAll('.log-item .activity');
|
|
resizeLogDivs(dateDivs);
|
|
resizeLogDivs(userDivs);
|
|
resizeLogDivs(activityDivs);
|
|
|
|
let firstLog = document.querySelector('.log-item-wrap.log-body').childNodes[0];
|
|
if (firstLog?.classList?.contains('log-item')) {
|
|
let dateWidth = firstLog.getElementsByClassName('date')[0].offsetWidth;
|
|
let userWidth = firstLog.getElementsByClassName('user')[0].offsetWidth;
|
|
let activityWidth = firstLog.getElementsByClassName('activity')[0].offsetWidth;
|
|
|
|
document.querySelector('.log-item-wrap.log-header .date').style.width = `${dateWidth}px`;
|
|
document.querySelector('.log-item-wrap.log-header .user').style.width = `${userWidth}px`;
|
|
document.querySelector('.log-item-wrap.log-header .activity').style.width = `${activityWidth}px`;
|
|
}
|
|
}
|
|
|
|
document.querySelector('.archive-modal .modal-body > .log-wrap').style.opacity = '1';
|
|
|
|
function getTextPixelWidth(text) {
|
|
let remPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
let fontPx = remPx * 0.8;
|
|
let font = `${fontPx}px "Noto Sans KR"`;
|
|
let canvas = document.createElement('canvas');
|
|
let context = canvas.getContext('2d');
|
|
context.font = font;
|
|
let width = context.measureText(text).width;
|
|
return width;
|
|
}
|
|
|
|
function resizeLogDivs(divs) {
|
|
let maxTextWidth = 0;
|
|
|
|
divs.forEach(div => {
|
|
let text = div.querySelector('.text');
|
|
let textWidth = getTextPixelWidth(text.textContent);
|
|
if (textWidth > maxTextWidth) maxTextWidth = textWidth;
|
|
})
|
|
|
|
divs.forEach(div => {
|
|
div.style.width = `${pxToRem(maxTextWidth+8)}rem`;
|
|
})
|
|
}
|
|
}
|
|
|
|
export async function renderEditAuthorUserList(inputWrap, keyword) {
|
|
let confirmBtn = document.querySelector('.archive-modal .modal-body .btn-wrap .confirm-btn');
|
|
confirmBtn.classList.add('disabled');
|
|
|
|
// 기존 경고문구 있으면 삭제
|
|
if (document.querySelector('.archive-modal .input-wrap .warn')) {
|
|
inputWrap.removeChild(document.querySelector('.archive-modal .input-wrap .warn'));
|
|
}
|
|
// 경고문구 dom 생성
|
|
let warn = document.createElement('div');
|
|
warn.classList.add('warn');
|
|
warn.style.top = `${inputWrap.offsetHeight}px`;
|
|
inputWrap.appendChild(warn);
|
|
|
|
// 이름으로 필터링 이후 직급으로 정렬
|
|
let users = vars.permissionList.all;
|
|
let positionMap = {'회장': 1, '부회장':2, '사장':3, '상임고문':4, '기술위원':5, '부사장':6, '고문':7, '전무이사':8, '수석연구원':9, '상무이사':10, '이사':11, '책임연구원':12, '부장':13, '차장':14, '선임연구원':15, '과장':16, '연구원':17, '대리':18, '사원':19};
|
|
let resultArr = users.filter(user => user.user_nm?.toLowerCase().includes(keyword)).sort((a,b) => {
|
|
let aPosition = positionMap[a.position] ?? 99;
|
|
let bPosition = positionMap[b.position] ?? 99;
|
|
if(aPosition !== bPosition) return aPosition - bPosition;
|
|
return a.user_id.localeCompare(b.user_id);
|
|
})
|
|
|
|
// 상황에 따라 경고문구 텍스트 추가 또는 renameTarget 진행
|
|
if (keyword == '') {
|
|
warn.innerText = '검색창에 이름을 입력해주세요.';
|
|
} else if (resultArr.length === 0) {
|
|
warn.innerText = '검색 결과가 없습니다.';
|
|
} else {
|
|
// 경고문구 dom 삭제
|
|
inputWrap.removeChild(warn);
|
|
|
|
// 직원 리스트 생성
|
|
let userListWrap = document.querySelector('.archive-modal .modal-body .user-list-wrap');
|
|
let userItemWrap = userListWrap.querySelector('.user-item-wrap');
|
|
// 작성자 변경 선택 결과 숨김
|
|
// let selectedUserItemWrap = userListWrap.querySelector('.selected-user-item-wrap');
|
|
// let selectedUserItem = selectedUserItemWrap.querySelector('.selected-user-item');
|
|
|
|
userListWrap.style.display = 'flex';
|
|
userListWrap.style.width = `${pxToRem(inputWrap.getBoundingClientRect().width)}rem`;
|
|
userItemWrap.innerHTML = '';
|
|
// 작성자 변경 선택 결과 숨김
|
|
// selectedUserItemWrap.style.display = 'none';
|
|
// selectedUserItem.innerHTML = '';
|
|
|
|
for (let result of resultArr) {
|
|
let userItem = document.createElement('div');
|
|
userItem.classList.add('user-item');
|
|
userItem.dataset.userId = result.user_id;
|
|
userItem.dataset.userNm = result.user_nm;
|
|
|
|
let userImage = document.createElement('img');
|
|
userImage.classList.add('user-image');
|
|
let defaultImageUrl = '/main/img/archive/empty-profile.svg';
|
|
let userImageUrl = defaultImageUrl;
|
|
if (/^[a-zA-Z]{1}\d{5}$/.test(result.user_id)) userImageUrl = `http://erp.hanmaceng.co.kr/erpphoto/${result.user_id}.jpg`;
|
|
userImage.src = userImageUrl;
|
|
// onerror 핸들러 바인딩 (한번만 동작하도록)
|
|
userImage.onerror = function() {
|
|
this.onerror = null; // 무한루프 방지
|
|
this.src = defaultImageUrl;
|
|
};
|
|
|
|
let userInfo = document.createElement('div');
|
|
userInfo.classList.add('user-info');
|
|
|
|
let userCompany = document.createElement('div');
|
|
userCompany.classList.add('user-company');
|
|
let userCompanyIcon = document.createElement('img');
|
|
userCompanyIcon.classList.add('user-company-icon');
|
|
userCompanyIcon.src = `/main/img/permission/s-icon__${result.company}-small-logo.svg`;
|
|
let userCompanyText = document.createElement('div');
|
|
userCompanyText.classList.add('user-company-text');
|
|
userCompanyText.innerHTML = result.company;
|
|
|
|
let userName = document.createElement('div');
|
|
userName.classList.add('user-name');
|
|
userName.innerHTML = (`${result.user_nm} ${result.position ?? ''}`).trim();
|
|
|
|
userItemWrap.appendChild(userItem);
|
|
|
|
userItem.appendChild(userImage);
|
|
userItem.appendChild(userInfo);
|
|
|
|
userInfo.appendChild(userCompany);
|
|
userInfo.appendChild(userName);
|
|
|
|
userCompany.appendChild(userCompanyIcon);
|
|
userCompany.appendChild(userCompanyText);
|
|
|
|
userItem.addEventListener('click', (e) => {
|
|
userItemWrap.childNodes.forEach(item => {
|
|
if (e.target == item) {
|
|
item.classList.add('selected');
|
|
} else {
|
|
item.classList.remove('selected');
|
|
}
|
|
})
|
|
// 작성자 변경 선택 결과 숨김
|
|
// let userItemClone = userItem.cloneNode(true);
|
|
// if (selectedUserItemWrap.style.display != 'flex') selectedUserItemWrap.style.display = 'flex';
|
|
// selectedUserItem.innerHTML = '';
|
|
// selectedUserItem.appendChild(userItemClone);
|
|
|
|
document.querySelector('.archive-modal .modal-body .btn-wrap .confirm-btn').classList.remove('disabled');
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
export function changeHeaderBtnStyle(headerBtn) {
|
|
// if (vars.lastHeaderBtn) vars.lastHeaderBtn.classList.remove('selected');
|
|
|
|
// document.querySelectorAll('.header-center .menu-tab .btn').forEach(e => {
|
|
document.querySelectorAll('body > .header .center .btn').forEach(e => {
|
|
e.classList.remove('selected');
|
|
});
|
|
|
|
// if (document.querySelector('.official-doc-btn')) document.querySelector('.official-doc-btn').classList.remove('selected');
|
|
|
|
if (headerBtn) vars.lastHeaderBtn = headerBtn;
|
|
if (vars.lastHeaderBtn) {
|
|
vars.lastHeaderBtn.classList.add('selected');
|
|
// let treeTitle = document.querySelector()
|
|
}
|
|
|
|
// vars.users[JSON.parse(vars.userInfoString).clientId].curPath = resourcePath;
|
|
|
|
// vars.lastMainTreeItem = undefined;
|
|
// vars.lastListItem = undefined;
|
|
// vars.lastContextTarget = undefined;
|
|
// vars.lastSelectTarget = undefined;
|
|
// document.querySelector('.archive-main-center .list-container .list-wrap.list-header').dataset.resourcePath = '';
|
|
// document.querySelector('.archive-main-center .list-container .list-wrap.list-body').dataset.resourcePath = '';
|
|
}
|
|
|
|
export function changeTreeItemStyle(treeItem, targetTree='main') {
|
|
if (targetTree == 'main') {
|
|
// if (vars.lastMainTreeItem) vars.lastMainTreeItem.style.background = '';
|
|
// vars.lastMainTreeItem = treeItem;
|
|
// vars.lastMainTreeItem.style.background = '#bccbc8';
|
|
document.querySelectorAll('.archive-main-left .tree-container .tree .tree-item-wrap.depth3').forEach(e => {
|
|
e.classList.remove('selected');
|
|
});
|
|
document.querySelector('.archive-main-left .tree-title').classList.remove('selected');
|
|
|
|
if (treeItem) vars.lastMainTreeItem = treeItem;
|
|
if (vars.lastMainTreeItem) {
|
|
vars.lastMainTreeItem.classList.add('selected');
|
|
vars.lastSelectTarget = vars.lastMainTreeItem.querySelector('.tree-item');
|
|
}
|
|
// } else {
|
|
// if (vars.lastModalTreeItem) vars.lastModalTreeItem.style.background = '';
|
|
// vars.lastModalTreeItem = treeItem;
|
|
// vars.lastModalTreeItem.style.background = '#bccbc8';
|
|
}
|
|
}
|
|
|
|
export function changeListItemStyle(listItem, from) {
|
|
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
|
|
|
if (isRecycleBinModal) {
|
|
//// 서브 list item 있는 경우 배경 색상 변경
|
|
if (vars.lastListGroupTarget_bin) {
|
|
vars.lastListGroupTarget_bin.classList.remove('group-style');
|
|
}
|
|
|
|
if (listItem.matches('.main-list-item')) {
|
|
vars.lastListGroupTarget_bin = listItem;
|
|
}
|
|
if (listItem.matches('.sub-list-item')) {
|
|
vars.lastListGroupTarget_bin = document.querySelector(`.list-item.main-list-item[data-main-file-name="${listItem.dataset.mainFileName}"]`);
|
|
// if (listItem.matches('.grid-item')) {
|
|
// vars.lastListGroupTarget_bin = document.querySelector(`.grid-item.main-list-item[data-main-file-name="${listItem.dataset.mainFileName}"]`);
|
|
// } else {
|
|
// vars.lastListGroupTarget_bin = listItem.closest('.main-list-item');
|
|
// }
|
|
}
|
|
|
|
if (vars.lastListGroupTarget_bin) {
|
|
vars.lastListGroupTarget_bin.classList.add('group-style');
|
|
}
|
|
|
|
//// 단일 list item 배경 색상 변경
|
|
if (vars.lastListItem_bin) {
|
|
vars.lastListItem_bin.classList.remove('selected');
|
|
}
|
|
|
|
vars.lastListItem_bin = listItem;
|
|
vars.lastListItem_bin.classList.add('selected');
|
|
vars.lastSelectTarget_bin = vars.lastListItem_bin;
|
|
|
|
let isExistsDataId = false;
|
|
vars.multiSelectListItemArr_bin.forEach((item) => {
|
|
if (item.dataset.dataId === listItem.dataset.dataId) isExistsDataId = true;
|
|
})
|
|
if (!isExistsDataId) vars.multiSelectListItemArr_bin.push(listItem);
|
|
} else {
|
|
//// 서브 list item 있는 경우 배경 색상 변경
|
|
if (vars.lastListGroupTarget) {
|
|
vars.lastListGroupTarget.classList.remove('group-style');
|
|
}
|
|
|
|
if (listItem.matches('.main-list-item')) {
|
|
vars.lastListGroupTarget = listItem;
|
|
}
|
|
if (listItem.matches('.sub-list-item')) {
|
|
vars.lastListGroupTarget = document.querySelector(`.list-item.main-list-item[data-main-file-name="${listItem.dataset.mainFileName}"]`);
|
|
// if (listItem.matches('.grid-item')) {
|
|
// vars.lastListGroupTarget = document.querySelector(`.grid-item.main-list-item[data-main-file-name="${listItem.dataset.mainFileName}"]`);
|
|
// } else {
|
|
// vars.lastListGroupTarget = listItem.closest('.main-list-item');
|
|
// }
|
|
}
|
|
|
|
if (vars.lastListGroupTarget) {
|
|
vars.lastListGroupTarget.classList.add('group-style');
|
|
}
|
|
|
|
//// 단일 list item 배경 색상 변경
|
|
if (vars.lastListItem) {
|
|
vars.lastListItem.classList.remove('selected');
|
|
}
|
|
|
|
vars.lastListItem = listItem;
|
|
vars.lastListItem.classList.add('selected');
|
|
vars.lastSelectTarget = vars.lastListItem;
|
|
|
|
let isExistsDataId = false;
|
|
vars.multiSelectListItemArr.forEach((item) => {
|
|
if (item.dataset.dataId === listItem.dataset.dataId) isExistsDataId = true;
|
|
})
|
|
if (!isExistsDataId) vars.multiSelectListItemArr.push(listItem);
|
|
}
|
|
|
|
let me = JSON.parse(vars.userInfoString);
|
|
me.selected = listItem.dataset.resourcePath;
|
|
vars.socket.emit('fileSelect',{me : me});
|
|
}
|
|
|
|
// 구성 모달 데이터 렌더링
|
|
export async function renderCompositionData() {
|
|
const modal = document.querySelector('.composition-modal');
|
|
|
|
let userString = vars.userInfoString;
|
|
let user = JSON.parse(userString);
|
|
|
|
// 최상단 폴더 (탭)
|
|
const tabList = Object.keys(vars.allTreeObject.folder).sort(naturalSort);
|
|
|
|
// 리스트 wrap
|
|
const listWrap = modal.querySelector('.modal-wrap > ul');
|
|
listWrap.innerHTML = ''; // 초기화
|
|
|
|
// 각 최상단 폴더(탭)에 대한 처리
|
|
for (const tab of tabList) {
|
|
let getTreeObjectParams = {
|
|
userInfoString: JSON.stringify(user),
|
|
storageType: vars.storageType,
|
|
resourcePath: tab
|
|
};
|
|
|
|
let getTreeObjectRes = await axios.get(`${vars.path_name}/getTreeObject`, { params: {params: getTreeObjectParams} });
|
|
if(getTreeObjectRes.data.message == 'getTreeObject_success') {
|
|
const treeData = getTreeObjectRes.data.currentTreeObject;
|
|
|
|
// 최상단 폴더 li 생성
|
|
const tabLi = document.createElement('li');
|
|
|
|
// 최상단 폴더 제목 (h3)
|
|
const tabTitle = document.createElement('h3');
|
|
tabTitle.textContent = tab;
|
|
tabLi.appendChild(tabTitle);
|
|
|
|
// 상단 폴더 (카테고리) ul 생성
|
|
const categoryUl = document.createElement('ul');
|
|
|
|
// 상단 폴더 (카테고리) 데이터가 있다면 처리
|
|
if (treeData && treeData.folder && Object.keys(treeData.folder).length > 0) {
|
|
const categories = Object.keys(treeData.folder).sort(naturalSort);
|
|
|
|
categories.forEach(category => {
|
|
// 상단 폴더 li 생성
|
|
const categoryLi = document.createElement('li');
|
|
|
|
// 상단 폴더 제목 (h5)
|
|
const categoryTitle = document.createElement('h5');
|
|
categoryTitle.textContent = category;
|
|
categoryLi.appendChild(categoryTitle);
|
|
|
|
// 폴더 ul 생성
|
|
const folderUl = document.createElement('ul');
|
|
|
|
const folders = treeData.folder[category] || [];
|
|
const folderObj = folders.child?.folder || {};
|
|
const folderKeys = Object.keys(folderObj).sort(naturalSort);
|
|
|
|
// 폴더가 없을 때 빈 항목 표시
|
|
if (folderKeys.length === 0) {
|
|
// const emptyFolderLi = document.createElement('li');
|
|
|
|
// const emptyIcon = document.createElement('i');
|
|
// emptyFolderLi.appendChild(emptyIcon);
|
|
|
|
// const emptyName = document.createElement('h6');
|
|
// emptyName.textContent = '-';
|
|
// emptyFolderLi.appendChild(emptyName);
|
|
|
|
// const emptyCount = document.createElement('h6');
|
|
// emptyCount.textContent = '-';
|
|
// emptyFolderLi.appendChild(emptyCount);
|
|
|
|
// folderUl.appendChild(emptyFolderLi);
|
|
} else {
|
|
// 폴더가 있을 때
|
|
folderKeys.forEach(folderName => {
|
|
const folderData = folderObj[folderName];
|
|
|
|
// 폴더 내 파일 개수 계산
|
|
let fileCount = 0;
|
|
|
|
// child.file의 파일 개수
|
|
fileCount += Object.keys(folderData.child?.file || {}).length;
|
|
|
|
// child.folder 안의 모든 파일들도 카운트 (첨부, 버전 파일)
|
|
if (folderData.child?.folder) {
|
|
Object.keys(folderData.child.folder).forEach(subFolderName => {
|
|
const subFolder = folderData.child.folder[subFolderName];
|
|
if (subFolder.child?.file) {
|
|
fileCount += Object.keys(subFolder.child.file).length;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 폴더 li 생성
|
|
const folderLi = document.createElement('li');
|
|
|
|
// 폴더 아이콘
|
|
const folderIcon = document.createElement('i');
|
|
folderLi.appendChild(folderIcon);
|
|
|
|
// 폴더명
|
|
const folderNameEl = document.createElement('h6');
|
|
folderNameEl.textContent = folderName;
|
|
folderLi.appendChild(folderNameEl);
|
|
|
|
// 파일 개수
|
|
const folderCountEl = document.createElement('h6');
|
|
folderCountEl.textContent = fileCount > 0 ? String(fileCount) : '-';
|
|
folderLi.appendChild(folderCountEl);
|
|
|
|
folderUl.appendChild(folderLi);
|
|
});
|
|
}
|
|
|
|
categoryLi.appendChild(folderUl);
|
|
categoryUl.appendChild(categoryLi);
|
|
});
|
|
} else {
|
|
|
|
// 카테고리가 없을 때 빈 항목 표시
|
|
const emptyCategoryLi = document.createElement('li');
|
|
|
|
const emptyCategoryTitle = document.createElement('h5');
|
|
emptyCategoryTitle.textContent = '-';
|
|
emptyCategoryLi.appendChild(emptyCategoryTitle);
|
|
|
|
const emptyFolderUl = document.createElement('ul');
|
|
const emptyFolderLi = document.createElement('li');
|
|
|
|
const emptyIcon = document.createElement('i');
|
|
emptyFolderLi.appendChild(emptyIcon);
|
|
|
|
const emptyName = document.createElement('h6');
|
|
emptyName.textContent = '-';
|
|
emptyFolderLi.appendChild(emptyName);
|
|
|
|
const emptyCount = document.createElement('h6');
|
|
emptyCount.textContent = '-';
|
|
emptyFolderLi.appendChild(emptyCount);
|
|
|
|
emptyFolderUl.appendChild(emptyFolderLi);
|
|
emptyCategoryLi.appendChild(emptyFolderUl);
|
|
categoryUl.appendChild(emptyCategoryLi);
|
|
|
|
}
|
|
|
|
tabLi.appendChild(categoryUl);
|
|
listWrap.appendChild(tabLi);
|
|
}
|
|
}
|
|
|
|
// 렌더링 완료 후 높이 동기화
|
|
setTimeout(() => {
|
|
const allLis = listWrap.querySelectorAll(':scope > li');
|
|
if (allLis.length === 0) return;
|
|
|
|
// 모든 li의 최대 높이 찾기
|
|
let maxHeight = 0;
|
|
allLis.forEach(li => {
|
|
const height = li.scrollHeight;
|
|
if (height > maxHeight) maxHeight = height;
|
|
});
|
|
|
|
// 모든 li 에 최대 높이 적용
|
|
allLis.forEach(li => {
|
|
li.style.minHeight = `${maxHeight}px`;
|
|
})
|
|
}, 0);
|
|
}
|
|
|
|
// 정렬 함수
|
|
function naturalSort(a, b) {
|
|
const regex = /(\d+)|(\D+)/g;
|
|
const aParts = a.match(regex);
|
|
const bParts = b.match(regex);
|
|
|
|
for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
|
|
const aPart = aParts[i];
|
|
const bPart = bParts[i];
|
|
|
|
// 둘 다 숫자인 경우
|
|
if (!isNaN(aPart) && !isNaN(bPart)) {
|
|
const diff = parseInt(aPart) - parseInt(bPart);
|
|
if (diff !== 0) return diff;
|
|
} else {
|
|
// 문자열 비교
|
|
if (aPart !== bPart) {
|
|
return aPart.localeCompare(bPart);
|
|
}
|
|
}
|
|
}
|
|
|
|
return aParts.length - bParts.length;
|
|
}
|