@burdenoff/microfe-files 2026.608.1 → 2026.624.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/CopyDialog.js +2 -2
- package/dist/files/components/CopyDialog.js.map +1 -1
- package/dist/files/components/FileInfoPanel.js +1 -1
- package/dist/files/components/FileInfoPanel.js.map +1 -1
- package/dist/files/components/FileVersionPanel.js +2 -2
- package/dist/files/components/FileVersionPanel.js.map +1 -1
- package/dist/files/components/MoveDialog.js +2 -2
- package/dist/files/components/MoveDialog.js.map +1 -1
- package/dist/files/components/UploadQueue.js +2 -2
- package/dist/files/components/UploadQueue.js.map +1 -1
- package/dist/files/components/viewers/AudioPlayer.js +3 -3
- package/dist/files/components/viewers/AudioPlayer.js.map +1 -1
- package/dist/files/components/viewers/VideoPlayer.js +2 -2
- package/dist/files/components/viewers/VideoPlayer.js.map +1 -1
- package/dist/files/hooks/useUploadFile.js +1 -0
- package/dist/files/hooks/useUploadFile.js.map +1 -1
- package/dist/files/pages/BrowserPage.js +3 -3
- package/dist/files/pages/BrowserPage.js.map +1 -1
- package/dist/files/pages/ShareAccessPage.js +2 -2
- package/dist/files/pages/ShareAccessPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -51,7 +51,7 @@ function u({ file: u, url: d }) {
|
|
|
51
51
|
value: h,
|
|
52
52
|
onChange: (e) => E(Number(e.target.value)),
|
|
53
53
|
"aria-label": "Seek",
|
|
54
|
-
className: "w-full h-1 bg-bg-
|
|
54
|
+
className: "w-full h-1 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg"
|
|
55
55
|
}), /* @__PURE__ */ l("div", {
|
|
56
56
|
className: "flex justify-between mt-1 text-xs text-text-secondary",
|
|
57
57
|
children: [/* @__PURE__ */ c("span", { children: j(h) }), /* @__PURE__ */ c("span", { children: j(_) })]
|
|
@@ -86,7 +86,7 @@ function u({ file: u, url: d }) {
|
|
|
86
86
|
value: y,
|
|
87
87
|
onChange: (e) => D(Number(e.target.value)),
|
|
88
88
|
"aria-label": "Volume",
|
|
89
|
-
className: "w-20 h-1 bg-bg-
|
|
89
|
+
className: "w-20 h-1 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg"
|
|
90
90
|
})]
|
|
91
91
|
}),
|
|
92
92
|
/* @__PURE__ */ l("select", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoPlayer.js","names":[],"sources":["../../../../src/files/components/viewers/VideoPlayer.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { Play, Pause, Volume2, VolumeX, Maximize } from \"lucide-react\";\nimport type { FileItem } from \"../../types\";\n\ninterface VideoPlayerProps {\n file: FileItem;\n url: string;\n}\n\nexport function VideoPlayer({ file, url }: VideoPlayerProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [volume, setVolume] = useState(1);\n const [isMuted, setIsMuted] = useState(false);\n const [playbackRate, setPlaybackRate] = useState(1);\n\n useEffect(() => {\n const video = videoRef.current;\n if (!video) return;\n\n const handleTimeUpdate = () => setCurrentTime(video.currentTime);\n const handleLoadedMetadata = () => setDuration(video.duration);\n const handleEnded = () => setIsPlaying(false);\n\n video.addEventListener(\"timeupdate\", handleTimeUpdate);\n video.addEventListener(\"loadedmetadata\", handleLoadedMetadata);\n video.addEventListener(\"ended\", handleEnded);\n\n return () => {\n video.removeEventListener(\"timeupdate\", handleTimeUpdate);\n video.removeEventListener(\"loadedmetadata\", handleLoadedMetadata);\n video.removeEventListener(\"ended\", handleEnded);\n };\n }, []);\n\n const togglePlay = () => {\n if (videoRef.current) {\n if (isPlaying) {\n videoRef.current.pause();\n } else {\n videoRef.current.play();\n }\n setIsPlaying(!isPlaying);\n }\n };\n\n const handleSeek = (time: number) => {\n if (videoRef.current) {\n videoRef.current.currentTime = time;\n setCurrentTime(time);\n }\n };\n\n const handleVolumeChange = (newVolume: number) => {\n setVolume(newVolume);\n setIsMuted(newVolume === 0);\n if (videoRef.current) {\n videoRef.current.volume = newVolume;\n }\n };\n\n const toggleMute = () => {\n if (videoRef.current) {\n const newMuted = !isMuted;\n setIsMuted(newMuted);\n videoRef.current.muted = newMuted;\n }\n };\n\n const handlePlaybackRateChange = (rate: number) => {\n setPlaybackRate(rate);\n if (videoRef.current) {\n videoRef.current.playbackRate = rate;\n }\n };\n\n const handleFullscreen = () => {\n if (videoRef.current) {\n videoRef.current.requestFullscreen();\n }\n };\n\n const formatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, \"0\")}`;\n };\n\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n {/* Video */}\n <div className=\"flex-1 flex items-center justify-center\">\n <video\n ref={videoRef}\n src={url}\n aria-label={file.name}\n className=\"max-w-full max-h-full\"\n onClick={togglePlay}\n >\n <track kind=\"captions\" />\n </video>\n </div>\n\n {/* Controls */}\n <div className=\"bg-bg-surface border-t border-border-default p-4\">\n {/* Progress Bar */}\n <div className=\"mb-4\">\n <input\n type=\"range\"\n min={0}\n max={duration || 0}\n value={currentTime}\n onChange={(e) => handleSeek(Number(e.target.value))}\n aria-label=\"Seek\"\n className=\"w-full h-1 bg-bg-
|
|
1
|
+
{"version":3,"file":"VideoPlayer.js","names":[],"sources":["../../../../src/files/components/viewers/VideoPlayer.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { Play, Pause, Volume2, VolumeX, Maximize } from \"lucide-react\";\nimport type { FileItem } from \"../../types\";\n\ninterface VideoPlayerProps {\n file: FileItem;\n url: string;\n}\n\nexport function VideoPlayer({ file, url }: VideoPlayerProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [volume, setVolume] = useState(1);\n const [isMuted, setIsMuted] = useState(false);\n const [playbackRate, setPlaybackRate] = useState(1);\n\n useEffect(() => {\n const video = videoRef.current;\n if (!video) return;\n\n const handleTimeUpdate = () => setCurrentTime(video.currentTime);\n const handleLoadedMetadata = () => setDuration(video.duration);\n const handleEnded = () => setIsPlaying(false);\n\n video.addEventListener(\"timeupdate\", handleTimeUpdate);\n video.addEventListener(\"loadedmetadata\", handleLoadedMetadata);\n video.addEventListener(\"ended\", handleEnded);\n\n return () => {\n video.removeEventListener(\"timeupdate\", handleTimeUpdate);\n video.removeEventListener(\"loadedmetadata\", handleLoadedMetadata);\n video.removeEventListener(\"ended\", handleEnded);\n };\n }, []);\n\n const togglePlay = () => {\n if (videoRef.current) {\n if (isPlaying) {\n videoRef.current.pause();\n } else {\n videoRef.current.play();\n }\n setIsPlaying(!isPlaying);\n }\n };\n\n const handleSeek = (time: number) => {\n if (videoRef.current) {\n videoRef.current.currentTime = time;\n setCurrentTime(time);\n }\n };\n\n const handleVolumeChange = (newVolume: number) => {\n setVolume(newVolume);\n setIsMuted(newVolume === 0);\n if (videoRef.current) {\n videoRef.current.volume = newVolume;\n }\n };\n\n const toggleMute = () => {\n if (videoRef.current) {\n const newMuted = !isMuted;\n setIsMuted(newMuted);\n videoRef.current.muted = newMuted;\n }\n };\n\n const handlePlaybackRateChange = (rate: number) => {\n setPlaybackRate(rate);\n if (videoRef.current) {\n videoRef.current.playbackRate = rate;\n }\n };\n\n const handleFullscreen = () => {\n if (videoRef.current) {\n videoRef.current.requestFullscreen();\n }\n };\n\n const formatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, \"0\")}`;\n };\n\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n {/* Video */}\n <div className=\"flex-1 flex items-center justify-center\">\n <video\n ref={videoRef}\n src={url}\n aria-label={file.name}\n className=\"max-w-full max-h-full\"\n onClick={togglePlay}\n >\n <track kind=\"captions\" />\n </video>\n </div>\n\n {/* Controls */}\n <div className=\"bg-bg-surface border-t border-border-default p-4\">\n {/* Progress Bar */}\n <div className=\"mb-4\">\n <input\n type=\"range\"\n min={0}\n max={duration || 0}\n value={currentTime}\n onChange={(e) => handleSeek(Number(e.target.value))}\n aria-label=\"Seek\"\n className=\"w-full h-1 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg\"\n />\n <div className=\"flex justify-between mt-1 text-xs text-text-secondary\">\n <span>{formatTime(currentTime)}</span>\n <span>{formatTime(duration)}</span>\n </div>\n </div>\n\n {/* Control Buttons */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n {/* Play/Pause */}\n <button\n type=\"button\"\n onClick={togglePlay}\n className=\"p-2 text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n title={isPlaying ? \"Pause\" : \"Play\"}\n aria-label={isPlaying ? \"Pause video\" : \"Play video\"}\n >\n {isPlaying ? (\n <Pause className=\"size-5\" />\n ) : (\n <Play className=\"size-5\" />\n )}\n </button>\n\n {/* Volume */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={toggleMute}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n title={isMuted ? \"Unmute\" : \"Mute\"}\n aria-label={isMuted ? \"Unmute video\" : \"Mute video\"}\n >\n {isMuted ? (\n <VolumeX className=\"size-5\" />\n ) : (\n <Volume2 className=\"size-5\" />\n )}\n </button>\n <input\n type=\"range\"\n min={0}\n max={1}\n step={0.1}\n value={volume}\n onChange={(e) => handleVolumeChange(Number(e.target.value))}\n aria-label=\"Volume\"\n className=\"w-20 h-1 bg-bg-sunken rounded-lg appearance-none cursor-pointer accent-action-primary-bg\"\n />\n </div>\n\n {/* Playback Speed */}\n <select\n value={playbackRate}\n onChange={(e) => handlePlaybackRateChange(Number(e.target.value))}\n className=\"px-2 py-1 text-sm bg-bg-surface border border-border-default rounded text-text-primary cursor-pointer\"\n >\n <option value=\"0.5\">0.5x</option>\n <option value=\"0.75\">0.75x</option>\n <option value=\"1\">1x</option>\n <option value=\"1.25\">1.25x</option>\n <option value=\"1.5\">1.5x</option>\n <option value=\"2\">2x</option>\n </select>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">{file.name}</span>\n <button\n type=\"button\"\n onClick={handleFullscreen}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-action-ghost-bg-hover rounded transition-colors duration-200\"\n title=\"Fullscreen\"\n aria-label=\"Open fullscreen\"\n >\n <Maximize className=\"size-5\" />\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUploadFile.js","names":[],"sources":["../../../src/files/hooks/useUploadFile.ts"],"sourcesContent":["import { useState, useRef, useCallback } from \"react\";\nimport {\n useInitiateUploadMutation,\n useCompleteUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useFilesContext } from \"../providers/FilesProvider\";\n\nexport interface InitiateUploadInput {\n fileName: string;\n fileSize: number;\n mimeType: string;\n folderId?: string;\n}\n\nexport interface CompleteUploadInput {\n sessionId: string;\n}\n\nexport interface UploadController {\n pause: () => void;\n isPaused: () => boolean;\n}\n\nexport const useUploadFile = () => {\n const { workspaceId } = useFilesContext();\n const [isUploading, setIsUploading] = useState(false);\n const [uploadError, setUploadError] = useState<Error | null>(null);\n\n const activeXhrRef = useRef<XMLHttpRequest | null>(null);\n const isPausedRef = useRef(false);\n\n const [initiateUploadMutation] = useInitiateUploadMutation();\n const [completeUploadMutation] = useCompleteUploadMutation({\n refetchQueries: [\n \"GetFilesRootBrowsePage\",\n \"GetFilesFolderBrowsePage\",\n \"GetFilesRecentPage\",\n \"GetFiles\",\n ],\n });\n\n const pause = useCallback(() => {\n if (activeXhrRef.current && !isPausedRef.current) {\n isPausedRef.current = true;\n activeXhrRef.current.abort();\n activeXhrRef.current = null;\n }\n }, []);\n\n const isPaused = useCallback(() => isPausedRef.current, []);\n\n const mutateAsync = async (params: {\n file: File;\n folderId?: string;\n onProgress?: (progress: number) => void;\n }) => {\n const { file, folderId, onProgress } = params;\n const extension = file.name.split(\".\").pop() || \"\";\n\n try {\n setIsUploading(true);\n setUploadError(null);\n isPausedRef.current = false;\n\n onProgress?.(0);\n\n const initiateResult = await initiateUploadMutation({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type || \"application/octet-stream\",\n extension: extension,\n folderId: folderId || null,\n },\n },\n });\n\n // Check for GraphQL errors FIRST - before checking data\n // This ensures backend error messages are properly surfaced to the user\n if (initiateResult.error) {\n const errorMsg = initiateResult.error.message || \"\";\n if (\n errorMsg.includes(\"STORAGE_QUOTA_EXCEEDED\") ||\n errorMsg.includes(\"quota\") ||\n errorMsg.toLowerCase().includes(\"quota exceeded\")\n ) {\n throw new Error(\"QUOTA_EXHAUSTED\");\n }\n throw new Error(errorMsg || \"Failed to initiate upload\");\n }\n\n const result = initiateResult.data?.initiateUpload;\n const session = result?.session;\n const uploadUrl = result?.uploadUrl;\n\n if (!session || !uploadUrl) {\n throw new Error(\"Failed to initiate upload session\");\n }\n\n onProgress?.(10);\n\n const uploadResponse = await new Promise<Response>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n activeXhrRef.current = xhr;\n\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable && !isPausedRef.current) {\n const percentComplete = 10 + Math.round((e.loaded / e.total) * 80);\n onProgress?.(percentComplete);\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n if (!isPausedRef.current) {\n onProgress?.(90);\n }\n resolve(\n new Response(xhr.response, {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: new Headers(\n xhr\n .getAllResponseHeaders()\n .split(\"\\r\\n\")\n .reduce(\n (acc, current) => {\n const parts = current.split(\": \");\n if (parts[0]) acc[parts[0]] = parts[1] || \"\";\n return acc;\n },\n {} as Record<string, string>,\n ),\n ),\n }),\n );\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n if (!isPausedRef.current) {\n reject(new Error(\"Upload failed\"));\n }\n });\n\n xhr.addEventListener(\"abort\", () => {\n if (isPausedRef.current) {\n reject(new Error(\"Upload paused\"));\n } else {\n reject(new Error(\"Upload aborted\"));\n }\n });\n\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type);\n xhr.send(file);\n });\n\n if (!uploadResponse.ok) {\n throw new Error(\"Failed to upload file to S3\");\n }\n\n if (isPausedRef.current) {\n throw new Error(\"Upload paused\");\n }\n\n const completeResult = await completeUploadMutation({\n variables: {\n input: {\n sessionId: session.id,\n folderId: folderId || null,\n },\n },\n });\n\n if (completeResult.error) {\n const errorMsg = completeResult.error.message || \"\";\n if (\n errorMsg.includes(\"STORAGE_QUOTA_EXCEEDED\") ||\n errorMsg.includes(\"quota\") ||\n errorMsg.toLowerCase().includes(\"quota exceeded\")\n ) {\n throw new Error(\"QUOTA_EXHAUSTED\");\n }\n throw new Error(\n completeResult.error.message || \"Failed to complete upload\",\n );\n }\n\n const uploadedFile = completeResult.data?.completeUpload?.file;\n if (!uploadedFile) {\n throw new Error(\n \"Upload completed but file data was not returned. The file may not have been created properly.\",\n );\n }\n\n onProgress?.(100);\n return uploadedFile;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(\"Upload failed\");\n setUploadError(err);\n throw err;\n } finally {\n setIsUploading(false);\n activeXhrRef.current = null;\n }\n };\n\n return {\n mutateAsync,\n isPending: isUploading,\n error: uploadError,\n pause,\n isPaused,\n };\n};\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"useUploadFile.js","names":[],"sources":["../../../src/files/hooks/useUploadFile.ts"],"sourcesContent":["import { useState, useRef, useCallback } from \"react\";\nimport {\n useInitiateUploadMutation,\n useCompleteUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useFilesContext } from \"../providers/FilesProvider\";\n\nexport interface InitiateUploadInput {\n fileName: string;\n fileSize: number;\n mimeType: string;\n folderId?: string;\n}\n\nexport interface CompleteUploadInput {\n sessionId: string;\n fileSize: string;\n}\n\nexport interface UploadController {\n pause: () => void;\n isPaused: () => boolean;\n}\n\nexport const useUploadFile = () => {\n const { workspaceId } = useFilesContext();\n const [isUploading, setIsUploading] = useState(false);\n const [uploadError, setUploadError] = useState<Error | null>(null);\n\n const activeXhrRef = useRef<XMLHttpRequest | null>(null);\n const isPausedRef = useRef(false);\n\n const [initiateUploadMutation] = useInitiateUploadMutation();\n const [completeUploadMutation] = useCompleteUploadMutation({\n refetchQueries: [\n \"GetFilesRootBrowsePage\",\n \"GetFilesFolderBrowsePage\",\n \"GetFilesRecentPage\",\n \"GetFiles\",\n ],\n });\n\n const pause = useCallback(() => {\n if (activeXhrRef.current && !isPausedRef.current) {\n isPausedRef.current = true;\n activeXhrRef.current.abort();\n activeXhrRef.current = null;\n }\n }, []);\n\n const isPaused = useCallback(() => isPausedRef.current, []);\n\n const mutateAsync = async (params: {\n file: File;\n folderId?: string;\n onProgress?: (progress: number) => void;\n }) => {\n const { file, folderId, onProgress } = params;\n const extension = file.name.split(\".\").pop() || \"\";\n\n try {\n setIsUploading(true);\n setUploadError(null);\n isPausedRef.current = false;\n\n onProgress?.(0);\n\n const initiateResult = await initiateUploadMutation({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type || \"application/octet-stream\",\n extension: extension,\n folderId: folderId || null,\n },\n },\n });\n\n // Check for GraphQL errors FIRST - before checking data\n // This ensures backend error messages are properly surfaced to the user\n if (initiateResult.error) {\n const errorMsg = initiateResult.error.message || \"\";\n if (\n errorMsg.includes(\"STORAGE_QUOTA_EXCEEDED\") ||\n errorMsg.includes(\"quota\") ||\n errorMsg.toLowerCase().includes(\"quota exceeded\")\n ) {\n throw new Error(\"QUOTA_EXHAUSTED\");\n }\n throw new Error(errorMsg || \"Failed to initiate upload\");\n }\n\n const result = initiateResult.data?.initiateUpload;\n const session = result?.session;\n const uploadUrl = result?.uploadUrl;\n\n if (!session || !uploadUrl) {\n throw new Error(\"Failed to initiate upload session\");\n }\n\n onProgress?.(10);\n\n const uploadResponse = await new Promise<Response>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n activeXhrRef.current = xhr;\n\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable && !isPausedRef.current) {\n const percentComplete = 10 + Math.round((e.loaded / e.total) * 80);\n onProgress?.(percentComplete);\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n if (!isPausedRef.current) {\n onProgress?.(90);\n }\n resolve(\n new Response(xhr.response, {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: new Headers(\n xhr\n .getAllResponseHeaders()\n .split(\"\\r\\n\")\n .reduce(\n (acc, current) => {\n const parts = current.split(\": \");\n if (parts[0]) acc[parts[0]] = parts[1] || \"\";\n return acc;\n },\n {} as Record<string, string>,\n ),\n ),\n }),\n );\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n if (!isPausedRef.current) {\n reject(new Error(\"Upload failed\"));\n }\n });\n\n xhr.addEventListener(\"abort\", () => {\n if (isPausedRef.current) {\n reject(new Error(\"Upload paused\"));\n } else {\n reject(new Error(\"Upload aborted\"));\n }\n });\n\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type);\n xhr.send(file);\n });\n\n if (!uploadResponse.ok) {\n throw new Error(\"Failed to upload file to S3\");\n }\n\n if (isPausedRef.current) {\n throw new Error(\"Upload paused\");\n }\n\n const completeResult = await completeUploadMutation({\n variables: {\n input: {\n sessionId: session.id,\n fileSize: file.size.toString(),\n folderId: folderId || null,\n },\n },\n });\n\n if (completeResult.error) {\n const errorMsg = completeResult.error.message || \"\";\n if (\n errorMsg.includes(\"STORAGE_QUOTA_EXCEEDED\") ||\n errorMsg.includes(\"quota\") ||\n errorMsg.toLowerCase().includes(\"quota exceeded\")\n ) {\n throw new Error(\"QUOTA_EXHAUSTED\");\n }\n throw new Error(\n completeResult.error.message || \"Failed to complete upload\",\n );\n }\n\n const uploadedFile = completeResult.data?.completeUpload?.file;\n if (!uploadedFile) {\n throw new Error(\n \"Upload completed but file data was not returned. The file may not have been created properly.\",\n );\n }\n\n onProgress?.(100);\n return uploadedFile;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(\"Upload failed\");\n setUploadError(err);\n throw err;\n } finally {\n setIsUploading(false);\n activeXhrRef.current = null;\n }\n };\n\n return {\n mutateAsync,\n isPending: isUploading,\n error: uploadError,\n pause,\n isPaused,\n };\n};\n"],"mappings":"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"}
|
|
@@ -879,7 +879,7 @@ function d() {
|
|
|
879
879
|
type: "button",
|
|
880
880
|
onClick: tn,
|
|
881
881
|
disabled: !G.trim() || $.isPending,
|
|
882
|
-
className: "px-6 py-2.5 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-
|
|
882
|
+
className: "px-6 py-2.5 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-sunken disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2",
|
|
883
883
|
children: [$.isPending && /* @__PURE__ */ l(s, { className: "size-4 animate-spin" }), $.isPending ? a(d, "files.browser.newFolderDialog.actions.creating", "Creating...") : a(d, "files.common.actions.create", "Create")]
|
|
884
884
|
})]
|
|
885
885
|
})
|
|
@@ -923,7 +923,7 @@ function d() {
|
|
|
923
923
|
type: "button",
|
|
924
924
|
onClick: En,
|
|
925
925
|
disabled: !U.trim() || U.trim() === V.name || zt.isPending || Bt.isPending,
|
|
926
|
-
className: "px-6 py-2.5 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-
|
|
926
|
+
className: "px-6 py-2.5 text-sm font-medium text-text-inverse bg-action-primary-bg rounded-lg hover:bg-action-primary-bg-hover disabled:bg-bg-sunken disabled:text-text-tertiary disabled:cursor-not-allowed transition-colors duration-200 flex items-center gap-2",
|
|
927
927
|
children: [(zt.isPending || Bt.isPending) && /* @__PURE__ */ l(s, { className: "size-4 animate-spin" }), zt.isPending || Bt.isPending ? a(d, "files.browser.renameDialog.actions.renaming", "Renaming...") : a(d, "files.common.actions.rename", "Rename")]
|
|
928
928
|
})]
|
|
929
929
|
})
|
|
@@ -1015,7 +1015,7 @@ function d() {
|
|
|
1015
1015
|
className: "text-center animate-in fade-in duration-300",
|
|
1016
1016
|
children: [
|
|
1017
1017
|
/* @__PURE__ */ l("div", {
|
|
1018
|
-
className: "inline-flex items-center justify-center size-16 bg-bg-
|
|
1018
|
+
className: "inline-flex items-center justify-center size-16 bg-bg-sunken rounded-full mb-4",
|
|
1019
1019
|
children: /* @__PURE__ */ l(Ye, { className: "size-8 text-text-tertiary" })
|
|
1020
1020
|
}),
|
|
1021
1021
|
/* @__PURE__ */ l("h3", {
|