@burdenoff/microfe-files 2026.525.3 → 2026.527.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/files/components/ActionMenu.js +3 -1
- package/dist/files/components/ActionMenu.js.map +1 -1
- package/dist/files/components/ContextMenu.js +16 -112
- package/dist/files/components/ContextMenu.js.map +1 -1
- package/dist/files/components/CopyDialog.js +8 -0
- package/dist/files/components/CopyDialog.js.map +1 -1
- package/dist/files/components/EmptyState.js +2 -0
- package/dist/files/components/EmptyState.js.map +1 -1
- package/dist/files/components/FileBrowser.js +6 -1
- package/dist/files/components/FileBrowser.js.map +1 -1
- package/dist/files/components/FileCard.js +4 -0
- package/dist/files/components/FileCard.js.map +1 -1
- package/dist/files/components/FileInfoPanel.js +2 -0
- package/dist/files/components/FileInfoPanel.js.map +1 -1
- package/dist/files/components/FileListItem.js +6 -0
- package/dist/files/components/FileListItem.js.map +1 -1
- package/dist/files/components/FilePreviewModal.js +9 -3
- package/dist/files/components/FilePreviewModal.js.map +1 -1
- package/dist/files/components/FileVersionPanel.js +5 -0
- package/dist/files/components/FileVersionPanel.js.map +1 -1
- package/dist/files/components/FolderCard.js +3 -0
- package/dist/files/components/FolderCard.js.map +1 -1
- package/dist/files/components/MoveDialog.js +8 -0
- package/dist/files/components/MoveDialog.js.map +1 -1
- package/dist/files/components/SearchBar.js +4 -0
- package/dist/files/components/SearchBar.js.map +1 -1
- package/dist/files/components/ShareDialog.js +109 -104
- package/dist/files/components/ShareDialog.js.map +1 -1
- package/dist/files/components/TabBar.js +1 -0
- package/dist/files/components/TabBar.js.map +1 -1
- package/dist/files/components/Toast.js +1 -0
- package/dist/files/components/Toast.js.map +1 -1
- package/dist/files/components/UploadQueue.js +5 -0
- package/dist/files/components/UploadQueue.js.map +1 -1
- package/dist/files/components/contextMenuItems.js +103 -0
- package/dist/files/components/contextMenuItems.js.map +1 -0
- package/dist/files/components/viewers/AudioPlayer.js +4 -1
- package/dist/files/components/viewers/AudioPlayer.js.map +1 -1
- package/dist/files/components/viewers/CodeViewer.js +2 -0
- package/dist/files/components/viewers/CodeViewer.js.map +1 -1
- package/dist/files/components/viewers/ImageViewer.js +4 -0
- package/dist/files/components/viewers/ImageViewer.js.map +1 -1
- package/dist/files/components/viewers/PDFViewer.js +2 -0
- package/dist/files/components/viewers/PDFViewer.js.map +1 -1
- package/dist/files/components/viewers/TextViewer.js +4 -2
- package/dist/files/components/viewers/TextViewer.js.map +1 -1
- package/dist/files/components/viewers/VideoPlayer.js +5 -1
- package/dist/files/components/viewers/VideoPlayer.js.map +1 -1
- package/dist/files/hooks/useBatchOperations.js.map +1 -1
- package/dist/files/hooks/useFileVersions.js.map +1 -1
- package/dist/files/pages/BrowserPage.js +18 -2
- package/dist/files/pages/BrowserPage.js.map +1 -1
- package/dist/files/pages/ShareAccessPage.js +6 -5
- package/dist/files/pages/ShareAccessPage.js.map +1 -1
- package/dist/files/utils/index.js +1 -1
- package/dist/files/utils/index.js.map +1 -1
- package/dist/files/utils/thumbnail.js.map +1 -1
- package/dist/files.js +2 -1
- package/dist/translations/en.js +259 -0
- package/dist/translations/en.js.map +1 -0
- package/package.json +1 -4
- package/dist/files/components/viewers/index.js +0 -6
|
@@ -5,7 +5,6 @@ import { VideoPlayer as r } from "./viewers/VideoPlayer.js";
|
|
|
5
5
|
import { AudioPlayer as i } from "./viewers/AudioPlayer.js";
|
|
6
6
|
import { CodeViewer as a } from "./viewers/CodeViewer.js";
|
|
7
7
|
import { TextViewer as o } from "./viewers/TextViewer.js";
|
|
8
|
-
import "./viewers/index.js";
|
|
9
8
|
import { useEffect as s } from "react";
|
|
10
9
|
import { Download as c, Share2 as l, Star as u, Trash2 as d, X as f } from "lucide-react";
|
|
11
10
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
@@ -71,13 +70,15 @@ function v({ file: v, onClose: y, onDownload: b, onShare: x, onStar: S, onDelete
|
|
|
71
70
|
}), /* @__PURE__ */ m("div", {
|
|
72
71
|
className: "flex items-center gap-2",
|
|
73
72
|
children: [
|
|
74
|
-
w?.map((e
|
|
73
|
+
w?.map((e) => /* @__PURE__ */ m("button", {
|
|
74
|
+
type: "button",
|
|
75
75
|
onClick: e.onClick,
|
|
76
76
|
className: "flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
77
77
|
title: e.label,
|
|
78
78
|
children: [e.icon, /* @__PURE__ */ p("span", { children: e.label })]
|
|
79
|
-
},
|
|
79
|
+
}, e.label)),
|
|
80
80
|
/* @__PURE__ */ p("button", {
|
|
81
|
+
type: "button",
|
|
81
82
|
onClick: b,
|
|
82
83
|
className: "p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
83
84
|
title: e(T, "files.common.actions.download", "Download"),
|
|
@@ -85,6 +86,7 @@ function v({ file: v, onClose: y, onDownload: b, onShare: x, onStar: S, onDelete
|
|
|
85
86
|
children: /* @__PURE__ */ p(c, { className: "size-5" })
|
|
86
87
|
}),
|
|
87
88
|
x && /* @__PURE__ */ p("button", {
|
|
89
|
+
type: "button",
|
|
88
90
|
onClick: x,
|
|
89
91
|
className: "p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
90
92
|
title: e(T, "files.common.actions.share", "Share"),
|
|
@@ -92,6 +94,7 @@ function v({ file: v, onClose: y, onDownload: b, onShare: x, onStar: S, onDelete
|
|
|
92
94
|
children: /* @__PURE__ */ p(l, { className: "size-5" })
|
|
93
95
|
}),
|
|
94
96
|
S && /* @__PURE__ */ p("button", {
|
|
97
|
+
type: "button",
|
|
95
98
|
onClick: S,
|
|
96
99
|
className: "p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
97
100
|
title: v.starred ? e(T, "files.preview.removeStar", "Remove star") : e(T, "files.preview.addStar", "Add star"),
|
|
@@ -99,6 +102,7 @@ function v({ file: v, onClose: y, onDownload: b, onShare: x, onStar: S, onDelete
|
|
|
99
102
|
children: /* @__PURE__ */ p(u, { className: `size-5 ${v.starred ? "fill-status-warning-text text-status-warning-text" : ""}` })
|
|
100
103
|
}),
|
|
101
104
|
C && /* @__PURE__ */ p("button", {
|
|
105
|
+
type: "button",
|
|
102
106
|
onClick: C,
|
|
103
107
|
className: "p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
104
108
|
title: e(T, "files.common.actions.delete", "Delete"),
|
|
@@ -106,6 +110,7 @@ function v({ file: v, onClose: y, onDownload: b, onShare: x, onStar: S, onDelete
|
|
|
106
110
|
children: /* @__PURE__ */ p(d, { className: "size-5" })
|
|
107
111
|
}),
|
|
108
112
|
/* @__PURE__ */ p("button", {
|
|
113
|
+
type: "button",
|
|
109
114
|
onClick: y,
|
|
110
115
|
className: "p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200 ml-2",
|
|
111
116
|
title: e(T, "files.common.actions.close", "Close"),
|
|
@@ -157,6 +162,7 @@ function v({ file: v, onClose: y, onDownload: b, onShare: x, onStar: S, onDelete
|
|
|
157
162
|
children: v.mimeType
|
|
158
163
|
}),
|
|
159
164
|
/* @__PURE__ */ p("button", {
|
|
165
|
+
type: "button",
|
|
160
166
|
onClick: b,
|
|
161
167
|
className: "px-4 py-2 bg-action-primary-bg text-text-inverse rounded-lg hover:bg-action-primary-bg-hover transition-colors duration-200",
|
|
162
168
|
children: e(T, "files.preview.downloadFile", "Download File")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilePreviewModal.js","names":[],"sources":["../../../src/files/components/FilePreviewModal.tsx"],"sourcesContent":["import { useEffect } from \"react\";\nimport { X, Download, Share2, Star, Trash2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport {\n ImageViewer,\n PDFViewer,\n VideoPlayer,\n AudioPlayer,\n CodeViewer,\n TextViewer,\n} from \"./viewers\";\n\ninterface FilePreviewModalProps {\n file: FileItem;\n onClose: () => void;\n onDownload: () => void;\n onShare?: () => void;\n onStar?: () => void;\n onDelete?: () => void;\n customActions?: Array<{\n label: string;\n icon: React.ReactNode;\n onClick: () => void;\n }>;\n}\n\ntype PreviewType =\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"code\"\n | \"text\"\n | \"unsupported\";\n\nfunction getFilePreviewType(mimeType: string, filename: string): PreviewType {\n // Image files\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n }\n\n // PDF files\n if (mimeType === \"application/pdf\") {\n return \"pdf\";\n }\n\n // Video files\n if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n }\n\n // Audio files\n if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n\n // Code files (by extension)\n const codeExtensions = [\n \"js\",\n \"jsx\",\n \"ts\",\n \"tsx\",\n \"py\",\n \"java\",\n \"cpp\",\n \"c\",\n \"cs\",\n \"rb\",\n \"go\",\n \"rs\",\n \"php\",\n \"html\",\n \"css\",\n \"scss\",\n \"json\",\n \"yaml\",\n \"yml\",\n \"xml\",\n \"sql\",\n \"sh\",\n \"bash\",\n \"vue\",\n \"swift\",\n \"kt\",\n \"r\",\n \"dart\",\n \"lua\",\n ];\n const ext = filename.split(\".\").pop()?.toLowerCase();\n if (ext && codeExtensions.includes(ext)) {\n return \"code\";\n }\n\n // Text files\n if (\n mimeType.startsWith(\"text/\") ||\n mimeType === \"application/json\" ||\n mimeType === \"application/xml\"\n ) {\n return \"text\";\n }\n\n return \"unsupported\";\n}\n\nfunction detectLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const languageMap: Record<string, string> = {\n js: \"javascript\",\n jsx: \"jsx\",\n ts: \"typescript\",\n tsx: \"tsx\",\n py: \"python\",\n java: \"java\",\n cpp: \"cpp\",\n c: \"c\",\n cs: \"csharp\",\n rb: \"ruby\",\n go: \"go\",\n rs: \"rust\",\n php: \"php\",\n html: \"html\",\n css: \"css\",\n json: \"json\",\n md: \"markdown\",\n yaml: \"yaml\",\n yml: \"yaml\",\n xml: \"xml\",\n sql: \"sql\",\n sh: \"bash\",\n };\n return languageMap[ext || \"\"] || \"text\";\n}\n\nexport function FilePreviewModal({\n file,\n onClose,\n onDownload,\n onShare,\n onStar,\n onDelete,\n customActions,\n}: FilePreviewModalProps) {\n const { t } = useI18n();\n const previewType = getFilePreviewType(file.mimeType, file.name);\n const url = file.downloadUrl || file.url;\n\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n onClose();\n }\n };\n\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }, [onClose]);\n\n const renderPreview = () => {\n switch (previewType) {\n case \"image\":\n return <ImageViewer file={file} url={url} />;\n case \"pdf\":\n return <PDFViewer file={file} url={url} />;\n case \"video\":\n return <VideoPlayer file={file} url={url} />;\n case \"audio\":\n return <AudioPlayer file={file} url={url} />;\n case \"code\":\n return (\n <CodeViewer\n file={file}\n url={url}\n language={detectLanguage(file.name)}\n />\n );\n case \"text\":\n return <TextViewer file={file} url={url} />;\n default:\n return (\n <div className=\"flex flex-col items-center justify-center h-full bg-bg-canvas p-8\">\n <div className=\"bg-bg-surface p-8 rounded-lg text-center max-w-md\">\n <p className=\"text-text-secondary mb-4\">\n {tWithFallback(\n t,\n \"files.preview.notAvailable\",\n \"Preview not available for this file type.\",\n )}\n </p>\n <p className=\"text-sm text-text-tertiary mb-6\">{file.mimeType}</p>\n <button\n onClick={onDownload}\n className=\"px-4 py-2 bg-action-primary-bg text-text-inverse rounded-lg hover:bg-action-primary-bg-hover transition-colors duration-200\"\n >\n {tWithFallback(\n t,\n \"files.preview.downloadFile\",\n \"Download File\",\n )}\n </button>\n </div>\n </div>\n );\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex flex-col z-[100] animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={`Preview ${file.name}`}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 bg-bg-elevated/90 backdrop-blur-sm border-b border-border-default\">\n <div className=\"flex items-center gap-4 flex-1 min-w-0\">\n <h2 className=\"text-base font-medium text-text-primary truncate\">\n {file.name}\n </h2>\n <span className=\"text-sm text-text-secondary flex-shrink-0\">\n {(file.size / 1024 / 1024).toFixed(2)} MB\n </span>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {/* Custom Actions */}\n {customActions?.map((action, index) => (\n <button\n key={index}\n onClick={action.onClick}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={action.label}\n >\n {action.icon}\n <span>{action.label}</span>\n </button>\n ))}\n\n {/* Standard Actions */}\n <button\n onClick={onDownload}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={tWithFallback(\n t,\n \"files.common.actions.download\",\n \"Download\",\n )}\n aria-label={tWithFallback(\n t,\n \"files.preview.downloadFile\",\n \"Download file\",\n )}\n >\n <Download className=\"size-5\" />\n </button>\n {onShare && (\n <button\n onClick={onShare}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={tWithFallback(t, \"files.common.actions.share\", \"Share\")}\n aria-label={tWithFallback(\n t,\n \"files.preview.shareFile\",\n \"Share file\",\n )}\n >\n <Share2 className=\"size-5\" />\n </button>\n )}\n {onStar && (\n <button\n onClick={onStar}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={\n file.starred\n ? tWithFallback(t, \"files.preview.removeStar\", \"Remove star\")\n : tWithFallback(t, \"files.preview.addStar\", \"Add star\")\n }\n aria-label={\n file.starred\n ? tWithFallback(t, \"files.preview.removeStar\", \"Remove star\")\n : tWithFallback(t, \"files.preview.addStar\", \"Add star\")\n }\n >\n <Star\n className={`size-5 ${\n file.starred\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"\"\n }`}\n />\n </button>\n )}\n {onDelete && (\n <button\n onClick={onDelete}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={tWithFallback(t, \"files.common.actions.delete\", \"Delete\")}\n aria-label={tWithFallback(\n t,\n \"files.preview.deleteFile\",\n \"Delete file\",\n )}\n >\n <Trash2 className=\"size-5\" />\n </button>\n )}\n <button\n onClick={onClose}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200 ml-2\"\n title={tWithFallback(t, \"files.common.actions.close\", \"Close\")}\n aria-label={tWithFallback(\n t,\n \"files.preview.close\",\n \"Close preview\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-hidden\">{renderPreview()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FilePreviewModal.js","names":[],"sources":["../../../src/files/components/FilePreviewModal.tsx"],"sourcesContent":["import { useEffect } from \"react\";\nimport { X, Download, Share2, Star, Trash2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport { ImageViewer } from \"./viewers/ImageViewer\";\nimport { PDFViewer } from \"./viewers/PDFViewer\";\nimport { VideoPlayer } from \"./viewers/VideoPlayer\";\nimport { AudioPlayer } from \"./viewers/AudioPlayer\";\nimport { CodeViewer } from \"./viewers/CodeViewer\";\nimport { TextViewer } from \"./viewers/TextViewer\";\n\ninterface FilePreviewModalProps {\n file: FileItem;\n onClose: () => void;\n onDownload: () => void;\n onShare?: () => void;\n onStar?: () => void;\n onDelete?: () => void;\n customActions?: Array<{\n label: string;\n icon: React.ReactNode;\n onClick: () => void;\n }>;\n}\n\ntype PreviewType =\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"code\"\n | \"text\"\n | \"unsupported\";\n\nfunction getFilePreviewType(mimeType: string, filename: string): PreviewType {\n // Image files\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n }\n\n // PDF files\n if (mimeType === \"application/pdf\") {\n return \"pdf\";\n }\n\n // Video files\n if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n }\n\n // Audio files\n if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n\n // Code files (by extension)\n const codeExtensions = [\n \"js\",\n \"jsx\",\n \"ts\",\n \"tsx\",\n \"py\",\n \"java\",\n \"cpp\",\n \"c\",\n \"cs\",\n \"rb\",\n \"go\",\n \"rs\",\n \"php\",\n \"html\",\n \"css\",\n \"scss\",\n \"json\",\n \"yaml\",\n \"yml\",\n \"xml\",\n \"sql\",\n \"sh\",\n \"bash\",\n \"vue\",\n \"swift\",\n \"kt\",\n \"r\",\n \"dart\",\n \"lua\",\n ];\n const ext = filename.split(\".\").pop()?.toLowerCase();\n if (ext && codeExtensions.includes(ext)) {\n return \"code\";\n }\n\n // Text files\n if (\n mimeType.startsWith(\"text/\") ||\n mimeType === \"application/json\" ||\n mimeType === \"application/xml\"\n ) {\n return \"text\";\n }\n\n return \"unsupported\";\n}\n\nfunction detectLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const languageMap: Record<string, string> = {\n js: \"javascript\",\n jsx: \"jsx\",\n ts: \"typescript\",\n tsx: \"tsx\",\n py: \"python\",\n java: \"java\",\n cpp: \"cpp\",\n c: \"c\",\n cs: \"csharp\",\n rb: \"ruby\",\n go: \"go\",\n rs: \"rust\",\n php: \"php\",\n html: \"html\",\n css: \"css\",\n json: \"json\",\n md: \"markdown\",\n yaml: \"yaml\",\n yml: \"yaml\",\n xml: \"xml\",\n sql: \"sql\",\n sh: \"bash\",\n };\n return languageMap[ext || \"\"] || \"text\";\n}\n\nexport function FilePreviewModal({\n file,\n onClose,\n onDownload,\n onShare,\n onStar,\n onDelete,\n customActions,\n}: FilePreviewModalProps) {\n const { t } = useI18n();\n const previewType = getFilePreviewType(file.mimeType, file.name);\n const url = file.downloadUrl || file.url;\n\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n onClose();\n }\n };\n\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }, [onClose]);\n\n const renderPreview = () => {\n switch (previewType) {\n case \"image\":\n return <ImageViewer file={file} url={url} />;\n case \"pdf\":\n return <PDFViewer file={file} url={url} />;\n case \"video\":\n return <VideoPlayer file={file} url={url} />;\n case \"audio\":\n return <AudioPlayer file={file} url={url} />;\n case \"code\":\n return (\n <CodeViewer\n file={file}\n url={url}\n language={detectLanguage(file.name)}\n />\n );\n case \"text\":\n return <TextViewer file={file} url={url} />;\n default:\n return (\n <div className=\"flex flex-col items-center justify-center h-full bg-bg-canvas p-8\">\n <div className=\"bg-bg-surface p-8 rounded-lg text-center max-w-md\">\n <p className=\"text-text-secondary mb-4\">\n {tWithFallback(\n t,\n \"files.preview.notAvailable\",\n \"Preview not available for this file type.\",\n )}\n </p>\n <p className=\"text-sm text-text-tertiary mb-6\">{file.mimeType}</p>\n <button\n type=\"button\"\n onClick={onDownload}\n className=\"px-4 py-2 bg-action-primary-bg text-text-inverse rounded-lg hover:bg-action-primary-bg-hover transition-colors duration-200\"\n >\n {tWithFallback(\n t,\n \"files.preview.downloadFile\",\n \"Download File\",\n )}\n </button>\n </div>\n </div>\n );\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex flex-col z-[100] animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={`Preview ${file.name}`}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 bg-bg-elevated/90 backdrop-blur-sm border-b border-border-default\">\n <div className=\"flex items-center gap-4 flex-1 min-w-0\">\n <h2 className=\"text-base font-medium text-text-primary truncate\">\n {file.name}\n </h2>\n <span className=\"text-sm text-text-secondary flex-shrink-0\">\n {(file.size / 1024 / 1024).toFixed(2)} MB\n </span>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {/* Custom Actions */}\n {customActions?.map((action) => (\n <button\n type=\"button\"\n key={action.label}\n onClick={action.onClick}\n className=\"flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={action.label}\n >\n {action.icon}\n <span>{action.label}</span>\n </button>\n ))}\n\n {/* Standard Actions */}\n <button\n type=\"button\"\n onClick={onDownload}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={tWithFallback(\n t,\n \"files.common.actions.download\",\n \"Download\",\n )}\n aria-label={tWithFallback(\n t,\n \"files.preview.downloadFile\",\n \"Download file\",\n )}\n >\n <Download className=\"size-5\" />\n </button>\n {onShare && (\n <button\n type=\"button\"\n onClick={onShare}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={tWithFallback(t, \"files.common.actions.share\", \"Share\")}\n aria-label={tWithFallback(\n t,\n \"files.preview.shareFile\",\n \"Share file\",\n )}\n >\n <Share2 className=\"size-5\" />\n </button>\n )}\n {onStar && (\n <button\n type=\"button\"\n onClick={onStar}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={\n file.starred\n ? tWithFallback(t, \"files.preview.removeStar\", \"Remove star\")\n : tWithFallback(t, \"files.preview.addStar\", \"Add star\")\n }\n aria-label={\n file.starred\n ? tWithFallback(t, \"files.preview.removeStar\", \"Remove star\")\n : tWithFallback(t, \"files.preview.addStar\", \"Add star\")\n }\n >\n <Star\n className={`size-5 ${\n file.starred\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"\"\n }`}\n />\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={onDelete}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n title={tWithFallback(t, \"files.common.actions.delete\", \"Delete\")}\n aria-label={tWithFallback(\n t,\n \"files.preview.deleteFile\",\n \"Delete file\",\n )}\n >\n <Trash2 className=\"size-5\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200 ml-2\"\n title={tWithFallback(t, \"files.common.actions.close\", \"Close\")}\n aria-label={tWithFallback(\n t,\n \"files.preview.close\",\n \"Close preview\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-hidden\">{renderPreview()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -76,6 +76,7 @@ var C = ({ file: C, onClose: w }) => {
|
|
|
76
76
|
children: C.name
|
|
77
77
|
})] })]
|
|
78
78
|
}), /* @__PURE__ */ b("button", {
|
|
79
|
+
type: "button",
|
|
79
80
|
onClick: w,
|
|
80
81
|
className: "p-2 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
81
82
|
"aria-label": r(T, "files.version.closeHistory", "Close version history"),
|
|
@@ -160,6 +161,7 @@ var C = ({ file: C, onClose: w }) => {
|
|
|
160
161
|
}), /* @__PURE__ */ x("div", {
|
|
161
162
|
className: "flex items-center gap-2",
|
|
162
163
|
children: [/* @__PURE__ */ b("button", {
|
|
164
|
+
type: "button",
|
|
163
165
|
onClick: (t) => {
|
|
164
166
|
t.stopPropagation(), V(e.id);
|
|
165
167
|
},
|
|
@@ -167,6 +169,7 @@ var C = ({ file: C, onClose: w }) => {
|
|
|
167
169
|
title: r(T, "files.version.downloadThisVersion", "Download this version"),
|
|
168
170
|
children: /* @__PURE__ */ b(p, { className: "size-4" })
|
|
169
171
|
}), !e.isCurrentVersion && /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b("button", {
|
|
172
|
+
type: "button",
|
|
170
173
|
onClick: (t) => {
|
|
171
174
|
t.stopPropagation(), R(e.id, e.versionNumber);
|
|
172
175
|
},
|
|
@@ -175,6 +178,7 @@ var C = ({ file: C, onClose: w }) => {
|
|
|
175
178
|
title: r(T, "files.version.restoreThisVersion", "Restore this version"),
|
|
176
179
|
children: F.isPending ? /* @__PURE__ */ b(m, { className: "size-4 animate-spin" }) : /* @__PURE__ */ b(h, { className: "size-4" })
|
|
177
180
|
}), /* @__PURE__ */ b("button", {
|
|
181
|
+
type: "button",
|
|
178
182
|
onClick: (t) => {
|
|
179
183
|
t.stopPropagation(), z(e.id, e.versionNumber);
|
|
180
184
|
},
|
|
@@ -200,6 +204,7 @@ var C = ({ file: C, onClose: w }) => {
|
|
|
200
204
|
" ",
|
|
201
205
|
r(T, "files.version.total", "total")
|
|
202
206
|
] }), /* @__PURE__ */ b("button", {
|
|
207
|
+
type: "button",
|
|
203
208
|
onClick: w,
|
|
204
209
|
className: "px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
205
210
|
children: r(T, "files.common.actions.close", "Close")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileVersionPanel.js","names":[],"sources":["../../../src/files/components/FileVersionPanel.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport {\n Clock,\n Download,\n RotateCcw,\n Trash2,\n X,\n CheckCircle2,\n AlertCircle,\n Loader2,\n Tag,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { formatFileSize, formatFileDate } from \"../utils\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport {\n useFileVersions,\n useRestoreFileVersion,\n useDeleteFileVersion,\n useVersionDownloadUrl,\n} from \"../hooks/useFileVersions\";\nimport { useToast } from \"../hooks/useToast\";\nimport { ConfirmDialog } from \"./ConfirmDialog\";\n\ninterface FileVersionPanelProps {\n file: FileItem;\n onClose: () => void;\n}\n\nexport const FileVersionPanel: FC<FileVersionPanelProps> = ({\n file,\n onClose,\n}) => {\n const { t } = useI18n();\n const [selectedVersionId, setSelectedVersionId] = useState<string | null>(\n null,\n );\n const [confirmAction, setConfirmAction] = useState<{\n title: string;\n description: string;\n confirmLabel: string;\n variant?: \"danger\" | \"default\";\n onConfirm: () => Promise<void>;\n loading?: boolean;\n } | null>(null);\n const toast = useToast();\n\n // Fetch versions\n const { data: versionsData, isLoading, error } = useFileVersions(file.id);\n const versions = versionsData?.versions || [];\n\n // Mutations\n const restoreMutation = useRestoreFileVersion();\n const deleteMutation = useDeleteFileVersion();\n const versionDownload = useVersionDownloadUrl();\n\n const handleRestore = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.restoreVersion\", \"Restore version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.restoreDescription\",\n \"This will create a new version with the restored content.\",\n ),\n confirmLabel: tWithFallback(t, \"files.version.restore\", \"Restore\"),\n onConfirm: async () => {\n try {\n await restoreMutation.mutateAsync({\n versionId,\n createNewVersion: true,\n changeMessage: `${tWithFallback(t, \"files.version.restoredFrom\", \"Restored from version\")} ${versionNumber}`,\n });\n toast.success(\n `${tWithFallback(t, \"files.version.versionRestored\", \"Version {{number}} restored successfully\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.restoreFailed\",\n \"Failed to restore version\",\n ),\n );\n }\n },\n });\n };\n\n const handleDelete = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.deleteVersion\", \"Delete version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.deleteDescription\",\n \"This action cannot be undone.\",\n ),\n confirmLabel: tWithFallback(\n t,\n \"files.version.deleteVersionBtn\",\n \"Delete version\",\n ),\n variant: \"danger\",\n onConfirm: async () => {\n try {\n await deleteMutation.mutateAsync(versionId);\n toast.success(\n `${tWithFallback(t, \"files.version.versionDeleted\", \"Version {{number}} deleted\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.deleteFailed\",\n \"Failed to delete version\",\n ),\n );\n }\n },\n });\n };\n\n const runConfirmAction = async () => {\n if (!confirmAction || confirmAction.loading) return;\n\n const action = confirmAction.onConfirm;\n setConfirmAction({ ...confirmAction, loading: true });\n await action();\n setConfirmAction(null);\n };\n\n const handleDownload = async (versionId: string) => {\n try {\n const downloadUrl = await versionDownload.getDownloadUrl(versionId);\n\n if (!downloadUrl) {\n throw new Error(\n tWithFallback(\n t,\n \"files.version.downloadUnavailable\",\n \"Version download URL is unavailable\",\n ),\n );\n }\n\n window.open(downloadUrl, \"_blank\", \"noopener,noreferrer\");\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tWithFallback(\n t,\n \"files.version.downloadFailed\",\n \"Failed to download version\",\n ),\n );\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 z-[110] bg-bg-overlay flex items-center justify-center p-4 animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.version.historyFor\",\n \"Version history for {{name}}\",\n { name: file.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-default\">\n <div className=\"flex items-center gap-3\">\n <Clock className=\"size-6 text-text-link\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(\n t,\n \"files.version.versionHistory\",\n \"Version History\",\n )}\n </h2>\n <p className=\"text-sm text-text-secondary\">{file.name}</p>\n </div>\n </div>\n <button\n onClick={onClose}\n className=\"p-2 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.version.closeHistory\",\n \"Close version history\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto p-6\">\n {isLoading && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 text-text-link animate-spin\" />\n </div>\n )}\n\n {error && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.loadFailed\",\n \"Failed to load version history\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <Clock className=\"size-12 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.noHistory\",\n \"No version history available\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length > 0 && (\n <div className=\"space-y-3\">\n {versions.map((version: (typeof versions)[number]) => (\n <div\n key={version.id}\n className={`p-4 border rounded-lg transition-all duration-200 hover:scale-[1.01] ${\n version.isCurrentVersion\n ? \"border-action-primary-border bg-bg-accent\"\n : selectedVersionId === version.id\n ? \"border-border-default bg-bg-muted\"\n : \"border-border-default hover:border-border-default hover:bg-action-ghost-bg-hover\"\n }`}\n onClick={() => setSelectedVersionId(version.id)}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Version info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {tWithFallback(t, \"files.version.version\", \"Version\")}{\" \"}\n {version.versionNumber}\n </span>\n\n {version.isCurrentVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-action-primary-bg text-text-inverse text-xs font-medium rounded\">\n <CheckCircle2 className=\"size-3\" />\n {tWithFallback(\n t,\n \"files.version.current\",\n \"Current\",\n )}\n </span>\n )}\n\n {version.isMajorVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-status-info-bg-subtle text-status-info-text text-xs font-medium rounded\">\n <Tag className=\"size-3\" />\n {tWithFallback(t, \"files.version.major\", \"Major\")}\n </span>\n )}\n\n {version.versionLabel && (\n <span className=\"inline-flex items-center px-2 py-0.5 bg-status-success-bg-subtle text-status-success-text text-xs font-medium rounded\">\n {version.versionLabel}\n </span>\n )}\n </div>\n\n {version.changeMessage && (\n <p className=\"text-sm text-text-primary mb-2\">\n {version.changeMessage}\n </p>\n )}\n\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span>{formatFileSize(version.size)}</span>\n <span>•</span>\n <span>{formatFileDate(version.createdAt)}</span>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleDownload(version.id);\n }}\n className=\"p-2 text-text-secondary hover:text-text-link hover:bg-bg-accent rounded-lg transition-colors duration-200\"\n title={tWithFallback(\n t,\n \"files.version.downloadThisVersion\",\n \"Download this version\",\n )}\n >\n <Download className=\"size-4\" />\n </button>\n\n {!version.isCurrentVersion && (\n <>\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleRestore(version.id, version.versionNumber);\n }}\n disabled={restoreMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-success-text hover:bg-status-success-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.restoreThisVersion\",\n \"Restore this version\",\n )}\n >\n {restoreMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCcw className=\"size-4\" />\n )}\n </button>\n\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(version.id, version.versionNumber);\n }}\n disabled={deleteMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.deleteThisVersion\",\n \"Delete this version\",\n )}\n >\n {deleteMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n </button>\n </>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"p-6 border-t border-border-default bg-bg-muted\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n {versionsData?.total || 0}{\" \"}\n {versionsData?.total !== 1\n ? tWithFallback(t, \"files.version.versions\", \"versions\")\n : tWithFallback(\n t,\n \"files.version.versionSingular\",\n \"version\",\n )}{\" \"}\n {tWithFallback(t, \"files.version.total\", \"total\")}\n </span>\n <button\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.close\", \"Close\")}\n </button>\n </div>\n </div>\n </div>\n\n {confirmAction && (\n <ConfirmDialog\n title={confirmAction.title}\n description={confirmAction.description}\n confirmLabel={confirmAction.confirmLabel}\n cancelLabel={tWithFallback(\n t,\n \"files.common.actions.cancel\",\n \"Cancel\",\n )}\n variant={confirmAction.variant}\n loading={confirmAction.loading}\n onConfirm={runConfirmAction}\n onCancel={() => {\n if (!confirmAction.loading) {\n setConfirmAction(null);\n }\n }}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FileVersionPanel.js","names":[],"sources":["../../../src/files/components/FileVersionPanel.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport {\n Clock,\n Download,\n RotateCcw,\n Trash2,\n X,\n CheckCircle2,\n AlertCircle,\n Loader2,\n Tag,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem } from \"../types\";\nimport { formatFileSize, formatFileDate } from \"../utils\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport {\n useFileVersions,\n useRestoreFileVersion,\n useDeleteFileVersion,\n useVersionDownloadUrl,\n} from \"../hooks/useFileVersions\";\nimport { useToast } from \"../hooks/useToast\";\nimport { ConfirmDialog } from \"./ConfirmDialog\";\n\ninterface FileVersionPanelProps {\n file: FileItem;\n onClose: () => void;\n}\n\nexport const FileVersionPanel: FC<FileVersionPanelProps> = ({\n file,\n onClose,\n}) => {\n const { t } = useI18n();\n const [selectedVersionId, setSelectedVersionId] = useState<string | null>(\n null,\n );\n const [confirmAction, setConfirmAction] = useState<{\n title: string;\n description: string;\n confirmLabel: string;\n variant?: \"danger\" | \"default\";\n onConfirm: () => Promise<void>;\n loading?: boolean;\n } | null>(null);\n const toast = useToast();\n\n // Fetch versions\n const { data: versionsData, isLoading, error } = useFileVersions(file.id);\n const versions = versionsData?.versions || [];\n\n // Mutations\n const restoreMutation = useRestoreFileVersion();\n const deleteMutation = useDeleteFileVersion();\n const versionDownload = useVersionDownloadUrl();\n\n const handleRestore = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.restoreVersion\", \"Restore version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.restoreDescription\",\n \"This will create a new version with the restored content.\",\n ),\n confirmLabel: tWithFallback(t, \"files.version.restore\", \"Restore\"),\n onConfirm: async () => {\n try {\n await restoreMutation.mutateAsync({\n versionId,\n createNewVersion: true,\n changeMessage: `${tWithFallback(t, \"files.version.restoredFrom\", \"Restored from version\")} ${versionNumber}`,\n });\n toast.success(\n `${tWithFallback(t, \"files.version.versionRestored\", \"Version {{number}} restored successfully\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.restoreFailed\",\n \"Failed to restore version\",\n ),\n );\n }\n },\n });\n };\n\n const handleDelete = async (versionId: string, versionNumber: number) => {\n setConfirmAction({\n title: `${tWithFallback(t, \"files.version.deleteVersion\", \"Delete version\")} ${versionNumber}`,\n description: tWithFallback(\n t,\n \"files.version.deleteDescription\",\n \"This action cannot be undone.\",\n ),\n confirmLabel: tWithFallback(\n t,\n \"files.version.deleteVersionBtn\",\n \"Delete version\",\n ),\n variant: \"danger\",\n onConfirm: async () => {\n try {\n await deleteMutation.mutateAsync(versionId);\n toast.success(\n `${tWithFallback(t, \"files.version.versionDeleted\", \"Version {{number}} deleted\", { number: versionNumber })}`,\n );\n } catch {\n toast.error(\n tWithFallback(\n t,\n \"files.version.deleteFailed\",\n \"Failed to delete version\",\n ),\n );\n }\n },\n });\n };\n\n const runConfirmAction = async () => {\n if (!confirmAction || confirmAction.loading) return;\n\n const action = confirmAction.onConfirm;\n setConfirmAction({ ...confirmAction, loading: true });\n await action();\n setConfirmAction(null);\n };\n\n const handleDownload = async (versionId: string) => {\n try {\n const downloadUrl = await versionDownload.getDownloadUrl(versionId);\n\n if (!downloadUrl) {\n throw new Error(\n tWithFallback(\n t,\n \"files.version.downloadUnavailable\",\n \"Version download URL is unavailable\",\n ),\n );\n }\n\n window.open(downloadUrl, \"_blank\", \"noopener,noreferrer\");\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tWithFallback(\n t,\n \"files.version.downloadFailed\",\n \"Failed to download version\",\n ),\n );\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 z-[110] bg-bg-overlay flex items-center justify-center p-4 animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.version.historyFor\",\n \"Version history for {{name}}\",\n { name: file.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-default\">\n <div className=\"flex items-center gap-3\">\n <Clock className=\"size-6 text-text-link\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(\n t,\n \"files.version.versionHistory\",\n \"Version History\",\n )}\n </h2>\n <p className=\"text-sm text-text-secondary\">{file.name}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-2 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.version.closeHistory\",\n \"Close version history\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto p-6\">\n {isLoading && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 text-text-link animate-spin\" />\n </div>\n )}\n\n {error && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.loadFailed\",\n \"Failed to load version history\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <Clock className=\"size-12 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.version.noHistory\",\n \"No version history available\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {!isLoading && !error && versions.length > 0 && (\n <div className=\"space-y-3\">\n {versions.map((version: (typeof versions)[number]) => (\n <div\n key={version.id}\n className={`p-4 border rounded-lg transition-all duration-200 hover:scale-[1.01] ${\n version.isCurrentVersion\n ? \"border-action-primary-border bg-bg-accent\"\n : selectedVersionId === version.id\n ? \"border-border-default bg-bg-muted\"\n : \"border-border-default hover:border-border-default hover:bg-action-ghost-bg-hover\"\n }`}\n onClick={() => setSelectedVersionId(version.id)}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Version info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {tWithFallback(t, \"files.version.version\", \"Version\")}{\" \"}\n {version.versionNumber}\n </span>\n\n {version.isCurrentVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-action-primary-bg text-text-inverse text-xs font-medium rounded\">\n <CheckCircle2 className=\"size-3\" />\n {tWithFallback(\n t,\n \"files.version.current\",\n \"Current\",\n )}\n </span>\n )}\n\n {version.isMajorVersion && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 bg-status-info-bg-subtle text-status-info-text text-xs font-medium rounded\">\n <Tag className=\"size-3\" />\n {tWithFallback(t, \"files.version.major\", \"Major\")}\n </span>\n )}\n\n {version.versionLabel && (\n <span className=\"inline-flex items-center px-2 py-0.5 bg-status-success-bg-subtle text-status-success-text text-xs font-medium rounded\">\n {version.versionLabel}\n </span>\n )}\n </div>\n\n {version.changeMessage && (\n <p className=\"text-sm text-text-primary mb-2\">\n {version.changeMessage}\n </p>\n )}\n\n <div className=\"flex items-center gap-4 text-xs text-text-secondary\">\n <span>{formatFileSize(version.size)}</span>\n <span>•</span>\n <span>{formatFileDate(version.createdAt)}</span>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDownload(version.id);\n }}\n className=\"p-2 text-text-secondary hover:text-text-link hover:bg-bg-accent rounded-lg transition-colors duration-200\"\n title={tWithFallback(\n t,\n \"files.version.downloadThisVersion\",\n \"Download this version\",\n )}\n >\n <Download className=\"size-4\" />\n </button>\n\n {!version.isCurrentVersion && (\n <>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleRestore(version.id, version.versionNumber);\n }}\n disabled={restoreMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-success-text hover:bg-status-success-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.restoreThisVersion\",\n \"Restore this version\",\n )}\n >\n {restoreMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCcw className=\"size-4\" />\n )}\n </button>\n\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(version.id, version.versionNumber);\n }}\n disabled={deleteMutation.isPending}\n className=\"p-2 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tWithFallback(\n t,\n \"files.version.deleteThisVersion\",\n \"Delete this version\",\n )}\n >\n {deleteMutation.isPending ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n </button>\n </>\n )}\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"p-6 border-t border-border-default bg-bg-muted\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n {versionsData?.total || 0}{\" \"}\n {versionsData?.total !== 1\n ? tWithFallback(t, \"files.version.versions\", \"versions\")\n : tWithFallback(\n t,\n \"files.version.versionSingular\",\n \"version\",\n )}{\" \"}\n {tWithFallback(t, \"files.version.total\", \"total\")}\n </span>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.close\", \"Close\")}\n </button>\n </div>\n </div>\n </div>\n\n {confirmAction && (\n <ConfirmDialog\n title={confirmAction.title}\n description={confirmAction.description}\n confirmLabel={confirmAction.confirmLabel}\n cancelLabel={tWithFallback(\n t,\n \"files.common.actions.cancel\",\n \"Cancel\",\n )}\n variant={confirmAction.variant}\n loading={confirmAction.loading}\n onConfirm={runConfirmAction}\n onCancel={() => {\n if (!confirmAction.loading) {\n setConfirmAction(null);\n }\n }}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -184,6 +184,7 @@ var x = ({ folder: x, isSelected: S = !1, isDragging: C = !1, isDropTarget: w =
|
|
|
184
184
|
className: "absolute bottom-3 right-3 flex items-center gap-1 bg-bg-elevated border border-border-subtle rounded shadow-elevation-2 p-1",
|
|
185
185
|
children: [
|
|
186
186
|
O && /* @__PURE__ */ v("button", {
|
|
187
|
+
type: "button",
|
|
187
188
|
onClick: (e) => Q(e, () => O(x)),
|
|
188
189
|
className: "p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors",
|
|
189
190
|
"aria-label": n(V, "files.card.share", "Share"),
|
|
@@ -191,6 +192,7 @@ var x = ({ folder: x, isSelected: S = !1, isDragging: C = !1, isDropTarget: w =
|
|
|
191
192
|
children: /* @__PURE__ */ v(h, { className: "size-4 text-text-primary" })
|
|
192
193
|
}),
|
|
193
194
|
j && /* @__PURE__ */ v("button", {
|
|
195
|
+
type: "button",
|
|
194
196
|
onClick: (e) => Q(e, () => j(x)),
|
|
195
197
|
className: "p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors",
|
|
196
198
|
"aria-label": n(V, "files.card.rename", "Rename"),
|
|
@@ -198,6 +200,7 @@ var x = ({ folder: x, isSelected: S = !1, isDragging: C = !1, isDropTarget: w =
|
|
|
198
200
|
children: /* @__PURE__ */ v(u, { className: "size-4 text-text-primary" })
|
|
199
201
|
}),
|
|
200
202
|
A && /* @__PURE__ */ v("button", {
|
|
203
|
+
type: "button",
|
|
201
204
|
onClick: (e) => Q(e, () => A(x)),
|
|
202
205
|
className: "p-1.5 hover:bg-status-success-bg-subtle rounded transition-colors",
|
|
203
206
|
"aria-label": n(V, "files.card.restore", "Restore"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FolderCard.js","names":[],"sources":["../../../src/files/components/FolderCard.tsx"],"sourcesContent":["import { type FC, useState, useMemo } from \"react\";\nimport {\n Folder,\n FolderOpen,\n Share2,\n Trash2,\n Edit,\n ChevronRight,\n Copy,\n FolderInput,\n Info,\n Activity,\n Star,\n RotateCcw,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FolderItem } from \"../types\";\nimport { formatFileDate } from \"../utils\";\nimport { useFilesStore } from \"../store/filesStore\";\nimport { ActionMenu } from \"./ActionMenu\";\nimport { getDragOperation, canDropOnFolder } from \"../hooks/useDragDrop\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport type { DragData } from \"../hooks/useDragDrop\";\n\ninterface FolderCardProps {\n folder: FolderItem;\n isSelected?: boolean;\n isDragging?: boolean;\n isDropTarget?: boolean;\n draggedItem?: DragData | null;\n onSelect?: (folderId: string) => void;\n onOpen?: (folder: FolderItem) => void;\n onShare?: (folder: FolderItem) => void;\n onDelete?: (folder: FolderItem) => void;\n onRestore?: (folder: FolderItem) => void;\n onRename?: (folder: FolderItem) => void;\n onFavorite?: (folder: FolderItem) => void;\n onCopy?: (folder: FolderItem) => void;\n onMove?: (folder: FolderItem) => void;\n onInfo?: (folder: FolderItem) => void;\n onActivity?: (folder: FolderItem) => void;\n onDragStart?: (folder: FolderItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n onDrop?: (folder: FolderItem) => void;\n onContextMenu?: (folder: FolderItem, x: number, y: number) => void;\n}\n\ninterface DraggableFolderCardProps extends Omit<\n FolderCardProps,\n \"onDragStart\" | \"onDragEnd\" | \"onDrop\"\n> {\n onDragStart?: (folder: FolderItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n onDrop?: (folder: FolderItem) => void;\n onContextMenu?: (folder: FolderItem, x: number, y: number) => void;\n}\n\nexport const FolderCard: FC<DraggableFolderCardProps> = ({\n folder,\n isSelected = false,\n isDragging = false,\n isDropTarget = false,\n draggedItem = null,\n onSelect,\n onOpen,\n onShare,\n onDelete,\n onRestore,\n onRename,\n onFavorite,\n onCopy,\n onMove,\n onInfo,\n onActivity,\n onDragStart,\n onDragEnd,\n onDrop,\n onContextMenu,\n}) => {\n const { t } = useI18n();\n const [showActions, setShowActions] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const { toggleFolderSelection } = useFilesStore();\n\n const canDrop = canDropOnFolder(draggedItem, folder);\n const isValidDropTarget = isDropTarget && canDrop;\n\n const handleClick = () => {\n if (onOpen) {\n onOpen(folder);\n }\n };\n\n const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n e.stopPropagation();\n if (onSelect) {\n onSelect(folder.id);\n } else {\n toggleFolderSelection(folder.id);\n }\n };\n\n const handleActionClick = (e: React.MouseEvent, action: () => void) => {\n e.stopPropagation();\n action();\n setShowActions(false);\n };\n\n const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {\n e.dataTransfer.effectAllowed = \"copyMove\";\n e.dataTransfer.setData(\"application/x-file-id\", folder.id);\n e.dataTransfer.setData(\"application/x-file-type\", \"folder\");\n const operation = getDragOperation(e);\n e.dataTransfer.setData(\"application/x-drag-operation\", operation);\n onDragStart?.(folder, operation);\n };\n\n const handleDragEnd = () => {\n onDragEnd?.();\n };\n\n const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n if (isValidDropTarget) {\n e.preventDefault();\n e.dataTransfer.dropEffect =\n draggedItem?.operation === \"copy\" ? \"copy\" : \"move\";\n }\n };\n\n const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n if (isValidDropTarget) {\n e.preventDefault();\n }\n };\n\n const handleDragLeave = () => {};\n\n const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n if (isValidDropTarget) {\n onDrop?.(folder);\n }\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu?.(folder, e.clientX, e.clientY);\n };\n\n const FolderIcon = isHovered ? FolderOpen : Folder;\n\n // Memoize menu items to prevent re-renders\n const menuItems = useMemo(\n () => [\n ...(onShare\n ? [\n {\n label: tWithFallback(t, \"files.card.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: () => onShare(folder),\n },\n ]\n : []),\n ...(onCopy\n ? [\n {\n label: tWithFallback(t, \"files.card.makeCopy\", \"Make a copy\"),\n icon: <Copy className=\"size-4\" />,\n onClick: () => onCopy(folder),\n },\n ]\n : []),\n ...(onMove\n ? [\n {\n label: tWithFallback(t, \"files.card.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: () => onMove(folder),\n },\n ]\n : []),\n ...(onRename\n ? [\n {\n label: tWithFallback(t, \"files.card.rename\", \"Rename\"),\n icon: <Edit className=\"size-4\" />,\n onClick: () => onRename(folder),\n divider: true,\n },\n ]\n : []),\n ...(onFavorite\n ? [\n {\n label: folder.starred\n ? tWithFallback(\n t,\n \"files.card.removeFromStarred\",\n \"Remove from starred\",\n )\n : tWithFallback(t, \"files.card.addToStarred\", \"Add to starred\"),\n icon: <Star className=\"size-4\" />,\n onClick: () => onFavorite(folder),\n },\n ]\n : []),\n ...(onInfo\n ? [\n {\n label: tWithFallback(t, \"files.card.details\", \"Details\"),\n icon: <Info className=\"size-4\" />,\n onClick: () => onInfo(folder),\n },\n ]\n : []),\n ...(onActivity\n ? [\n {\n label: tWithFallback(t, \"files.card.activity\", \"Activity\"),\n icon: <Activity className=\"size-4\" />,\n onClick: () => onActivity(folder),\n },\n ]\n : []),\n ...(onDelete\n ? [\n {\n label: tWithFallback(t, \"files.card.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: () => onDelete(folder),\n variant: \"danger\" as const,\n divider: true,\n },\n ]\n : []),\n ...(onRestore\n ? [\n {\n label: tWithFallback(t, \"files.card.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: () => onRestore(folder),\n },\n ]\n : []),\n ],\n [\n folder,\n onShare,\n onCopy,\n onMove,\n onRename,\n onFavorite,\n onInfo,\n onActivity,\n onDelete,\n onRestore,\n t,\n ],\n );\n\n return (\n <div\n className={`@container group relative cursor-pointer overflow-hidden rounded-card border bg-bg-surface shadow-elevation-1 transition-shadow hover:shadow-elevation-2 focus-within:shadow-elevation-2 ${isDragging ? \"opacity-50\" : \"\"} ${isValidDropTarget ? \"border-action-primary-bg ring-2 ring-action-primary-bg ring-offset-2\" : \"border-border-subtle\"}`}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n onMouseEnter={() => {\n setShowActions(true);\n setIsHovered(true);\n }}\n onMouseLeave={() => {\n setShowActions(false);\n setIsHovered(false);\n }}\n onFocus={() => {\n setShowActions(true);\n setIsHovered(true);\n }}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n setShowActions(false);\n setIsHovered(false);\n }\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n handleClick();\n }\n }}\n draggable={Boolean(onDragStart)}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragOver={handleDragOver}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n role=\"button\"\n tabIndex={0}\n aria-label={`${tWithFallback(t, \"files.browser.openFolder\", \"Open folder\")} ${folder.name}`}\n >\n {/* Selection checkbox */}\n <div\n className={`absolute top-2 left-2 z-10 transition-opacity ${\n isSelected\n ? \"opacity-100\"\n : \"opacity-0 group-hover:opacity-100 group-focus-within:opacity-100\"\n }`}\n >\n <input\n type=\"checkbox\"\n checked={isSelected}\n onChange={handleCheckboxChange}\n className=\"size-4 rounded border-border-default bg-bg-surface text-action-primary-bg focus:ring-2 focus:ring-action-primary-bg focus:ring-offset-2\"\n aria-label={`Select ${folder.name}`}\n />\n </div>\n\n {/* Favorite indicator */}\n {folder.starred && (\n <div className=\"absolute top-2 right-2 z-10\">\n <Star className=\"size-4 fill-yellow-400 text-yellow-400\" />\n </div>\n )}\n\n {/* Folder icon section */}\n <div className=\"aspect-[4/3] bg-bg-sunken flex items-center justify-center\">\n <FolderIcon className=\"size-10 @md:size-14 text-action-primary-bg\" />\n </div>\n\n {/* Folder info */}\n <div className=\"p-3 @md:p-4\">\n <div className=\"flex items-center justify-between gap-2 mb-1\">\n <h3 className=\"font-text-md font-weight-medium text-text-primary truncate flex-1\">\n {folder.name}\n </h3>\n <ChevronRight className=\"size-4 text-text-muted flex-shrink-0\" />\n </div>\n\n <div className=\"flex items-center gap-2 text-text-secondary font-text-sm\">\n <span>\n {folder.filesCount ?? 0}{\" \"}\n {(folder.filesCount ?? 0) === 1\n ? tWithFallback(t, \"files.folder.item\", \"item\")\n : tWithFallback(t, \"files.browser.items\", \"items\")}\n </span>\n <span className=\"hidden @sm:inline text-border-subtle\">•</span>\n <span className=\"hidden @sm:inline\">\n {formatFileDate(folder.updatedAt)}\n </span>\n </div>\n\n {/* Extended metadata on larger containers */}\n {folder.createdBy && (\n <div className=\"hidden @md:flex items-center gap-2 mt-2 text-text-muted font-text-xs\">\n <div className=\"flex items-center gap-1\">\n {folder.createdBy.avatar && (\n <img\n src={folder.createdBy.avatar}\n alt={folder.createdBy.name}\n className=\"size-4 rounded-full\"\n />\n )}\n <span>{folder.createdBy.name}</span>\n </div>\n </div>\n )}\n </div>\n\n {/* Action buttons (on hover) with dropdown menu */}\n {showActions && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-1 bg-bg-elevated border border-border-subtle rounded shadow-elevation-2 p-1\">\n {onShare && (\n <button\n onClick={(e) => handleActionClick(e, () => onShare(folder))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.share\", \"Share\")}\n title={tWithFallback(t, \"files.card.share\", \"Share\")}\n >\n <Share2 className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onRename && (\n <button\n onClick={(e) => handleActionClick(e, () => onRename(folder))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n title={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n >\n <Edit className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onRestore && (\n <button\n onClick={(e) => handleActionClick(e, () => onRestore(folder))}\n className=\"p-1.5 hover:bg-status-success-bg-subtle rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n title={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n >\n <RotateCcw className=\"size-4 text-status-success-text\" />\n </button>\n )}\n\n {/* More options dropdown */}\n <div onClick={(e) => e.stopPropagation()}>\n <ActionMenu\n items={menuItems}\n align=\"right\"\n triggerClassName=\"p-1.5\"\n isOpen={menuOpen}\n onToggle={setMenuOpen}\n />\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FolderCard.js","names":[],"sources":["../../../src/files/components/FolderCard.tsx"],"sourcesContent":["import { type FC, useState, useMemo } from \"react\";\nimport {\n Folder,\n FolderOpen,\n Share2,\n Trash2,\n Edit,\n ChevronRight,\n Copy,\n FolderInput,\n Info,\n Activity,\n Star,\n RotateCcw,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FolderItem } from \"../types\";\nimport { formatFileDate } from \"../utils\";\nimport { useFilesStore } from \"../store/filesStore\";\nimport { ActionMenu } from \"./ActionMenu\";\nimport { getDragOperation, canDropOnFolder } from \"../hooks/useDragDrop\";\nimport { tWithFallback } from \"../utils/i18n\";\nimport type { DragData } from \"../hooks/useDragDrop\";\n\ninterface FolderCardProps {\n folder: FolderItem;\n isSelected?: boolean;\n isDragging?: boolean;\n isDropTarget?: boolean;\n draggedItem?: DragData | null;\n onSelect?: (folderId: string) => void;\n onOpen?: (folder: FolderItem) => void;\n onShare?: (folder: FolderItem) => void;\n onDelete?: (folder: FolderItem) => void;\n onRestore?: (folder: FolderItem) => void;\n onRename?: (folder: FolderItem) => void;\n onFavorite?: (folder: FolderItem) => void;\n onCopy?: (folder: FolderItem) => void;\n onMove?: (folder: FolderItem) => void;\n onInfo?: (folder: FolderItem) => void;\n onActivity?: (folder: FolderItem) => void;\n onDragStart?: (folder: FolderItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n onDrop?: (folder: FolderItem) => void;\n onContextMenu?: (folder: FolderItem, x: number, y: number) => void;\n}\n\ninterface DraggableFolderCardProps extends Omit<\n FolderCardProps,\n \"onDragStart\" | \"onDragEnd\" | \"onDrop\"\n> {\n onDragStart?: (folder: FolderItem, operation: \"move\" | \"copy\") => void;\n onDragEnd?: () => void;\n onDrop?: (folder: FolderItem) => void;\n onContextMenu?: (folder: FolderItem, x: number, y: number) => void;\n}\n\nexport const FolderCard: FC<DraggableFolderCardProps> = ({\n folder,\n isSelected = false,\n isDragging = false,\n isDropTarget = false,\n draggedItem = null,\n onSelect,\n onOpen,\n onShare,\n onDelete,\n onRestore,\n onRename,\n onFavorite,\n onCopy,\n onMove,\n onInfo,\n onActivity,\n onDragStart,\n onDragEnd,\n onDrop,\n onContextMenu,\n}) => {\n const { t } = useI18n();\n const [showActions, setShowActions] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const { toggleFolderSelection } = useFilesStore();\n\n const canDrop = canDropOnFolder(draggedItem, folder);\n const isValidDropTarget = isDropTarget && canDrop;\n\n const handleClick = () => {\n if (onOpen) {\n onOpen(folder);\n }\n };\n\n const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n e.stopPropagation();\n if (onSelect) {\n onSelect(folder.id);\n } else {\n toggleFolderSelection(folder.id);\n }\n };\n\n const handleActionClick = (e: React.MouseEvent, action: () => void) => {\n e.stopPropagation();\n action();\n setShowActions(false);\n };\n\n const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {\n e.dataTransfer.effectAllowed = \"copyMove\";\n e.dataTransfer.setData(\"application/x-file-id\", folder.id);\n e.dataTransfer.setData(\"application/x-file-type\", \"folder\");\n const operation = getDragOperation(e);\n e.dataTransfer.setData(\"application/x-drag-operation\", operation);\n onDragStart?.(folder, operation);\n };\n\n const handleDragEnd = () => {\n onDragEnd?.();\n };\n\n const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n if (isValidDropTarget) {\n e.preventDefault();\n e.dataTransfer.dropEffect =\n draggedItem?.operation === \"copy\" ? \"copy\" : \"move\";\n }\n };\n\n const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n if (isValidDropTarget) {\n e.preventDefault();\n }\n };\n\n const handleDragLeave = () => {};\n\n const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n if (isValidDropTarget) {\n onDrop?.(folder);\n }\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onContextMenu?.(folder, e.clientX, e.clientY);\n };\n\n const FolderIcon = isHovered ? FolderOpen : Folder;\n\n // Memoize menu items to prevent re-renders\n const menuItems = useMemo(\n () => [\n ...(onShare\n ? [\n {\n label: tWithFallback(t, \"files.card.share\", \"Share\"),\n icon: <Share2 className=\"size-4\" />,\n onClick: () => onShare(folder),\n },\n ]\n : []),\n ...(onCopy\n ? [\n {\n label: tWithFallback(t, \"files.card.makeCopy\", \"Make a copy\"),\n icon: <Copy className=\"size-4\" />,\n onClick: () => onCopy(folder),\n },\n ]\n : []),\n ...(onMove\n ? [\n {\n label: tWithFallback(t, \"files.card.moveTo\", \"Move to\"),\n icon: <FolderInput className=\"size-4\" />,\n onClick: () => onMove(folder),\n },\n ]\n : []),\n ...(onRename\n ? [\n {\n label: tWithFallback(t, \"files.card.rename\", \"Rename\"),\n icon: <Edit className=\"size-4\" />,\n onClick: () => onRename(folder),\n divider: true,\n },\n ]\n : []),\n ...(onFavorite\n ? [\n {\n label: folder.starred\n ? tWithFallback(\n t,\n \"files.card.removeFromStarred\",\n \"Remove from starred\",\n )\n : tWithFallback(t, \"files.card.addToStarred\", \"Add to starred\"),\n icon: <Star className=\"size-4\" />,\n onClick: () => onFavorite(folder),\n },\n ]\n : []),\n ...(onInfo\n ? [\n {\n label: tWithFallback(t, \"files.card.details\", \"Details\"),\n icon: <Info className=\"size-4\" />,\n onClick: () => onInfo(folder),\n },\n ]\n : []),\n ...(onActivity\n ? [\n {\n label: tWithFallback(t, \"files.card.activity\", \"Activity\"),\n icon: <Activity className=\"size-4\" />,\n onClick: () => onActivity(folder),\n },\n ]\n : []),\n ...(onDelete\n ? [\n {\n label: tWithFallback(t, \"files.card.delete\", \"Delete\"),\n icon: <Trash2 className=\"size-4\" />,\n onClick: () => onDelete(folder),\n variant: \"danger\" as const,\n divider: true,\n },\n ]\n : []),\n ...(onRestore\n ? [\n {\n label: tWithFallback(t, \"files.card.restore\", \"Restore\"),\n icon: <RotateCcw className=\"size-4\" />,\n onClick: () => onRestore(folder),\n },\n ]\n : []),\n ],\n [\n folder,\n onShare,\n onCopy,\n onMove,\n onRename,\n onFavorite,\n onInfo,\n onActivity,\n onDelete,\n onRestore,\n t,\n ],\n );\n\n return (\n <div\n className={`@container group relative cursor-pointer overflow-hidden rounded-card border bg-bg-surface shadow-elevation-1 transition-shadow hover:shadow-elevation-2 focus-within:shadow-elevation-2 ${isDragging ? \"opacity-50\" : \"\"} ${isValidDropTarget ? \"border-action-primary-bg ring-2 ring-action-primary-bg ring-offset-2\" : \"border-border-subtle\"}`}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n onMouseEnter={() => {\n setShowActions(true);\n setIsHovered(true);\n }}\n onMouseLeave={() => {\n setShowActions(false);\n setIsHovered(false);\n }}\n onFocus={() => {\n setShowActions(true);\n setIsHovered(true);\n }}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n setShowActions(false);\n setIsHovered(false);\n }\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n handleClick();\n }\n }}\n draggable={Boolean(onDragStart)}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragOver={handleDragOver}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n role=\"button\"\n tabIndex={0}\n aria-label={`${tWithFallback(t, \"files.browser.openFolder\", \"Open folder\")} ${folder.name}`}\n >\n {/* Selection checkbox */}\n <div\n className={`absolute top-2 left-2 z-10 transition-opacity ${\n isSelected\n ? \"opacity-100\"\n : \"opacity-0 group-hover:opacity-100 group-focus-within:opacity-100\"\n }`}\n >\n <input\n type=\"checkbox\"\n checked={isSelected}\n onChange={handleCheckboxChange}\n className=\"size-4 rounded border-border-default bg-bg-surface text-action-primary-bg focus:ring-2 focus:ring-action-primary-bg focus:ring-offset-2\"\n aria-label={`Select ${folder.name}`}\n />\n </div>\n\n {/* Favorite indicator */}\n {folder.starred && (\n <div className=\"absolute top-2 right-2 z-10\">\n <Star className=\"size-4 fill-yellow-400 text-yellow-400\" />\n </div>\n )}\n\n {/* Folder icon section */}\n <div className=\"aspect-[4/3] bg-bg-sunken flex items-center justify-center\">\n <FolderIcon className=\"size-10 @md:size-14 text-action-primary-bg\" />\n </div>\n\n {/* Folder info */}\n <div className=\"p-3 @md:p-4\">\n <div className=\"flex items-center justify-between gap-2 mb-1\">\n <h3 className=\"font-text-md font-weight-medium text-text-primary truncate flex-1\">\n {folder.name}\n </h3>\n <ChevronRight className=\"size-4 text-text-muted flex-shrink-0\" />\n </div>\n\n <div className=\"flex items-center gap-2 text-text-secondary font-text-sm\">\n <span>\n {folder.filesCount ?? 0}{\" \"}\n {(folder.filesCount ?? 0) === 1\n ? tWithFallback(t, \"files.folder.item\", \"item\")\n : tWithFallback(t, \"files.browser.items\", \"items\")}\n </span>\n <span className=\"hidden @sm:inline text-border-subtle\">•</span>\n <span className=\"hidden @sm:inline\">\n {formatFileDate(folder.updatedAt)}\n </span>\n </div>\n\n {/* Extended metadata on larger containers */}\n {folder.createdBy && (\n <div className=\"hidden @md:flex items-center gap-2 mt-2 text-text-muted font-text-xs\">\n <div className=\"flex items-center gap-1\">\n {folder.createdBy.avatar && (\n <img\n src={folder.createdBy.avatar}\n alt={folder.createdBy.name}\n className=\"size-4 rounded-full\"\n />\n )}\n <span>{folder.createdBy.name}</span>\n </div>\n </div>\n )}\n </div>\n\n {/* Action buttons (on hover) with dropdown menu */}\n {showActions && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-1 bg-bg-elevated border border-border-subtle rounded shadow-elevation-2 p-1\">\n {onShare && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onShare(folder))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.share\", \"Share\")}\n title={tWithFallback(t, \"files.card.share\", \"Share\")}\n >\n <Share2 className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onRename && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onRename(folder))}\n className=\"p-1.5 hover:bg-action-ghost-bg-hover rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n title={tWithFallback(t, \"files.card.rename\", \"Rename\")}\n >\n <Edit className=\"size-4 text-text-primary\" />\n </button>\n )}\n\n {onRestore && (\n <button\n type=\"button\"\n onClick={(e) => handleActionClick(e, () => onRestore(folder))}\n className=\"p-1.5 hover:bg-status-success-bg-subtle rounded transition-colors\"\n aria-label={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n title={tWithFallback(t, \"files.card.restore\", \"Restore\")}\n >\n <RotateCcw className=\"size-4 text-status-success-text\" />\n </button>\n )}\n\n {/* More options dropdown */}\n <div onClick={(e) => e.stopPropagation()}>\n <ActionMenu\n items={menuItems}\n align=\"right\"\n triggerClassName=\"p-1.5\"\n isOpen={menuOpen}\n onToggle={setMenuOpen}\n />\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -37,6 +37,7 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
|
|
|
37
37
|
className: "text-lg font-semibold text-text-primary",
|
|
38
38
|
children: e(g, "files.moveDialog.title", "Move \"{{name}}\"", { name: d.name })
|
|
39
39
|
}), /* @__PURE__ */ c("button", {
|
|
40
|
+
type: "button",
|
|
40
41
|
onClick: p,
|
|
41
42
|
className: "p-1 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200",
|
|
42
43
|
"aria-label": e(g, "files.moveDialog.actions.close", "Close move dialog"),
|
|
@@ -48,12 +49,14 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
|
|
|
48
49
|
children: /* @__PURE__ */ l("div", {
|
|
49
50
|
className: "flex items-center gap-2 text-sm overflow-x-auto",
|
|
50
51
|
children: [/* @__PURE__ */ l("button", {
|
|
52
|
+
type: "button",
|
|
51
53
|
onClick: () => D(-1),
|
|
52
54
|
className: "flex items-center gap-1.5 text-text-primary hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 whitespace-nowrap",
|
|
53
55
|
children: [/* @__PURE__ */ c(a, { className: "size-4" }), /* @__PURE__ */ c("span", { children: e(g, "files.browser.rootLabel", "My Drive") })]
|
|
54
56
|
}), y.map((e, t) => /* @__PURE__ */ l("div", {
|
|
55
57
|
className: "flex items-center gap-2 whitespace-nowrap",
|
|
56
58
|
children: [/* @__PURE__ */ c(r, { className: "size-4 text-text-tertiary" }), /* @__PURE__ */ c("button", {
|
|
59
|
+
type: "button",
|
|
57
60
|
onClick: () => D(t),
|
|
58
61
|
className: `hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${t === y.length - 1 ? "text-text-primary font-medium" : "text-text-secondary"}`,
|
|
59
62
|
children: e.name
|
|
@@ -75,6 +78,7 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
|
|
|
75
78
|
}) : /* @__PURE__ */ l("div", {
|
|
76
79
|
className: "space-y-1",
|
|
77
80
|
children: [/* @__PURE__ */ l("button", {
|
|
81
|
+
type: "button",
|
|
78
82
|
onClick: () => S(_),
|
|
79
83
|
className: `w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${x === _ ? "bg-bg-accent border-2 border-action-primary-border" : "hover:bg-action-ghost-bg-hover border-2 border-transparent"}`,
|
|
80
84
|
children: [/* @__PURE__ */ c(i, { className: "size-5 text-text-link" }), /* @__PURE__ */ c("span", {
|
|
@@ -84,6 +88,7 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
|
|
|
84
88
|
}), k.map((t) => /* @__PURE__ */ l("div", {
|
|
85
89
|
className: `flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${x === t.id ? "bg-bg-accent border-2 border-action-primary-border" : "hover:bg-action-ghost-bg-hover border-2 border-transparent"}`,
|
|
86
90
|
children: [/* @__PURE__ */ l("button", {
|
|
91
|
+
type: "button",
|
|
87
92
|
onClick: () => S(t.id),
|
|
88
93
|
className: "flex items-center gap-3 flex-1 min-w-0",
|
|
89
94
|
children: [/* @__PURE__ */ c(i, { className: "size-5 text-text-link flex-shrink-0" }), /* @__PURE__ */ c("span", {
|
|
@@ -91,6 +96,7 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
|
|
|
91
96
|
children: t.name
|
|
92
97
|
})]
|
|
93
98
|
}), /* @__PURE__ */ c("button", {
|
|
99
|
+
type: "button",
|
|
94
100
|
onClick: () => E(t),
|
|
95
101
|
className: "p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200 flex-shrink-0",
|
|
96
102
|
title: e(g, "files.moveDialog.actions.openFolder", "Open folder"),
|
|
@@ -103,10 +109,12 @@ var d = ({ item: d, type: f, onClose: p, onMove: m, isLoading: h = !1 }) => {
|
|
|
103
109
|
/* @__PURE__ */ l("div", {
|
|
104
110
|
className: "flex items-center justify-end gap-2 px-6 py-4 border-t border-border-default",
|
|
105
111
|
children: [/* @__PURE__ */ c("button", {
|
|
112
|
+
type: "button",
|
|
106
113
|
onClick: p,
|
|
107
114
|
className: "px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200",
|
|
108
115
|
children: e(g, "files.common.actions.cancel", "Cancel")
|
|
109
116
|
}), /* @__PURE__ */ l("button", {
|
|
117
|
+
type: "button",
|
|
110
118
|
onClick: O,
|
|
111
119
|
disabled: h || x === void 0,
|
|
112
120
|
className: "px-4 py-2 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-muted disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoveDialog.js","names":[],"sources":["../../../src/files/components/MoveDialog.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport { Folder, Home, ChevronRight, Loader2, X } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem, FolderItem } from \"../types\";\nimport { useFoldersQuery } from \"../hooks/useFoldersQuery\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface MoveDialogProps {\n item: FileItem | FolderItem;\n type: \"file\" | \"folder\";\n onClose: () => void;\n onMove: (destinationFolderId: string | null) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport const MoveDialog: FC<MoveDialogProps> = ({\n item,\n type,\n onClose,\n onMove,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const [currentFolderId, setCurrentFolderId] = useState<string | null>(null);\n const [breadcrumbs, setBreadcrumbs] = useState<\n Array<{ id: string; name: string }>\n >([]);\n const [selectedDestination, setSelectedDestination] = useState<string | null>(\n null,\n );\n\n // Fetch folders in current location\n const { data, loading: loadingFolders } = useFoldersQuery(\n currentFolderId || undefined,\n );\n const folders = (data?.getFolders?.folders || []) as FolderItem[];\n\n const handleFolderClick = (folder: FolderItem) => {\n setCurrentFolderId(folder.id);\n setBreadcrumbs([...breadcrumbs, { id: folder.id, name: folder.name }]);\n };\n\n const handleBreadcrumbClick = (index: number) => {\n if (index === -1) {\n // Go to root\n setCurrentFolderId(null);\n setBreadcrumbs([]);\n } else {\n const crumb = breadcrumbs[index];\n setCurrentFolderId(crumb.id);\n setBreadcrumbs(breadcrumbs.slice(0, index + 1));\n }\n };\n\n const handleMove = async () => {\n if (selectedDestination === item.id) {\n // Can't move to itself\n return;\n }\n\n await onMove(selectedDestination);\n onClose();\n };\n\n const filteredFolders = folders.filter((folder) => {\n // Don't show the item itself if it's a folder\n if (type === \"folder\" && folder.id === item.id) {\n return false;\n }\n return true;\n });\n\n return (\n <div\n className=\"fixed inset-0 backdrop-blur-sm flex items-center justify-center z-[100] animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.ariaLabel\",\n \"Move {{type}} {{name}}\",\n { type, name: item.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl w-full max-w-md mx-4 max-h-[80vh] flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(t, \"files.moveDialog.title\", 'Move \"{{name}}\"', {\n name: item.name,\n })}\n </h2>\n <button\n onClick={onClose}\n className=\"p-1 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.close\",\n \"Close move dialog\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Breadcrumb navigation */}\n <div className=\"px-6 py-3 border-b border-border-default bg-bg-muted\">\n <div className=\"flex items-center gap-2 text-sm overflow-x-auto\">\n <button\n onClick={() => handleBreadcrumbClick(-1)}\n className=\"flex items-center gap-1.5 text-text-primary hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 whitespace-nowrap\"\n >\n <Home className=\"size-4\" />\n <span>\n {tWithFallback(t, \"files.browser.rootLabel\", \"My Drive\")}\n </span>\n </button>\n {breadcrumbs.map((crumb, index) => (\n <div\n key={crumb.id}\n className=\"flex items-center gap-2 whitespace-nowrap\"\n >\n <ChevronRight className=\"size-4 text-text-tertiary\" />\n <button\n onClick={() => handleBreadcrumbClick(index)}\n className={`hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${\n index === breadcrumbs.length - 1\n ? \"text-text-primary font-medium\"\n : \"text-text-secondary\"\n }`}\n >\n {crumb.name}\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Folder list */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">\n {loadingFolders ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 text-text-tertiary animate-spin\" />\n </div>\n ) : filteredFolders.length === 0 ? (\n <div className=\"text-center py-8\">\n <Folder className=\"size-12 text-text-tertiary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.moveDialog.empty.noFolders\",\n \"No folders here\",\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-1\">\n {/* Option to move to current folder */}\n <button\n onClick={() => setSelectedDestination(currentFolderId)}\n className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${\n selectedDestination === currentFolderId\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <Folder className=\"size-5 text-text-link\" />\n <span className=\"font-medium text-text-primary\">\n {tWithFallback(\n t,\n \"files.moveDialog.actions.moveHere\",\n \"Move here\",\n )}\n </span>\n </button>\n\n {/* List of subfolders */}\n {filteredFolders.map((folder) => (\n <div\n key={folder.id}\n className={`flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${\n selectedDestination === folder.id\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <button\n onClick={() => setSelectedDestination(folder.id)}\n className=\"flex items-center gap-3 flex-1 min-w-0\"\n >\n <Folder className=\"size-5 text-text-link flex-shrink-0\" />\n <span className=\"font-medium text-text-primary truncate\">\n {folder.name}\n </span>\n </button>\n <button\n onClick={() => handleFolderClick(folder)}\n className=\"p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200 flex-shrink-0\"\n title={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolder\",\n \"Open folder\",\n )}\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolderNamed\",\n \"Open {{name}}\",\n { name: folder.name },\n )}\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-2 px-6 py-4 border-t border-border-default\">\n <button\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleMove}\n disabled={isLoading || selectedDestination === undefined}\n className=\"px-4 py-2 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-muted disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2\"\n >\n {isLoading && <Loader2 className=\"size-4 animate-spin\" />}\n {isLoading\n ? tWithFallback(t, \"files.moveDialog.actions.moving\", \"Moving...\")\n : tWithFallback(t, \"files.common.actions.move\", \"Move\")}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MoveDialog.js","names":[],"sources":["../../../src/files/components/MoveDialog.tsx"],"sourcesContent":["import { type FC, useState } from \"react\";\nimport { Folder, Home, ChevronRight, Loader2, X } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport type { FileItem, FolderItem } from \"../types\";\nimport { useFoldersQuery } from \"../hooks/useFoldersQuery\";\nimport { tWithFallback } from \"../utils/i18n\";\n\ninterface MoveDialogProps {\n item: FileItem | FolderItem;\n type: \"file\" | \"folder\";\n onClose: () => void;\n onMove: (destinationFolderId: string | null) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport const MoveDialog: FC<MoveDialogProps> = ({\n item,\n type,\n onClose,\n onMove,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const [currentFolderId, setCurrentFolderId] = useState<string | null>(null);\n const [breadcrumbs, setBreadcrumbs] = useState<\n Array<{ id: string; name: string }>\n >([]);\n const [selectedDestination, setSelectedDestination] = useState<string | null>(\n null,\n );\n\n // Fetch folders in current location\n const { data, loading: loadingFolders } = useFoldersQuery(\n currentFolderId || undefined,\n );\n const folders = (data?.getFolders?.folders || []) as FolderItem[];\n\n const handleFolderClick = (folder: FolderItem) => {\n setCurrentFolderId(folder.id);\n setBreadcrumbs([...breadcrumbs, { id: folder.id, name: folder.name }]);\n };\n\n const handleBreadcrumbClick = (index: number) => {\n if (index === -1) {\n // Go to root\n setCurrentFolderId(null);\n setBreadcrumbs([]);\n } else {\n const crumb = breadcrumbs[index];\n setCurrentFolderId(crumb.id);\n setBreadcrumbs(breadcrumbs.slice(0, index + 1));\n }\n };\n\n const handleMove = async () => {\n if (selectedDestination === item.id) {\n // Can't move to itself\n return;\n }\n\n await onMove(selectedDestination);\n onClose();\n };\n\n const filteredFolders = folders.filter((folder) => {\n // Don't show the item itself if it's a folder\n if (type === \"folder\" && folder.id === item.id) {\n return false;\n }\n return true;\n });\n\n return (\n <div\n className=\"fixed inset-0 backdrop-blur-sm flex items-center justify-center z-[100] animate-in fade-in duration-200\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.ariaLabel\",\n \"Move {{type}} {{name}}\",\n { type, name: item.name },\n )}\n >\n <div className=\"bg-bg-surface rounded-lg shadow-xl w-full max-w-md mx-4 max-h-[80vh] flex flex-col animate-in zoom-in-95 duration-200\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tWithFallback(t, \"files.moveDialog.title\", 'Move \"{{name}}\"', {\n name: item.name,\n })}\n </h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.close\",\n \"Close move dialog\",\n )}\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Breadcrumb navigation */}\n <div className=\"px-6 py-3 border-b border-border-default bg-bg-muted\">\n <div className=\"flex items-center gap-2 text-sm overflow-x-auto\">\n <button\n type=\"button\"\n onClick={() => handleBreadcrumbClick(-1)}\n className=\"flex items-center gap-1.5 text-text-primary hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 whitespace-nowrap\"\n >\n <Home className=\"size-4\" />\n <span>\n {tWithFallback(t, \"files.browser.rootLabel\", \"My Drive\")}\n </span>\n </button>\n {breadcrumbs.map((crumb, index) => (\n <div\n key={crumb.id}\n className=\"flex items-center gap-2 whitespace-nowrap\"\n >\n <ChevronRight className=\"size-4 text-text-tertiary\" />\n <button\n type=\"button\"\n onClick={() => handleBreadcrumbClick(index)}\n className={`hover:bg-action-ghost-bg-hover px-2 py-1 rounded transition-colors duration-200 ${\n index === breadcrumbs.length - 1\n ? \"text-text-primary font-medium\"\n : \"text-text-secondary\"\n }`}\n >\n {crumb.name}\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Folder list */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">\n {loadingFolders ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 text-text-tertiary animate-spin\" />\n </div>\n ) : filteredFolders.length === 0 ? (\n <div className=\"text-center py-8\">\n <Folder className=\"size-12 text-text-tertiary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n \"files.moveDialog.empty.noFolders\",\n \"No folders here\",\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-1\">\n {/* Option to move to current folder */}\n <button\n type=\"button\"\n onClick={() => setSelectedDestination(currentFolderId)}\n className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${\n selectedDestination === currentFolderId\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <Folder className=\"size-5 text-text-link\" />\n <span className=\"font-medium text-text-primary\">\n {tWithFallback(\n t,\n \"files.moveDialog.actions.moveHere\",\n \"Move here\",\n )}\n </span>\n </button>\n\n {/* List of subfolders */}\n {filteredFolders.map((folder) => (\n <div\n key={folder.id}\n className={`flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${\n selectedDestination === folder.id\n ? \"bg-bg-accent border-2 border-action-primary-border\"\n : \"hover:bg-action-ghost-bg-hover border-2 border-transparent\"\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setSelectedDestination(folder.id)}\n className=\"flex items-center gap-3 flex-1 min-w-0\"\n >\n <Folder className=\"size-5 text-text-link flex-shrink-0\" />\n <span className=\"font-medium text-text-primary truncate\">\n {folder.name}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={() => handleFolderClick(folder)}\n className=\"p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200 flex-shrink-0\"\n title={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolder\",\n \"Open folder\",\n )}\n aria-label={tWithFallback(\n t,\n \"files.moveDialog.actions.openFolderNamed\",\n \"Open {{name}}\",\n { name: folder.name },\n )}\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-2 px-6 py-4 border-t border-border-default\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-4 py-2 text-sm font-medium text-text-primary hover:bg-action-ghost-bg-hover rounded-lg transition-colors duration-200\"\n >\n {tWithFallback(t, \"files.common.actions.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleMove}\n disabled={isLoading || selectedDestination === undefined}\n className=\"px-4 py-2 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-muted disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2\"\n >\n {isLoading && <Loader2 className=\"size-4 animate-spin\" />}\n {isLoading\n ? tWithFallback(t, \"files.moveDialog.actions.moving\", \"Moving...\")\n : tWithFallback(t, \"files.common.actions.move\", \"Move\")}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -91,12 +91,14 @@ function d({ onSearch: d, placeholder: f = "Search files and folders...", initia
|
|
|
91
91
|
/* @__PURE__ */ c("div", {
|
|
92
92
|
className: "absolute right-2 flex items-center gap-1",
|
|
93
93
|
children: [g && /* @__PURE__ */ s("button", {
|
|
94
|
+
type: "button",
|
|
94
95
|
onClick: E,
|
|
95
96
|
className: "p-1.5 text-text-tertiary hover:text-text-secondary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200",
|
|
96
97
|
title: e(h, "files.search.actions.clearSearch", "Clear search"),
|
|
97
98
|
"aria-label": e(h, "files.search.actions.clearSearch", "Clear search"),
|
|
98
99
|
children: /* @__PURE__ */ s(o, { className: "size-4" })
|
|
99
100
|
}), m && /* @__PURE__ */ c("button", {
|
|
101
|
+
type: "button",
|
|
100
102
|
onClick: () => y(!v),
|
|
101
103
|
"aria-expanded": v,
|
|
102
104
|
"aria-controls": "files-search-filter-panel",
|
|
@@ -235,12 +237,14 @@ function d({ onSearch: d, placeholder: f = "Search files and folders...", initia
|
|
|
235
237
|
/* @__PURE__ */ c("div", {
|
|
236
238
|
className: "flex justify-end gap-2 pt-2 border-t border-border-default",
|
|
237
239
|
children: [/* @__PURE__ */ s("button", {
|
|
240
|
+
type: "button",
|
|
238
241
|
onClick: () => {
|
|
239
242
|
x({}), y(!1);
|
|
240
243
|
},
|
|
241
244
|
className: "px-3 py-1.5 text-sm text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200",
|
|
242
245
|
children: e(h, "files.search.filters.actions.clear", "Clear filters")
|
|
243
246
|
}), /* @__PURE__ */ s("button", {
|
|
247
|
+
type: "button",
|
|
244
248
|
onClick: () => y(!1),
|
|
245
249
|
className: "px-3 py-1.5 text-sm bg-action-primary-bg text-text-inverse rounded hover:bg-action-primary-bg-hover transition-colors duration-200",
|
|
246
250
|
children: e(h, "files.search.filters.actions.apply", "Apply")
|