Files
PM_test/views/main/jsm/archive/dataManager.js
2026-06-15 13:51:06 +09:00

2216 lines
89 KiB
JavaScript

import { vars } from './variable.js';
import {
formatBytes,
getDepth,
splitBaseAndExt,
splitTopPathAndTargetName,
extractPathByLength,
hasSpecialChar,
getNextFileName,
getDataFromTreeObject,
buildResourcePathFromSegments
} from './common.js';
import { toggleModal } from './modalManager.js'
import { showNotification, toggleContextmenu, toggleContextFocusBox } from './eventManager.js'
import { renderMemo, resetViewer, preparePageRendering } from './pageRenderer.js'
let listContainer = document.querySelector('.archive-main-center .list-container');
// 딜레이 함수
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ** 권한 관련
function changeUserPermission(num) {
// 유저 퍼미션 숫자를 데이터 퍼미션 숫자와 비교하기 위해 변환
let result;
if (num == 1) result = 1;
if (num == 3) result = 2;
if (num == 7) result = 4;
if (num == 15) result = 8;
if (num >= 191) result = 99999;
return result;
}
async function checkTargetExists(dataType, resourcePathArr) {
let result = {};
if (!Array.isArray(resourcePathArr)) resourcePathArr = [resourcePathArr];
if (dataType == 'folder' && resourcePathArr.length == 1) {
// dataType이 folder이고, resourcePathArr의 length가 1인 경우는 폴더 생성/이름변경 경우만 해당
// depth1 폴더 생성/이름변경 시 헤더 페이지버튼(과업개요, 공문)과 중복 안되도록 처리
let depth = getDepth(resourcePathArr[0]);
let pageBtnNameArr = ['/과업개요', '/공문', '/휴지통', '/위치기반모델'];
if (depth == 1 && pageBtnNameArr.includes(resourcePathArr[0])) {
result.isExists = true;
return result;
}
}
let checkTargetExistsParams = {
storageType: vars.storageType,
dataType: dataType,
resourcePathArr: JSON.stringify(resourcePathArr)
};
let checkTargetExistsRes = await axios.post(`${vars.path_name}/checkTargetExists`, { params: checkTargetExistsParams } );
if (checkTargetExistsRes.data.message == 'checkTargetExists_success') {
let rows = checkTargetExistsRes.data.rows;
result.existingPathArr = [];
if (rows.length == 0) {
result.isExists = false;
} else {
result.rows = rows;
result.isExists = true;
result.existingPathArr = buildResourcePathFromSegments(rows);
}
return result;
}
}
// async function getPageCountQuick(file) {
// try {
// // 처음 100KB만 읽어서 페이지 수 확인
// const chunk = file.slice(0, Math.min(file.size, 1024 * 100));
// const arrayBuffer = await chunk.arrayBuffer();
// const text = new TextDecoder('latin1').decode(new Uint8Array(arrayBuffer));
// // /Count 값 찾기
// const countMatch = text.match(/\/Count\s+(\d+)/);
// if (countMatch) {
// return parseInt(countMatch[1]);
// }
// // /N 값 찾기
// const nMatch = text.match(/\/N\s+(\d+)/);
// if (nMatch) {
// return parseInt(nMatch[1]);
// }
// return null;
// } catch (error) {
// console.error('페이지 수 확인 실패:', error);
// return null;
// }
// }
export async function getPdfData(file) {
const arrayBuffer = await file.arrayBuffer();
const loadingTask = window.pdfjsLib.getDocument({ data: new Uint8Array(arrayBuffer) });
const pdf = await loadingTask.promise;
return pdf;
}
export async function createPdfThumbnail(pdf, targetWidth, outputFileName) {
const page = await pdf.getPage(1);
try {
// 회전 반영 + targetWidth에 맞춰 스케일 계산
const rotation = page.rotate || 0;
const initialViewport = page.getViewport({ scale: 1, rotation });
const scale = targetWidth / initialViewport.width;
const viewport = page.getViewport({ scale, rotation });
// 캔버스 준비
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: false });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
// JPEG 저장 시 투명 배경이 검게 나오는 현상 방지용 (흰 배경)
ctx.save();
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// 캔버스에 1페이지 렌더
await page.render({ canvasContext: ctx, viewport }).promise;
// 캔버스를 JPEG Blob으로 변환 후 File로 래핑
let blob = await canvasToBlob(canvas, 0.9);
let file = new File([blob], outputFileName, { type: blob.type });
// 캔버스 메모리 즉시 축소
canvas.width = 0; canvas.height = 0;
return file;
} finally {
// pdf 문서 해제
try { await pdf.cleanup(); } catch {}
try { await pdf.destroy(); } catch {}
}
}
function fitScaleForWidth(viewport, targetWidth = 300) {
const scale = targetWidth / viewport.width;
return scale;
}
export async function createTextThumbnail(textFile, outputFileName) {
return new Promise((resolve, reject) => {
const viewerTextWrap = document.createElement('div');
viewerTextWrap.classList.add('viewer-text-wrap', 'scrollbar');
viewerTextWrap.style.position = 'absolute';
viewerTextWrap.style.left = '-9999px';
viewerTextWrap.style.top = '0';
viewerTextWrap.style.width = '800px';
viewerTextWrap.style.height = '600px';
viewerTextWrap.style.overflow = 'hidden';
viewerTextWrap.style.backgroundColor = 'white';
viewerTextWrap.style.zIndex= 9999;
const ext = (() => {
const parts = textFile.name.split('.');
return parts.length > 1 ? parts.pop().toLowerCase() : '';
})();
const reader = new FileReader();
reader.onload = async (e) => {
try {
const data = e.target.result;
const viewerText = document.createElement('div');
viewerText.classList.add('viewer-text');
if (ext === 'md') {
viewerText.style.whiteSpace = 'normal';
viewerText.classList.add('markdown-body');
const renderer = new marked.Renderer();
const originalCode = renderer.code.bind(renderer);
renderer.code = (code, lang) => {
if (lang === 'mermaid') {
return `<div class="mermaid">${code}</div>`;
}
return originalCode(code, lang);
};
marked.setOptions({ gfm: true, breaks: true, renderer });
viewerText.innerHTML = marked.parse(data);
} else {
viewerText.textContent = data;
}
viewerTextWrap.appendChild(viewerText);
document.body.appendChild(viewerTextWrap);
// highlight.js 적용
if (ext === 'md' && typeof hljs !== 'undefined') {
hljs.highlightAll();
}
// Mermaid 렌더링
if (ext === 'md' && typeof mermaid !== 'undefined') {
mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' });
const mermaidDivs = viewerTextWrap.querySelectorAll('pre code.language-mermaid');
if (mermaidDivs.length > 0) {
const renderPromises = [];
mermaidDivs.forEach((div, i) => {
const code = div.textContent;
const uniqueId = `mermaid-${Date.now()}-${i}`;
div.innerHTML = ''; // 이전 텍스트 제거
renderPromises.push(
mermaid.render(uniqueId, code)
.then(({ svg, bindFunctions }) => {
div.innerHTML = svg;
if (bindFunctions) bindFunctions(div);
})
.catch(err => {
console.error('Mermaid 렌더링 실패:', err);
div.innerHTML = `<pre>Mermaid 렌더링 오류:\n${code}</pre>`;
})
);
});
await Promise.all(renderPromises);
// 렌더링 반영 기다리기
await new Promise(res => setTimeout(res, 100));
}
}
// html2canvas 캡처
const canvas = await html2canvas(viewerTextWrap, {
logging: false,
useCORS: true,
backgroundColor: null,
onclone: (clonedDoc) => {
const clonedEl = clonedDoc.querySelector('.viewer-text-wrap');
if (clonedEl) {
clonedEl.style.position = 'static';
clonedEl.style.left = '0';
clonedEl.style.top = '0';
clonedEl.style.width = '800px';
clonedEl.style.height = '600px';
}
}
});
viewerTextWrap.remove();
if (canvas.width === 0 || canvas.height === 0) {
resolve(undefined);
return;
}
const blob = await canvasToBlob(canvas, 0.8);
let finalName = outputFileName;
if (!finalName.toLowerCase().endsWith('.jpg')) {
const base = finalName.split('.').slice(0, -1).join('.');
finalName = base + '.jpg';
}
const file = new File([blob], finalName, { type: 'image/jpeg' });
resolve(file);
} catch (err) {
if (viewerTextWrap.parentNode) viewerTextWrap.remove();
reject(err);
}
};
reader.onerror = () => resolve(undefined);
reader.readAsText(textFile);
});
}
//text썸네일 필요함수
async function textCanvasToBlob(canvas, quality = 0.9) {
return new Promise(resolve => {
// JPG 파일 형식으로 변환
canvas.toBlob(blob => {
resolve(blob);
}, 'image/jpeg', quality);
});
}
export async function createVideoThumbnail(videoFile, outputFileName) {
return new Promise((resolve, reject) => {
let video = document.createElement('video');
video.preload = 'metadata';
video.src = URL.createObjectURL(videoFile);
video.crossOrigin = 'anonymous';
video.muted = true;
video.addEventListener('loadeddata', async () => {
try {
video.currentTime = 1;
video.addEventListener('seeked', async () => {
let canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 동영상 문제로 캔버스가 생성되지 않는 경우 undefined 반환
if (canvas.width == 0 || canvas.height == 0) {
resolve(undefined);
return;
}
let ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
let blob = await canvasToBlob(canvas, 0.9);
let file = new File([blob], outputFileName, { type: blob.type });
resolve(file);
}, { once: true });
} catch (err) {
reject(err);
}
});
video.addEventListener('error', () => {
resolve(undefined);
// reject(new Error('동영상 로딩 중 오류 발생'));
});
});
}
export async function resizeImage(img, maxSizeBytes, outputFileName) {
let originalWidth = img.width;
let originalHeight = img.height;
let aspectRatio = originalHeight / originalWidth;
let scale = 0.8; // 초기 스케일
let quality = 0.9;
// 초기 캔버스 생성
let canvas = document.createElement('canvas');
canvas.width = Math.round(originalWidth * scale);
canvas.height = Math.round(canvas.width * aspectRatio);
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 반복하면서 canvas 다시 리사이즈
while (scale > 0.1) {
let blob = await canvasToBlob(canvas, quality);
let file = new File([blob], outputFileName, { type: blob.type });
if (file.size <= maxSizeBytes) {
return file;
}
scale -= 0.09;
quality -= 0.05;
let newWidth = Math.round(originalWidth * scale);
let newHeight = Math.round(newWidth * aspectRatio);
// 현재 canvas → 새로운 크기로 줄이기
let resizedCanvas = document.createElement('canvas');
resizedCanvas.width = newWidth;
resizedCanvas.height = newHeight;
resizedCanvas.getContext('2d').drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, newWidth, newHeight);
canvas = resizedCanvas;
}
// 루프에서 리사이즈 통과 안되면 최소 품질/크기로 강제 리사이즈
let finalBlob = await canvasToBlob(canvas, 0.3);
return new File([finalBlob], outputFileName, { type: finalBlob.type });
}
// function canvasToBlob(canvas, quality = 0.9) {
// return new Promise(resolve => {
// canvas.toBlob(blob => resolve(blob), 'image/jpeg', quality);
// });
// }
function canvasToBlob(canvas, quality = 0.9) {
return new Promise(resolve => {
canvas.toBlob(
(blob) => {
if (blob) {
resolve(blob);
} else {
resolve(undefined);
// reject(new Error('canvas.toBlob 실패: blob이 null입니다.'));
}
},
'image/jpeg',
quality
);
});
}
//////// createFolder - 새 폴더 생성 모달창 표시
if (document.querySelector('.menu-add .add-btn')) {
document.querySelector('.menu-add .add-btn').addEventListener('click', () => {
openCreateFolderModal('/');
});
}
export function openCreateFolderModal(resourcePath) {
let text = `
<span>폴더명을 입력한 후 확인을 눌러주세요.</span>
`;
let toggleParams = {
title: '새 폴더',
text: text,
type: 'createFolder',
resourcePath: resourcePath
};
toggleModal(true, toggleParams);
}
//////// createFolder - 클라이언트 측 createFolder
export async function createFolder(inputWrap, resourcePath, folderType) {
let folderName = (inputWrap.getElementsByTagName('input')[0].value).trim();
let folderPath = (resourcePath == '/') ? `/${folderName}` : `/${resourcePath}/${folderName}`.replaceAll('//', '/');
// 기존 경고문구 있으면 삭제
if (document.querySelector('.archive-modal .input-wrap .warn')) {
inputWrap.removeChild(document.querySelector('.archive-modal .input-wrap .warn'));
}
// 경고문구 dom 생성
let warn = document.createElement('div');
warn.classList.add('warn');
warn.style.top = `${inputWrap.offsetHeight}px`;
inputWrap.appendChild(warn);
let checkTargetExistsResult = await checkTargetExists('folder', folderPath);
// 상황에 따라 경고문구 텍스트 추가 또는 createFolder 진행
if (!folderName || folderName == '' || folderName == null) {
// 빈문자 체크
warn.innerText = '폴더명을 입력해주세요.';
} else if (hasSpecialChar(folderName)) {
// 특수문자 체크
warn.innerHTML = `다음 특수문자는 사용할 수 없습니다.<br>\\ / : * ? ' " \` < > | #`;
} else if (checkTargetExistsResult.isExists) {
// 동일이름 여부 체크
let text = `동일한 폴더명이 존재합니다.`;
// ** 권한 관련
if (checkTargetExistsResult.rows) {
let userInfo = JSON.parse(vars.userInfoString);
let changedUserPermission = changeUserPermission(userInfo.permission);
let targetDataPermission = checkTargetExistsResult.rows[0].data_permission;
targetDataPermission = (targetDataPermission == 0) ? 99999 : targetDataPermission;
if (targetDataPermission > changedUserPermission) text = '접근이 제한되어 보이지 않는 폴더 중에\n' + text;
}
warn.innerText = text;
} else {
// 경고문구 dom 삭제
inputWrap.removeChild(warn);
// 모달창 닫기
toggleModal(false);
// createFolder 진행
let parentPermission = 1;
if (getDepth(folderPath) > 1) {
let parentPath = splitTopPathAndTargetName(folderPath).topPath;
let parentData = getDataFromTreeObject(parentPath, 'folder').data;
parentPermission = parentData.permission;
}
let createFolderParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dateArr: [Date.now()],
resourcePathArr: [folderPath],
dataPermission: parentPermission,
folderType: folderType
};
let createFolderRes = await axios.post(`${vars.path_name}/createFolder`, { params: createFolderParams });
if (createFolderRes.data.message == 'createFolder_success') {
console.log(createFolderRes.data.message);
// 폴더 생성 성공 시 로컬 브라우저 화면 즉시 갱신 (소켓 지연/연결끊김 대비)
let userCurPath = (vars.users && vars.socket && vars.users[vars.socket.id]?.curPath) || JSON.parse(vars.userInfoString).curPath || '';
let extractedPath = extractPathByLength(userCurPath, 1);
// 1. 헤더 버튼 갱신
await preparePageRendering({
scope: 'headerBtn',
from: 'createFolder - local',
resourcePath: userCurPath,
pushState: false
});
// 2. 트리 갱신
await preparePageRendering({
scope: 'tree',
resourcePath: extractedPath,
userCurPath: userCurPath,
pushState: false
});
// 3. 리스트 갱신
await preparePageRendering({
scope: 'list',
resourcePath: userCurPath,
pushState: false
});
}
}
}
//////// uploadData - 드래그앤드롭 이벤트(dragover, dragleave, drop) / 파일 input change 이벤트는 pageRenderer.js의 renderContextmenu안에서 추가
let fileArr = [];
let notDndArea = document.querySelector('.not-dnd-area');
let listBody = listContainer?.querySelector('.list-wrap.list-body');
let dndArea = listContainer?.querySelector('.dnd-area');
let dndAreaImage = dndArea?.querySelector('.image');
let dndAreaText = dndArea?.querySelector('.text');
let dndAreaWarn1 = dndArea?.querySelector('.warn1');
let dndAreaWarn2 = dndArea?.querySelector('.warn2');
let isDragging = false;
window.addEventListener('dragover', async (e) => {
e.preventDefault();
// listContariner가 화면에 표시되어 있지 않으면 리턴
if (listContainer.style.display != 'flex') return;
// 갤러리 폴더 지도모드일 때 안내 표시 및 리턴
let mapContainer = listContainer?.querySelector('.map-container');
if (mapContainer.style.display == 'flex') {
let notificatitonParams = { text: '지도모드에서는 드래그앤드롭 업로드 기능이 제한됩니다.' }
showNotification(notificatitonParams);
return;
}
// 페이지영역에 드래그 들어오면 notDndArea, dndArea 표시
notDndArea.style.display = 'flex';
dndArea.style.display = 'flex';
// 드래그 상태 플래그 true
isDragging = true;
let listBodyWidth = listBody.offsetWidth;
let listBodyHeight = listBody.offsetHeight;
let listScrollTop = listBody.scrollTop;
let listScrollLeft = listBody.scrollLeft;
let plusNum = -8;
let minusNum = -4;
dndArea.style.width = `${listBodyWidth + plusNum}px`;
dndArea.style.height = `${listBodyHeight + plusNum}px`;
dndArea.style.top = `${listScrollTop - minusNum}px`;
dndArea.style.left = `${listScrollLeft - minusNum}px`;
let backgroundColor, color, url, text, warn1, warn2;
if (e.target.matches('.dnd-area')) {
// dndArea 안으로 이동
backgroundColor = '#b5c6c3dd';
color = '#1e5149';
url = '/main/img/archive/upload_in_dnd.svg';
text = '파일을 드롭하면 업로드가 진행됩니다.';
warn1 = '폴더는 드롭해도 업로드되지 않습니다.';
warn2 = '&nbsp;';
// dndAreaWarn.style.display = 'flex';
} else {
// dndArea 밖으로 이동
backgroundColor = '#e9eeeddd';
color = '#a5b9b6';
url = '/main/img/archive/upload_out_dnd.svg';
text = '파일을 여기에 드래그하세요.';
warn1 = '&nbsp;';
warn2 = '&nbsp;';
// dndAreaWarn.style.display = 'none';
}
dndArea.style.background = backgroundColor;
dndArea.style.border = `2px solid ${color}`;
dndAreaImage.style.backgroundImage = `url(${url})`;
dndAreaText.innerHTML = text;
dndAreaText.style.color = color;
dndAreaWarn1.innerHTML = warn1;
dndAreaWarn2.innerHTML = warn2;
})
window.addEventListener('dragleave', (e) => {
e.preventDefault();
if (e.target.matches('.not-dnd-area')) {
if (!e.relatedTarget || e.relatedTarget == null) {
// notDndArea에서 페이지영역 밖으로 나갈 때 notDndArea, dndArea 숨김
notDndArea.style.display = 'none';
dndArea.style.display = 'none';
// 드래그 상태 플래그 false
isDragging = false;
}
}
if (e.relatedTarget && e.relatedTarget.matches('.dnd-area')) {
// notDndArea에서 dndArea로 들어올 때 아무 변화 없도록 리턴
return;
}
})
window.addEventListener('drop', async (e) => {
e.preventDefault();
if (dndArea.contains(e.target)) {
// dndArea 안에 드롭
const items = e.dataTransfer.items;
const promises = [];
const notAllowedFileArr = [];
// let targetStr = '';
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry();
if (item) {
if (item.isFile) {
promises.push(addFileToFileArr(item, 'file'));
// targetStr += 'file/'
// } else if (item.isDirectory) {
// promises.push(readFolder(item, 'folder'));
// targetStr += 'folder/'
}
}
}
await Promise.all(promises);
let uploadDataOption = {
functionId: 'dnd_file',
dataType: 'file'
}
uploadData(fileArr, uploadDataOption);
} else {
// dndArea 밖에 드롭
}
// notDndArea에서 페이지영역 밖으로 나갈 때 notDndArea, dndArea 숨김
notDndArea.style.display = 'none';
dndArea.style.display = 'none';
// 드래그 상태 플래그 false
isDragging = false;
})
// 드래그 상태에서 웹페이지 밖으로 나갈 때 notDndArea, dndArea 남아있는 경우 초기화
window.addEventListener('mousemove', () => {
if (isDragging) {
notDndArea.style.display = 'none';
dndArea.style.display = 'none';
isDragging = false;
}
})
// uploadData에 전달할 fileArr에 드롭된 파일들 담기
async function addFileToFileArr(data, dataType) {
return new Promise((resolve, reject) => {
data.file(file => {
let dataFullPath = data.fullPath;
let dataFullPathSplit = dataFullPath.split('/');
dataFullPathSplit.shift();
dataFullPath = dataFullPathSplit.join('/');
file.resourcePath = dataFullPath;
file.dataType = dataType;
fileArr.push(file);
resolve();
}, error => {
console.error("파일을 추가하는 중에 오류 발생:", error);
reject(error);
});
});
}
// // 폴더가 드롭된 경우 폴더 내 데이터 확인
// async function readFolder(folder, dataType) {
// const folderReader = folder.createReader();
// return new Promise((resolve, reject) => {
// let dataList = [];
// function readDataListRecursively() {
// folderReader.readEntries(async (newDataList) => {
// if (newDataList.length === 0) {
// resolve();
// return;
// }
// dataList = dataList.concat(newDataList);
// const promises = dataList.map(data => {
// if (data.isFile) {
// return addFileToFileArr(data, dataType);
// } else if (data.isDirectory) {
// return readFolder(data, dataType);
// }
// });
// await Promise.all(promises);
// readDataListRecursively();
// }, error => {
// console.error("폴더를 확인하는 중에 오류 발생:", error);
// reject(error);
// });
// }
// readDataListRecursively();
// });
// }
export async function uploadData(files, option) {
let { functionId, dataType, addOnTarget } = option;
// Viewer 권한(lev = 1 이하)인 경우 업로드 조기 차단
let userInfo = JSON.parse(vars.userInfoString || '{}');
let permission = userInfo.permission;
if (permission !== undefined && permission !== null && permission <= 1) {
let toggleParams = {
text: '파일 업로드 권한이 없습니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
fileArr = [];
return;
}
let totalSize = 0;
Array.from(files).forEach(file => {
totalSize += file.size;
});
// 여유 공간 초과 시 제한
if (totalSize > vars.remainingSize) {
let toggleParams = {
text: '여유 공간이 부족합니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
fileArr = [];
return;
}
// 업로드 크기 초과 시 제한
let sizeLimit = 20 * 1024 * 1024 * 1024;
if (totalSize > sizeLimit) {
let toggleParams = {
text: '한 번에 업로드할 수 있는 최대 용량은 20 GB 입니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
fileArr = [];
return;
}
let topPath = vars.lastMainTreeItem.dataset.resourcePath;
//// 도면 ctb xref 관련 코드
let functionIdSplit = functionId.split('_');
// if (functionIdSplit[0] == 'addOn' && functionIdSplit[1]) {
if (addOnTarget) {
// 추가 파일(버전/첨부) 업로드인 경우 db에 depth4파일_attachment/version 폴더 존재 확인 후
// 없으면 생성, 있으면 그대로 진행해서 response로 'ensureAddOnFolder_success' 메시지 전달
let lastContextTargetResourcePath = vars.lastContextTarget.dataset.resourcePath;
topPath = `${lastContextTargetResourcePath}_${functionIdSplit[1]}`;
let ensureAddOnFolderParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
date: Date.now(),
resourcePath: topPath
}
await axios.post(`${vars.path_name}/ensureAddOnFolder`, { params: ensureAddOnFolderParams });
}
let dateArr = [], resourcePathArr = [], sizeArr = [], objectKeyArr = [], thumbnailSizeArr = [], thumbnailKeyArr = [], existingDataIdArr = [], coordArr = [];
let uploadFailedFileArr = [], uploadSuccessFileArr = [];
// 경로 존재 확인을 위해 resourcePathArr 저장
for (let i = 0; i < files.length; i++) {
const file = files[i];
let resourcePath = `${topPath}/${file.name}`;
resourcePathArr.push(resourcePath);
}
// resourcePathArr을 사용해서 경로 존재 확인
let checkTargetExistsResult = await checkTargetExists('file', resourcePathArr);
// 이미 존재하는 경로만 existingPathArr에 저장
let existingPathArr = checkTargetExistsResult.existingPathArr;
// console.log(existingPathArr);
// resourcePathArr 초기화
resourcePathArr = [];
let progressTitle;
if (functionId == 'uploadFile' || functionId == 'dnd_file') {
if (dataType == 'folder') progressTitle = '폴더 업로드';
if (dataType == 'file') progressTitle = '파일 업로드';
} else if (functionId == 'addOn_version') {
progressTitle = '버전파일 추가';
} else if (functionId == 'addOn_attachment') {
progressTitle = '첨부파일 추가';
}
/*
JS는 동기적으로 실행되고, DOM 변경이 실제로 화면에 반영(렌더링)되는 것은
현재 JS 실행이 모두 끝난 후에 (이벤트 루프에서 브라우저가 한 번 쉬는 순간)에 그려짐
그런데 confirm()은 **브라우저가 강제로 UI 스레드를 블록**해서 JS 실행도 멈추고, 렌더링도 멈춤
그래서 confirm()이 종료될 때까지 화면 변화가 적용되지 않음
이를 해결하기 위해 브라우저가 한 번 렌더링을 할 틈을 만들어줘야 함 -> await new Promise(r => requestAnimationFrame(r));
*/
/*
for문 안에서 toggleFileProgress를 실행하면 confirm()이 표시되기 전까지는 프로그레스 화면이 표시가 되지 않기 때문에
첫 번째 파일의 프로그레스가 표시되지 않으므로 for문 밖에서 먼저 toggleFileProgress를 실행해서 프로그레스 화면 및 첫 번째 파일의 프로그레스 표시
*/
let progressData = {};
progressData.title = progressTitle;
progressData.warn = true;
progressData.fileName = '';
progressData.count = '';
progressData.idx = 1;
await toggleFileProgress(true, progressData);
await new Promise(r => requestAnimationFrame(r));
let progressSizeSpan = document.querySelector('.file-progress .size span');
progressSizeSpan.textContent = ' ';
let imageExtArr = ['jpg', 'jpeg', 'png', 'webp', 'gif'];
let videoExtArr = ['mp4', 'mov', 'webm'];
let textExtArr = ['txt', 'md'];
for (let i = 0; i < files.length; i++) {
const file = files[i];
progressData = {};
progressData.title = progressTitle;
progressData.warn = true;
progressData.fileName = file.name;
progressData.count = files.length;
progressData.idx = i+1;
await toggleFileProgress(true, progressData);
await new Promise(r => requestAnimationFrame(r));
progressSizeSpan.textContent = ' ';
let resourcePath = `${topPath}/${file.name}`;
let dataId;
if (existingPathArr.includes(resourcePath)) {
let isUpload = confirm(`'${file.name}' 파일이 이미 존재합니다.\n기존 파일을 새 파일로 교체하시겠습니까?`);
if (isUpload) dataId = getDataFromTreeObject(resourcePath, 'file').data.dataId;
if (!isUpload) {
if (i == files.length-1) {
toggleFileProgress(false);
fileArr = [];
}
continue;
}
}
try {
// 1) 서버에 presigned URL 요청
let date = Date.now();
let needsThumbnailExtArr = [...imageExtArr, ...videoExtArr, ...textExtArr, 'pdf'];
let isLowerExt = true;
let ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
//// 이미지, 비디오 썸네일 이미지 생성
let needsThumbnail = needsThumbnailExtArr.includes(ext);
//// 이미지 썸네일 이미지 생성
// let needsThumbnail = imageExtArr.includes(ext);
let thumbnailPath;
if (needsThumbnail) thumbnailPath = splitBaseAndExt(resourcePath, isLowerExt).base + '.jpeg';
let generateUploadUrlParams = {
resourcePath: resourcePath,
date: date,
needsThumbnail: needsThumbnail,
thumbnailPath: thumbnailPath
};
let generateUploadUrlRes = await axios.post(`${vars.path_name}/generateUploadUrl`, generateUploadUrlParams);
if (generateUploadUrlRes.data.message == 'generateUploadUrl_success') {
let { originUrl, objectKey, date, thumbnailUrl, thumbnailKey } = generateUploadUrlRes.data.result;
// if (i === 1 || i === 2) {
// throw new Error(`강제로 실패 처리: ${resourcePath}`);
// }
let isImage = imageExtArr.includes(ext);
let isVideo = videoExtArr.includes(ext);
let isText = textExtArr.includes(ext);
let isPdf = ext == 'pdf';
// 이미지 경도/위도 데이터 추출
let lon = null, lat = null, height = null;
if (isImage) {
try {
// let gpsData = await exifr.gps(file);
// if (gpsData?.latitude && gpsData?.longitude) {
// let lon = gpsData.longitude;
// let lat = gpsData.latitude;
// coordArr.push({lon, lat});
// }
let parse = await exifr.parse(file);
if (parse?.latitude && parse?.longitude) {
lon = parse.longitude;
lat = parse.latitude;
height = (parse.GPSAltitude) ? parse.GPSAltitude : 0;
} else {
console.error(`❌ GPS 정보 없음 (${resourcePath})`);
}
} catch (error) {
console.error(`❌ exifr.gps 실패 (${resourcePath})`);
}
}
coordArr.push({lon, lat, height});
let thumbnailFile;
if (thumbnailUrl && thumbnailKey) {
progressSizeSpan.textContent = '썸네일 생성중';
let resizeTarget;
//// 이미지, 비디오, 텍스트 썸네일 이미지 생성
if (isImage) resizeTarget = file;
if (isVideo) {
resizeTarget = await createVideoThumbnail(file, thumbnailPath);
}
if (isPdf) {
let pdfData = await getPdfData(file);
resizeTarget = await createPdfThumbnail(pdfData, 960, thumbnailPath);
}
if(isText) {
resizeTarget = await createTextThumbnail(file, thumbnailPath);
}
if (resizeTarget) {
// if (ext != 'pdf') {
let img = new Image();
img.src = URL.createObjectURL(resizeTarget);
await new Promise(r => img.onload = r);
let maxSizeBytes = 100 * 1024; // 100 kb
if (resizeTarget.size > maxSizeBytes) {
thumbnailFile = await resizeImage(img, maxSizeBytes, thumbnailPath);
} else {
progressSizeSpan.textContent = ' ';
thumbnailFile = resizeTarget;
}
await axios.put(thumbnailUrl, thumbnailFile, {
headers: {
'Content-Type': thumbnailFile.type || 'application/octet-stream'
}
})
// }
} else {
alert('파일 오류로 인해 썸네일 생성이 실패했습니다.\n파일을 다시 확인해주세요.\n파일명: ' + file.name);
}
}
// 2) presigned URL로 직접 파일 PUT 전송
await axios.put(originUrl, file, {
headers: {
'Content-Type': file.type || 'application/octet-stream'
},
onUploadProgress: async progress => {
let totalSize = progress.total;
let loadedSize = progress.loaded;
let percentage = (loadedSize / totalSize) * 100;
progressData.loadedSize = loadedSize;
progressData.totalSize = totalSize;
progressData.percentage = percentage;
toggleFileProgress(true, progressData);
}
});
let thumbnailSize = 0;
if (thumbnailFile) thumbnailSize = thumbnailFile.size;
dateArr.push(date);
resourcePathArr.push(resourcePath);
sizeArr.push(file.size);
objectKeyArr.push(objectKey);
thumbnailSizeArr.push(thumbnailSize);
thumbnailKeyArr.push(thumbnailKey);
if (dataId) existingDataIdArr.push(dataId);
uploadSuccessFileArr.push(file.name);
} else if (generateUploadUrlRes.data.message == 'generateUploadUrl_failed_permission') {
let toggleParams = {
text: '파일 업로드 권한이 없습니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
throw new Error('generateUploadUrl_failed_permission');
} else {
throw new Error('generateUploadUrl_failed');
}
} catch (err) {
console.error(`❌ 업로드 실패: ${resourcePath}`, err);
uploadFailedFileArr.push(file.name);
}
}
if (resourcePathArr.length > 0) {
// resourcePathArr[0]에서 depth3 경로 추출
// -> depth3 경로로 getDataFromTreeObject 사용해서 depth3폴더 dataId 조회
// -> 파라미터에 depth3DataIdArr 추가
let depth3Path = extractPathByLength(resourcePathArr[0], 3);
let dapth3DataId = getDataFromTreeObject(depth3Path, 'folder').data.dataId;
let uploadDataParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dateArr: JSON.stringify(dateArr),
resourcePathArr: JSON.stringify(resourcePathArr),
sizeArr: JSON.stringify(sizeArr),
objectKeyArr: JSON.stringify(objectKeyArr),
thumbnailSizeArr: JSON.stringify(thumbnailSizeArr),
thumbnailKeyArr: JSON.stringify(thumbnailKeyArr),
existingDataIdArr: JSON.stringify(existingDataIdArr),
coordArr: JSON.stringify(coordArr),
dataType: dataType,
functionId: functionId,
depth3DataIdArr: [dapth3DataId]
}
let uploadDataRes = await axios.post(`${vars.path_name}/uploadData`, { params: uploadDataParams });
if (uploadDataRes.data.message == 'uploadData_success') {
fileArr = [];
toggleContextmenu(false);
toggleContextFocusBox(false);
let length = JSON.parse(uploadDataParams.resourcePathArr).length;
for (let i = 0; i < length ; i++) {
let resourcePath = JSON.parse(uploadDataParams.resourcePathArr)[i];
let objectKey = JSON.parse(uploadDataParams.objectKeyArr)[i]
let isLowerExt = true
let ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
if (ext === 'pdf') {
let pdfData = await getPdfData(files[i]);
let pageCount = pdfData.numPages;
if (pageCount > 10){
let thumbParam = {
resourcePath : resourcePath,
userInfoString : uploadDataParams.userInfoString,
objectKey: objectKey,
storageType : uploadDataParams.storageType,
}
await axios.post(`${vars.path_name}/makeThumbPdf`, { params: thumbParam });
}
try { await pdfData.cleanup(); } catch {}
try { await pdfData.destroy(); } catch {}
}
if (videoExtArr.includes(ext)) {
let postProcessVideoParams = {
resourcePath : resourcePath,
userInfoString : uploadDataParams.userInfoString,
objectKey: objectKey,
storageType : uploadDataParams.storageType,
}
await axios.post(`${vars.path_name}/postProcessVideo`, { params: postProcessVideoParams });
}
let progressData = {};
progressData.title = '업로드 마무리';
progressData.warn = true;
progressData.count = length;
progressData.idx = i+1;
toggleFileProgress(true, progressData);
await sleep(50);
}
}
}
// 업로드 마무리 프로그레스 끝나고 잠깐 대기 후 프로그레스 화면 종료되도록 setTimeout 사용
let timeoutId = setTimeout(async function() {
await toggleFileProgress(false, undefined);
clearTimeout(timeoutId);
}, vars.progressDuration);
if (uploadFailedFileArr.length == 0) {
// alert('모든 파일이 정상적으로 업로드 되었습니다.');
let toggleParams = {
text: '모든 파일의 업로드가 정상적으로 완료되었습니다.',
type: 'alertModal',
};
toggleModal(true, toggleParams);
} else {
// let uploadFailedList = '';
// uploadFailedFileArr.map(file => {
// uploadFailedList += `${file}\n`;
// });
// alert(`총 ${uploadSuccessFileArr.length+uploadFailedFileArr.length}개의 파일 중 ${uploadFailedFileArr.length}개의 파일이 업로드 실패하였습니다.
// ${uploadFailedList}`);
// let uploadFailedList = '';
// uploadFailedFileArr.map(file => {
// uploadFailedList += `${file}<br>`;
// });
// let toggleParams = {
// text: `총 ${uploadSuccessFileArr.length+uploadFailedFileArr.length}개 중 ${uploadFailedFileArr.length}개 파일이 업로드에 실패했습니다.<br>${uploadFailedList}`,
// type: 'alertModal',
// };
// toggleModal(true, toggleParams);
let toggleParams = {
text: `${uploadSuccessFileArr.length+uploadFailedFileArr.length}개 중 ${uploadFailedFileArr.length}개 파일이 업로드에 실패했습니다.`,
type: 'alertModal',
};
toggleModal(true, toggleParams);
}
}
//////// renameTarget - 이름 변경 모달창 표시
export function openRenameModal(resourcePath, target) {
// resetViewer();
let dataType, dataTypeKor;
target = (target) ? target : vars.lastContextTarget;
if (target.matches('.folder')) dataType = 'folder', dataTypeKor = '폴더';
if (target.matches('.file')) dataType = 'file', dataTypeKor = '파일';
let toggleParams = {
title: '이름 변경',
text: `변경할 ${dataTypeKor}명을 입력한 후 확인을 눌러주세요.<br>이름 변경 대상 : ${resourcePath}`,
type: 'renameTarget',
data: target,
dataType: dataType,
dataTypeKor: dataTypeKor,
resourcePath: resourcePath
};
toggleModal(true, toggleParams);
}
//////// renameTarget - 클라이언트 측 renameTarget
export async function renameTarget(toggleParams, inputWrap) {
let data = toggleParams.data;
let dataType = toggleParams.dataType;
let dataTypeKor = toggleParams.dataTypeKor;
// let oldName = data.getElementsByClassName('name-text')[0].innerText;
// let oldName = data.getElementsByClassName('name-text')[0].innerHTML;
let oldName = data.getElementsByClassName('name-text')[0].textContent;
let newName = (inputWrap.getElementsByTagName('input')[0].value).trim();
let dataId = data.dataset.id;
let ext;
if (dataTypeKor == '파일') {
let split = splitBaseAndExt(oldName);
ext = split.ext;
oldName = `${split.base}.${ext}`;
newName = `${newName}.${ext}`;
}
let oldPath = toggleParams.resourcePath;
let topFolderPath = splitTopPathAndTargetName(oldPath).topPath;
let newPath = (topFolderPath == '/') ? `/${newName}` : `${topFolderPath}/${newName}`;
// 기존 경고문구 있으면 삭제
if (document.querySelector('.archive-modal .input-wrap .warn')) {
inputWrap.removeChild(document.querySelector('.archive-modal .input-wrap .warn'));
}
// 경고문구 dom 생성
let warn = document.createElement('div');
warn.classList.add('warn');
warn.style.top = `${inputWrap.offsetHeight}px`;
inputWrap.appendChild(warn);
let checkTargetExistsResult = await checkTargetExists(dataType, newPath);
// 상황에 따라 경고문구 텍스트 추가 또는 renameTarget 진행
if (!newName || newName == '' || newName == null) {
// 빈문자 체크
warn.innerText = `${dataTypeKor}명을 입력해주세요.`;
} else if (hasSpecialChar(newName)) {
// 특수문자 체크
warn.innerHTML = `다음 특수문자는 사용할 수 없습니다.<br>\\ / : * ? ' " \` < > | #`;
} else if (checkTargetExistsResult.isExists) {
// 동일이름 여부 체크
let text = `동일한 ${dataTypeKor}명이 존재합니다.`;
// ** 권한 관련
if (checkTargetExistsResult.rows) {
let userInfo = JSON.parse(vars.userInfoString);
let changedUserPermission = changeUserPermission(userInfo.permission);
let targetDataPermission = checkTargetExistsResult.rows[0].data_permission;
targetDataPermission = (targetDataPermission == 0) ? 99999 : targetDataPermission;
if (dataType == 'folder') {
if (targetDataPermission > changedUserPermission) text = '접근이 제한되어 보이지 않는 폴더 중에\n' + text;
}
}
warn.innerText = text;
} else {
// 경고문구 dom 삭제
inputWrap.removeChild(warn);
// 모달창 닫기
toggleModal(false);
// renameTarget 진행
// 컨트롤러 실행
let renameTargetParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
resourcePath: toggleParams.resourcePath,
dataType: dataType,
newName: newName,
oldName: oldName,
newPath: newPath,
oldPath: oldPath,
dataId: dataId
}
let renameTargetRes = await axios.post(`${vars.path_name}/renameTarget`, { params: renameTargetParams });
if (renameTargetRes.data.message == 'renameTarget_success') {
console.log('renameTarget_success');
toggleContextmenu(false);
// } else if (renameTargetRes.data.message == 'renameTarget_failed') {
// let toggleParams = { text: '해당 폴더 내 파일이 사용 중이어서 이름을 변경할 수 없습니다.<br>잠시 후 다시 시도해 주세요.' };
// toggleModal(true, toggleParams);
}
if (renameTargetRes.data.message == 'renameTarget_failed_permission') {
let params = {
text: `${toggleParams.title} 실패 : 권한이 부족하여 작업이 실패했습니다.`,
type: 'alertModal'
};
toggleModal(true, params);
}
}
}
//////// editAuthor - 작성자 변경 모달창 표시
export function openEditAuthorModal(resourcePath, target) {
target = (target) ? target : vars.lastContextTarget;
let resourcePathArr = [], dataIdArr = [], prevAuthorIdArr = [], prevAuthorNmArr = [];
if (vars.multiSelectListItemArr.length == 0) {
resourcePathArr = [resourcePath];
// dataIdArr = [vars.lastContextTarget.dataset.id];
dataIdArr = [target.dataset.id];
let data = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject).data;
prevAuthorIdArr = (data.authorId) ? [data.authorId] : [data.userId];
prevAuthorNmArr = (data.authorNm) ? [data.authorNm] : [data.userNm];
} else {
vars.multiSelectListItemArr.forEach(elem => {
resourcePathArr.push(elem.dataset.resourcePath);
dataIdArr.push(elem.dataset.id);
let data = getDataFromTreeObject(elem.dataset.resourcePath, 'file', vars.currentTreeObject).data;
prevAuthorIdArr.push((data.authorId) ? data.authorId : data.userId);
prevAuthorNmArr.push((data.authorNm) ? data.authorNm : data.userNm);
})
}
let toggleParams = {
title: '작성자 변경',
text: '이름 검색 후 작성자를 선택해주세요.',
type: 'editAuthor',
resourcePathArr: resourcePathArr,
dataIdArr: dataIdArr,
prevAuthorIdArr: prevAuthorIdArr,
prevAuthorNmArr: prevAuthorNmArr
};
toggleModal(true, toggleParams);
}
//////// editAuthor - 클라이언트 측 editAuthor
export async function editAuthor(toggleParams) {
let resourcePathArr = toggleParams.resourcePathArr;
let dataIdArr = toggleParams.dataIdArr;
let prevAuthorIdArr = toggleParams.prevAuthorIdArr;
let prevAuthorNmArr = toggleParams.prevAuthorNmArr;
let newAuthorId = toggleParams.newAuthorId;
let newAuthorNm = toggleParams.newAuthorNm;
let editAuthorParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
resourcePathArr: resourcePathArr,
dataIdArr: dataIdArr,
prevAuthorIdArr: prevAuthorIdArr,
prevAuthorNmArr: prevAuthorNmArr,
newAuthorId: newAuthorId,
newAuthorNm: newAuthorNm
}
let editAuthorRes = await axios.post(`${vars.path_name}/editAuthor`, { params: editAuthorParams });
if (editAuthorRes.data.message == 'editAuthor_success') {
toggleModal(false);
toggleContextmenu(false);
toggleContextFocusBox(false);
}
}
export async function openDownloadModal(resourcePath) {
let lastContextTarget, multiSelectListItemArr;
let dataType, dataTypeKor;
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
if (isRecycleBinModal) {
lastContextTarget = vars.lastContextTarget_bin;
multiSelectListItemArr = vars.multiSelectListItemArr_bin;
} else {
lastContextTarget = vars.lastContextTarget;
multiSelectListItemArr = vars.multiSelectListItemArr;
}
if (lastContextTarget.matches('.folder')) dataType = 'folder', dataTypeKor = '폴더';
if (lastContextTarget.matches('.file')) dataType = 'file', dataTypeKor = '파일';
let resourcePathArr = [], dataIdArr = [];
if (multiSelectListItemArr.length == 0) {
resourcePathArr = [resourcePath];
dataIdArr = [lastContextTarget.dataset.id];
} else {
multiSelectListItemArr.forEach(elem => {
resourcePathArr.push(elem.dataset.resourcePath);
dataIdArr.push(elem.dataset.id);
})
}
let text;
if (dataType == 'folder') {
let filesCount = getDataFromTreeObject(resourcePath, 'folder').data.filesCount;
text = `<span>다음 폴더에 포함된 ${filesCount}개의 파일을 다운로드 하시겠습니까?</span><span>${resourcePath}</span>`;
} else {
if (resourcePathArr.length == 1) {
text = `<span>다음 파일을 다운로드 하시겠습니까?</span><span>${resourcePathArr[0]}</span>`;
} else {
text = `<span>선택된 ${resourcePathArr.length}개의 파일을 다운로드 하시겠습니까?</span>`;
}
}
let toggleParams = {
title: '다운로드',
text: text,
type: 'downloadTarget',
resourcePathArr: resourcePathArr,
dataIdArr: dataIdArr,
dataType: dataType,
dataTypeKor: dataTypeKor
};
toggleModal(true, toggleParams);
}
//////// downloadTarget - 클라이언트 측 downloadTarget
export async function downloadTarget(toggleParams) {
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
if(toggleParams.dataType !== 'folder'){
//파일인 경우 다운로드
let getDataInfoParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dataIdArr: toggleParams.dataIdArr,
isRemoved: isRecycleBinModal,
debug: "'downloadTarget'에서 실행"
}
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams } );
if (getDataInfoRes.data.message == 'getDataInfo_success') {
let resultArr = getDataInfoRes.data.result;
if (!Array.isArray(resultArr)) resultArr = [resultArr];
for (let i = 0; i < resultArr.length; i++) {
let result = resultArr[i];
let objectKey = result.object_key;
let pathLimit = 8;
let segmentArr = [];
for (let j = 0; j <pathLimit; j++) {
if (result[`path${j+1}`]) segmentArr.push(result[`path${j+1}`]);
}
let resourcePath = `/${segmentArr.join('/')}`;
let generateDownloadUrlParams = {
objectKey: objectKey,
resourcePath: resourcePath
}
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
let url = generateDownloadUrlRes.data.url;
const a = document.createElement('a');
a.href = url;
a.download = resourcePath.split('/').pop();
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
await sleep(500);
}
}
}
let downloadTargetParams = {
userInfoString: vars.userInfoString,
resourcePathArr: toggleParams.resourcePathArr,
dataIdArr: toggleParams.dataIdArr,
dataType: toggleParams.dataType
}
let downloadTargetRes = await axios.post(`${vars.path_name}/downloadTarget`, { params: downloadTargetParams });
if (downloadTargetRes.message == 'downloadTarget_success') {
console.log('downloadTarget_success');
}
}else{
//폴더인 경우 다운로드
let downloadZipParam = {
resourcePath : toggleParams.resourcePathArr,
userInfoString : vars.userInfoString,
storageType: vars.storageType,
}
alert(`폴더 다운로드는 압축 시간이 필요합니다. \n다운로드가 준비되면 다운로드창에서 다운로드가 가능합니다. \n압축파일은 준비가 완료된 시점으로부터 24시간동안 다운로드가 가능합니다.`);
let res = await axios.get(`${vars.path_name}/downloadzip`, {params : downloadZipParam} );
}
}
export function toggleRelocateCover(state, resourcePath) {
let relocateTargetArr = [];
if (vars.multiSelectListItemArr.length == 0) {
relocateTargetArr = [vars.lastContextTarget];
} else {
if (vars.multiSelectListItemArr.length > 0) {
vars.multiSelectListItemArr.forEach(item => {
if (item.classList.contains('depth5')) {
let subItemData = getDataFromTreeObject(item.dataset.resourcePath, 'file', vars.currentTreeObject).data;
let subCategory = subItemData.subCategory;
let mainItemResourcePath = item.dataset.resourcePath.split(`_${subCategory}`)[0];
let mainItem = document.querySelector(`.list-item[data-resource-path="${mainItemResourcePath}"]`);
if (!vars.multiSelectListItemArr.includes(mainItem)) relocateTargetArr.push(item);
} else {
relocateTargetArr.push(item);
}
})
}
}
let overviewBtn = document.querySelector('.overview-btn');
if (state == false) {
if (overviewBtn) overviewBtn.classList.remove('disabled');
document.querySelectorAll('.relocate-cover').forEach(cover => {
cover.style.display = 'none';
});
}
if (state == true) {
if (overviewBtn) overviewBtn.classList.add('disabled');
//// 커버 세팅 및 표시
// 헤더 타이틀 커버 세팅
let headerTitleCover = document.querySelector('.header-title-cover');
let headerLeftWidth = document.querySelector('.header .left').offsetWidth;
headerTitleCover.style.width = `${headerLeftWidth}px`;
headerTitleCover.style.left = 0;
// 헤더 우측 버튼 영역 커버 세팅
let headerRightCover = document.querySelector('.header-right-cover');
let headerRightWidth = document.querySelector('.header .center .right').offsetWidth;
headerRightCover.style.width = `${headerRightWidth}px`;
headerRightCover.style.left = 0;
// 접속 인원 영역 커버 세팅
let connectedUsersCover = document.querySelector('.connected-users-cover');
let connectedUsersWidth = document.querySelector('.connected-users').offsetWidth;
connectedUsersCover.style.width = `${connectedUsersWidth}px`;
// 리스트/뷰어 커버 세팅
let listViewerCover = document.querySelector('.list-viewer-cover');
let listWidth = document.querySelector('.archive-main-center').offsetWidth;
let viewerWidth = document.querySelector('.archive-main-right').offsetWidth;
listViewerCover.style.width = `${listWidth + viewerWidth}px`;
listViewerCover.style.right = '0';
// 푸터 커버 세팅
let footerCover = document.querySelector('.footer-cover');
let footerWidth = document.querySelector('.footer').offsetWidth;
let footerHeight = document.querySelector('.footer').offsetHeight;
footerCover.style.width = `${footerWidth}px`;
footerCover.style.height = `${footerHeight}px`;
footerCover.style.bottom = 0;
// 전체 커버 표시
document.querySelectorAll('.relocate-cover').forEach(cover => {
cover.style.display = 'flex';
});
//// 리스트/뷰어 커버 안에 이동 기능 ui 세팅
// 이동 기능 ui가 화면 중앙에 위치하도록 컨테이너 width값 세팅
// 리스트/뷰어 커버 width값에서 좌측 트리 width값만큼 빼면 컨테이너가 화면 중앙에 위치
let container = listViewerCover.querySelector('.container');
let archiveMainLeftWidth = document.querySelector('.archive-main-left').offsetWidth;
container.style.width = `calc(100% - ${archiveMainLeftWidth}px)`;
// 기존 폴더 경로 추가
let oldPath = extractPathByLength(resourcePath, 3);
listViewerCover.querySelector('.old-path .value').textContent = oldPath;
// 선택 폴더 경로 초기화
listViewerCover.querySelector('.new-path .value').textContent = '-';
// 확인/취소 버튼 생성
let confirmBtn = document.createElement('div');
confirmBtn.classList.add('btn');
confirmBtn.classList.add('confirm-btn');
confirmBtn.innerText = '확인';
confirmBtn.addEventListener('click', function() {
let oldPath = listViewerCover.querySelector('.old-path .value').textContent;
let newPath = listViewerCover.querySelector('.new-path .value').textContent;
let oldResourcePathArr = relocateTargetArr.map(target => target.dataset.resourcePath);
let newResourcePathArr = oldResourcePathArr.map(path => path.replace(oldPath, newPath))
if (oldPath == newPath) {
let toggleParams = {
text: '기존 폴더 경로와 선택 폴더 경로가 동일합니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
} else {
if (newPath == '-') {
let toggleParams = {
text: '파일을 이동할 폴더가 선택되지 않았습니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
} else {
let oldFolderDataId = getDataFromTreeObject(oldPath, 'folder').data.dataId;
let newFolderDataId = getDataFromTreeObject(newPath, 'folder').data.dataId;
let depth3DataIdArr = [oldFolderDataId, newFolderDataId];
relocateTarget(oldResourcePathArr, newResourcePathArr, newPath, depth3DataIdArr);
// toggleRelocateCover(false);
}
}
})
let cancelBtn = document.createElement('div');
cancelBtn.classList.add('btn');
cancelBtn.classList.add('cancel-btn');
cancelBtn.innerText = '취소';
cancelBtn.addEventListener('click', function() {
toggleRelocateCover(false);
})
let btnWrap = listViewerCover.querySelector('.btn-wrap');
btnWrap.innerHTML = '';
btnWrap.appendChild(confirmBtn);
btnWrap.appendChild(cancelBtn);
}
}
async function relocateTarget(oldResourcePathArr, newResourcePathArr, newPath, depth3DataIdArr) {
let fromPathArr = oldResourcePathArr;
let toPathArr = [];
// 선택된 파일 중 버전/첨부 파일이 있는 경우 버전/첨부 해제된다는 alert 표시
let containsAddOnFilie = false;
for (let i = 0; i < newResourcePathArr.length; i++) {
let newResourcePath = newResourcePathArr[i];
if (getDepth(newResourcePath) === 5) {
containsAddOnFilie = true;
// 진행할 경우 바로 이어서 목적지 폴더와 동일한 이름이 있는지 확인하기 위해 depth5 경로를 depth4 경로로 변경
let newResourcePathSplit = newResourcePath.split('/');
newResourcePathSplit.splice(4, 1);
newResourcePathArr[i] = newResourcePathSplit.join('/');
}
}
if (containsAddOnFilie) {
let text = [
'선택된 파일 중 버전/첨부 파일이 있습니다.\n\n',
'버전/첨부 파일을 다른 폴더로 이동할 경우 버전/첨부가 해제되며,\n버전/첨부 파일로 되돌리려면 새로 추가해야 합니다.\n\n',
'진행하시겠습니까?'
];
let confirmResult = confirm(text.join(''));
if (!confirmResult) return;
}
// resourcePathArr을 사용해서 경로 존재 확인
let checkTargetExistsResult = await checkTargetExists('file', newResourcePathArr);
// 이미 존재하는 경로만 existingPathArr에 저장
let existingPathArr = checkTargetExistsResult.existingPathArr;
for (let i = 0; i < newResourcePathArr.length; i++) {
let newResourcePath = newResourcePathArr[i];
if (existingPathArr.includes(newResourcePath)) {
let fileName = splitTopPathAndTargetName(newResourcePath).targetName;
newResourcePath = `${newPath}/${getNextFileName(Object.keys(vars.currentTreeObject.file), fileName)}`;
alert('이동하려는 파일 중 일부와 동일한 이름의 파일이\n이미 목적지 폴더에 존재하여 이동이 취소되었습니다.\n\n확인 후 다시 시도해주세요.');
return;
}
toPathArr.push(newResourcePath);
}
fromPathArr = getFilteredFromPaths(fromPathArr, toPathArr);
let dataIdArr = [];
for (let i = 0; i < fromPathArr.length; i++) {
let fromPath = fromPathArr[i];
let data = getDataFromTreeObject(fromPath, 'file').data;
dataIdArr.push(data.dataId)
}
let relocateTargetParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dataType: 'file',
fromPathArr: fromPathArr,
toPathArr: toPathArr,
dataIdArr: dataIdArr,
depth3DataIdArr: depth3DataIdArr
}
let relocateTargetRes = await axios.post(`${vars.path_name}/relocateTarget`, { params: relocateTargetParams });
if (relocateTargetRes.data.message == 'relocateTarget_success') {
console.log('relocateTarget_success');
vars.lastListItem = undefined;
vars.lastContextTarget = undefined;
vars.lastSelectTarget = undefined;
toggleRelocateCover(false);
}
// toPath 기준으로 fromPath 필터링
function getFilteredFromPaths(fromPathArr, toPathArr) {
return fromPathArr.filter(fromPath => {
// const fromName = getFileName(fromPath);
// const fromBase = getNameWithoutExt(fromName);
// const fromExt = getExt(fromName);
const fromName = splitTopPathAndTargetName(fromPath).targetName;
const fromBase = splitBaseAndExt(fromName).base;
const fromExt = splitBaseAndExt(fromName).ext;
return toPathArr.some(toPath => {
// const toName = getFileName(toPath);
// const toBase = getNameWithoutExt(toName);
// const toExt = getExt(toName);
const toName = splitTopPathAndTargetName(toPath).targetName;
const toBase = splitBaseAndExt(toName).base;
const toExt = splitBaseAndExt(toName).ext;
// 확장자가 같고, 파일명이 시작 문자열로 포함되면 매칭
return toExt === fromExt && toBase.startsWith(fromBase);
});
});
}
// function getFileName(path) {
// return path.split('/').pop(); // "파일명.확장자"
// }
// function getNameWithoutExt(name) {
// return name.replace(/\.[^/.]+$/, ''); // 확장자 제거
// }
// function getExt(name) {
// return name.slice(name.lastIndexOf('.')); // ".확장자"
// }
}
//////// removeTarget - 휴지통으로 이동 모달창 표시
export function openRemoveModal(resourcePath, target) {
if (!target) {
if (vars.lastContextTarget) target = vars.lastContextTarget;
else target = vars.multiSelectListItemArr[0];
}
let dataType, dataTypeKor;
if (target.matches('.folder')) dataType = 'folder', dataTypeKor = '폴더';
if (target.matches('.file')) dataType = 'file', dataTypeKor = '파일';
let resourcePathArr = [], dataIdArr = [];
if (vars.multiSelectListItemArr.length == 0) {
resourcePathArr = [resourcePath];
// dataIdArr = [vars.lastContextTarget.dataset.id];
dataIdArr = [target.dataset.id];
} else {
vars.multiSelectListItemArr.forEach(elem => {
resourcePathArr.push(elem.dataset.resourcePath);
dataIdArr.push(elem.dataset.id);
})
}
// vars.multiSelectListItemArr 비어있으면 resourcePath만 사용, 비어있지 않으면 vars.multiSelectListArr의 dom객체 사용해서 resourcePath 배열 추출
// vars.multiSelectListItemArr 비어있으면 text에 파일명 하나만 표시, 비어있지 않으면 여러개 표시
let text;
if (dataType == 'folder') {
text = `<span>다음 폴더를 완전히 삭제하고, 폴더에 포함된 모든 파일을 휴지통으로 이동하시겠습니까?</span><span>${resourcePath}</span>`;
} else {
text = `<span>다음 파일을 휴지통으로 이동하시겠습니까?</span><span>${resourcePath}</span>`;
if (vars.multiSelectListItemArr.length > 1) text = `<span>${vars.multiSelectListItemArr.length}개의 파일을 휴지통으로 이동하시겠습니까?</span>`;
}
let toggleParams = {
title: (dataType == 'folder') ? '폴더 삭제' : '휴지통으로 이동',
text: text,
type: 'removeTarget',
resourcePathArr: resourcePathArr,
dataIdArr: dataIdArr,
dataType: dataType,
dataTypeKor: dataTypeKor
};
toggleModal(true, toggleParams);
}
//////// removeTarget - 클라이언트 측 removeTarget
export async function removeTarget(toggleParams) {
let depth3DataIdArr = [];
if (toggleParams.dataType == 'file') {
// dataType이 파일일 때 toggleParams.resourcePathArr[0]에서 depth3 경로 추출
// -> depth3 경로로 getDataFromTreeObject 사용해서 depth3폴더 dataId 조회
// -> 파라미터에 depth3DataIdArr 추가
let depth3Path = extractPathByLength(toggleParams.resourcePathArr[0], 3);
let dapth3DataId = getDataFromTreeObject(depth3Path, 'folder').data.dataId;
depth3DataIdArr.push(dapth3DataId);
}
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
let removeTargetParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
resourcePathArr: toggleParams.resourcePathArr,
dataIdArr: toggleParams.dataIdArr,
dataType: toggleParams.dataType,
isExpiredFolder: toggleParams.isExpiredFolder,
depth3DataIdArr: depth3DataIdArr,
isRecycleBinModal: isRecycleBinModal
}
let removeTargetRes = await axios.post(`${vars.path_name}/removeTarget`, { params: removeTargetParams });
if (removeTargetRes.data.message == 'removeTarget_success') {
console.log('removeTarget_success');
toggleContextmenu(false);
}
if (removeTargetRes.data.message == 'removeTarget_failed_permission') {
let params = {
text: `${toggleParams.title} 실패 : 권한이 부족하여 작업이 실패했습니다.`,
type: 'alertModal'
};
toggleModal(true, params);
}
}
//////// deleteTarget - 삭제 모달창 표시
export function openDeleteModal(resourcePath, target) {
let lastContextTarget, multiSelectListItemArr;
let dataType, dataTypeKor;
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
if (isRecycleBinModal) {
lastContextTarget = vars.lastContextTarget_bin;
multiSelectListItemArr = vars.multiSelectListItemArr_bin;
} else {
lastContextTarget = vars.lastContextTarget;
multiSelectListItemArr = vars.multiSelectListItemArr;
}
target = (target) ? target : lastContextTarget;
if (target.matches('.folder')) dataType = 'folder', dataTypeKor = '폴더';
if (target.matches('.file')) dataType = 'file', dataTypeKor = '파일';
let resourcePathArr = [], dataIdArr = [];
if (multiSelectListItemArr.length == 0) {
resourcePathArr = [resourcePath];
// dataIdArr = [vars.lastContextTarget.dataset.id];
dataIdArr = [target.dataset.id];
} else {
multiSelectListItemArr.forEach(elem => {
resourcePathArr.push(elem.dataset.resourcePath);
dataIdArr.push(elem.dataset.id);
})
}
// vars.multiSelectListItemArr 비어있으면 resourcePath만 사용, 비어있지 않으면 vars.multiSelectListArr의 dom객체 사용해서 resourcePath 배열 추출
// vars.multiSelectListItemArr 비어있으면 text에 파일명 하나만 표시, 비어있지 않으면 여러개 표시
let text;
if (dataType == 'folder') {
text = `<span>폴더를 완전히 삭제하시겠습니까?</span><span>${resourcePath}</span>`;
} else {
text = `<span>파일을 완전히 삭제하시겠습니까?</span><span>${resourcePath}</span>`;
if (multiSelectListItemArr.length > 1) text = `<span>${multiSelectListItemArr.length}개의 파일을 완전히 삭제하시겠습니까?</span>`;
}
let toggleParams = {
title: (dataType == 'folder') ? '폴더 삭제' : '파일 삭제',
text: text,
type: 'deleteTarget',
resourcePathArr: resourcePathArr,
dataIdArr: dataIdArr,
dataType: dataType,
dataTypeKor: dataTypeKor
};
toggleModal(true, toggleParams);
}
//////// deleteTarget - 클라이언트 측 deleteTarget
export async function deleteTarget(toggleParams) {
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
let chunkSize = 10;
let fullDataIdArr = toggleParams.dataIdArr;
let progressCount = 0;
let progressMax = fullDataIdArr.length;
console.log('================== 삭제 시작');
let progressData = {};
progressData.title = '파일 삭제';
progressData.warn = false;
progressData.count = progressMax;
progressData.idx = progressCount;
toggleFileProgress(true, progressData);
for (let i = 0; i < fullDataIdArr.length; i += chunkSize) {
let chunkDataIdArr = fullDataIdArr.slice(i, i + chunkSize);
let getDataInfoParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dataIdArr: JSON.stringify(chunkDataIdArr),
isRemoved: isRecycleBinModal,
debug: "'deleteTarget'에서 실행"
};
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams });
if (getDataInfoRes.data.message == 'getDataInfo_success') {
console.log('================== 삭제 데이터 준비');
let resultArr = getDataInfoRes.data.result;
if (!Array.isArray(resultArr)) resultArr = [resultArr];
let chunkResourcePathArr = [];
let chunkObjectKeyArr = [];
let chunkPreviewKeyArr = [];
let chunkPopupKeyArr = [];
let chunkThumbnailKeyArr = [];
let currentCount = resultArr.length;
for (let j = 0; j < resultArr.length; j++) {
let result = resultArr[j];
chunkResourcePathArr.push(...buildResourcePathFromSegments(result));
chunkObjectKeyArr.push(result.object_key);
chunkPreviewKeyArr.push(result.preview_key);
chunkPopupKeyArr.push(result.popup_key);
chunkThumbnailKeyArr.push(result.thumbnail_key);
}
let deleteTargetParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
resourcePathArr: JSON.stringify(chunkResourcePathArr),
dataIdArr: JSON.stringify(chunkDataIdArr),
objectKeyArr: JSON.stringify(chunkObjectKeyArr),
previewKeyArr: JSON.stringify(chunkPreviewKeyArr),
popupKeyArr: JSON.stringify(chunkPopupKeyArr),
thumbnailKeyArr: JSON.stringify(chunkThumbnailKeyArr),
dataType: toggleParams.dataType,
isRecycleBinModal: isRecycleBinModal
};
if (progressCount < progressMax) {
// 프로그레스 표시
let progressData = {};
progressData.title = '파일 삭제';
progressData.warn = false;
progressData.count = progressMax;
progressData.idx = progressCount;
toggleFileProgress(true, progressData);
}
let deleteTargetRes = await axios.post(`${vars.path_name}/deleteTarget`, deleteTargetParams);
if (deleteTargetRes.data.message == 'deleteTarget_success') {
// console.log('deleteTarget_success');
progressCount += currentCount;
// if (progressCount < progressMax) {
// // 프로그레스 표시
// let progressData = {};
// progressData.title = '파일 삭제';
// progressData.warn = false;
// progressData.count = progressMax;
// progressData.idx = progressCount;
// toggleFileProgress(true, progressData);
// } else {
// console.log('================== 삭제 완료');
// // 프로그레스 종료
// toggleFileProgress(false);
// }
if (progressCount >= progressMax) {
console.log('================== 삭제 완료');
// 프로그레스 종료
toggleFileProgress(false);
toggleContextmenu(false);
// resetViewer();
}
}
if (deleteTargetRes.data.message == 'deleteTarget_failed_permission') {
toggleFileProgress(false);
let params = {
text: `${toggleParams.title} 실패 : 권한이 부족하여 작업이 실패했습니다.`,
type: 'alertModal'
};
toggleModal(true, params);
}
}
}
}
export async function setDataPermission(resourcePath, targetId) {
let dataType, dataTypeKor;
if (vars.lastContextTarget.matches('.folder')) dataType = 'folder', dataTypeKor = '폴더';
if (vars.lastContextTarget.matches('.file')) dataType = 'file', dataTypeKor = '파일';
// ** 권한 관련 (클래스에는 포함 안될 예정)
// targetId : lev_1, lev_2, lev_4, lev_8, lev_0
let targetPermission = Number(targetId.split('_')[1]);
if (getDepth(resourcePath) > 1) {
// depth2, depth3 폴더 권한 설정 시 타겟폴더의 권한이 부모폴더의 권한보다 낮으면 안내모달 표시 후 리턴
let parentPath = splitTopPathAndTargetName(resourcePath).topPath;
let parentPermission = getDataFromTreeObject(parentPath, 'folder').data.permission;
let compareTargetPermission = (targetPermission == 0) ? 99999 : targetPermission;
let compareParentPermission = (parentPermission == 0) ? 99999 : parentPermission;
if (compareParentPermission > compareTargetPermission) {
let toggleParams = {
text: '상위 폴더보다 낮은 권한으로는 설정할 수 없습니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
return;
}
}
let beforePermission = getDataFromTreeObject(resourcePath, 'folder').data.permission;
let setDataPermissionParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
resourcePath: resourcePath,
dataId: vars.lastContextTarget.dataset.id,
dataType: dataType,
newPermission: targetPermission,
beforePermission: beforePermission
}
let setDataPermissionRes = await axios.post(`${vars.path_name}/setDataPermission`, { params: setDataPermissionParams });
if (setDataPermissionRes.data.message == 'setDataPermission_success') {
console.log('setDataPermission_success');
}
}
export async function convertPdf(resourcePath, dataId) {
let getDataInfoParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dataIdArr: [dataId],
isRemoved: false,
debug: "'convertPdf'에서 실행"
}
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams } );
if (getDataInfoRes.data.message == 'getDataInfo_success') {
let objectKey = getDataInfoRes.data.result.object_key;
let dataId = getDataInfoRes.data.result.data_id;
let convertPdfParams = {
dataId: dataId,
resourcePath: resourcePath,
depth1: extractPathByLength(resourcePath, 1),
depth2: extractPathByLength(resourcePath, 2),
depth3: extractPathByLength(resourcePath, 3),
userInfoString: vars.userInfoString,
objectKey: objectKey,
storageType: vars.storageType
};
await axios.post(`${vars.path_name}/convertPdf`, { params: convertPdfParams });
}
}
//////// uploadData, downloadTarget 할 때 전체 화면 프로그레스 토글
export function toggleFileProgress(state, progressData) {
let progress = document.querySelector('.file-progress');
let title = document.querySelector('.file-progress .title .title-wrap span');
let warn = document.querySelector('.file-progress .title .warn');
let fileName = document.querySelector('.file-progress .text .file-name span');
let count = document.querySelector('.file-progress .text .count span');
let size = document.querySelector('.file-progress .size span');
let percentage = document.querySelector('.file-progress .percentage span');
warn.style.display = 'none';
fileName.style.display = 'none';
count.style.display = 'none';
size.textContent = ' ';
percentage.textContent = ' ';
if (state == false) progress.style.display = 'none';
if (state == true) {
progress.style.display = 'flex';
if (progressData) {
fileName.style.display = 'flex';
count.style.display = 'flex';
title.textContent = progressData.title;
if (progressData.warn) {
warn.style.display = 'flex';
} else {
warn.style.display = 'none';
}
fileName.textContent = progressData.fileName;
// count.textContent = `${progressData.count}개 중 ${progressData.idx}번째`;
count.textContent = `${progressData.idx} / ${progressData.count}`;
if (progressData.loadedSize && progressData.totalSize) {
size.textContent = `${formatBytes(progressData.loadedSize)} / ${formatBytes(progressData.totalSize)}`;
} else {
size.textContent = ' ';
}
if (progressData.percentage) {
percentage.textContent = `${progressData.percentage.toFixed(2)} %`;
} else {
percentage.textContent = ' ';
}
} else {
title.textContent = '파일 업로드 준비중';
}
}
}
// 초기화면 템플릿 파일 다운로드
export async function downloadTempFile() {
let url = `https://api.digitalarchive.work/%EC%B4%88%EA%B8%B0%ED%99%94%EB%A9%B4%ED%85%9C%ED%94%8C%EB%A6%BF_v2.pptx`;
// let url = `https://api.digitalarchive.work/%EC%B4%88%EA%B8%B0%ED%99%94%EB%A9%B4%ED%85%9C%ED%94%8C%EB%A6%BF_v1.pptx`;
const a = document.createElement('a');
a.href = url;
a.download = `초기화면템플릿_v1.pptx`;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
await sleep(300);
}
// MAIN_TITLE_IMAGE 업로드
export async function uploadMainTitleImage(files, uploadType) {
let file = files[0];
let totalSize = 0;
totalSize += file.size;
// 여유 공간 초과 시 제한
if (totalSize > vars.remainingSize) {
let toggleParams = {
text: '여유 공간이 부족합니다.',
type: 'alertModal'
};
toggleModal(true, toggleParams);
fileArr = [];
return;
}
// 업로드 크기 초과 시 제한
let sizeLimit = 20 * 1024 * 1024 * 1024;
if (totalSize > sizeLimit) {
let toggleParams = {
text: '한 번에 업로드할 수 있는 최대 용량은 20 GB 입니다.',
type: 'alertMode'
};
toggleModal(true, toggleParams);
fileArr = [];
return;
}
let dateArr = [], resourcePathArr = [], sizeArr = [], objectKeyArr = [];
let uploadFailedFileArr = [], uploadSuccessFileArr = [];
let progressTitle = '파일 업로드';
let topPath = vars.lastHeaderBtn.dataset.resourcePath;
let resourcePath = `${topPath}/${file.name}`;
try {
if(uploadType == 'fileInput2') {
let param = { userInfoString: vars.userInfoString, resourcePath: resourcePath };
let res = await axios.post(`${vars.path_name}/deleteMainTitleImage`, {params: param});
}
// 1) 서버에 presigend URL 요청
let generateUploadUrlParams = {
date: Date.now(),
resourcePath: resourcePath
};
let generateUploadUrlRes = await axios.post(`${vars.path_name}/generateUploadUrl`, generateUploadUrlParams);
if(generateUploadUrlRes.data.message == 'generateUploadUrl_success') {
let { originUrl, objectKey, date} = generateUploadUrlRes.data.result;
// 2) presigend URL로 직접 파일 PUT 전송
await axios.put(originUrl, file, {
headers: {
'Content-Type': file.type || 'application/octet-stream'
},
onUploadProgress: async progress => {
// let progressData = {};
// let totalSize = progress.total;
// let loadedSize = progress.loaded;
// let percentage = (loadedSize / totalSize) * 100;
// progressData.loadedSize = loadedSize;
// progressData.totalSize = totalSize;
// progressData.percentage = percentage;
// if (percentage != 100) progressData.state = 'working';
// if (percentage == 100) progressData.state = 'finish';
// toggleFileProgress(true, progressData);
}
});
dateArr.push(date);
resourcePathArr.push(resourcePath);
sizeArr.push(file.size);
objectKeyArr.push(objectKey);
}
}catch(err){
console.error(`❌ 업로드 실패 : ${resourcePath}`, err);
uploadFailedFileArr.push(file.name);
}
// 이미지 파일 db 및 로그 등록
let uploadData_titleImgParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
dateArr: JSON.stringify(dateArr),
resourcePathArr: JSON.stringify(resourcePathArr),
sizeArr: JSON.stringify(sizeArr),
objectKeyArr: JSON.stringify(objectKeyArr),
dataType: 'file',
activity: 'uploadData_file'
}
let uploadData_titleImgRes = await axios.post(`${vars.path_name}/uploadData_titleImg`, { params: uploadData_titleImgParams});
if(uploadData_titleImgRes.data.message == 'uploadData_success') {
// url 받아서 가시화
let generateDownloadUrlParams = {
objectKey: objectKeyArr[0],
resourcePath: resourcePath
};
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
let imageUrl = generateDownloadUrlRes.data.url;
document.querySelector('.list-notice-viewer').src = imageUrl;
document.querySelector('.list-notice-viewer').style.display = 'flex';
document.querySelector('.list-notice-top').style.display = 'none';
document.querySelector('.list-notice-bottom').style.display = 'none';
}
}
// AI 버튼 상태 업데이트 공통 함수
export async function updateAiButtonState(dataId, state, btnType) {
// 특정 dataId에 해당하는 컨테이너만 찾기
const container = document.querySelector(`.viewer-container[data-data-id="${dataId}"]`);
if(!container) return;
// const aiBtn = container.querySelector('.api-btn');
let aiBtn;
if(btnType == 'gemini') {
aiBtn = container.querySelector('.ai-btn');
}else {
aiBtn = container.querySelector('.api-btn');
}
if(!aiBtn) return;
let aiStart = aiBtn.querySelector('.ai-start');
let aiLoading = aiBtn.querySelector('.ai-loading');
if (state === 'loading') {
aiStart.style.display = 'none';
aiLoading.style.display = 'flex';
aiLoading.style.height = '100%';
aiBtn.style.pointerEvents = 'none';
} else if (state === 'initial') {
aiStart.style.display = 'flex';
aiLoading.style.display = 'none';
aiBtn.style.pointerEvents = 'auto';
}
}