444 lines
16 KiB
HTML
444 lines
16 KiB
HTML
|
|
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>구성</title>
|
|
<link rel="stylesheet" href="/main/css/reset.css" />
|
|
|
|
<style>
|
|
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css");
|
|
|
|
* {
|
|
font-family: 'Pretendard Variable', 'Pretendard';
|
|
font-size: 1rem;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.composition-modal{
|
|
display: flex;
|
|
position: fixed;
|
|
inset: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background: transparent;
|
|
align-items: stretch;
|
|
justify-content: stretch;
|
|
}
|
|
|
|
.composition-modal .modal-wrap{
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: none;
|
|
max-height: none;
|
|
margin: 0;
|
|
border-radius: 0;
|
|
border: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.composition-modal .modal-wrap h3 {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
line-height: 1.25rem;
|
|
letter-spacing: -0.0175rem;
|
|
margin: 0;
|
|
}
|
|
|
|
.composition-modal .modal-wrap h5 {
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
line-height: 1.25rem;
|
|
letter-spacing: -0.0175rem;
|
|
margin: 0;
|
|
}
|
|
|
|
.composition-modal .modal-wrap h6 {
|
|
font-size: 0.75rem;
|
|
font-weight: 300;
|
|
line-height: 1.25rem;
|
|
letter-spacing: -0.0175rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
margin: 0;
|
|
}
|
|
|
|
.composition-modal .modal-wrap .head {
|
|
display: flex;
|
|
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
|
border-bottom: 0.0625rem solid #ddd;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
background: #fff;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.composition-modal .modal-wrap .head i {
|
|
min-width: 1rem;
|
|
min-height: 1rem;
|
|
background: url(/main/img/archive/close.svg) no-repeat center / contain;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul {
|
|
display: flex;
|
|
flex: 1 1 auto;
|
|
overflow: auto;
|
|
line-height: 1.25rem;
|
|
letter-spacing: -0.0175rem;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li {
|
|
padding: 0.5rem 1rem 0 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
color: var(--primary-lv-6, #1E5149);
|
|
min-width: 14.5rem;
|
|
max-width: 14.5rem;
|
|
border-right: 0.0625rem solid #eee;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li:last-child {
|
|
padding-right: 0.5rem;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li {
|
|
border-bottom: 0.0625rem solid var(--primary-lv-0, #E9EEED);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li:last-child {
|
|
padding-bottom: 0.5rem;
|
|
border: none;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li > ul {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
color: #111;
|
|
padding-bottom: 0.25rem;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li > ul > li {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li > ul > li > i {
|
|
min-width: 1rem;
|
|
min-height: 1rem;
|
|
background: url(/main/img/archive/folder-bullet.svg) no-repeat center / contain;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li > ul > li > h6:last-child {
|
|
color: var(--primary-lv-3, #789792);
|
|
margin-left: auto;
|
|
min-width: 1.625rem;
|
|
text-align: left;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li:nth-child(even) {
|
|
background-color: #f6f8f8;
|
|
}
|
|
|
|
._scrollbar {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #888 #f1f1f1;
|
|
}
|
|
|
|
._scrollbar::-webkit-scrollbar { height: 8px; width: 8px; }
|
|
._scrollbar::-webkit-scrollbar-track { background: #f1f1f1; }
|
|
._scrollbar::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
|
|
._scrollbar::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
|
|
.loading {
|
|
padding: 1rem;
|
|
color: #666;
|
|
}
|
|
|
|
/*hover*/
|
|
.composition-modal .modal-wrap > ul > li > h3:hover,
|
|
.composition-modal .modal-wrap > ul > li > ul > li > ul > li:hover {
|
|
background-color: rgba(30, 81, 73, 0.08);
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.composition-modal .modal-wrap > ul > li > ul > li > ul > li:hover h6 {
|
|
color: #1E5149;
|
|
font-weight: 500;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<article class="composition-modal">
|
|
<div class="modal-wrap">
|
|
<div class="head">
|
|
<h3>구성</h3>
|
|
<i id="btnClose"></i>
|
|
</div>
|
|
|
|
<ul class="_scrollbar" id="composition-list">
|
|
<li class="loading">데이터를 불러오는 중...</li>
|
|
</ul>
|
|
</div>
|
|
</article>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
<script>
|
|
// 탭 닫기
|
|
document.getElementById('btnClose')?.addEventListener('click', () => window.close());
|
|
|
|
let userInfoString, storageType, allTreeObject, path_name;
|
|
let isDataReceived = false;
|
|
|
|
// 부모 창으로부터 데이터 수신
|
|
window.addEventListener('message', (event) => {
|
|
|
|
if (event.origin !== window.location.origin) return;
|
|
|
|
if (event.data.type === 'INIT_DATA') {
|
|
const { data } = event.data;
|
|
|
|
userInfoString = data.userInfoString;
|
|
storageType = data.storageType;
|
|
allTreeObject = data.allTreeObject;
|
|
path_name = data.path_name;
|
|
|
|
isDataReceived = true;
|
|
|
|
// 데이터 수신 후 렌더링
|
|
renderCompositionData();
|
|
}
|
|
});
|
|
|
|
// 정렬 함수
|
|
function naturalSort(a, b) {
|
|
const regex = /(\d+)|(\D+)/g;
|
|
const aParts = String(a).match(regex) || [];
|
|
const bParts = String(b).match(regex) || [];
|
|
|
|
for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
|
|
const aPart = aParts[i];
|
|
const bPart = bParts[i];
|
|
|
|
const aNum = Number(aPart);
|
|
const bNum = Number(bPart);
|
|
|
|
if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) {
|
|
const diff = aNum - bNum;
|
|
if (diff !== 0) return diff;
|
|
} else {
|
|
if (aPart !== bPart) return aPart.localeCompare(bPart);
|
|
}
|
|
}
|
|
return aParts.length - bParts.length;
|
|
}
|
|
|
|
// 구성 데이터 렌더링
|
|
async function renderCompositionData() {
|
|
const listWrap = document.getElementById('composition-list');
|
|
listWrap.innerHTML = '';
|
|
|
|
if (!allTreeObject || !userInfoString) {
|
|
listWrap.innerHTML = '<li class="loading">데이터를 불러올 수 없습니다.</li>';
|
|
return;
|
|
}
|
|
|
|
const user = JSON.parse(userInfoString);
|
|
const tabList = Object.keys(allTreeObject.folder || {}).sort(naturalSort);
|
|
|
|
for (const tab of tabList) {
|
|
const getTreeObjectParams = {
|
|
userInfoString: JSON.stringify(user),
|
|
storageType: storageType,
|
|
resourcePath: tab
|
|
};
|
|
|
|
try {
|
|
const getTreeObjectRes = await axios.get(`${path_name}/getTreeObject`, {
|
|
params: { params: getTreeObjectParams }
|
|
});
|
|
|
|
if (getTreeObjectRes.data.message === 'getTreeObject_success') {
|
|
const treeData = getTreeObjectRes.data.currentTreeObject;
|
|
|
|
const tabLi = document.createElement('li');
|
|
|
|
const tabTitle = document.createElement('h3');
|
|
tabTitle.textContent = tab;
|
|
tabTitle.style.cursor = 'pointer';
|
|
// 탭 클릭 시 해당 탭으로 이동
|
|
tabTitle.addEventListener('click', () => {
|
|
navigateToPath(tab);
|
|
});
|
|
tabLi.appendChild(tabTitle);
|
|
|
|
const categoryUl = document.createElement('ul');
|
|
|
|
if (treeData && treeData.folder && Object.keys(treeData.folder).length > 0) {
|
|
const categories = Object.keys(treeData.folder).sort(naturalSort);
|
|
|
|
categories.forEach(category => {
|
|
const categoryLi = document.createElement('li');
|
|
|
|
const categoryTitle = document.createElement('h5');
|
|
categoryTitle.textContent = category;
|
|
categoryLi.appendChild(categoryTitle);
|
|
|
|
const folderUl = document.createElement('ul');
|
|
|
|
const folders = treeData.folder[category] || {};
|
|
const folderObj = folders.child?.folder || {};
|
|
const folderKeys = Object.keys(folderObj).sort(naturalSort);
|
|
|
|
if (folderKeys.length === 0) {
|
|
// 빈 폴더 처리
|
|
} else {
|
|
folderKeys.forEach(folderName => {
|
|
const folderData = folderObj[folderName];
|
|
let fileCount = 0;
|
|
|
|
fileCount += Object.keys(folderData.child?.file || {}).length;
|
|
|
|
if (folderData.child?.folder) {
|
|
Object.keys(folderData.child.folder).forEach(subFolderName => {
|
|
const subFolder = folderData.child.folder[subFolderName];
|
|
if (subFolder.child?.file) {
|
|
fileCount += Object.keys(subFolder.child.file).length;
|
|
}
|
|
});
|
|
}
|
|
|
|
const folderLi = document.createElement('li');
|
|
folderLi.style.cursor = 'pointer';
|
|
|
|
// 폴더 전체 클릭 가능하도록
|
|
folderLi.addEventListener('click', () => {
|
|
navigateToPath(`${tab}/${category}/${folderName}`);
|
|
});
|
|
|
|
const folderIcon = document.createElement('i');
|
|
folderLi.appendChild(folderIcon);
|
|
|
|
const folderNameEl = document.createElement('h6');
|
|
folderNameEl.textContent = folderName;
|
|
folderLi.appendChild(folderNameEl);
|
|
|
|
const folderCountEl = document.createElement('h6');
|
|
folderCountEl.textContent = fileCount > 0 ? String(fileCount) : '-';
|
|
folderLi.appendChild(folderCountEl);
|
|
|
|
folderUl.appendChild(folderLi);
|
|
});
|
|
}
|
|
|
|
categoryLi.appendChild(folderUl);
|
|
categoryUl.appendChild(categoryLi);
|
|
});
|
|
} else {
|
|
// 탭은 있는데 폴더가 비어있는 경우
|
|
const emptyCategoryLi = document.createElement('li');
|
|
const emptyCategoryTitle = document.createElement('h5');
|
|
emptyCategoryTitle.textContent = '-';
|
|
emptyCategoryLi.appendChild(emptyCategoryTitle);
|
|
|
|
const emptyFolderUl = document.createElement('ul');
|
|
const emptyFolderLi = document.createElement('li');
|
|
|
|
const emptyIcon = document.createElement('i');
|
|
emptyFolderLi.appendChild(emptyIcon);
|
|
|
|
const emptyName = document.createElement('h6');
|
|
emptyName.textContent = '-';
|
|
emptyFolderLi.appendChild(emptyName);
|
|
|
|
const emptyCount = document.createElement('h6');
|
|
emptyCount.textContent = '-';
|
|
emptyFolderLi.appendChild(emptyCount);
|
|
|
|
emptyFolderUl.appendChild(emptyFolderLi);
|
|
emptyCategoryLi.appendChild(emptyFolderUl);
|
|
categoryUl.appendChild(emptyCategoryLi);
|
|
}
|
|
|
|
tabLi.appendChild(categoryUl);
|
|
listWrap.appendChild(tabLi);
|
|
}
|
|
} catch (error) {
|
|
console.error('데이터 로드 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 높이 동기화
|
|
setTimeout(() => {
|
|
const allLis = listWrap.querySelectorAll(':scope > li');
|
|
if (allLis.length === 0) return;
|
|
|
|
let maxHeight = 0;
|
|
allLis.forEach(li => {
|
|
const height = li.scrollHeight;
|
|
if (height > maxHeight) maxHeight = height;
|
|
});
|
|
|
|
allLis.forEach(li => {
|
|
li.style.minHeight = `${maxHeight}px`;
|
|
});
|
|
}, 0);
|
|
}
|
|
|
|
// 원본 탭에 경로 이동 요청
|
|
function navigateToPath(path) {
|
|
if (!window.opener || window.opener.closed) {
|
|
alert('원본 페이지를 찾을 수 없습니다.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// postMessage로 경로 이동 요청 전송
|
|
window.opener.postMessage({
|
|
type: 'NAVIGATE_TO_PATH',
|
|
path: path
|
|
}, window.location.origin);
|
|
|
|
} catch (error) {
|
|
console.error('페이지 이동 오류:', error);
|
|
alert('페이지 이동 중 오류가 발생했습니다.');
|
|
}
|
|
}
|
|
|
|
// 페이지 로드 시 데이터가 아직 수신되지 않았다면 대기
|
|
window.addEventListener('load', () => {
|
|
// 일정 시간 후에도 데이터가 수신되지 않으면 에러 메시지 표시
|
|
setTimeout(() => {
|
|
if (!isDataReceived) {
|
|
document.getElementById('composition-list').innerHTML =
|
|
'<li class="loading">데이터를 불러올 수 없습니다. 페이지를 새로고침해주세요.</li>';
|
|
}
|
|
}, 3000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |