Skip to content

Commit b1a9a70

Browse files
authored
Merge pull request #102 from DMU-DebugVisual/sunwoong
파일 CRUD API 통합 및 사이드바 UI 개선 (안정화 포함)
2 parents 7425f30 + 64976c7 commit b1a9a70

File tree

2 files changed

+218
-11
lines changed

2 files changed

+218
-11
lines changed

src/components/ide/IDE.css

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
--primary-hover: #5339a0;
77
--success: #43a047;
88
--warning: #fb8c00;
9+
--danger-color: #e53935; /* 🆕 삭제 색상 정의 */
910
--bg: #f8f9fa;
1011
--element: #ffffff;
1112
--element-light: #f3f4f6;
@@ -16,7 +17,7 @@
1617
--text-medium: #4b5563;
1718
--shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1819
--radius: 6px;
19-
--transition: none;
20+
--transition: all 0.2s ease; /* transition 속성 통일 */
2021
--header-height: 64px;
2122
--footer-height: 60px;
2223
}
@@ -27,6 +28,7 @@ body.dark-mode {
2728
--primary-hover: #5e35b1;
2829
--success: #4caf50;
2930
--warning: #ff9800;
31+
--danger-color: #ff5252; /* 🆕 다크 모드 삭제 색상 정의 */
3032
--bg: #1e1e1e;
3133
--element: #252525;
3234
--element-light: #2d2d2d;
@@ -164,7 +166,7 @@ body {
164166
.sidebar-title {
165167
font-size: 13px !important;
166168
font-weight: 600 !important;
167-
color: var(--text);
169+
color: var(--text) !important; /* 덮어쓰기 위해 !important 추가 */
168170
margin: 0;
169171
line-height: 1.4 !important;
170172
display: flex;
@@ -306,6 +308,8 @@ body.dark-mode .section-icon {
306308
height: 26px !important;
307309
position: relative !important;
308310
min-height: 26px !important;
311+
/* 🆕 삭제 버튼 공간 확보 */
312+
padding-right: 32px !important;
309313
}
310314

311315
.file-item:hover {
@@ -372,6 +376,65 @@ body.dark-mode .file-item.active:hover {
372376
.file-icon.json-icon { background-color: #eab308; }
373377
.file-icon.default-icon { background-color: var(--text-light); font-size: 8px; }
374378

379+
/* ⛔ 뱃지(S/L) 스타일 완전히 숨기기 */
380+
.file-badge {
381+
display: none !important;
382+
visibility: hidden !important;
383+
width: 0 !important;
384+
margin: 0 !important;
385+
}
386+
/* ------------------------------------- */
387+
388+
/* 🆕 삭제 버튼 스타일 */
389+
.delete-file-button {
390+
background: none;
391+
border: none;
392+
cursor: pointer;
393+
color: var(--text-light);
394+
/* 🎨 평소에는 숨기고 hover 시 나타나도록 opacity 0 */
395+
opacity: 0;
396+
transition: opacity 0.2s, color 0.2s;
397+
padding: 4px;
398+
line-height: 1;
399+
position: absolute;
400+
/* 뱃지 공간 확보 후 오른쪽에 위치 */
401+
right: 4px;
402+
top: 50%;
403+
transform: translateY(-50%);
404+
z-index: 10;
405+
border-radius: 4px;
406+
height: 24px;
407+
width: 24px;
408+
display: flex;
409+
align-items: center;
410+
justify-content: center;
411+
flex-shrink: 0;
412+
}
413+
414+
/* 🎨 파일 아이템 호버 시 삭제 버튼 표시 */
415+
.file-item:hover .delete-file-button {
416+
opacity: 0.7;
417+
}
418+
419+
/* 🎨 삭제 버튼 자체 호버 시 색상 강조 */
420+
.delete-file-button:hover {
421+
color: var(--danger-color);
422+
background-color: rgba(229, 57, 53, 0.1); /* 라이트 모드 */
423+
opacity: 1;
424+
}
425+
426+
body.dark-mode .delete-file-button:hover {
427+
background-color: rgba(255, 82, 82, 0.1); /* 다크 모드 */
428+
}
429+
430+
.delete-file-button i[data-feather="trash-2"] {
431+
width: 14px;
432+
height: 14px;
433+
stroke-width: 2px;
434+
}
435+
/* ------------------------------------- */
436+
437+
375438
.file-item.example-file { opacity: 0.9; }
376439
.file-item.example-file:hover { opacity: 1; }
377440
.file-item.json-file { border-left: 2px solid #eab308; padding-left: 6px; }
@@ -1075,4 +1138,67 @@ body.dark-mode .active-file-name {
10751138
/* 2. 다크 모드일 때만 밝은 색으로 덮어쓰기 */
10761139
body.dark-mode .sidebar-title {
10771140
color: #e0e0e0 !important;
1141+
}
1142+
1143+
/* --- 🆕 파일 아이템 및 삭제 버튼 최종 수정 --- */
1144+
1145+
.file-item {
1146+
/* padding-right를 늘려 삭제 버튼 공간 확보 */
1147+
padding-right: 32px !important;
1148+
}
1149+
1150+
/* ⛔ 뱃지(S/L) 스타일 완전히 숨기기 */
1151+
.file-badge {
1152+
display: none !important;
1153+
visibility: hidden !important;
1154+
width: 0 !important;
1155+
margin: 0 !important;
1156+
}
1157+
1158+
/* 🆕 삭제 버튼 스타일 */
1159+
.delete-file-button {
1160+
background: none;
1161+
border: none;
1162+
cursor: pointer;
1163+
color: var(--text-light);
1164+
/* 🎨 평소에는 숨기고 hover 시 나타나도록 opacity 0 */
1165+
opacity: 0;
1166+
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
1167+
padding: 4px;
1168+
line-height: 1;
1169+
position: absolute;
1170+
/* 오른쪽에 위치 */
1171+
right: 4px;
1172+
top: 50%;
1173+
transform: translateY(-50%);
1174+
z-index: 10;
1175+
border-radius: 4px;
1176+
height: 24px;
1177+
width: 24px;
1178+
display: flex;
1179+
align-items: center;
1180+
justify-content: center;
1181+
flex-shrink: 0;
1182+
}
1183+
1184+
/* 🎨 파일 아이템 호버 시 삭제 버튼 표시 */
1185+
.file-item:hover .delete-file-button {
1186+
opacity: 0.7;
1187+
}
1188+
1189+
/* 🎨 삭제 버튼 자체 호버 시 색상 강조 */
1190+
.delete-file-button:hover {
1191+
color: var(--danger-color);
1192+
background-color: rgba(229, 57, 53, 0.1);
1193+
opacity: 1;
1194+
}
1195+
1196+
body.dark-mode .delete-file-button:hover {
1197+
background-color: rgba(255, 82, 82, 0.15); /* 다크 모드 배경색을 조금 더 진하게 */
1198+
}
1199+
1200+
.delete-file-button i[data-feather="trash-2"] {
1201+
width: 14px;
1202+
height: 14px;
1203+
stroke-width: 2px;
10781204
}

src/components/ide/IDE.jsx

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ if (!document.querySelector('script[src*="feather"]')) {
1919
document.head.appendChild(script);
2020
}
2121

22-
// ⛔ Script error 방지를 위해 applyResizeObserverFix 함수 정의 전체를 제거했습니다.
23-
2422
const IDE = () => {
2523
// 🆕 더미 파일 데이터 (mockData 기반)
2624
const [dummyFiles] = useState(() => [...codeExampleMocks, ...jsonExampleMocks]);
@@ -210,7 +208,7 @@ const IDE = () => {
210208
}
211209
}, [toast]);
212210

213-
// 🔑 개선된 파일 선택 핸들러
211+
// 🔑 개선된 파일 선택 핸들러 (파일 상세 보기 및 조회)
214212
const handleFileSelect = async (identifier, isServerFile = false) => {
215213
if (!isSaved) {
216214
const shouldSave = window.confirm('변경 사항을 저장하시겠습니까?');
@@ -270,6 +268,71 @@ const IDE = () => {
270268
}
271269
};
272270

271+
// 🔑 파일 삭제 핸들러 (새로 추가된 기능)
272+
const handleDeleteFile = async (fileUUID, fileName) => {
273+
if (!isLoggedIn) {
274+
toast("로그인 후 이용 가능한 기능입니다.", 'toast-error');
275+
return;
276+
}
277+
278+
if (!window.confirm(`정말로 서버 파일 "${fileName}"을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.`)) {
279+
return;
280+
}
281+
282+
const token = localStorage.getItem('token');
283+
if (!token) {
284+
toast("인증 토큰이 없습니다. 다시 로그인해 주세요.", 'toast-error');
285+
return;
286+
}
287+
288+
try {
289+
// API 호출: 파일 삭제
290+
const response = await fetch(`${config.API_BASE_URL}/api/file/${fileUUID}`, {
291+
method: 'DELETE',
292+
headers: {
293+
'Authorization': `Bearer ${token}`,
294+
},
295+
});
296+
297+
if (!response.ok) {
298+
let errorMsg = `파일 삭제 실패: ${response.statusText}`;
299+
if (response.status === 404) errorMsg = "삭제할 파일을 서버에서 찾을 수 없습니다.";
300+
throw new Error(errorMsg);
301+
}
302+
303+
// 1. 로컬 상태 업데이트: savedFiles에서 제거
304+
setSavedFiles(prev => prev.filter(f => f.fileUUID !== fileUUID));
305+
306+
// 2. 현재 활성화 파일 상태 확인 및 리셋
307+
if (activeFileUUID === fileUUID) {
308+
const defaultLang = supportedLanguages.find(lang => lang.id === selectedLanguage) || supportedLanguages[0];
309+
const newDefaultFile = {
310+
name: "untitled.py",
311+
code: defaultLang.template,
312+
type: 'code',
313+
fileUUID: null,
314+
isServerFile: false
315+
};
316+
317+
// 삭제된 파일이 현재 편집 중이었습니다. 새 기본 파일로 상태 리셋.
318+
setCode(newDefaultFile.code);
319+
setFileName(newDefaultFile.name);
320+
setActiveFileUUID(null);
321+
setIsSaved(true);
322+
setCurrentFileType(newDefaultFile.type);
323+
setSelectedLanguage(getLanguageFromFileName(newDefaultFile.name));
324+
toast(`삭제된 파일이 현재 편집 중이었습니다. 기본 파일로 돌아갑니다.`, 'toast-warning');
325+
}
326+
327+
toast(`파일 "${fileName}"이(가) 성공적으로 삭제되었습니다.`);
328+
329+
} catch (error) {
330+
console.error('❌ 파일 삭제 중 오류:', error);
331+
toast(`파일 삭제 실패: ${error.message}`, 'toast-error');
332+
}
333+
};
334+
335+
273336
// 🆕 더미 파일 선택 핸들러 (원본 유지)
274337
const handleDummyFileSelect = (file) => {
275338
if (!isSaved) {
@@ -770,7 +833,6 @@ const IDE = () => {
770833

771834
// 3. 🐛 에디터 레이아웃 관련 useEffect (최종 안정화)
772835
useEffect(() => {
773-
// applyResizeObserverFix() 제거했으므로, 브라우저 resize 이벤트에만 의존합니다.
774836
const updateAllEditorLayouts = () => {
775837
if (editorRef.current) {
776838
window.requestAnimationFrame(() => {
@@ -788,7 +850,6 @@ const IDE = () => {
788850

789851
// 4. 🐛 사이드바 접힘/펼침 상태 변경 시 에디터 레이아웃 업데이트 (간소화 유지)
790852
useEffect(() => {
791-
// 애니메이션 완료 후 한 번만 레이아웃을 최종 업데이트
792853
const timeoutId = setTimeout(() => {
793854
if (editorRef.current) {
794855
try {
@@ -803,6 +864,13 @@ const IDE = () => {
803864
return () => clearTimeout(timeoutId);
804865
}, [isLeftPanelCollapsed]);
805866

867+
// 5. 🎨 사이드바 파일 목록 업데이트 시 Feather Icons 새로고침
868+
useEffect(() => {
869+
if (window.feather) {
870+
window.feather.replace();
871+
}
872+
}, [savedFiles, sidebarSections]); // 파일 목록이나 섹션 토글 시 업데이트
873+
806874
// 🎨 다크 모드 토글 시 에디터 테마 변경 (원본 유지)
807875
useEffect(() => {
808876
const observer = new MutationObserver((mutations) => {
@@ -827,7 +895,7 @@ const IDE = () => {
827895
return () => { observer.disconnect(); };
828896
}, [isDarkMode]);
829897

830-
// 🆕 ModernSidebar 렌더링 함수 (원본 유지)
898+
// 🆕 ModernSidebar 렌더링 함수 (삭제 버튼 추가)
831899
const renderModernSidebar = () => {
832900
const myServerFiles = savedFiles.filter(f => f.isServerFile && f.fileUUID);
833901
const myLocalFiles = savedFiles.filter(f => !f.isServerFile && !f.fileUUID);
@@ -861,7 +929,8 @@ const IDE = () => {
861929
{sidebarSections.myFiles ? '▼' : '▶'}
862930
</span>
863931
<i data-feather="folder" className="section-icon"></i>
864-
<span className="section-title">내 파일 ({myServerFiles.length + myLocalFiles.length}개)</span>
932+
{/* 💡 파일 개수 표시 제거 */}
933+
<span className="section-title">내 파일</span>
865934
</button>
866935

867936
{sidebarSections.myFiles && (
@@ -875,7 +944,19 @@ const IDE = () => {
875944
>
876945
{getFileIcon(file.name)}
877946
<span className="file-name">{file.name}</span>
878-
<span className="file-badge server-badge" title="서버 저장 파일">S</span>
947+
{/* ⛔ 뱃지 제거됨 */}
948+
949+
{/* 🔑 파일 삭제 버튼 추가 */}
950+
<button
951+
className="delete-file-button"
952+
onClick={(e) => {
953+
e.stopPropagation(); // 파일 선택 이벤트 방지
954+
handleDeleteFile(file.fileUUID, file.name);
955+
}}
956+
title="파일 삭제"
957+
>
958+
<i data-feather="trash-2"></i>
959+
</button>
879960
</div>
880961
))}
881962

@@ -888,7 +969,7 @@ const IDE = () => {
888969
>
889970
{getFileIcon(file.name)}
890971
<span className="file-name">{file.name}</span>
891-
<span className="file-badge local-badge" title="로컬 임시 파일">L</span>
972+
{/* ⛔ 뱃지 제거됨 */}
892973
</div>
893974
))}
894975
</div>

0 commit comments

Comments
 (0)