-
-
- 저장 용량
-
- {capacityPercentage.toFixed(1)}%
-
-
-
- {formatBytes(capacityBytes)} / 1 MB
-
-
-
+
+ >
+
+
+
+ {formatBytes(capacityBytes)} / 1 MB
+
+
+
);
}
diff --git a/apps/client/src/widgets/dialog/DuplicateDialog.tsx b/apps/client/src/widgets/dialog/DuplicateDialog.tsx
index 398ddc2e..13ec7f40 100644
--- a/apps/client/src/widgets/dialog/DuplicateDialog.tsx
+++ b/apps/client/src/widgets/dialog/DuplicateDialog.tsx
@@ -1,12 +1,18 @@
import {
- RadixDialog as Dialog,
- RadixDialogContent as DialogContent,
- RadixDialogDescription as DialogDescription,
- RadixDialogFooter as DialogFooter,
- RadixDialogHeader as DialogHeader,
- RadixDialogTitle as DialogTitle,
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogMedia,
+ AlertDialogTitle,
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
} from '@codejam/ui';
-import { Button } from '@codejam/ui';
+import { AlertCircle } from 'lucide-react';
import { useFileStore } from '@/stores/file';
import { extname, purename } from '@/shared/lib/file';
import { uploadFile } from '@/shared/lib/file';
@@ -14,113 +20,145 @@ import { uploadFile } from '@/shared/lib/file';
interface DuplicateDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
+ filename: string;
+ onClick: () => void;
+ file?: File;
+ onOverwrite?: () => void;
+ onAutoRename?: (newName: string) => void;
}
-export function DuplicateDialog({ open, onOpenChange }: DuplicateDialogProps) {
+export function DuplicateDialog({
+ open,
+ onOpenChange,
+ filename,
+ onClick,
+ file,
+ onOverwrite,
+ onAutoRename,
+}: DuplicateDialogProps) {
const overwriteFile = useFileStore((state) => state.overwriteFile);
const createFile = useFileStore((state) => state.createFile);
const getFileId = useFileStore((state) => state.getFileId);
const getFileNamesMap = useFileStore((state) => state.getFileNamesMap);
- const tempFiles = useFileStore((state) => state.tempFiles);
- const shiftTempFile = useFileStore((state) => state.shiftTempFile);
- const fileName = tempFiles && tempFiles[0] ? tempFiles[0].name : '';
+ const generateAutoName = (): string => {
+ const fileNamesMap = getFileNamesMap();
+ if (!fileNamesMap) {
+ return filename;
+ }
- const checkRepeat = () => {
- shiftTempFile();
- if (tempFiles.length === 0) {
- onOpenChange(false);
+ const ext = extname(filename);
+ const baseName = purename(filename).replace(/ \(\d+\)$/, '');
+
+ let maxNum = 0;
+ for (const name of Object.keys(fileNamesMap.toJSON())) {
+ if (extname(name) !== ext) continue;
+
+ const pure = purename(name);
+ if (pure === baseName) {
+ // baseName 자체가 존재 → 최소 (1)이 필요
+ maxNum = Math.max(maxNum, 0);
+ continue;
+ }
+
+ const escaped = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const match = pure.match(new RegExp(`^${escaped} \\((\\d+)\\)$`));
+ if (match) {
+ maxNum = Math.max(maxNum, parseInt(match[1]));
+ }
}
+
+ return `${baseName} (${maxNum + 1}).${ext}`;
};
const handleOverwrite = async () => {
- const fileId = getFileId(tempFiles[0].name);
- if (!fileId) {
+ if (onOverwrite) {
+ onOverwrite();
+ onOpenChange(false);
+ onClick();
return;
}
+ const fileId = getFileId(filename);
+ if (!fileId || !file) return;
+
try {
- const { content, type } = await uploadFile(tempFiles[0]);
+ const { content, type } = await uploadFile(file);
overwriteFile(fileId, content, type);
- checkRepeat();
+ onOpenChange(false);
+ onClick();
} catch (error) {
console.error('Failed to overwrite file:', error);
}
};
const handleRename = async () => {
- const fileId = getFileId(fileName);
+ const name = generateAutoName();
- if (!fileId) {
+ if (onAutoRename) {
+ onAutoRename(name);
+ onOpenChange(false);
+ onClick();
return;
}
- const newName = (): string => {
- const fileNamesMap = getFileNamesMap();
- if (!fileNamesMap) {
- return fileName;
- }
-
- const entries: [string, string][] = Object.entries(
- fileNamesMap.toJSON(),
- ).filter(([name]) => {
- const pure = purename(fileName);
- const size = pure.length;
- if (name.length < size) {
- return false;
- }
- return name.substring(0, size).trim() === pure.trim();
- });
-
- const top = entries.sort((a, b) => b[1].localeCompare(a[1]))[0];
- const ext = extname(top[0]);
- const pure = purename(top[0]);
- const fileMatch = pure.match(/.+\((\d+)\)$/i);
-
- if (!fileMatch) {
- return `${pure} (1).${ext}`;
- }
+ const fileId = getFileId(filename);
+ if (!fileId) return;
- return `${pure.replace(/\((\d+)\)$/i, `(${(parseInt(fileMatch[1]) + 1).toString()})`)}.${ext}`;
- };
+ if (!file) return;
try {
- const { content, type } = await uploadFile(tempFiles[0]);
- createFile(newName(), content, type);
- checkRepeat();
+ const { content, type } = await uploadFile(file);
+ createFile(name, content, type);
+ onOpenChange(false);
+ onClick();
} catch (error) {
- console.error('Failed to rename file:', error);
+ console.error('Failed to create file with new name:', error);
}
};
- const handleCancel = () => {
- checkRepeat();
- };
+ const expectedName = generateAutoName();
return (
-
+
+
+ (
+
+ 사본으로 저장
+
+ )}
+ />
+
+
+ 새로운 파일명:{' '}
+ {expectedName}
+
+
+
+
+
+
);
}
diff --git a/apps/client/src/widgets/dialog/DuplicateDialog_new.tsx b/apps/client/src/widgets/dialog/DuplicateDialog_new.tsx
deleted file mode 100644
index 0d31c656..00000000
--- a/apps/client/src/widgets/dialog/DuplicateDialog_new.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import {
- RadixDialog as Dialog,
- RadixDialogClose as DialogClose,
- RadixDialogContent as DialogContent,
- RadixDialogDescription as DialogDescription,
- RadixDialogFooter as DialogFooter,
- RadixDialogHeader as DialogHeader,
- RadixDialogTitle as DialogTitle,
-} from '@codejam/ui';
-import { Button } from '@codejam/ui';
-import { useFileStore } from '@/stores/file';
-import { extname, purename } from '@/shared/lib/file';
-import { uploadFile } from '@/shared/lib/file';
-
-interface DuplicateDialogProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- filename: string;
- onClick: () => void;
- file?: File;
-}
-
-export function DuplicateDialog({
- open,
- onOpenChange,
- filename,
- onClick,
- file,
-}: DuplicateDialogProps) {
- const overwriteFile = useFileStore((state) => state.overwriteFile);
- const createFile = useFileStore((state) => state.createFile);
- const getFileId = useFileStore((state) => state.getFileId);
- const getFileNamesMap = useFileStore((state) => state.getFileNamesMap);
-
- const handleOverwrite = async () => {
- const fileId = getFileId(filename);
- if (!fileId || !file) return;
-
- try {
- const { content, type } = await uploadFile(file);
- overwriteFile(fileId, content, type);
- onOpenChange(false);
- onClick();
- } catch (error) {
- console.error('Failed to overwrite file:', error);
- }
- };
-
- const handleRename = async () => {
- const fileId = getFileId(filename);
- if (!fileId) return;
-
- const newName = (): string => {
- const fileNamesMap = getFileNamesMap();
- if (!fileNamesMap) {
- return filename;
- }
-
- const entries: [string, string][] = Object.entries(
- fileNamesMap.toJSON(),
- ).filter(([name]) => {
- const pure = purename(filename);
- const size = pure.length;
- if (name.length < size) {
- return false;
- }
- return name.substring(0, size).trim() === pure.trim();
- });
-
- const top = entries.sort((a, b) => b[1].localeCompare(a[1]))[0];
- const ext = extname(top[0]);
- const pure = purename(top[0]);
- const fileMatch = pure.match(/.+\((\d+)\)$/i);
-
- if (!fileMatch) {
- return `${pure} (1).${ext}`;
- }
-
- return `${pure.replace(/\((\d+)\)$/i, `(${(parseInt(fileMatch[1]) + 1).toString()})`)}.${ext}`;
- };
-
- if (!file) return;
-
- try {
- const { content, type } = await uploadFile(file);
- const name = newName();
- createFile(name, content, type);
- onOpenChange(false);
- onClick();
- } catch (error) {
- console.error('Failed to create file with new name:', error);
- }
- };
-
- return (
-
- );
-}
diff --git a/apps/client/src/widgets/error-dialog/ErrorDialog.tsx b/apps/client/src/widgets/dialog/ErrorDialog.tsx
similarity index 67%
rename from apps/client/src/widgets/error-dialog/ErrorDialog.tsx
rename to apps/client/src/widgets/dialog/ErrorDialog.tsx
index e251c16c..24741321 100644
--- a/apps/client/src/widgets/error-dialog/ErrorDialog.tsx
+++ b/apps/client/src/widgets/dialog/ErrorDialog.tsx
@@ -1,12 +1,12 @@
import {
- RadixDialog as Dialog,
- RadixDialogContent as DialogContent,
- RadixDialogDescription as DialogDescription,
- RadixDialogFooter as DialogFooter,
- RadixDialogHeader as DialogHeader,
- RadixDialogTitle as DialogTitle,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ Button,
} from '@codejam/ui';
-import { Button } from '@codejam/ui';
import type { FormEvent } from 'react';
interface ErrorDialogProps {
@@ -32,11 +32,11 @@ export function ErrorDialog({
return (