초기 PM 소스 전체 업로드

This commit is contained in:
koj729
2026-06-12 17:14:03 +09:00
commit 4e33c9a02a
1769 changed files with 377797 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"title": "DocumentSummary",
"type": "object",
"properties": {
"파일제목": { "type": "string" },
"파일내용요약": { "type": "string" }
},
"required": [
"파일제목", "파일내용요약"
]
}

View File

@@ -0,0 +1,7 @@
오타나 줄바꿈 오류가 있을 수 있으니 의미를 유추하여 정확한 정보를 추출해주세요.
정확성이 매우 중요하므로 반드시 파일에 포함된 텍스트만 사용하여 작성해주세요. 추론하거나 임의로 보충하지 마세요.
불필요한 설명 문구(예: "다음은 문서에 작성된 내용입니다", "문서 기반으로 요약해 드립니다" 등)와 특수문자(*등)는 포함하지 마세요.
한 문장이 끝나면 다음줄에서 문장을 시작해주세요. 가독성 좋게 표출해주세요.
모든 문서는 꼭 개조식으로 정리해서 표출해주세요.
1. 파일 내용 요약: 파일 내용을 요약해주세요. 반드시 한글로 작성합니다.

View File

@@ -0,0 +1,5 @@
project_name 값을 실제 프로젝트명으로 사용하고, "project_name"이라는 단어를 그대로 쓰지 마.
project_name 값이 null 이면 'err: project name is null' 이라는 단어가 출력되게 해줘
project_name에 대한 온라인 반응을 최신 보도와 공식 문서 중심으로 정리해줘. 추측은 금지야 .
아래 예제 형식처럼 글만 나오게 해줘. '결과입니다' 같은 불필요한 말은 하지마.
글 정리는 개조식으로 진행해줘.

View File

