2216 lines
89 KiB
JavaScript
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 = ' ';
|
|
|
|
// dndAreaWarn.style.display = 'flex';
|
|
|
|
} else {
|
|
// dndArea 밖으로 이동
|
|
backgroundColor = '#e9eeeddd';
|
|
color = '#a5b9b6';
|
|
url = '/main/img/archive/upload_out_dnd.svg';
|
|
text = '파일을 여기에 드래그하세요.';
|
|
warn1 = ' ';
|
|
warn2 = ' ';
|
|
|
|
// 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';
|
|
}
|
|
}
|
|
|