@@ -0,0 +1,503 @@
import { vars } from './variable.js';
// 파일 사이즈 변환
export function formatBytes(bytes) {
const abs = Math.abs(bytes);
let size, unit;
if (abs < 1024) {
size = addCommasToNumber(bytes);
unit = ' Bytes';
} else if (abs < 1048576) {
size = addCommasToNumber((bytes / 1024).toFixed(2));
unit = ' KB';
} else if (abs < 1073741824) {
size = addCommasToNumber((bytes / 1048576).toFixed(2));
unit = ' MB';
} else if (abs < 1099511627776) {
size = addCommasToNumber((bytes / 1073741824).toFixed(2));
unit = ' GB';
} else {
size = addCommasToNumber((bytes / 1099511627776).toFixed(2));
unit = ' TB';
}
if (size != 0) {
let decimalPointNum = size.split('.')[1];
if (decimalPointNum) {
// 소수점 아래 숫자가 00인 경우
if (decimalPointNum == '00') size = size.split('.')[0];
// 소수점 아래 두번째 숫자가 0인 경우
else if (decimalPointNum[1] == '0') size = `${size.split('.')[0]}.${decimalPointNum[0]}`;
}
}
return size + unit;
}
// 1000 단위 콤마 추가
// export function addCommasToNumber(num) {
// return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// }
export function addCommasToNumber(num) {
const isNegative = Number(num) < 0;
const parts = Math.abs(Number(num)).toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
const formatted = parts.join('.');
return isNegative ? `-${formatted}` : formatted;
}
export function getDepth(path) {
path = path.startsWith('/') ? path.slice(1) : path;
let pathSplit = path.split('/');
let result = pathSplit.length;
return result;
}
export function updatePartialPath(sourcePath, targetPath) {
let sourcePathSplit = sourcePath.split('/');
let targetPathSplit = targetPath.split('/');
for (let i = 0; i < sourcePathSplit.length; i++) {
targetPathSplit[i] = sourcePathSplit[i];
}
return targetPathSplit.join('/');
}
// 파일명, 확장자 분리
export function splitBaseAndExt(target, isLowerExt) {
let targetSplit = target.split('.');
let ext = targetSplit.pop();
if (isLowerExt) ext = ext.toLowerCase();
let base = targetSplit.join('.');
return { ext: ext, base: base }
}
// 선택한 대상이 포함된 상위 폴더 경로 추출
export function splitTopPathAndTargetName(resourcePath) {
let resourcePathSplit = resourcePath.split('/');
let targetName = resourcePathSplit.pop();
let topPath = resourcePathSplit.join('/');
if (!topPath || topPath == '' || topPath == null) topPath = '/';
return { targetName: targetName, topPath: topPath };
}
export function extractPathByLength(target, count) {
if (!Array.isArray(target)) {
target = target.split('/');
}
const parts = target.filter(Boolean).slice(0, count);
return '/' + parts.join('/');
}
// 특수문자를 체크하는 정규표현식
export function hasSpecialChar(target) {
// const pattern = /[\/\\;:?`'"<>|#%&*]/;
const pattern = /[\/\\:*?'"`<>|#]/;
return pattern.test(target);
}
// 동일한 폴더명/파일명 존재 여부 확인
export function existEqualName(type, comparisonPath, targetName) {
let result;
if (comparisonPath != '' && comparisonPath != undefined) {
result = getTypeDataFromFolderData(type, comparisonPath)[targetName];
} else {
// result = vars.currentTreeObject[type][targetName];
result = vars.allTreeObject[type][targetName];
}
// 헤더 버튼 이름 변경 시 페이지 버튼과 이름 같은 경우 변경 불가
let pageBtnNameArr = ['과업개요', '공문'];
if (comparisonPath == '/' && result == undefined) result = pageBtnNameArr.includes(targetName);
return result;
// folderData에서 타입(폴더/파일)과 경로를 사용해서 해당 경로의 타입별 데이터 가져오기
function getTypeDataFromFolderData(type, path) {
let data;
if (type == 'folder') data = vars.allTreeObject[type];
if (type == 'file') data = vars.currentTreeObject[type];
let pathSplit = path.split('/');
pathSplit.shift();
// if (pathSplit[0] == '') return data;
for (let i = 0; i < pathSplit.length; i++) {
if (data[pathSplit[i]]) {
if (type == 'folder') data = data[pathSplit[i]]['child'][type];
if (type == 'file') data = data;
} else {
data = data;
}
}
return data;
}
}
export function getNextFileName(nameArr, newName) {
const dotIdx = newName.lastIndexOf('.');
const base = dotIdx !== -1 ? newName.slice(0, dotIdx) : newName;
const ext = dotIdx !== -1 ? newName.slice(dotIdx) : '';
const set = new Set(nameArr);
let maxNum = 0;
const regex = new RegExp(`^${escapeRegExp(base)}(?: \\((\\d+)\\))?${escapeRegExp(ext)}$`);
for (const name of set) {
const match = name.match(regex);
if (match) {
const num = match[1] ? parseInt(match[1], 10) : 0;
if (num > maxNum) maxNum = num;
}
}
if (maxNum === 0 && !set.has(newName)) return newName;
return `${base} (${maxNum + 1})${ext}`;
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
}
export function getDataFromTreeObject(path, type, data) {
let parentData;
if (!data) data = vars.allTreeObject;
let pathSplit = path.split('/');
pathSplit.shift();
for (let i = 0; i < pathSplit.length; i++) {
if (i != pathSplit.length - 1) {
if (data['folder'][pathSplit[i]]) {
data = data['folder'][pathSplit[i]]['child'];
}
} else {
if (type == 'folder') {
parentData = data;
data = data['folder'][pathSplit[i]];
}
if (type == 'file') {
if (data['file'][pathSplit[i]]) {
parentData = data;
data = data['file'][pathSplit[i]];
}
}
}
}
return { parentData: parentData, data: data };
}
export function getCurrentTreeObject(path) {
let data = vars.allTreeObject;
let pathSplit = path.split('/');
pathSplit.shift();
for (let i = 0; i < pathSplit.length; i++) {
if (i != pathSplit.length - 1) {
if (data['folder'][pathSplit[i]]) {
data = data['folder'][pathSplit[i]]['child'];
}
} else {
if (data['folder'][pathSplit[i]]) data = data['folder'][pathSplit[i]]['child'];
}
}
return data;
}
export function targetFocus(target, behavior = 'smooth') {
target.scrollIntoView({
behavior: behavior, // 부드럽게
block: 'center', // 화면 중앙에 위치하도록 (start, end, nearest 도 가능)
inline: 'center'
});
target.focus();
}
export function pxToRem(px) {
let remPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
return px / remPx;
}
export function buildResourcePathFromSegments(rows) {
if (!Array.isArray(rows)) rows = [rows];
let result = [];
rows.map(row => {
let resourcePath = '/';
let maxDepth = 8;
let arr = [];
for (let i = 0; i < maxDepth; i++) {
let num = i+1;
let segment = row[`path${num}`];
if (segment) arr.push(segment);
}
if (arr.length != 0) resourcePath += arr.join('/');
result.push(resourcePath);
})
return result;
}
export function headerBtnForceClick(headerBtn) {
headerBtn.click();
}
export function treeBtnForceClick(treeBtn) {
treeBtn.click();
}
export function closeInitProgress() {
document.querySelector('.init-progress').style.display = 'none';
}
export async function getMyDownloadList(){
let res = await axios.get(`${vars.path_name}/getMyDownloadList`, {params : {user_id : JSON.parse(vars.userInfoString).user_id}});
// console.log(res.data);
if(res.data.length > 0){
if(document.querySelector('.download-btn .download-reddot')){
document.querySelector('.download-btn .download-reddot').style.display = 'block';
}
}else{
if(document.querySelector('.download-btn .download-reddot')){
document.querySelector('.download-btn .download-reddot').style.display = 'none';
}
}
let ul = document.querySelector('.download-modal__body_list');
ul.innerHTML = '';
for(let i = 0; i < res.data.length; i++){
let li = document.createElement('li');
li.classList.add('download-modal__body_list_item');
let expireDate = new Date(res.data[i].expire_date);
let now = new Date();
let yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
let state = (expireDate < yesterday)? `<img class="--button__xsmall-icon --disable" src="/main/img/archive/icon__download--111.svg" alt="/" />`: (!res.data[i].made)?`준비중`:`<img class="--button__xsmall-icon downloadFolder__btn__icon" src="/main/img/archive/icon__download--111.svg" alt="/" />`
let expire =(state == '준비중')?state :(expireDate < yesterday)? '만료됨': formatDateTime(expireDate);
li.innerHTML = `
<img class="--icon" src="/main/img/header/header-folder-icon.svg" alt="/" />
<div class="download-modal__body_list_item_text">
<h4 class="download-modal__body_list_item_text_title">
${res.data[i].name}
</h4>
<p class="download-modal__body_list_item_text_subtitle">
${res.data[i].project_nm}${res.data[i].path}
</p>
</div>
<h6 class="download-modal__body_list_item_time">
${expire}
</h6>
<div class="download-modal__body_list_item_status">${state}</div>
`;
ul.appendChild(li);
li.querySelector('.downloadFolder__btn__icon')?.addEventListener('click', () => {
window.location.href = res.data[i].url;
})
}
function formatDateTime(date) {
const padZero = (num) => String(num).padStart(2, '0');
const year = date.getFullYear();
const month = padZero(date.getMonth() + 1); // getMonth()는 0부터 시작하므로 +1
const day = padZero(date.getDate());
const hours = padZero(date.getHours());
const minutes = padZero(date.getMinutes());
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
}
export async function openNewWindowViewer() {
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
let lastListItem = (isRecycleBinModal) ? vars.lastListItem_bin : vars.lastListItem;
let resourcePath = lastListItem.dataset.resourcePath;
let dataId = lastListItem.dataset.id;
let isLowerExt = true, ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
let thumbnail_key = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject).data?.thumbnailKey;
//Presigned URL
let PresignedUrl = undefined;
let getDataInfoParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dataIdArr: [dataId],
isRemoved: isRecycleBinModal,
debug: "'우측 미리보기 새 창으로 열기 버튼 클릭 이벤트'에서 실행"
}
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams } );
if (getDataInfoRes.data.message == 'getDataInfo_success') {
let objectKey = getDataInfoRes.data.result.popup_key;
if(objectKey == undefined || objectKey == `` || objectKey == null){
return;
}
let generateDownloadUrlParams = {
objectKey: objectKey,
resourcePath: resourcePath
}
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
PresignedUrl = generateDownloadUrlRes.data.url;
}
}
//Presigned URL end
thumbnail_key = encodeURIComponent(thumbnail_key);
resourcePath = encodeURIComponent(resourcePath);
let fullPath = encodeURIComponent(PresignedUrl);
// 뷰어 기본 width값은 현재 화면 width값의 절반
let denominator = 2;
// url 뷰어는 현재 화면 width값과 동일하게 설정
if (ext == 'url') denominator = 1;
let width = screen.width / denominator;
let height = screen.height;
let left = screen.width * 2 + 10;
let open_ext = `pdf`;
switch(ext){
case 'pdf' :
case 'hwp' :
case 'hwpx' :
case 'xls' :
case 'xlsm' :
case 'ppt' :
case 'pptx' :
case 'doc' :
case 'docx' :
case 'dwg' :
case 'dxf' :
case 'grm' :
open_ext = 'pdf';
break
case 'gsim' :
open_ext = 'gsim';
break
case 'ifc' :
open_ext = 'ifc';
break
case 'png' :
case 'jpg' :
case 'jpeg' :
case 'webp' :
case 'gif' :
open_ext = 'png';
break
case 'mp4' :
case 'mov' :
case 'webm' :
open_ext = 'mp4';
break
case 'log' :
case 'txt' :
case 'md' :
open_ext = 'txt';
break
case 'url' :
open_ext = 'url';
break;
case 'zip' :
open_ext = 'zip';
break
case 'glb':
case 'gltf':
case 'obj':
case 'stl':
case 'fbx':
case '3dm':
open_ext = 'glb';
break;
case 'html':
open_ext = 'html';
break;
}
//presigned url은 ext를 읽을수 없엉...
const jsonData = JSON.stringify({$ext : open_ext});
const encodedData = btoa(jsonData);
// 3d모델뷰어 썸네일 생성을 위해 dataId, path_name 추가
// let popup = window.open(`/popup?path=${fullPath}&data=${encodedData}`, '', `width=${width}, height=${height}, left=${left},`);
let popup = window.open(`/popup?thumbnail_key=${thumbnail_key}&path_name=${vars.path_name}&resourcePath=${resourcePath}&dataId=${dataId}&path=${fullPath}&data=${encodedData}`, '', `width=${width}, height=${height}, left=${left},`);
}
export function initFileAreaUI(fileAreaMode) {
let listContainer = document.querySelector('.archive-main .archive-main-center .list-container');
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
if (isRecycleBinModal) listContainer = document.querySelector('.recycle-bin-modal .recycle-bin-wrap .list-container');
let listHeader = listContainer.querySelector('.list-header');
let listBody = listContainer.querySelector('.list-body');
let listBodyItemWrap = listBody.querySelector('.list-item-wrap');
let mapContainer = listBody.querySelector('.map-container');
let archiveMainRight = document.querySelector('.archive-main-right');
let viewerHeader = archiveMainRight.querySelector('.viewer-header');
if (fileAreaMode == 'map') {
listHeader.style.display = 'none';
listBody.style.height = '100%';
listBody.style.overflowY = 'hidden';
listBodyItemWrap.style.display = 'none';
if (mapContainer) mapContainer.style.display = 'flex';
archiveMainRight.style.minWidth = '36rem';
archiveMainRight.style.maxWidth = '36rem';
archiveMainRight.style.height = 'calc(100% - 2.75rem)';
archiveMainRight.style.position = 'absolute';
archiveMainRight.style.right = '0.625rem';
archiveMainRight.style.top = '0.5rem';
archiveMainRight.style.background = '#fffa';
archiveMainRight.style.backdropFilter = 'blur(0.2rem)';
archiveMainRight.style.webkitBackdropFilter = 'blur(0.2rem)';
archiveMainRight.style.borderRadius = '0.25rem';
archiveMainRight.style.boxShadow = '#ccc 0 0 0rem 0.0625rem, #0c0c0db3 0rem 0.5rem 1rem -0.25rem';
viewerHeader.style.borderRadius = '0.25rem 0.25rem 0 0';
} else {
listHeader.style.display = 'flex';
listBody.style.height = 'calc(100% - 2.25rem)';
listBody.style.overflowY = 'scroll';
listBodyItemWrap.style.display = 'flex';
if (mapContainer) mapContainer.style.display = 'none';
archiveMainRight.style.minWidth = '41rem';
archiveMainRight.style.maxWidth = '41rem';
archiveMainRight.style.height = '100%';
archiveMainRight.style.position = 'relative';
archiveMainRight.style.right = 'unset';
archiveMainRight.style.top = 'unset';
archiveMainRight.style.background = '#fff';
archiveMainRight.style.backdropFilter = 'none';
archiveMainRight.style.webkitBackdropFilter = 'none';
archiveMainRight.style.borderRadius = 'unset';
archiveMainRight.style.boxShadow = 'none';
viewerHeader.style.borderRadius = 'unset';
}
}
export function syncGroupStyle() {
let listContainer = document.querySelector('.archive-main .archive-main-center .list-container');
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
if (isRecycleBinModal) listContainer = document.querySelector('.recycle-bin-modal .recycle-bin-wrap .list-container');
listContainer.querySelectorAll('.main-list-item').forEach(item => {
if (listContainer.querySelectorAll(`.list-item.selected[data-main-file-name="${item.dataset.mainFileName}"]`).length > 0) {
listContainer.querySelectorAll(`.list-item[data-main-file-name="${item.dataset.mainFileName}"]`).forEach(item => {
item.classList.add('group-style');
item.classList.remove('non-selected');
})
} else {
listContainer.querySelectorAll(`.list-item[data-main-file-name="${item.dataset.mainFileName}"]`).forEach(item => {
item.classList.remove('group-style');
})
}
})
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,348 @@
import { vars } from './variable.js';
import {
targetFocus,
syncGroupStyle
} from './common.js';
import {
resetViewer,
} from './pageRenderer.js';
let archiveMain = document.querySelector('.archive-main');
let viewerContainer = archiveMain?.querySelector('.archive-main-right .viewer-container');
addFileDragEvent(document.querySelector('.archive-main-center .list-container .list-body'), 'file-area');
addFileDragEvent(document.querySelector('.recycle-bin-modal .list-container .list-body'), 'recycle-bin');
export function addFileDragEvent(listBody, target) {
let draggingStartTarget, draggingEndTarget;
let draggingStart = false, isDragging = false;
let isCtrl = false, isShift = false;
let processedItems = new Set();
let originalSelection = new Set();
let multiSelectBox = document.createElement('div');
multiSelectBox.style.position = 'absolute';
multiSelectBox.style.border = '1px solid rgba(0, 120, 215, 1)';
multiSelectBox.style.background = 'rgba(0, 120, 215, 0.2)';
multiSelectBox.style.zIndex = '9999';
multiSelectBox.style.pointerEvents = 'none';
multiSelectBox.style.display = 'none';
listBody.appendChild(multiSelectBox);
vars.preventNextClick = false;
let startX = 0;
let startY = 0;
let prevClientX = 0;
let prevClientY = 0;
let autoScrollDirection = null;
let autoScrollFrameId = null;
const scrollSpeed = 8;
const scrollMargin = 36;
function autoScrollLoop() {
if (!autoScrollDirection) return;
// 더 작으면 느리고 부드러움
if (autoScrollDirection === 'down') {
listBody.scrollTop += scrollSpeed;
} else if (autoScrollDirection === 'up') {
listBody.scrollTop -= scrollSpeed;
}
autoScrollFrameId = requestAnimationFrame(autoScrollLoop);
}
listBody.addEventListener('pointerdown', (e) => {
// 마우스 좌클릭이 아닌 경우 리턴
if (e.button !== 0) return;
// 갤러리 폴더 지도화면인 경우 리턴
if (target == 'file-area' && listBody.querySelector('.map-container').style.display === 'flex') return;
draggingStartTarget = e.target;
draggingStart = true;
isDragging = false;
isCtrl = e.ctrlKey;
isShift = e.shiftKey;
processedItems.clear();
if (target == 'file-area') originalSelection = new Set(vars.multiSelectListItemArr);
if (target == 'recycle-bin') originalSelection = new Set(vars.multiSelectListItemArr_bin);
vars.preventNextClick = false;
// if (isCtrl) {
// vars.lastListItem = e.target.closest('.list-item');
// } else {
// listBody.querySelectorAll('.list-item').forEach(listItem => {
// listItem.classList.remove('selected');
// listItem.classList.remove('group-style');
// })
// vars.multiSelectListItemArr = [];
// }
const dragStartListItem = e.target.closest('.list-item');
if (dragStartListItem && !isShift) {
if (target == 'file-area') vars.lastListItem = dragStartListItem;
if (target == 'recycle-bin') vars.lastListItem_bin = dragStartListItem;
}
if (!isCtrl) {
// Ctrl이 아닐 경우 기존 선택 모두 해제
listBody.querySelectorAll('.list-item').forEach(listItem => {
listItem.classList.remove('selected');
listItem.classList.remove('group-style');
});
if (target == 'file-area') vars.multiSelectListItemArr = [];
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = [];
}
const bodyRect = listBody.getBoundingClientRect();
startX = e.clientX + listBody.scrollLeft - bodyRect.left;
startY = e.clientY + listBody.scrollTop - bodyRect.top;
prevClientX = e.clientX;
prevClientY = e.clientY;
Object.assign(multiSelectBox.style, {
left: `${startX}px`,
top: `${startY}px`,
width: `0px`,
height: `0px`,
display: 'none'
});
});
listBody.addEventListener('pointermove', (e) => {
// 마우스 좌클릭이 아닌 경우 리턴
if (e.buttons !== 1) return;
// list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴
if (!draggingStart) return;
if (e.clientX === prevClientX && e.clientY === prevClientY) return;
prevClientX = e.clientX;
prevClientY = e.clientY;
const listBodyRect = listBody.getBoundingClientRect();
const currX = e.clientX + listBody.scrollLeft - listBodyRect.left;
const currY = e.clientY + listBody.scrollTop - listBodyRect.top;
const dx = currX - startX;
const dy = currY - startY;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
isDragging = true;
vars.preventNextClick = true;
const left = Math.min(startX, currX);
const top = Math.min(startY, currY);
const width = Math.abs(dx);
const height = Math.abs(dy);
Object.assign(multiSelectBox.style, {
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`,
display: 'block'
});
const boxRect = multiSelectBox.getBoundingClientRect();
listBody.querySelectorAll('.list-item .wrap').forEach(elem => {
const rect = elem.getBoundingClientRect();
const overlaps = !(
rect.right < boxRect.left ||
rect.left > boxRect.right ||
rect.bottom < boxRect.top ||
rect.top > boxRect.bottom
);
let listItem = elem.parentElement;
if (isCtrl) {
if (overlaps && !processedItems.has(listItem)) {
processedItems.add(listItem);
// ✅ 토글: 원래 선택되어 있었으면 해제, 아니면 선택
if (originalSelection.has(listItem)) {
listItem.classList.remove('selected');
listItem.classList.remove('group-style');
if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem);
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== listItem);
} else {
listItem.classList.add('selected');
listItem.classList.add('group-style');
if (target == 'file-area') vars.multiSelectListItemArr.push(listItem);
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin.push(listItem);
}
}
// ✅ 박스에서 벗어난 항목 복원
if (!overlaps && processedItems.has(listItem)) {
processedItems.delete(listItem);
if (originalSelection.has(listItem)) {
listItem.classList.add('selected');
listItem.classList.add('group-style');
if (target == 'file-area') {
if (!vars.multiSelectListItemArr.includes(listItem)) vars.multiSelectListItemArr.push(listItem);
}
if (target == 'recycle-bin') {
if (!vars.multiSelectListItemArr_bin.includes(listItem)) vars.multiSelectListItemArr_bin.push(listItem);
}
} else {
listItem.classList.remove('selected');
listItem.classList.remove('group-style');
if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem);
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== listItem);
}
}
} else {
listItem.classList.toggle('selected', overlaps);
listItem.classList.toggle('group-style', overlaps);
if (target == 'file-area') {
if (overlaps && !vars.multiSelectListItemArr.includes(listItem)) vars.multiSelectListItemArr.push(listItem);
}
if (target == 'recycle-bin') {
if (overlaps && !vars.multiSelectListItemArr_bin.includes(listItem)) vars.multiSelectListItemArr_bin.push(listItem);
}
}
});
if (e.clientY > listBodyRect.bottom - scrollMargin) {
if (autoScrollDirection !== 'down') {
cancelAnimationFrame(autoScrollFrameId);
autoScrollDirection = 'down';
autoScrollLoop();
}
} else if (e.clientY < listBodyRect.top + scrollMargin) {
if (autoScrollDirection !== 'up') {
cancelAnimationFrame(autoScrollFrameId);
autoScrollDirection = 'up';
autoScrollLoop();
}
} else {
if (autoScrollDirection !== null) {
cancelAnimationFrame(autoScrollFrameId);
autoScrollDirection = null;
}
}
}
});
document.addEventListener('pointerup', async (e) => {
// list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴
if (!draggingStart) return;
if (target == 'file-area') {
if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn);
if (vars.lastMainTreeItem) targetFocus(vars.lastMainTreeItem);
}
if (isShift && isDragging) {
const dragStartListItem = draggingStartTarget.closest('.list-item');
if (dragStartListItem) {
if (target == 'file-area') vars.lastListItem = dragStartListItem;
if (target == 'recycle-bin') vars.lastListItem_bin = dragStartListItem;
}
}
multiSelectBox.style.display = 'none';
draggingEndTarget = e.target;
draggingStart = false;
isDragging = false;
isCtrl = false;
isShift = false;
// 드래그가 아이템 바깥 영역에서만 진행되고, 드래그에 포함된 아이템이 하나도 없을 때 뷰어 초기화
if (
(
(draggingStartTarget.matches('.list-body') && draggingEndTarget.matches('.list-body'))
|| (draggingStartTarget.matches('.list-item-wrap') && draggingEndTarget.matches('.list-item-wrap'))
)
&&
(
(target == 'file-area' && vars.multiSelectListItemArr.length == 0)
|| (target == 'recycle-bin' && vars.multiSelectListItemArr_bin.length == 0)
)
)
{
// 모든 아이템에서 non-selected 클래스 삭제
document.querySelectorAll('.grid-item').forEach(gridItem => {
gridItem.classList.remove('non-selected')
})
// if (viewerContainer.style.display == 'flex') resetViewer();
resetViewer();
vars.lastContextTarget = undefined;
if (target == 'file-area') {
vars.lastListGroupTarget = undefined;
vars.lastListItem = undefined;
// vars.lastContextTarget = undefined;
}
if (target == 'recycle-bin') {
vars.lastListGroupTarget_bin = undefined;
vars.lastListItem_bin = undefined;
// vars.lastContextTarget_bin = undefined;
}
// userSelected 삭제
let me = JSON.parse(vars.userInfoString);
me.selected = undefined;
vars.socket.emit('fileSelect',{me : me});
} else {
if (target == 'file-area') {
// 모든 아이템에 non-selected 클래스 추가
document.querySelectorAll('.grid-item').forEach(gridItem => {
gridItem.classList.add('non-selected')
})
}
}
// 드래그 시작한 대상과 종료된 대상이 다르면 뷰어 초기화
if (draggingStartTarget != draggingEndTarget) {
if (target == 'file-area') {
vars.lastListItem = undefined;
vars.lastListGroupTarget = undefined;
}
if (target == 'recycle-bin') {
vars.lastListItem_bin = undefined;
vars.lastListGroupTarget_bin = undefined;
}
if (viewerContainer.style.display == 'flex') resetViewer();
}
// 드래그가 끝나고 pointerup 이벤트가 발생하는 시점에 selected 클래스가 있는 listItem만 필터링
if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item.classList.contains('selected'));
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item.classList.contains('selected'));
// vars.multiSelectListItemArr/vars.multiSelectListItemArr_bin에 포함된 아이템에서 non-selected 클래스 삭제
if (target == 'file-area') {
vars.multiSelectListItemArr.forEach(item => {
item.classList.remove('non-selected');
})
}
if (target == 'recycle-bin') {
vars.multiSelectListItemArr_bin.forEach(item => {
item.classList.remove('non-selected');
})
}
await syncGroupStyle();
// 드래그 시작한 대상의 부모요소와 종료된 대상의 부모요소가 다르면 뷰어 초기화
// if (draggingStartTarget.parentElement != draggingEndTarget.parentElement) {
// if (viewerContainer.style.display == 'flex') resetViewer();
// }
if (target == 'file-area') vars.lastSelectTarget = vars.multiSelectListItemArr[0];
if (target == 'recycle-bin') vars.lastSelectTarget_bin = vars.multiSelectListItemArr_bin[0];
cancelAnimationFrame(autoScrollFrameId);
autoScrollDirection = null;
});
}

View File

@@ -0,0 +1,59 @@
import { vars } from './variable.js';
import * as socketManager from './socketManager.js';
import * as eventManager from './eventManager.js';
import * as fileDrag from './fileDrag.js';
import {
preparePageRendering,
renderSizeBar,
renderLog
} from './pageRenderer.js';
export let isLoaded = false;
export function loadArchive() {
isLoaded = true;
}
//// 뷰어 요약 ai버튼 삭제
// document.querySelector('.ai-btn').remove();
//// archive controller에 useEncrypt 변수 설정
// let setUseEncryptRes = await axios.put(`${vars.path_name}/setUseEncrypt`, {useEncrypt: vars.convertOption.useEncrypt});
let pageRanderingOption = {
isInit: true,
// favorite: 즐겨찾기 한 경로
scope: 'headerBtn'
}
await preparePageRendering(pageRanderingOption);
//// 폴더 사용 용량 표시
await renderSizeBar();
//// 활동 정보
let isInit = true;
await renderLog(isInit);
// ol.map = undefined;
// ol.map = new ol.Map({
// target: 'map-container',
// layers: [vars.baseLayer],
// // layers: [vars.roadLayer, vars.satelliteLayer, vars.hybridLayer],
// view: new ol.View({
// projection: 'EPSG:3857',
// center: ol.proj.fromLonLat([127.8, 35.9]),
// zoom: 7,
// constrainResolution: true,
// smoothResolutionConstraint: true
// })
// });
// 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);

View File

@@ -0,0 +1,241 @@
import { vars } from './variable.js';
import { renderLog } from './pageRenderer.js';
const modal = document.querySelector('.log-filter');
const customDisplay = modal.querySelector('.custom-select-display');
const customList = modal.querySelector('.custom-select-list');
const userSelect = document.querySelector('#log-user-select');
// 기본 로그필터 정보 불러오기
export async function getDefaultFilter() {
// 해당 프로젝트에 존재하는 모든 사용자
let userLogRes = await axios.get(`${vars.path_name}/getUserLog`);
if (userLogRes.data.message == 'getUserLog_success') {
const userLogDataArr = Object.values(userLogRes.data.logData);
// user_id -> user_nm 매핑 (중복 제거)
const userMap = new Map();
userLogDataArr.forEach(data => {
if(!userMap.has(data.user_id)) {
userMap.set(data.user_id, data.user_nm);
}
});
// selectbox 사용자 리스트 추가
userSelect.innerHTML = '';
userSelect.insertAdjacentHTML('beforeend', '<option value="allUser">모든 사용자</option>');
// Map -> Array로 변환 후 user_id로 정렬
const sortedUsers = [...userMap.entries()].sort((a, b) => {
const userIdA = a[0];
const userIdB = b[0];
return userIdA.localeCompare(userIdB, 'ko-KR');
});
// 정렬된 사용자 추가
sortedUsers.forEach(([userId, userNm]) => {
let option = document.createElement('option');
option.value = userId;
option.textContent = `${userId} (${userNm})`;
userSelect.appendChild(option);
});
// custom select 리스트에도 반영
customList.innerHTML = '';
Array.from(userSelect.options).forEach(opt => {
const li = document.createElement('li');
li.dataset.value = opt.value;
li.textContent = opt.textContent;
customList.appendChild(li);
});
// 기본값으로 설정
customDisplay.textContent = "모든 사용자";
userSelect.value = "allUser";
customList.style.display = "none"; // 혹시 열려있으면 닫기
}
// 활동유형 초기화
let activityCheckbox = modal.querySelectorAll('.body .log-activity label input[type="checkbox"]');
activityCheckbox.forEach(checkbox => {
checkbox.checked = true;
})
// 활동로그에 존재하는 '오늘 ~ 일주일' 간의 로그 기록
let logRes = await axios.get(`${vars.path_name}/getLog`);
if (logRes.data.message == 'getLog_success') {
const logDataArr = Object.values(logRes.data.logData);
// 기본 활동시간 - 오늘 ~ 일주일로 설정
const dateInputs = modal.querySelectorAll('.log-date .log-date-wrap input[type="date"]');
const toDateStr = (d) => d.toISOString().slice(0, 10);
const today = new Date();
const weekAgo = new Date(today)
weekAgo.setDate(today.getDate() - 7);
let startDate = toDateStr(weekAgo);
let endDate = toDateStr(today);
// 달력창 UI
dateInputs[0].value = startDate;
dateInputs[1].value = endDate;
return { logDataArr }
}
}
// custom select 동작
customDisplay.addEventListener('click', () => {
customList.style.display = customList.style.display === 'flex' ? 'none': 'flex';
});
// 항목 선택 시
customList.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
const value = e.target.dataset.value;
const text = e.target.textContent;
// 표시 UI 업데이트
customDisplay.textContent = text;
// 실제 select 값도 변경
userSelect.value = value;
// 리스트 닫기
customList.style.display = 'none';
}
})
// 로그필터 저장 버튼 클릭 이벤트
modal.querySelector('.foot ._button-medium').addEventListener('click', async() => {
// date 불러오기
let { startDate, endDate } = getLogFilterDate();
// user 불러오기
let { user } = getLogFilterUser();
// activity 불러오기
let { activity } = getLogFilterActivity();
// 서버로 전달한 파라미터
const logFilterParams = {
startDate: startDate,
endDate: endDate,
user: user,
activity: activity
};
let filterLogRes = await axios.get(`${vars.path_name}/getFilterLog`, { params: logFilterParams });
if (filterLogRes.data.message == 'getFilterLog_success') {
const isInit = true;
const addFooterLog = false;
const archiveModalLogList = document.querySelector('.archive-modal > .wrap .modal-wrap .modal-body .log-wrap .log-item-wrap.log-body');
archiveModalLogList.scrollTop = 0;
renderLog(isInit, addFooterLog, filterLogRes.data.logData)
}
});
document.querySelector('.archive-modal').addEventListener('click', async(e) => {
if (modal.style.display == 'flex') {
// 로그 필터 켜져있을 때에만 이벤트 작동
if (e.target.classList.contains('custom-select-display') || e.target.classList.contains('custom-select-list') || e.target.closest('.custom-select-list')) {
// 사용자 셀렉트 박스 또는 옵션 영역 클릭했을 때
} else {
// 사용자 셀렉트 박스 또는 옵션 영역을 제외한 나머지 모달 영역 클릭했을 때
customList.style.display = 'none';
}
}
})
// date 불러오기
function getLogFilterDate() {
// 날짜 선택 (start, end)
const dateInputs = modal.querySelectorAll('.log-date .log-date-wrap input[type="date"]');
let startDate = dateInputs[0].value; // 시작일
let endDate = dateInputs[1].value; // 종료일
return { startDate, endDate };
}
// user 불러오기
function getLogFilterUser() {
// 선택한 사용자 user에 추가
const userSelect = modal.querySelector('.body .log-user select');
const selectValue = userSelect.value;
let user = [];
if(selectValue == 'allUser') {
// 전체 사용자 선택
user = Array.from(userSelect.options)
.map(opt => opt.value)
.filter(value => value !== 'allUser' && value !== '');
} else {
user.push(selectValue);
}
return { user };
}
// activity 불러오기
function getLogFilterActivity() {
// 체크한 활동유형 activity에 추가
const activityMapping = {
'uploadData_file' : ['uploadData_file', 'addOn_version', 'addOn_attachment'],
'renameTarget' : ['renameTarget_folder', 'renameTarget_file'],
'removeTarget' : ['removeTarget_folder', 'removeTarget_file', 'removeTarget_folder_expired'],
'downloadTarget': ['downloadTarget_folder', 'downloadTarget_file'],
'addPermission': ['addPermission_worker', 'addPermission_viewer', 'addPermission_subMaster', 'addPermission_securityWorker'],
'deletePermission': ['deletePermission_worker', 'deletePermission_viewer', 'deletePermission_subMaster', 'deletePermission_securityWorker']
}
const activity = Array.from(modal.querySelectorAll('.body .log-activity label input[type="checkbox"]:checked')).flatMap(check => activityMapping[check.value] || [check.value]);
return { activity };
}
// 초기화 / 전체해제 버튼 클릭 이벤트
const logFilterBtn = modal.querySelectorAll('._button-xsmall');
logFilterBtn.forEach(btn => {
btn.addEventListener('click', (e) => {
if(e.target.classList.contains('reset')) {
// 활동시간 초기화- '오늘~일주일'
const dateInputs = modal.querySelectorAll('.log-date .log-date-wrap input[type="date"]');
const toDateStr = (d) => d.toISOString().slice(0, 10);
const today = new Date();
const weekAgo = new Date(today);
weekAgo.setDate(today.getDate() - 7);
let startDate = toDateStr(weekAgo);
let endDate = toDateStr(today);
dateInputs[0].value = startDate;
dateInputs[1].value = endDate;
// 사용자 초기화 - '모든 사용자'
customDisplay.textContent = '모든 사용자';
userSelect.value = 'allUser';
customList.style.display = 'none';
}
const inputs = modal.querySelectorAll('.body .log-activity label input[type="checkbox"]');
inputs.forEach(input => {
if(e.target.classList.contains('reset')) {
input.checked = true;
} else if (e.target.classList.contains('select-all')) {
input.checked = true;
} else if (e.target.classList.contains('clear-all')) {
input.checked = false;
}
})
})
})

View File

@@ -0,0 +1,32 @@
import { vars } from './variable.js';
import { toggleContextmenu, toggleContextFocusBox } from './eventManager.js'
export async function mgmtFunc_changeProjectState(toggleParams) {
if (toggleParams.type == 'changeProjectState') toggleParams.targetColumn = 'is_active';
await axios.post('/common/mgmtFunc_updateProject', { params: toggleParams });
}
export async function mgmtFunc_changeBannerNotice(toggleParams) {
if (toggleParams.type == 'changeBannerNotice') toggleParams.targetColumn = 'banner_notice';
await axios.post('/common/mgmtFunc_updateProject', { params: toggleParams });
}
export async function mgmtFunc_resetConvert(resourcePath, dataId) {
let resetConvertParams = {
resourcePath: resourcePath,
dataId: dataId
}
let resetConvertRes = await axios.post(`${vars.path_name}/mgmtFunc_resetConvert`, { params: resetConvertParams });
if (resetConvertRes.data.message == 'mgmtFunc_resetConvert_success') {
toggleContextmenu(false);
toggleContextFocusBox(false);
}
}
export async function mgmtFunc_addClickLog(params) {
// params.dataId -> 컨트롤러에서 숫자형으로 형변환해서 db추가
// project_id, activity, user_id, user_ip는 컨트롤러에서 params에 추가 (user_id는 params.userInfoString 사용)
let addClickLogRes = await axios.post(`${vars.path_name}/mgmtFunc_addClickLog`, { params: params });
// console.log(addClickLogRes.data.message);
}

View File

@@ -0,0 +1,72 @@
let archiveManualSwiper = undefined;
export async function renderManual() {
await createManualSwiperArea();
archiveManualSwiper = new Swiper('.modal-body > .manual-wrap .manual-swiper', {
slidesPerView: 1,
spaceBetween: 0,
centeredSlides: true,
speed: 400,
loop: true,
navigation: {
nextEl: '.swiper.manual-swiper .button-next',
prevEl: '.swiper.manual-swiper .button-prev',
},
pagination: {
el: '.swiper.manual-swiper .swiper-pagination',
clickable: true,
renderBullet: function (index, className) {
return '<span class="' + className + '">' + (index + 1) + "</span>";
},
},
});
}
/** swiper area
* [div] manual-wrap
* [div] manual-swiper (v) <-- swiper
* [ul] manual-swiper-wrap <-- swiper-wrapper
* [li] manual-swiper-item <-- swiper-slide
* [img] manual-img
* [div] button-prev
* [div] button-next
* [div] swiper-pagination
*/
export async function createManualSwiperArea() {
const manualWrap = document.querySelector('.modal-body > .manual-wrap');
manualWrap.innerHTML = ''; // swiper 초기화
const manualSwiper = document.createElement('div');
manualSwiper.classList.add('swiper', 'manual-swiper');
const manualSwiperWrap = document.createElement('ul');
manualSwiperWrap.classList.add('swiper-wrapper', 'manual-swiper-wrap');
manualSwiper.appendChild(manualSwiperWrap);
manualWrap.appendChild(manualSwiper);
// swiper 동적 생성
for (let i = 0; i < 5; i++) {
const manualSwiperItem = document.createElement('li');
manualSwiperItem.classList.add('swiper-slide', 'manual-swiper-item');
manualSwiperItem.dataset.save = true;
manualSwiperItem.dataset.fileName = `${[i+1]}.jpg`;
const manualImg = document.createElement('img');
manualImg.classList.add('manual-img');
manualImg.src = `/main/img/manual/${[i+1]}.jpg`;
manualSwiperItem.appendChild(manualImg);
manualSwiperWrap.appendChild(manualSwiperItem);
}
// 이전버튼, 다음버튼, 페이지네이션버튼 생성
const swiperButtonPrev = document.createElement('div');
swiperButtonPrev.classList.add('button-prev');
const swiperButtonNext = document.createElement('div');
swiperButtonNext.classList.add('button-next');
const swiperPagination = document.createElement('div');
swiperPagination.classList.add('swiper-pagination');
manualSwiper.appendChild(swiperButtonPrev);
manualSwiper.appendChild(swiperButtonNext);
manualSwiper.appendChild(swiperPagination);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,754 @@
import { PMTiles } from 'https://esm.sh/pmtiles@3.0.7';
const MAX_LEVEL = 20;
const CLICK_HIGHLIGHT_OUTER_WIDTH = 10; // 바깥쪽 흰색 테두리 두께
const CLICK_HIGHLIGHT_INNER_WIDTH = 5; // 안쪽 선 두께
const HOVER_HIGHLIGHT_OUTER_WIDTH = 6; // 바깥쪽 흰색 테두리 두께
const HOVER_HIGHLIGHT_INNER_WIDTH = 2; // 안쪽 선 두께
// 선택/호버 상태 추적
let selectedFeature = null;
let hoveredFeature = null;
let selectLayer = null;
let hoverLayer = null;
// 현재 선택된 feature 저장
let currentSelectedFeature = null;
// 선택된 feature의 레이어 키 추가
let currentSelectedLayerKey = null;
// ---- OpenLayers VectorTile Layer ----
class OLVectorLayer {
constructor(olMap, layerId) {
this.olMap = olMap;
this.layerId = layerId;
this.type = null;
this.templateUrl = null;
this.pmtilesInstance = null;
this.maxLevel = MAX_LEVEL;
this.vectorLayer = null;
}
async setTileSource(source, type, zoomRange = null) {
this.type = type;
if (type === 'pbf') {
this.templateUrl = source;
try {
const baseUrl = source.replace('/{z}/{x}/{y}.pbf', '');
const metadataUrl = `${baseUrl}/metadata.json`;
const response = await fetch(metadataUrl);
const metadata = await response.json();
if (metadata.maxzoom) {
this.maxLevel = parseInt(metadata.maxzoom);
}
} catch (error) {
console.warn(`[${this.layerId}] Could not read metadata.json`);
}
this.vectorLayer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: new ol.format.MVT(),
url: source,
maxZoom: this.maxLevel
}),
style: styleFunction,
renderMode: 'vector',
zIndex: 1,
// 줌 범위 설정
minZoom: zoomRange ? zoomRange.min : undefined,
maxZoom: zoomRange ? zoomRange.max : undefined
});
this.olMap.addLayer(this.vectorLayer);
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
console.log(`[${this.layerId}] PBF Vector layer initialized${zoomInfo}`);
} else if (type === 'pmtiles') {
this.pmtilesInstance = source;
try {
const metadata = await this.pmtilesInstance.getMetadata();
if (metadata.maxzoom) {
this.maxLevel = parseInt(metadata.maxzoom);
}
// console.log(`[${this.layerId}] PMTiles metadata:`, metadata);
} catch (error) {
console.warn(`[${this.layerId}] Could not read PMTiles metadata`);
}
this.vectorLayer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: new ol.format.MVT(),
tileUrlFunction: (tileCoord) => {
const z = tileCoord[0];
const x = tileCoord[1];
const y = tileCoord[2];
return `pmtiles://${this.layerId}/${z}/${x}/${y}`;
},
tileLoadFunction: async (tile, url) => {
const urlParts = url.split('/');
const z = parseInt(urlParts[urlParts.length - 3]);
const x = parseInt(urlParts[urlParts.length - 2]);
const y = parseInt(urlParts[urlParts.length - 1]);
// console.log(`Loading PMTiles tile: ${z}/${x}/${y}`);
try {
const tileData = await this.pmtilesInstance.getZxy(z, x, y);
if (tileData && tileData.data) {
// console.log(`PMTiles tile ${z}/${x}/${y} loaded, size: ${tileData.data.byteLength} bytes`);
const format = new ol.format.MVT();
const tileGrid = ol.tilegrid.createXYZ({maxZoom: this.maxLevel});
const tileExtent = tileGrid.getTileCoordExtent([z, x, y]);
const features = format.readFeatures(tileData.data, {
extent: tileExtent,
featureProjection: 'EPSG:3857'
});
// console.log(`PMTiles tile ${z}/${x}/${y} features: ${features.length}`);
if (tile.setFeatures) {
tile.setFeatures(features);
} else {
tile.setLoader(() => Promise.resolve(features));
}
} else {
// console.log(`PMTiles tile ${z}/${x}/${y} - no data`);
if (tile.setFeatures) {
tile.setFeatures([]);
} else {
tile.setLoader(() => Promise.resolve([]));
}
}
} catch (error) {
// console.error(`Failed to load PMTiles tile ${z}/${x}/${y}:`, error);
if (tile.setFeatures) {
tile.setFeatures([]);
} else {
tile.setLoader(() => Promise.resolve([]));
}
}
},
maxZoom: this.maxLevel
}),
style: styleFunction,
zIndex: 1,
updateWhileAnimating: false,
updateWhileInteracting: false,
renderBuffer: 50,
declutter: false,
// 줌 범위 설정
minZoom: zoomRange ? zoomRange.min : undefined,
maxZoom: zoomRange ? zoomRange.max : undefined
});
this.olMap.addLayer(this.vectorLayer);
// this.pmtilesInstance = source;
// try {
// const metadata = await this.pmtilesInstance.getMetadata();
// if (metadata.maxzoom) this.maxLevel = parseInt(metadata.maxzoom, 10);
// } catch (e) {
// console.warn(`[${this.layerId}] Could not read PMTiles metadata`);
// }
// // PMTiles가 512 기준이면 512 권장
// const tileSize = 256;
// const tileGrid = ol.tilegrid.createXYZ({
// maxZoom: this.maxLevel,
// tileSize
// });
// // 기존 styleFunction 그대로 사용
// const styleFn = styleFunction;
// // 벡터 → 래스터 변환용 레이어
// this.rasterLayer = new ol.layer.Tile({
// source: new ol.source.TileImage({
// tileGrid,
// tileUrlFunction: (tileCoord) => {
// const [z, x, y] = tileCoord;
// return `pmtiles://${this.layerId}/${z}/${x}/${y}`;
// },
// tileLoadFunction: async (imageTile, url) => {
// const parts = url.split('/');
// const z = parseInt(parts[parts.length - 3], 10);
// const x = parseInt(parts[parts.length - 2], 10);
// const y = parseInt(parts[parts.length - 1], 10);
// // 타일 extent (EPSG:3857)
// const tileExtent = tileGrid.getTileCoordExtent([z, x, y]);
// const resolution = tileGrid.getResolution(z);
// // 해상도/부하 트레이드오프
// const pixelRatio = 1; // 필요시 window.devicePixelRatio
// const canvas = document.createElement('canvas');
// canvas.width = tileSize * pixelRatio;
// canvas.height = tileSize * pixelRatio;
// const ctx = canvas.getContext('2d');
// // ★ extent 반영하여 벡터 컨텍스트 생성
// const vectorCtx = ol.render.toContext(ctx, {
// size: [tileSize * pixelRatio, tileSize * pixelRatio],
// pixelRatio,
// extent: tileExtent // ← 중요: 지도좌표 → 픽셀 변환 기준
// });
// try {
// const tileData = await this.pmtilesInstance.getZxy(z, x, y);
// if (tileData && tileData.data) {
// // MVT 파싱 (extent는 featureProjection과 동일 좌표계여야 함)
// const format = new ol.format.MVT();
// const features = format.readFeatures(tileData.data, {
// extent: tileExtent, // EPSG:3857
// featureProjection: 'EPSG:3857'
// });
// // zIndex 반영을 위해 (feature, style) 쌍 정렬
// const drawQueue = [];
// for (const f of features) {
// let s = styleFn ? styleFn(f, resolution) : null;
// if (!s) continue;
// const styles = Array.isArray(s) ? s : [s];
// for (const one of styles) {
// if (one && typeof one.getZIndex === 'function') {
// const zi = one.getZIndex() ?? 0;
// drawQueue.push({ f, style: one, z: zi });
// }
// }
// }
// drawQueue.sort((a, b) => a.z - b.z);
// for (const { f, style } of drawQueue) {
// vectorCtx.drawFeature(f, style);
// }
// } else {
// // 데이터 없음 → 투명 타일
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// }
// imageTile.getImage().src = canvas.toDataURL('image/png');
// } catch (err) {
// console.warn(`PMTiles raster tile load failed ${z}/${x}/${y}`, err);
// const empty = document.createElement('canvas');
// empty.width = tileSize;
// empty.height = tileSize;
// imageTile.getImage().src = empty.toDataURL();
// }
// },
// // crossOrigin: 'anonymous',
// }),
// zIndex: 100000,
// minZoom: zoomRange ? zoomRange.min : undefined,
// maxZoom: zoomRange ? zoomRange.max : undefined
// });
// this.olMap.addLayer(this.rasterLayer);
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
console.log(`[${this.layerId}] PMTiles Vector layer initialized${zoomInfo}`);
try {
const metadata = await this.pmtilesInstance.getMetadata();
// PMTiles metadata에서 bounds 정보 확인
if (metadata && (metadata.bounds || metadata.antimeridian_adjusted_bounds || metadata.center)) {
let extent;
let bounds;
if (metadata.bounds) bounds = metadata.bounds;
if (metadata.antimeridian_adjusted_bounds) bounds = metadata.antimeridian_adjusted_bounds.split(',');
if (bounds) {
// bounds: [minLon, minLat, maxLon, maxLat]
const [minLon, minLat, maxLon, maxLat] = bounds;
const bottomLeft = ol.proj.fromLonLat([minLon, minLat]);
const topRight = ol.proj.fromLonLat([maxLon, maxLat]);
extent = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
} else if (metadata.center) {
// center와 zoom을 이용한 대략적인 영역 계산
const [centerLon, centerLat, centerZoom] = metadata.center;
const centerPoint = ol.proj.fromLonLat([centerLon, centerLat]);
const resolution = this.olMap.getView().getResolutionForZoom(centerZoom || 10);
const size = 1000; // 대략적인 크기
extent = [
centerPoint[0] - size * resolution,
centerPoint[1] - size * resolution,
centerPoint[0] + size * resolution,
centerPoint[1] + size * resolution
];
}
let pmtilesFitBtn = document.querySelector('.map-container .control-btn-wrap .pmtiles-fit-btn');
if (extent) {
if (ol.map.getOverlays().getLength() == 0) {
this.olMap.getView().fit(extent, {
padding: [50, 50, 50, 50],
// duration: 500,
// constrainResolution: true,
maxZoom: 18
});
// console.log(`[${this.layerId}] PMTiles layer fitted to bounds`);
}
pmtilesFitBtn.style.display = 'flex';
pmtilesFitBtn.addEventListener('click', async() => {
this.olMap.getView().fit(extent, {
padding: [50, 50, 50, 50],
// duration: 500,
// constrainResolution: true,
maxZoom: 18
});
})
}
}
} catch (error) {
console.warn(`[${this.layerId}] Could not fit PMTiles bounds:`, error);
}
}
}
// GeoJSON/KML용 벡터 소스 설정 메서드
async setVectorSource(url, type, zoomRange = null) {
this.type = type;
if (type === 'geojson') {
console.log(`[${this.layerId}] Loading GeoJSON from: ${url}`);
this.vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: url,
format: new ol.format.GeoJSON()
}),
style: styleFunction,
zIndex: 1,
updateWhileAnimating: false,
updateWhileInteracting: false,
renderBuffer: 50,
declutter: false,
// 줌 범위 설정
minZoom: zoomRange ? zoomRange.min : undefined,
maxZoom: zoomRange ? zoomRange.max : undefined
});
this.olMap.addLayer(this.vectorLayer);
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
console.log(`[${this.layerId}] GeoJSON Vector layer initialized${zoomInfo}`);
// GeoJSON 로드 완료 이벤트 처리
const source = this.vectorLayer.getSource();
source.once('change', () => {
if (source.getState() === 'ready') {
const features = source.getFeatures();
const featureCount = features.length;
console.log(`[${this.layerId}] GeoJSON loaded: ${featureCount} features`);
// feature 수에 따른 경고
if (featureCount > 10000) {
console.warn(`⚠️ Large dataset detected (${featureCount} features). Performance may be affected.`);
}
// 로드된 features가 있으면 해당 영역으로 자동 줌 (단독 레이어인 경우만)
if (features.length > 0 && !zoomRange) {
const extent = source.getExtent();
this.olMap.getView().fit(extent, {
padding: [50, 50, 50, 50],
maxZoom: 18
});
}
}
});
} else if (type === 'kml') {
console.log(`[${this.layerId}] Loading KML from: ${url}`);
this.vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: url,
format: new ol.format.KML({
extractStyles: false,
showPointNames: false
})
}),
style: styleFunction,
zIndex: 1,
updateWhileAnimating: false,
updateWhileInteracting: false,
renderBuffer: 50,
declutter: false,
// 줌 범위 설정
minZoom: zoomRange ? zoomRange.min : undefined,
maxZoom: zoomRange ? zoomRange.max : undefined
});
this.olMap.addLayer(this.vectorLayer);
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
console.log(`[${this.layerId}] KML Vector layer initialized${zoomInfo}`);
// KML 로드 완료 이벤트 처리
const source = this.vectorLayer.getSource();
source.once('change', () => {
if (source.getState() === 'ready') {
const features = source.getFeatures();
console.log(`[${this.layerId}] KML loaded: ${features.length} features`);
if (features.length > 0 && !zoomRange) {
const extent = source.getExtent();
this.olMap.getView().fit(extent, {
padding: [50, 50, 50, 50],
maxZoom: 18
});
}
}
});
}
}
clear() {
if (this.vectorLayer) {
this.olMap.removeLayer(this.vectorLayer);
this.vectorLayer = null;
console.log(`[${this.layerId}] Layer cleared`);
}
}
}
// ---- 통합 멀티 레이어 관리자 (OpenLayers) ----
export class OLMultiLayerManager {
constructor(olMap) {
this.olMap = olMap;
this.layerMap = new Map();
this.pmtilesInstances = new Map();
console.log('OLMultiLayerManager initialized');
}
async toggleLayer(layerId, format) {
const key = `${format}_${layerId}`;
if (this.layerMap.has(key)) {
// 레이어 제거
// PMTiles 제거 시 연결된 detail GeoJSON도 함께 제거
const layer = this.layerMap.get(key);
layer.clear();
this.layerMap.delete(key);
if (this.pmtilesInstances.has(key)) {
this.pmtilesInstances.delete(key);
}
console.log(`[${key}] Layer removed`);
return false;
} else {
// 레이어 추가 로직
if (format === 'pbf') {
const layer = new OLVectorLayer(this.olMap, key);
const templateUrl = `http://172.16.41.52:3003/vector_tile_pbf/${layerId}/{z}/{x}/{y}.pbf`;
await layer.setTileSource(templateUrl, 'pbf');
this.layerMap.set(key, layer);
} else if (format === 'pmtiles') {
// console.log(`🚀 Creating hybrid PMTiles + GeoJSON layers for: ${layerId}`);
// // 1. PMTiles 레이어 추가 (zoom 0-17만 표시)
// const pmtilesLayer = new OLVectorLayer(this.olMap, key);
// const pmtilesPath = `http://172.16.41.52:3003/vector_tile_pmtiles/${layerId}.pmtiles`;
// const pmtiles = new PMTiles(pmtilesPath);
// this.pmtilesInstances.set(key, pmtiles);
// await pmtilesLayer.setTileSource(pmtiles, 'pmtiles', { min: 0, max: 18.1 });
// this.layerMap.set(key, pmtilesLayer);
// console.log(`✅ [${key}] PMTiles layer added (zoom 0-17)`);
// // 2. GeoJSON 레이어 추가 (zoom 18-24만 표시)
// const detailGeojsonKey = `geojson_detail_${layerId}`;
// const detailGeojsonLayer = new OLVectorLayer(this.olMap, detailGeojsonKey);
// const geojsonUrl = `http://172.16.41.52:3003/vector_tile_geojson/${layerId}.geojson`;
// await detailGeojsonLayer.setVectorSource(geojsonUrl, 'geojson', { min: 17.9, max: 24 });
// this.layerMap.set(detailGeojsonKey, detailGeojsonLayer);
// console.log(`✅ [${detailGeojsonKey}] Detail GeoJSON layer added (zoom 18-24)`);
// console.log(`🎯 Hybrid layer setup complete. PMTiles will show at zoom 0-17, GeoJSON at zoom 18+`);
const pmtilesLayer = new OLVectorLayer(this.olMap, key);
// const pmtilesPath = `http://172.16.41.52:3003/vector_tile_pmtiles/${layerId}.pmtiles`;
let pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/${layerId}.pmtiles`;
if (layerId == 'testbim') pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/dsdj2.pmtiles`;
// let mapName = layerId;
// if (layerId == 'testbim') mapName = 'dsdj2';
// let pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/${mapName}.pmtiles`;
let checkUrlExistsResult = await checkUrlExists(pmtilesPath);
if (checkUrlExistsResult == true) {
const pmtiles = new PMTiles(pmtilesPath);
this.pmtilesInstances.set(key, pmtiles);
await pmtilesLayer.setTileSource(pmtiles, 'pmtiles');
this.layerMap.set(key, pmtilesLayer);
} else {
let pmtilesFitBtn = document.querySelector('.map-container .control-btn-wrap .pmtiles-fit-btn');
pmtilesFitBtn.style.display = 'none';
return;
}
} else if (format === 'geojson') {
const layer = new OLVectorLayer(this.olMap, key);
const geojsonUrl = `http://172.16.41.52:3003/vector_tile_geojson/${layerId}.geojson`;
await layer.setVectorSource(geojsonUrl, 'geojson');
this.layerMap.set(key, layer);
} else if (format === 'kml') {
const layer = new OLVectorLayer(this.olMap, key);
const kmlUrl = `http://172.16.41.52:3003/vector_tile_kml/${layerId}.kml`;
await layer.setVectorSource(kmlUrl, 'kml');
this.layerMap.set(key, layer);
}
// console.log(`[${key}] Layer added`);
return true;
}
}
clearAll() {
this.layerMap.forEach(layer => layer.clear());
this.layerMap.clear();
this.pmtilesInstances.clear();
console.log('All OpenLayers layers cleared');
}
getActiveLayers() {
return Array.from(this.layerMap.keys());
}
}
// ---- 피처별 스타일 함수 ----
function styleFunction(feature, resolution) {
const props = feature.getProperties();
const entityType = props?.EntityType;
let color = props.Color || '#ff0000';
const constWidth = props.constWidth || 0;
const ltscale = props.ltscale || 1.0;
// let dashPattern = props.dashPattern;
// if (dashPattern && typeof dashPattern === 'string') {
// try {
// // "[0.5,-0.25]" → [0.5, -0.25]
// dashPattern = JSON.parse(dashPattern);
// } catch (e) {
// console.warn('dashPattern 파싱 실패:', dashPattern, e);
// dashPattern = null;
// }
// }
// ⭐ constWidth에 따른 선 두께 결정
let strokeWidth = 1; // 기본값
// if (constWidth === 0) {
// strokeWidth = 1;
// } else if (constWidth === 1) {
// strokeWidth = 5;
// } else {
// // 향후 다른 값 대비
// // strokeWidth = 2 + (constWidth * 2);
// // strokeWidth = 1 + (constWidth * 5);
// strokeWidth = constWidth * 5;
// }
// if (constWidth === 0) {
// strokeWidth = 1;
// } else {
// // 향후 다른 값 대비
// // strokeWidth = 2 + (constWidth * 2);
// // strokeWidth = 1 + (constWidth * 5);
// strokeWidth = constWidth * 10;
// }
// if (constWidth === 0) {
// strokeWidth = 1;
// } else if (constWidth > 0 && constWidth <= 1) {
// strokeWidth = constWidth * 10;
// } else {
// strokeWidth = constWidth * 5;
// }
// if (constWidth === 0) {
// strokeWidth = 1;
// } else {
// console.log(constWidth);
// strokeWidth = constWidth*2;
// }
// // ⭐ dashPattern을 OpenLayers lineDash로 변환
// let lineDash = null;
// if (dashPattern && Array.isArray(dashPattern) && dashPattern.length > 0) {
// // const zoomFactor = Math.min(1 / resolution, 1.0);
// const scaleFactor = 10; // 조정 가능 (0.3 ~ 1.0 추천)
// lineDash = dashPattern.map(value =>
// Math.abs(value) * ltscale * scaleFactor
// );
// }
// 투명도 처리
const hexToRgba = (hex, alpha = 1) => {
if (!hex || hex.length < 7) return `rgba(255, 0, 0, ${alpha})`;
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
const geomType = feature.getGeometry().getType();
if (entityType) {
if (entityType === 'TEXT_POLYGON' || entityType === 'LINETYPE_PATTERN_TEXT') {
return new ol.style.Style({
fill: new ol.style.Fill({
color: hexToRgba(color, 1.0)
}),
zIndex: 40
});
} else if (entityType === 'LINETYPE_PATTERN_SHAPE') {
return new ol.style.Style({
fill: new ol.style.Fill({
color: hexToRgba(color, 0.4)
}),
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 20
});
} else if (entityType === 'HATCH') {
return new ol.style.Style({
fill: new ol.style.Fill({
color: hexToRgba(color, 0.4)
}),
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 10
});
} else if (entityType === 'SOLID_LINE_POLYGON' || entityType === 'DASHED_LINE_POLYGON') {
return new ol.style.Style({
fill: new ol.style.Fill({
color: hexToRgba(color, 1)
}),
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 10
});
} else if (entityType === 'LINETYPE_PATTERN_SHAPE_LINE') {
return new ol.style.Style({
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 30
});
} else if (entityType === 'LWPOLYLINE' || entityType === 'LINE') {
return new ol.style.Style({
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 30
});
}
} else {
if (geomType === 'Polygon' || geomType === 'MultiPolygon') {
return new ol.style.Style({
fill: new ol.style.Fill({
color: hexToRgba(color, 0.4)
}),
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 20
});
} else if (geomType === 'LineString' || geomType === 'MultiLineString') {
// let strokeWidth, color;
// if (props.DIVI == '주곡선') {
// strokeWidth = 2;
// color = '#cccccc';
// } else {
// strokeWidth = 1;
// color = '#777777';
// }
return new ol.style.Style({
stroke: new ol.style.Stroke({
color: hexToRgba(color, 1.0),
width: strokeWidth,
// lineDash: lineDash,
lineCap: 'butt',
lineJoin: 'miter'
}),
zIndex: 30
});
} else if (geomType === 'Point' || geomType === 'MultiPoint') {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 4,
fill: new ol.style.Fill({
color: hexToRgba(color, 1.0)
})
}),
zIndex: 30
});
}
}
}
// async function checkUrlExists(url) {
// try {
// const res = await fetch(url, { method: "HEAD" });
// if (res.ok) {
// return true; // 200~299 → 정상 URL
// }
// return false; // 404, 403 등
// } catch (e) {
// return false; // 네트워크 오류, CORS 오류 포함
// }
// }
async function checkUrlExists(url) {
try {
const res = await fetch(url, { method: "HEAD" });
// 200~299
return res.ok;
} catch (e) {
// 여기서 swallow(삼키기)
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,710 @@
import { typeStep2Kor } from '../main.js';
let projectTypeBtn;
let projectTypeCapsule;
let projectTypeList = null;
let projectStepList = null;
let mainLayerState = null; // 메인 레이어 상태
// 프로젝트 설정 저장 버튼 클릭 이벤트
document.getElementById('project-save-btn').addEventListener('click', async () => {
const projectSaveBtn = document.getElementById('project-save-btn');
const projectStepBtn = document.getElementById('project-step-btn');
const projectNameInput = document.getElementById('project-name-input');
if (projectSaveBtn.textContent === '저장') {
// type
let typeVal = projectTypeBtn.children[0].textContent;
let typeValResult = stepTypeEng(typeVal);
// step
let stepVal = projectStepBtn.children[0].textContent;
let stepValResult = stepTypeEng(stepVal);
// project_name
let nameVal = projectNameInput.value;
let projectInfoParams = {
projectId : vars.project.project_id,
category : vars.project.category,
project_type : typeValResult,
step : stepValResult,
project_nm : nameVal,
}
let res = await axios.post('/common/updateProjectInfo', {params: projectInfoParams});
if (res.data.message == 'updateProjectInfo_success') {
// 수정된 프로젝트 정보 가져와서 전역변수에 저장
let projectRes = await axios.get('/common/getProject');
for (let project of projectRes.data.data) {
vars.allProject[project.project_id] = project;
}
vars.project = vars.allProject[vars.project_id];
setReadOnlyMode(vars.project.category);
}
}
});
// 프로젝트 설정 취소 클릭 이벤트
document.querySelector('#project-cancel-btn').addEventListener('click', () => {
const projectSaveBtn = document.getElementById('project-save-btn');
if (projectSaveBtn.textContent === '저장') projectSaveBtn.textContent = '수정';
const list = document.querySelector('#project-type-wrap-overseas .project-type__list');
list.classList.remove('--project-list__open');
setReadOnlyMode(vars.project.category);
});
export function initProjectSetting(param) {
const projectSaveBtn = document.getElementById('project-save-btn');
const projectCancelBtn = document.getElementById('project-cancel-btn');
const projectLocationBtn = document.getElementById('project-location-btn');
let projectTypeWrap = document.getElementById('project-type-wrap');
let projectTypeWrapOverseas = document.getElementById('project-type-wrap-overseas');
if (param === undefined) {
// overseas, bim 아닌 경우
projectTypeWrap.style.display = 'none';
projectTypeWrapOverseas.style.display = 'none';
projectTypeBtn = document.getElementById('project-type-btn');
projectTypeCapsule = document.getElementById('project-type-capsule');
} else {
if (param === 'overseas') {
// overseas
projectTypeWrap.style.display = 'none';
projectTypeWrapOverseas.style.display = 'flex';
projectTypeBtn = document.getElementById('project-type-btn-overseas');
projectTypeCapsule = document.getElementById('project-type-capsule-overseas');
} else {
//bim
projectTypeWrap.style.display = 'flex';
projectTypeWrapOverseas.style.display = 'none';
projectTypeBtn = document.getElementById('project-type-btn');
projectTypeCapsule = document.getElementById('project-type-capsule');
}
adjustSelectBoxWidth(projectTypeBtn);
}
setReadOnlyMode(param);
// 권한이 있는 경우 저장 버튼 이벤트 추가
if (vars.permission.checkPermission('change-project-btn')) {
// 기존 리스너 제거 후 추가 (중복 방지)
projectSaveBtn.textContent = '수정';
projectSaveBtn.removeEventListener('click', handleProjectSaveClick);
projectSaveBtn.addEventListener('click', () => handleProjectSaveClick(param));
} else {
// 권한 없으면 버튼 제거
projectSaveBtn.remove();
projectCancelBtn.remove();
projectLocationBtn.remove();
}
// 이벤트 리스너 초기화
initEventListeners();
}
function handleProjectSaveClick(param) {
const projectSaveBtn = document.getElementById('project-save-btn');
if (projectSaveBtn.textContent === '저장') {
// 저장 버튼 클릭 : 읽기 모드로 전환
projectSaveBtn.textContent = '수정';
setReadOnlyMode(param);
} else {
// 수정 버튼 클릭: 편집 모드로 전환
projectSaveBtn.textContent = '저장';
setEditMode(param);
}
}
// 읽기 모드 : 관리자 이상이면서 읽기모드 or 일반유저
function setReadOnlyMode(param) {
const projectStepBtn = document.getElementById('project-step-btn');
const projectStepCapsule = document.getElementById('project-step-capsule');
const projectNameView = document.getElementById('project-name-view');
const projectNameInput = document.getElementById('project-name-input');
const projectSaveBtn = document.getElementById('project-save-btn');
const projectCancelBtn = document.getElementById('project-cancel-btn');
// step - 버튼 숨기고 캡슐 표시
projectStepBtn.style.display = 'none';
projectStepCapsule.style.display = 'flex';
projectStepCapsule.innerHTML = typeStep2Kor(vars.project.step);
projectStepCapsule.className = `project-step-capsule --step-capsule__${vars.project.step}`;
// type - 버튼 숨기고 캡슐 표시
projectTypeBtn.style.display = 'none';
projectTypeCapsule.style.display = 'flex';
projectTypeCapsule.innerHTML = typeStep2Kor(vars.project.project_type);
// name - input 숨기고 view 표시
projectNameInput.style.display = 'none';
projectNameView.style.display = 'flex';
projectNameView.innerHTML = vars.project.short_nm;
if(param !== 'overseas' && param !== 'bimproject') {
projectStepCapsule.style.display = 'none';
projectTypeCapsule.style.display = 'none';
projectNameView.innerHTML = vars.project.project_nm;
projectSaveBtn.style.display = 'none';
projectCancelBtn.style.display = 'none';
}
// location - 관리자이상:버튼 o , 관리자이하: 버튼x
if (vars.permission.checkPermission('change-project-btn')) {
document.querySelector('#project-location-btn').style.display = 'flex';
}
const lat = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lat');
lat.innerHTML =`<div class="project-location-lat">위도 ${vars.project.lat}</div>`
const lon = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lon');
lon.innerHTML =`<div class="project-location-lon">경도 ${vars.project.lon}</div>`
}
// 편집모드 : 관리자 이상이면서 편집모드
function setEditMode(param) {
const projectStepBtn = document.getElementById('project-step-btn');
const projectStepCapsule = document.getElementById('project-step-capsule');
const projectNameView = document.getElementById('project-name-view');
const projectNameInput = document.getElementById('project-name-input');
// step - 캡슐 숨기고 버튼 표시
projectStepCapsule.style.display = 'none';
projectStepBtn.style.display = 'flex';
projectStepBtn.innerHTML = `
<h5 class="project-step__label --step__${vars.project.step}">${typeStep2Kor(vars.project.step)}</h5>
<i class="project-step__icon"></i>
`;
// type - 캡슐 숨기고 버튼 표시
projectTypeCapsule.style.display = 'none';
projectTypeBtn.style.display = 'flex';
projectTypeBtn.innerHTML = `
<h5 class="project-type__label --type__${vars.project.project_type}">${typeStep2Kor(vars.project.project_type)}</h5>
<i class="project-type__icon"></i>
`;
// name - view 숨기고 input 표시
projectNameView.style.display = 'none';
projectNameInput.style.display = 'flex';
projectNameInput.value = projectNameView.textContent;
if(param !== 'overseas' && param !== 'bimproject') {
projectStepBtn.style.display = 'none';
projectTypeBtn.style.display = 'none';
}
// location - 수정 버튼, 위경도 위치 표시
document.querySelector('#project-location-btn').style.display = 'flex';
const lat = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lat');
lat.innerHTML =`<div class="project-location-lat">위도 ${vars.project.lat}</div>`
const lon = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lon');
lon.innerHTML =`<div class="project-location-lon">경도 ${vars.project.lon}</div>`
}
// 접속 인원 모달 - 프로젝트 type, step 변경 이벤트
function initEventListeners() {
// project-type-btn 클릭 이벤트
if (projectTypeBtn) {
// 기존 이벤트 제거 후 추가 (중복 방지)
const newBtn = projectTypeBtn.cloneNode(true);
projectTypeBtn.parentNode.replaceChild(newBtn, projectTypeBtn);
projectTypeBtn = newBtn;
projectTypeList = projectTypeBtn.parentElement.querySelector('.project-type__list');
projectTypeBtn.addEventListener('click', (e) => {
e.stopPropagation();
projectTypeList?.classList.toggle('--project-list__open');
});
}
// project-step-btn 클릭 이벤트
const projectStepBtn = document.getElementById('project-step-btn');
if (projectStepBtn) {
projectStepList = document.querySelector('.project-step__list');
projectStepBtn.addEventListener('click', (e) => {
e.stopPropagation();
projectStepList?.classList.toggle('--project-list__open');
});
}
// modal-wrap 클릭 이벤트
if (projectTypeBtn) {
const modalWrap = projectTypeBtn.closest('.modal-wrap');
if (modalWrap) {
modalWrap.addEventListener('click', (e) => {
projectTypeList?.classList.remove('--project-list__open');
projectStepList?.classList.remove('--project-list__open');
});
}
}
// project-type__list_item 클릭 이벤트
if (projectTypeList) {
projectTypeList.querySelectorAll('.project-type__list_item').forEach(item => {
item.addEventListener('click', async(e) => {
e.stopPropagation();
projectTypeList.classList.remove('--project-list__open');
let type = e.target.classList[1].split('__')[1];
let kor = e.target.textContent;
projectTypeBtn.innerHTML = `
<h5 class="project-type__label --type__${type}">${kor}</h5>
<i class="project-type__icon"></i>`;
});
});
}
// project-step__list_item 클릭 이벤트
if (projectStepList) {
projectStepList.querySelectorAll('.project-step__list_item').forEach(item => {
item.addEventListener('click', async(e) => {
e.stopPropagation();
projectStepList.classList.remove('--project-list__open');
let step = e.target.classList[1].split('__')[1];
let btn = document.getElementById('project-step-btn');
let kor = typeStep2Kor(step);
if (btn) {
btn.innerHTML = `
<h5 class="project-step__label --step__${step}">${kor}</h5>
<i class="project-step__icon"></i>`;
}
});
});
}
}
function stepTypeEng(param) {
let result = param;
switch(param) {
case '완료':
result = 'done';
break;
case '진행':
result = 'active';
break;
case '중지':
result = 'stop';
break;
case '대기':
result = 'wait';
break;
case '시공':
result = 'construction';
break;
case '설계':
result = 'design';
break;
case '제안':
result = 'surgest';
break;
case '연구':
result = 'research';
break;
case '지원':
result = 'support';
break;
case '센터':
result = 'center';
break;
case '측량':
result = 'survey';
break;
case 'MP (기본계획)':
result = 'MP';
break;
case 'DD (실시설계)':
result = 'DD';
break;
case 'FS (타당성조사)':
result = 'FS';
break;
case 'PD (기본설계)':
result = 'PD';
break;
case 'DS (설계감리)':
result = 'DS';
break;
case 'CS (시공감리)':
result = 'CS';
break;
case 'PMC (실시설계)':
result = 'PMC';
break;
case 'IDC (타당성조사)':
result = 'IDC';
break;
case 'DR (설계검토)':
result = 'DR';
break;
case 'BD (수주영업)':
result = 'BD';
break;
case 'ETC (기타)':
result = 'ETC';
break;
default:
result = '없음';
break;
}
return result;
}
// 셀렉트박스 크기 조정
function adjustSelectBoxWidth(button) {
if (!button) return;
const list = button.parentElement.querySelector('.project-type__list');
if (!list) return;
// canvas를 사용한 텍스트 너비 측정
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 현재 폰트 스타일 가져오기
const computedStyle = window.getComputedStyle(button);
context.font =`${computedStyle.fontSize} ${computedStyle.fontFamily}`;
let maxWidth = 0;
const items = list.querySelectorAll('.project-type__list_item');
items.forEach(item => {
const text = item.textContent.trim();
const metrics = context.measureText(text);
const width = metrics.width;
if (width > maxWidth) {
maxWidth = width;
}
});
// 적용
if (maxWidth > 0) {
const finalWidth = maxWidth + 25;
// 버튼에 적용
button.style.minWidth = `${finalWidth}px`;
button.style.textAlign = 'center'; // 가운데 정렬
button.style.justifyContent = 'center'; // flex인 경우
button.style.display = 'flex'; // flex 적용
button.style.alignItems = 'center'; // 세로 가운데 정렬
// 캡슐에 적용
const capsule = button.parentElement.querySelector('.--type-capsule');
if (capsule) {
capsule.style.minWidth = `${finalWidth}px`;
capsule.style.textAlign = 'center'; // 가운데 정렬
capsule.style.justifyContent = 'center'; // flex인 경우
capsule.style.display = 'flex'; // flex 적용
capsule.style.alignItems = 'center'; // 세로 가운데 정렬
}
// 리스트에 적용
list.style.minWidth = `${finalWidth}px`;
// 리스트 글씨 정렬
items.forEach(item => {
item.style.paddingLeft = '5px';
item.style.paddingRight = '5px';
})
}
}
// 프로젝트 위치 수정 버튼 클릭 이벤트
document.getElementById('project-location-btn').addEventListener('click', () => {
document.querySelector('.project-location-modal').style.display = 'flex';
openLocationModal();
})
// 지도 모달창 닫기 버튼, 취소 버튼 클릭 이벤트
document.querySelector('.project-location-modal .modal-wrap .modal-head .close').addEventListener('click', () => {
closeLocationModal();
});
document.querySelector('.project-location-modal .modal-wrap .modal-foot .button').addEventListener('click', () => {
closeLocationModal();
});
// 기본 지도 모달창 버튼 클릭 이벤트
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .xs-button').addEventListener('click', () => {
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .base-map').style.display = 'flex';
});
// 기본 지도 모달창 닫기 버튼 클릭 이벤트
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .base-map .text-wrap .close').addEventListener('click', () => {
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .base-map').style.display = 'none';
});
// 지도 모달창 저장 버튼 클릭 이벤트
document.querySelector('.project-location-modal .modal-wrap .modal-foot .primary-button').addEventListener('click', async() => {
if(!ol.locationMap || !ol.locationMap.clickCoord) {
alert('위치를 선택해주세요');
return;
}
let updateLocationParams = {
projectId: vars.project.project_id,
category: vars.project.category,
project_nm: vars.project.project_nm,
lon: ol.locationMap.clickCoord.lon,
lat: ol.locationMap.clickCoord.lat
};
let res = await axios.post('/common/updateLocationInfo', {params: updateLocationParams});
if (res.data.message == 'updateLocationInfo_success') {
vars.project.lon = res.data.data.lon;
vars.project.lat = res.data.data.lat;
closeLocationModal();
const lat = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lat');
lat.innerHTML = `<div class="project-location-lat">위도 ${res.data.data.lat}</div>`;
const lon = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lon');
lon.innerHTML = `<div class="project-location-lon">경도 ${res.data.data.lon}</div>`;
}
})
// 프로젝트 위치 수정
function openLocationModal() {
// 메인 지도 레이어 상태 백업
if (vars.roadLayer && vars.satelliteLayer && vars.hybridLayer) {
mainLayerState = {
road: vars.roadLayer.getVisible(),
satellite: vars.satelliteLayer.getVisible(),
hybrid: vars.hybridLayer.getVisible()
};
}
initLocationMap();
// 기존 좌표가 있을 때만 블루 마커 생성
if (vars.project.lon && vars.project.lat) {
if (!ol.locationMap.blueMarkerOverlay) {
// 블루 마커 (저장된 위치)
let blueMarker = document.createElement('div');
blueMarker.style.width = '1rem';
blueMarker.style.height = '1rem';
blueMarker.style.backgroundColor = '#0D8DF2';
blueMarker.style.borderRadius = '50%';
blueMarker.style.border = '0.1875rem solid #fff';
blueMarker.style.boxShadow = '0 0.25rem 0.5rem rgba(0, 0, 0, 0.05)';
ol.locationMap.blueMarkerOverlay = new ol.Overlay({
element: blueMarker,
positioning: 'center-center',
stopEvent: false,
zIndex: 9998
});
ol.locationMap.addOverlay(ol.locationMap.blueMarkerOverlay);
}
if (!ol.locationMap.blueLabelOverlay) {
let blueLabel = document.createElement('div');
ol.locationMap.blueLabelOverlay = new ol.Overlay({
element: blueLabel,
positioning: 'bottom-center',
offset: [0, -10],
stopEvent: false,
zIndex: 9998,
});
ol.locationMap.addOverlay(ol.locationMap.blueLabelOverlay);
}
const position3857 = ol.proj.fromLonLat([vars.project.lon, vars.project.lat]);
ol.locationMap.blueMarkerOverlay.setPosition(position3857);
ol.locationMap.blueLabelOverlay.setPosition(position3857);
ol.locationMap.blueLabelOverlay.element.innerHTML = `
<div style="background: #fff; color: #111; padding: 0.25rem 0.5rem; border-radius: 0.25rem; border: 0.1875rem solid #0D8DF2; box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.05); font-size: 0.875rem; font-weight: 500; line-height: 1.25rem; letter-spacing: -0.0175rem;">
저장된 위치 <br>
경도 : ${vars.project.lon}<br>
위도 : ${vars.project.lat}
</div>
`;
}
}
// 지도 초기화
function initLocationMap() {
// 위치 모달 지도만 삭제
if (ol.locationMap) {
ol.locationMap.getOverlays()?.clear();
ol.locationMap.getLayers()?.forEach(l => {
const src = l.getSource?.();
if (src && src instanceof ol.source.Vector) src.clear(true);
});
ol.locationMap.getLayers()?.clear();
if (typeof ol.locationMap.dispose === 'function') {
ol.locationMap.dispose();
}
ol.locationMap = null;
}
// 위치 설정 모달용 임시 레이어 생성
const tempRoadLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.roadLayer.getSource().getUrls()[0]
}),
visible: mainLayerState?.road ?? true
});
const tempSatelliteLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.satelliteLayer.getSource().getUrls()[0]
}),
visible: mainLayerState?.satellite ?? false
});
const tempHybridLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.hybridLayer.getSource().getUrls()[0]
}),
visible: mainLayerState?.hybrid ?? false
});
ol.locationMap = new ol.Map({
target: 'map-location',
layers: [tempRoadLayer, tempSatelliteLayer, tempHybridLayer],
view: new ol.View({
projection: 'EPSG:3857',
center: ol.proj.fromLonLat([127.8, 35.9]),
zoom: 7,
constrainResolution: true,
smoothResolutionConstraint: true
})
});
// 임시 레이어 참조 저장
ol.locationMap.tempRoadLayer = tempRoadLayer;
ol.locationMap.tempSatelliteLayer = tempSatelliteLayer;
ol.locationMap.tempHybridLayer = tempHybridLayer;
// proj4 설정
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);
// 레드 마커 생성
let marker = document.createElement('div');
marker.classList.add('marker');
marker.style.width = '1rem';
marker.style.height = '1rem';
marker.style.backgroundColor = '#F21D0D';
marker.style.borderRadius = '50%';
marker.style.border = '0.1875rem solid #fff';
marker.style.boxShadow = '0 0.25rem 0.5rem rgba(0, 0, 0, 0.05)';
ol.locationMap.redMarkerOverlay = new ol.Overlay({
element: marker,
positioning: 'center-center',
stopEvent: false,
zIndex: 9999
});
ol.locationMap.addOverlay(ol.locationMap.redMarkerOverlay);
let label = document.createElement('div');
ol.locationMap.redLabelOverlay = new ol.Overlay({
element: label,
positioning: 'bottom-center',
offset: [0, -10],
stopEvent: false,
zIndex: 9999,
});
ol.locationMap.addOverlay(ol.locationMap.redLabelOverlay);
// 클릭 이벤트
ol.locationMap.on('click', function(e) {
const lonLat = ol.proj.toLonLat(e.coordinate);
const lon = lonLat[0].toFixed(6);
const lat = lonLat[1].toFixed(6);
ol.locationMap.clickCoord = {
lon: Number(lon),
lat: Number(lat),
coordinate3857: e.coordinate
};
ol.locationMap.redMarkerOverlay.setPosition(e.coordinate);
ol.locationMap.redLabelOverlay.element.innerHTML = `
<div style="background: #fff; color: #111; padding: 0.25rem 0.5rem; border-radius: 0.25rem; border: 0.1875rem solid #F21D0D; box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.05); font-size: 0.875rem; font-weight:500; line-height: 1.25rem; letter-spacing: -0.0175rem;">
선택한 위치 <br>
경도 : ${lon} <br>
위도 : ${lat}
</div>
`;
ol.locationMap.redLabelOverlay.setPosition(e.coordinate);
});
}
// 기본지도 타입 라디오 버튼 클릭 이벤트
document.querySelectorAll('.project-location-modal input[name="location-base-map"]').forEach(radio => {
radio.addEventListener('change', function() {
if (!ol.locationMap) return;
const type = this.value;
// 위치 설정 모달의 임시 레이어만 변경
ol.locationMap.tempRoadLayer.setVisible(false);
ol.locationMap.tempSatelliteLayer.setVisible(false);
ol.locationMap.tempHybridLayer.setVisible(false);
switch(type) {
case 'road':
ol.locationMap.tempRoadLayer.setVisible(true);
break;
case 'hybrid':
ol.locationMap.tempHybridLayer.setVisible(true);
break;
case 'satellite':
ol.locationMap.tempSatelliteLayer.setVisible(true);
break;
}
});
});
// 모달 닫기 함수
function closeLocationModal() {
document.querySelector('.project-location-modal').style.display = 'none';
if (ol.locationMap) {
ol.locationMap.setTarget(null);
if (typeof ol.locationMap.dispose === 'function') {
ol.locationMap.dispose();
}
ol.locationMap = null;
}
// 메인 지도 레이어 상태 복원
if (mainLayerState) {
vars.roadLayer.setVisible(mainLayerState.road);
vars.satelliteLayer.setVisible(mainLayerState.satellite);
vars.hybridLayer.setVisible(mainLayerState.hybrid);
mainLayerState = null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,663 @@
import { vars } from './variable.js';
//설정창 close 25-08-18 권한 설정 모달 사용 중에 모달이 꺼지는일이 많아 주석처리로 꺼지는 기능을 막음
// document.querySelector('.permission-modal').addEventListener('click', (e) => {
// if (e.target.className.includes('permission-modal')) document.querySelector('.permission-modal').style.display = 'none';
// })
document.querySelector('.permission-modal img[alt="닫기"]').addEventListener('click', () => {
// 이름 검색 결과창 초기화
searchResultInit();
document.querySelector('.permission-modal').style.display = 'none';
})
//설정창 저장
document.getElementById('permission-submit').addEventListener('click', () => {
if(vars.permissionList.changed && Object.keys(vars.permissionList.changed).length > 0){
if (confirm(`${Object.keys(vars.permissionList.changed).length}개의 변경사항을 저장하시겠습니까?`)) {
upsertAuthList();
}
} else {
searchResultInit();
document.querySelector('.permission-modal').style.display = 'none';
}
});
//설정창 취소
document.getElementById('permission-cancel').addEventListener('click', () => {
if (vars.permissionList.changed && Object.keys(vars.permissionList.changed).length > 0) {
if (!confirm(`${Object.keys(vars.permissionList.changed).length}개의 변경사항을 저장하지 않고 취소하시겠습니까?`)) {
return;
}
}
// 이름 검색 결과창 초기화
searchResultInit();
document.querySelector('.permission-modal').style.display = 'none';
});
//tab 변경
document.querySelectorAll('.permission-modal input[type="radio"]').forEach(ele => {
ele.addEventListener('click', (e) => {
document.querySelector('.permission-modal .modal-wrap .left .search-result-container').style.display = 'none';
document.querySelector('.permission-modal .modal-wrap .left ul').style.display = 'block';
let id = e.target.id;
let tab = document.querySelector('.permission-modal .select-list-' + id);
drawUserPermissionList(vars.permissionList, id);
[...tab.parentElement.children].forEach(element => {
if (element.classList.contains('select-list')) element.style.display = 'none';
});
tab.style.display = 'block';
})
});
export async function getList() {
let param = {
// project_id : JSON.parse(localStorage.getItem('data')).id
project_id: vars.project_id
}
let res = await axios.get('/auth/getMemberList', { params: param });
return res.data;
}
// 유저권한설정창
export async function drawUserPermissionList(list, rightId = 'sub-master') {
document.querySelector('.permission-modal .left ul').innerHTML = '';
document.querySelectorAll(`.permission-modal .select-list ul`).forEach(ul => ul.innerHTML = '');
// DB에 저장횐 company-list가 6개 외에 더 있어서 회사 리스트 고정
const companyList = [ '한맥기술', '삼안', '장헌산업', `(주)장헌` , '피티씨' , '한라산업개발', '바론컨설턴트' , '기타'];
// 기존 권한을 가진 인원(key-value)들을 배열화
const permission = Object.values(list.changePermission);
try {
// changePermission에는 user_id - lev만 있기때문에 배열을 DB로 보내 유저 정보를 가져온다
const usersInfoRes = await axios.post('/auth/getPermissionUserInfo', {permission : permission});
const userInfoArr = usersInfoRes.data.result;
const rightUl = document.querySelector(`.permission-modal .select-list.select-list-${rightId} ul`);
userInfoArr.forEach(user => {
if (findPermission(user.user_id, permission) !== rightId) return;
if (rightUl.querySelector(`#_${user.user_id}`)) return;
// 유저 리스트 DOM 생성
const userLi = drawUserList(user, rightId, permission, true);
rightUl.appendChild(userLi);
// 클릭 이벤트
bindUserLiClick(userLi, user, rightId, permission, true);
});
// 유저 수 체크
checkPermissionUserNum(rightId);
} catch (err) {
console.error('getPermissionUserInfo', err);
}
// 회사 리스트 생성
companyList.forEach(company => {
const companyTitle = drawCompanyTitle(company);
const companyToggleImg = companyTitle.querySelector('.company-toggle img');
// 회사 리스트 클릭시 부서명 생성
companyTitle.addEventListener('click', async () => {
let deptTitle = companyTitle.nextElementSibling;
// 부서명 최초 1회 DOM 생성 이후에 토글
if (!deptTitle || !deptTitle.classList.contains('dept-container')) {
companyToggleImg.src = '/main/img/permission/down.svg';
deptTitle = document.createElement('ul');
deptTitle.className = 'dept-container _scrollbar';
deptTitle.dataset.company = company;
companyTitle.insertAdjacentElement('afterend', deptTitle);
try {
// 회사명을 통해 부서리스트 조회
const deptRes = await axios.get('/auth/getDeptList', { params: { company } });
if (deptRes.data.message === '200') {
const deptList = deptRes.data.result;
// 한글 정렬
deptList.sort((a, b) => {
if (!a.dept || !b.dept) return 0;
return a.dept.localeCompare(b.dept, 'ko');
});
// 부서 리스트 생성
for (const depts of deptList) {
if (!depts.dept) continue;
const deptLi = drawDeptTitle(company, depts.dept);
const deptToggleImg = deptLi.querySelector('.dept-toggle img');
const deptCheckBox = deptLi.querySelector('input[type="checkbox"]');
const deptToggelWrap = deptLi.querySelector('.toggle-wrap');
// 부서 개별 체크 박스 change 이벤트
deptCheckBox.addEventListener('change', async (e) => {
const isChecked = deptCheckBox.checked;
const rightUl = document.querySelector(`.permission-modal .right .select-list.select-list-${rightId} ul`);
try {
// 회사명 - 부서를 통해 부서의 유저를 전부 조회
const userRes = await axios.get('/auth/getUserList', { params: { company, dept: depts.dept }});
const userList = userRes.data.result;
// 부서 전체 선택시에 이미 권한이 있어 권한을 부여할 수 없는 인원 수를 체크하는 배열
const diffGroupArr = [];
// 유저 리스트 생성
for (let user of userList) {
const existingRight = rightUl.querySelector(`#_${user.user_id}`);
const existingLeft = document.querySelector(`.permission-modal .left ul li#_${user.user_id} .user-wrap`);
const lev = findPermission(user.user_id, Object.values(vars.permissionList.changePermission));
// 전체 선택시
if (isChecked) {
// 이미 오른쪽(권한이 있던)에 있는 인원과 권한이 다른 인원은 continue로 건너뛰기
if (existingRight) continue;
if (lev && lev !== rightId){
diffGroupArr.push(user);
continue;
}
// 부관리자 10명 제한
if (rightId === 'sub-master' && rightUl.children.length >= 10) {
updateCheckboxState(user.company, user.dept);
return alert('부관리자는 최대 10명까지 선택 가능합니다.');
}
// 전체선택으로 권한 변경된 유저 권한 변경
changePermission(user.user_id, rightId, undefined);
// 오른쪽 리스트에 생성
const copy = drawUserList(user, rightId, permission, true);
rightUl.appendChild(copy);
// 클릭이벤트 추카
bindUserLiClick(copy, user, rightId, permission, true);
// 왼쪽 유저리스트에 권한 뱃지 생성
if (existingLeft) {
const roleDiv = createRoleDiv(rightId);
existingLeft.insertAdjacentHTML('beforeend', roleDiv);
existingLeft.classList.add('selected_' + rightId);
existingLeft.style.display = 'flex';
}
} else {
// 전체 선택해제
if (existingRight) {
// 오른쪽 DOM 삭제와 권한 해제
existingRight.remove();
changePermission(user.user_id, undefined, rightId);
}
// 왼쪽 유저 리스트에 권한 뱃지 삭제
existingLeft?.querySelector(`.user-permission-${rightId}`)?.remove();
existingLeft?.classList.remove(`selected_${rightId}`);
}
}
// 체크 박스 상태 변화 감지
updateCheckboxState(company, depts.dept);
if (diffGroupArr.length > 0){
alert(`이미 권한을 가진 ${diffGroupArr.length}명은 ${{'sub-master' : '부관리자', 'security-worker' : '보안참여자', 'worker' : '일반참여자', 'viewer' : '참관자'}[rightId]} 권한을 부여할 수 없습니다.`);
deptCheckBox.checked = true;
}
} catch (err) {
console.error('getUserList Error (checkbox): ', err);
}
});
// 체크 박스 상태 변화 감지
updateCheckboxState(company, depts.dept);
// 부서명 클릭 이벤트
deptToggelWrap.addEventListener('click', async () => {
let userTitle = deptLi.nextElementSibling;
// 유저 최초 1회 생성 이후에 토글
if (!userTitle || !userTitle.classList.contains('user-container')) {
deptToggleImg.src = '/main/img/permission/down.svg';
userTitle = document.createElement('ul');
userTitle.className = 'user-container _scrollbar';
deptLi.insertAdjacentElement('afterend', userTitle);
// 회사명,부서명으로 유저리스트 조회
const userRes = await axios.get('/auth/getUserList', { params: { company, dept: depts.dept }});
if (userRes.data.message === '200') {
const userList = userRes.data.result;
// 유저리스트 생성
userList.forEach(user => {
// 유저 DOM 생성
const userLi = drawUserList(user, rightId, permission);
userTitle.appendChild(userLi);
// 유저 클릭 이벤트
bindUserLiClick(userLi, user, rightId, permission);
});
}
}
// 유저 토글 처리
userTitle.style.display = userTitle.style.display === 'block' ? 'none' : 'block';
deptToggleImg.src = userTitle.style.display === 'block' ? '/main/img/permission/down.svg' : '/main/img/permission/up.svg';
});
}
}
} catch (error) {
console.error('getDeptList Error', error);
}
}
// 부서 토글처리
deptTitle.style.display = deptTitle.style.display === 'block' ? 'none' : 'block';
companyToggleImg.src = deptTitle.style.display === 'block' ? '/main/img/permission/down.svg' : '/main/img/permission/up.svg';
});
});
}
// 유저 검색 기능 엔터 이벤트
document.getElementById('permission-search').addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
// input 값 추출 이후 DOM 생성
const keyword = e.target.value.trim().toLowerCase();
searchResultUser(keyword);
}
});
// 유저 검색 기능 클릭 이벤트
document.querySelector('.permission-modal .modal-wrap .left .search-box .wrap .search-image').addEventListener('click', () => {
const keyword = document.querySelector('.permission-modal .modal-wrap .left .search-input').value.trim().toLowerCase();
searchResultUser(keyword);
});
// 유저 검색 기능(엔터, 검색바 클릭)
function searchResultUser(keyword) {
// 프로젝트 관리자를 제외하고 검색하기 위해 필터링
const users = vars.permissionList.all.filter(user => user.user_id !== vars.project.user_id);
const searchResult = document.querySelector('.permission-modal .modal-wrap .left .search-result-container');
const permissionLeftUl = document.querySelector('.permission-modal .modal-wrap .left ul');
const permission = Object.values(vars.permissionList.changePermission);
const rightId = document.querySelector('.permission-modal .modal-wrap .tab-input:checked').id;
const positionMap = {'회장': 1, '부회장':2, '사장':3, '상임고문':4, '기술위원':5, '부사장':6, '고문':7, '전무이사':8, '수석연구원':9, '상무이사':10, '이사':11, '책임연구원':12, '부장':13, '차장':14, '선임연구원':15, '과장':16, '연구원':17, '대리':18, '사원':19};
if (keyword !== '') {
// 이름으로 필터링 이후 직급으로 정렬
const result = users.filter(user => user.user_nm?.toLowerCase().includes(keyword)).sort((a,b) => {
const aPosition = positionMap[a.position] ?? 99;
const bPosition = positionMap[b.position] ?? 99;
if(aPosition !== bPosition) return aPosition - bPosition;
return a.user_id.localeCompare(b.user_id);
})
if (result.length === 0) {
searchResult.style.display = 'none';
permissionLeftUl.style.display = 'block';
return alert('일치하는 사용자가 없습니다.');
}
searchResult.innerHTML = '';
// 조회된 유저 있을때
result.forEach(user => {
//유저 DOM 생성
const userLi = drawUserList(user, rightId, permission);
searchResult.appendChild(userLi);
//클릭이벤트
bindUserLiClick(userLi, user, rightId, permission, false);
});
// 검색결과창 띄우기
searchResult.style.display = 'block';
permissionLeftUl.style.display = 'none';
} else {
// 빈 값 입력시 검색결과창 내리기
searchResult.style.display = 'none';
permissionLeftUl.style.display = 'block';
}
}
// 유저 검색결과 초기화(닫기버튼, X버튼 클릭시 실행)
function searchResultInit(){
const searchResult = document.querySelector('.search-result-container');
searchResult.innerHTML = '';
searchResult.style.display = 'none';
document.querySelector('.permission-modal .modal-wrap .left ul').style.display = 'block';
document.querySelector('.permission-modal .left .search-input').value = '';
}
// 회사명 DOM 생성
function drawCompanyTitle(company){
if(company == '(주)장헌') company = '장헌';
if(company == '피티씨') company = 'PTC';
const companyTitle = document.createElement('li');
companyTitle.className = 'company-title';
companyTitle.innerHTML += `
<img class="" src="/main/img/permission/s-icon__${company}-large-logo.svg" alt="" />
<div class="toggle-wrap">
<span class="company-name">${(company == '기타')? '' :"(주)"}${company}</span>
<div class="company-toggle">
<img src="/main/img/permission/up.svg" alt="방향표">
</div>
</div>
`
document.querySelector('.permission-modal .left ul').append(companyTitle);
return companyTitle;
}
// 부서명 DOM 생성
function drawDeptTitle(company, dept){
const deptLi = document.createElement('li');
deptLi.className = 'dept-title';
deptLi.dataset.company = company;
deptLi.dataset.dept = dept;
deptLi.innerHTML = `
<label class="custom-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<div class="toggle-wrap">
<span class="dept-name">${dept}</span>
<div class="dept-toggle">
<img src="/main/img/permission/up.svg" alt="방향표">
</div>
</div>
`;
document.querySelector(`.permission-modal .left ul .dept-container[data-company="${company}"]`).appendChild(deptLi)
return deptLi;
}
// 유저리스트 DOM 생성
function drawUserList(user, rightId, permissionList, isRightSide = false) {
const li = document.createElement('li');
li.className = 'user-li';
li.id = `_${user.user_id}`;
li.dataset.company = user.company;
li.dataset.dept = user.dept;
const userWrap = document.createElement('div');
userWrap.className = `user-wrap${isRightSide ? ` selected_${rightId} _scrollbar` : ''}`;
userWrap.innerHTML += `
<div class="user-img">
<img draggable="false" src="http://erp.${(user.company === '장헌산업' ? 'jangheon' : 'hanmaceng')}.co.kr/erpphoto/${user.user_id}.jpg" alt="이미지" onerror="this.onerror=null; this.src='/main/img/archive/empty-profile.svg'">
</div>
<div class="user-info">
<div class="user-path">
<img class="user-logo" src="/main/img/permission/s-icon__${(user.company == '피티씨')?'PTC':user.company}-small-logo.svg" alt="" />
<span class="user-team">${user.dept}</span>
</div>
<div class="user-top">
<span class="user-name">${user.user_nm}</span>
<span class="user-position">${user.position ?? ''}</span>
</div>
</div>
`;
// 유저 권한 확인
const lev = findPermission(user.user_id, Object.values(vars.permissionList.changePermission));
const role = isRightSide ? rightId : lev;
// 유저권한 생성
if (role) {
const roleDiv = createRoleDiv(role);
userWrap.insertAdjacentHTML('beforeend', roleDiv);
if (!isRightSide && lev !== rightId) userWrap.classList.add('disabled');
if (!isRightSide) userWrap.classList.add(`selected_${lev}`);
}
li.appendChild(userWrap);
return li;
}
// 유저 권한 뱃지 생성
function createRoleDiv(role){
return `<div class="user-permission-${role}">
<h6>${{'sub-master' : '부관리', 'security-worker' : '보안', 'worker' : '일반', 'viewer' : '참관'}[role]}</h6>
</div>`;
}
// 유저 클릭이벤트 바인딩
function bindUserLiClick(userLi, user, rightId, permissionList, isRightSide = false) {
const userWrap = userLi.querySelector('.user-wrap');
const rightUl = document.querySelector(`.permission-modal .select-list.select-list-${rightId} ul`);
userLi.addEventListener('click', () => {
const rightUser = rightUl.querySelector(`li#_${user.user_id}`);
const leftUserWrap = document.querySelector(`.permission-modal .left ul li#_${user.user_id} .user-wrap`);
const leftUserPermission = leftUserWrap?.querySelector(`.user-permission-${rightId}`);
if (userWrap.classList.contains('disabled')) return alert('다른 그룹에 등록된 인원입니다.');
const alreadySelected = userWrap.classList.contains(`selected_${rightId}`);
// 이미 권한을 가진 인원 클릭했을때 해제 이벤트
if (alreadySelected) {
changePermission(user.user_id, undefined, rightId);
// 오른쪽 유저 삭제(오른쪽 리스트 유저 삭제)
rightUser.remove();
// 왼쪽 유저 삭제(뱃지삭제 -> userWrap 클래스 삭제) 리스트가 펼쳐지지않아 아직 DOM 생성전에는 삭제가 불가능하기 때문에 옵셔널체이닝 추가
leftUserPermission?.remove();
leftUserWrap?.classList.remove(`selected_${rightId}`);
} else {
// 권한 부여 이벤트
if (rightId === 'sub-master' && rightUl.children.length >= 10) return alert('부관리자는 최대 10명까지 선택 가능합니다.');
changePermission(user.user_id, rightId, undefined);
userWrap.classList.add(`selected_${rightId}`);
userWrap.insertAdjacentHTML('beforeend', createRoleDiv(rightId));
// 왼쪽 유저리스트 클릭시 오른쪽에 복사하여 생성+이벤트 추가
const copy = drawUserList(user, rightId, permissionList, true);
rightUl.appendChild(copy);
bindUserLiClick(copy, user, rightId, permissionList, true);
}
updateCheckboxState(user.company, user.dept);
// 유저 수 조회 이후 갱신
checkPermissionUserNum(rightId);
});
}
document.querySelector('.permission-modal #help-btn').addEventListener('click', () => toggleHelp(true));
document.querySelector('.permission-modal .modal-permission-help .modal-permission-help__head img').addEventListener('click', ()=>toggleHelp(false));
function toggleHelp(show){
const helpModal = document.querySelector('.permission-modal .modal-permission-help');
const modalBack = document.querySelector('.permission-modal .modal-background');
helpModal.style.display = show ? 'block' : 'none';
modalBack.style.display = show ? 'block' : 'none';
}
// 권한을 통해 유저 수 체크
function checkPermissionUserNum(rightId){
// 오른쪽 리스트에 몇명이 있는지 체크
const userNum = document.querySelector(`.permission-modal .modal-wrap .right .select-list.select-list-${rightId} ul`).children.length;
// 유저 수를 나타낼 dom
const headerUserNum = document.querySelector('.permission-modal .modal-header .header-user-num-wrap .user-num');
// 부관리자 10명이 넘을때 강조효과
let text = `현재 ${userNum}`;
let color = '#111';
if(rightId === 'sub-master'){
text += ' (최대 10명)';
if(userNum >= 10) color = '#F21D0D';
}
headerUserNum.innerText = text;
headerUserNum.style.color = color;
}
// 체크박스 DOM, 회사명, 부서를 통해 체크박스 상태 반영 함수
async function updateCheckboxState(company, dept){
const deptUsers = document.querySelectorAll(`.permission-modal .modal-wrap .right ul li[data-company="${company}"][data-dept="${dept}"]`);
const checkbox = document.querySelector(`.permission-modal .modal-wrap .left li[data-company="${company}"][data-dept="${dept}"] input[type=checkbox]`);
const checkmark = document.querySelector(`.permission-modal .modal-wrap .left li[data-company="${company}"][data-dept="${dept}"] label .checkmark`);
const rightId = document.querySelector('.permission-modal .modal-wrap .tab-input:checked').id;
const key = `${company}_${dept}`;
//permissionList.deptUserCount와 현재 권한 부여된 유저의 길이를 비교해 체크박스 상태변화
if(vars.permissionList.deptUserCount[key]){
if(!checkbox) return;
if(deptUsers.length === 0){
checkbox.checked = false;
checkmark.classList.remove('indeterminate');
} else if(deptUsers.length === vars.permissionList.deptUserCount[key]) {
checkbox.checked = true;
checkmark.classList.remove('indeterminate');
} else {
checkbox.checked = false;
checkmark.classList.add('indeterminate');
}
}
checkPermissionUserNum(rightId);
}
function findPermission(id, permissionList) {
for (let i = 0; i < permissionList.length; i++) {
if (permissionList[i].user_id.toUpperCase() == id.toUpperCase()) return permissionList[i].lev;
}
return undefined;
}
function getUserInfo(id) {
for (let i = 0; i < vars.permissionList.all.length; i++) {
if (vars.permissionList.all[i].user_id == id) return vars.permissionList.all[i];
}
}
function changePermission(id, group, rightId) {
if (!vars.permissionList.changed) vars.permissionList.changed = [];
if (!vars.permissionList.changePermission) vars.permissionList.changePermission = {};
id = id.toUpperCase();
/*
vars.permissionList.changed => 변화객체
vars.permissionList.permission => 원본 permission
vars.permissionList.changePermission => 변화후 permission
*/
if (group) {
//그룹에 넣을때 key, value 형태로 저장
vars.permissionList.changePermission[id] = {user_id : id, lev : group}
} else {
//그룹에서 제외
delete vars.permissionList.changePermission[id];
}
// 기존 permission에서 이전 Lev 가져오기
const original = vars.permissionList.permission?.find(p => p.user_id.toUpperCase() === id);
const originalLev = original?.lev;
// 기존 permission과 새로운 permission을 비교해 변화 감지
if(group === originalLev || (!group && !originalLev)){
delete vars.permissionList.changed[id];
return;
}
// 변화감지하여 변화객체 저장 (중복성 피하기 위해)
vars.permissionList.changed[id] = {user_id : id, lev : group, before : originalLev, user_nm : getUserInfo(id)?.user_nm}
}
async function upsertAuthList() {
//lev undefined는 deleteAuthList
let upsertArr = [];
let deleteArr = [];
//key-value 형태로 만들었던 changed를 배열로 전환
const changedArr = Object.values(vars.permissionList.changed);
if (changedArr.length > 0) {
for (let i = 0; i < changedArr.length; i++) {
if (changedArr[i].lev == undefined) {
deleteArr.push(changedArr[i]);
} else {
upsertArr.push(changedArr[i]);
}
}
let upsertMessage = (upsertArr.length > 0) ? false : true;
let deleteMessage = (deleteArr.length > 0) ? false : true;
console.log(upsertArr);
console.log(deleteArr);
if (upsertArr.length > 0) {
let upsertRes = await axios.post('/auth/upsertPermission', { project_id: vars.project_id, targetArr: upsertArr, userInfoString: vars.userInfoString });
if (upsertRes.data.message == 'upsert success'){
const logs = upsertRes.data.logs;
if(logs.length > 0){
try{ // upsert 이후 로그 추가
const logRes = await axios.post(`${vars.path_name}/addPermissionLog`, {logs});
if(logRes.data.message === 'addPermissionLog success') upsertMessage = true;
} catch(err) {
console.error('upsert addPermissionLog Error', err);
}
}
}
}
if (deleteArr.length > 0) {
let deleteRes = await axios.post('/auth/deletePermission', { project_id: vars.project_id, targetArr: deleteArr, userInfoString: vars.userInfoString });
if (deleteRes.data.message == 'delete success'){
const logs = deleteRes.data.logs;
if(logs.length > 0){
try{ // delete 이후 로그 추가
const logRes = await axios.post(`${vars.path_name}/addPermissionLog`, {logs});
if(logRes.data.message === 'addPermissionLog success') deleteMessage = true;
} catch(err) {
console.error('delete addPermssionLog Error', err);
}
}
}
}
if (upsertMessage && deleteMessage) {
alert(`${Object.keys(vars.permissionList.changed).length}개의 변경사항이 저장되었습니다.`);
}
}
searchResultInit();
document.querySelector('.permission-modal').style.display = 'none';
}
export class Permission {
constructor(){
//permission 정의
this.permissionDef = {
1535 : '개발자',
255 : '관리자',
191 : '부관리자',
15 : '보안참여자',
7 : '일반참여자',
1 : '참관자'
}
//기능 정의 -'기능명': 허용 권한 code -> ex) 'headerPlusBtn' : 191
this.funcDef = {
'dev-menu' : 1535,
'permission-btn' : 191,
'overview-left-edit' : 191,
'overview-middle-edit' : 191,
'overview-task-history-add' : 191,
'overview-task-history-save' : 191,
'overview-task-history-delete' : 191,
'overview-schedule-add' : 7,
'overview-schedule-edit' : 7,
'overview-issue-edit' : 191,
'header-menu-add' : 191,
'memo-ai' : 7,
'memo-edit' : 7,
'memo-text' : 7,
'set-user-permission' : 191,
'context-menu-viewer' : 7,
'context-menu-sub-master' : 191,
'add-badge-master' : 255,
'add-badge-sub-master' : 191,
'add-badge-security-worker': 15,
'add-badge-worker': 7,
'add-badge-viewer': 1,
'convert-btn-viewer': 7,
'project-inactive-sign' : 1535,
'createFolder-gallery' : 1535,
'download-folder' : 255,
'change-project-btn' : 255,
}
this.user = (vars.userInfoString)?JSON.parse(vars.userInfoString) : undefined;
this.permission = this.user.permission;
this.permissionString = this._toPermissionString();
}
_toPermissionString(){
return this.permissionDef[`${this.permission}`];
}
checkPermission(func){
return this.permission >= this.funcDef[func];
}
}

View File

@@ -0,0 +1,213 @@
let path_name = decodeURIComponent(window.location.pathname);
let project_id = path_name.split('/')[1];
//////// pm-bcmf 연결용 테스트 코드 - project_id가 pm-bcmf일 때 project_id는 dsdj2, path_name은 /dsdj2/archive로 변경
if (project_id == 'pm-bcmf') {
project_id = 'dsdj2';
path_name = '/dsdj2/archive';
}
//세종안성(sjas)(gtb.) and 청용천교(cheongyong)(cheongyong.) 연결용 테스트 코드
if(project_id == '' || project_id == undefined) {
if(window.location.origin.includes('gtb.')){
project_id = 'sjas';
path_name = '/sjas/archive';
}/* else if(window.location.origin.includes('cheongyong.')){
project_id = 'cheongyong';
path_name = '/cheongyong/archive';
} */
}
export const vars = {
// 아카이브 라우터 연결을 위한 변수
path_name: path_name,
// 프로젝트 id
project_id: project_id,
// 오브젝트 스토리지 타입 (ONPREMISE / CLOUD)
storageType: undefined,
// 클라우드 타입 (ONPREMISE / CLOUD)
cloudType: undefined,
// 버킷 (프로젝트 id와 동일)
bucket: project_id,
// 현재 로그인 한 유저 정보
userInfoString: undefined,
// 현재 로그인 한 유저의 permission 관리 객체
permission: undefined,
// 현재 로그인 한 유저의 마우스커서 정보를 담는 객체
cursors: {},
// 전체 프로젝트 정보
allProject: {},
// 현재 프로젝트 정보
project: undefined,
// 최상위 폴더 사용 용량 / 남은 용량 / 저장 가능한 최대 용량 (50 GB) -> DB에서 가져오기 / 폴더별 사이즈 (아카이브, 공문, 과업개요, GSIM 모델)
bucketSize: undefined, remainingSize: undefined, bucketMaxSize: 50*1024*1024*1024, foldersSize: undefined,
// 최상위 폴더 아래에 있는 모든 폴더/파일 데이터
allTreeObject: undefined,
// 현재 선택된 폴더 아래에 있는 모든 폴더/파일 데이터
currentTreeObject: undefined,
// 휴지통에 있는 모든 파일 데이터
recycleBinObject: undefined,
// 마지막으로 선택한 header-btn/tree-item/list-item -> 스타일 변경에 사용 - changeListItemStyle(listItem)
lastHeaderBtn: undefined, lastMainTreeItem: undefined, lastModalTreeItem: undefined, lastListItem: undefined, lastListGroupTarget: undefined,
// lastMainTreeDepth2Item: undefined, lastModalTreeDepth2Item: undefined,
// lastMainTreeDepth3Item: undefined, lastModalTreeDepth3Item: undefined,
// 마지막으로 우클릭한 타겟 -> 컨텍스트 메뉴에 사용
lastContextTarget: undefined,
// 마지막으로 선택한 header/tree/list item
lastSelectTarget: undefined,
// 드래그가 시작된 list-item
dragStartListItem: undefined,
// 클릭, 더블클릭 구분을 위한 시간
clickDuration: 500,
// 업로드, 다운로드 완료 후 프로그레스 종료 전 완료 후 잠깐 대기하는 시간
progressDuration: 500,
// 현재 변환중인 파일 정보를 저장하는 배열
convertingDataArr: [],
// 현재 요약중인 파일 정보를 저장하는 배열
summarizeAiDataArr: [],
// file area list에서 다중 선택된 item 배열
multiSelectListItemArr: [],
// 휴지통에서 사용할 lastListItem, lastListGroupTarget, lastContextTarget, lastSelectTarget, multiSelectListItemArr
// 휴지통에서 다중 선택된 item 배열
lastListItem_bin: undefined,
lastListGroupTarget_bin: undefined,
lastContextTarget_bin: undefined,
lastSelectTarget_bin: undefined,
multiSelectListItemArr_bin: [],
// 로그 데이터 -> 활동 정보 activity-info-item(로그 데이터)에 사용 (활동 정보 아이템 클릭 시 상세 정보 표시에 사용)
logData: undefined,
// 로그 모달창이 열렸는지 안열렸는지 판단하는 변수 -> 최초에만 false이고 한 번이라도 열리면 true로 유지
isLogModalOpened: false,
// 새로 추가되는 로그 아이템을 저장하는 배열
newLogItemArr: [],
// 일반아이템리스트/휴지통아이템리스트 구분에 사용
listItemWrap: document.querySelector('.archive-main-center .list-container .list-body .list-item-wrap'),
// 미리보기 뷰어
viewer: undefined,
// 메모 임시 저장
tempMemo: [],
// 파일 리스트 sort용 현재 컬럼, 현재 정렬순
curSortCol : 'name',
curSortOrder : 'asc',
// 휴지통 모달창 sort용 현재 컬럼, 현재 정렬순
curSortCol_bin : 'remove-date',
curSortOrder_bin : 'desc',
// 리스트/그리드 파일 목록에서 파일 선택했을 때 뷰어에 연결중 표시에 사용할 시간
viewerConnectingTime: 0, viewerConnectingTimeValue: 700,
// 트리 영역에서 마지막으로 선택한 폴더 타입
lastFolderType: undefined,
// 컨트롤 박스에서 마지막으로 선택한 파일 영역 모드
// lastFileAreaMode: undefined,
lastFileAreaMode: 'list',
// 지도 모드 (normal <-> edit)
mapMode: 'normal',
// 배경지도 url (일반, 위성, 하이브리드)
road: 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&hl=ko&&apistyle=s.e:l.i|p.v:off',
satellite: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&hl=ko&apistyle=s.e:l.i|p.v:off',
hybrid: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&hl=ko&apistyle=s.e:l.i|p.v:off',
// 클러스터 거리 (픽셀 기준)
clusterDistance: 1,
// 마지막으로 선택한 클러스터
lastSelectCluster: undefined,
// 모든 오버레이를 표시하는 줌 레벨
allOverlayVisibleZoom: 19,
// 클러스터 레이어, 포인트 레이어
olClusterLayer: undefined,
olPointLayer: undefined,
listOrder: [],
countdownTimer: undefined,
}
// 오픈레이어스 타일 레이어
vars.baseLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.road,
crossOrigin: 'anonymous',
transition: 0
})
});
vars.roadLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.road,
crossOrigin: 'anonymous',
transition: 0
}),
visible: true
});
vars.satelliteLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.satellite,
crossOrigin: 'anonymous',
transition: 0
}),
visible: false
});
vars.hybridLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: vars.hybrid,
crossOrigin: 'anonymous',
transition: 0
}),
visible: false
});
// 구성 popup ver 위한 전역 함수
// vars.navigateToPath = function(path) {
// const resourcePath = `/${path}`;
// // 헤더 버튼 클릭 (탭 선택)
// const tabPath = resourcePath.split('/')[1]; // '모델뷰어테스트/모델/3dm' -> '모델뷰어테스트'
// const headerBtn = document.querySelector(`[data-resource-path="/${tabPath}"]`);
// if (headerBtn) headerBtn.click();
// // 트리 아이템 클릭 (폴더 선택)
// setTimeout(() => {
// const treeItem = document.querySelector(`.tree-item-wrap[data-resource-path="${resourcePath}"] .tree-item`);
// if (treeItem) treeItem.click();
// }, 500);
// }
window.vars = vars;