@useupup/svelte 3.3.0
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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/UpupUploader.svelte +96 -0
- package/dist/UpupUploader.svelte.d.ts +5 -0
- package/dist/UpupUploader.svelte.d.ts.map +1 -0
- package/dist/assets/logos.d.ts +5 -0
- package/dist/assets/logos.d.ts.map +1 -0
- package/dist/assets/logos.js +4 -0
- package/dist/components/AudioUploader.svelte +220 -0
- package/dist/components/AudioUploader.svelte.d.ts +4 -0
- package/dist/components/AudioUploader.svelte.d.ts.map +1 -0
- package/dist/components/AudioWaveform.svelte +72 -0
- package/dist/components/AudioWaveform.svelte.d.ts +7 -0
- package/dist/components/AudioWaveform.svelte.d.ts.map +1 -0
- package/dist/components/BoxUploader.svelte +17 -0
- package/dist/components/BoxUploader.svelte.d.ts +19 -0
- package/dist/components/BoxUploader.svelte.d.ts.map +1 -0
- package/dist/components/CameraUploader.svelte +140 -0
- package/dist/components/CameraUploader.svelte.d.ts +4 -0
- package/dist/components/CameraUploader.svelte.d.ts.map +1 -0
- package/dist/components/ClientBoxUploader.svelte +60 -0
- package/dist/components/ClientBoxUploader.svelte.d.ts +19 -0
- package/dist/components/ClientBoxUploader.svelte.d.ts.map +1 -0
- package/dist/components/ClientDropboxUploader.svelte +60 -0
- package/dist/components/ClientDropboxUploader.svelte.d.ts +19 -0
- package/dist/components/ClientDropboxUploader.svelte.d.ts.map +1 -0
- package/dist/components/ClientGoogleDriveUploader.svelte +57 -0
- package/dist/components/ClientGoogleDriveUploader.svelte.d.ts +19 -0
- package/dist/components/ClientGoogleDriveUploader.svelte.d.ts.map +1 -0
- package/dist/components/ClientOneDriveUploader.svelte +60 -0
- package/dist/components/ClientOneDriveUploader.svelte.d.ts +19 -0
- package/dist/components/ClientOneDriveUploader.svelte.d.ts.map +1 -0
- package/dist/components/DefaultAddMoreIcon.svelte +11 -0
- package/dist/components/DefaultAddMoreIcon.svelte.d.ts +8 -0
- package/dist/components/DefaultAddMoreIcon.svelte.d.ts.map +1 -0
- package/dist/components/DefaultLoaderIcon.svelte +5 -0
- package/dist/components/DefaultLoaderIcon.svelte.d.ts +19 -0
- package/dist/components/DefaultLoaderIcon.svelte.d.ts.map +1 -0
- package/dist/components/DropboxUploader.svelte +17 -0
- package/dist/components/DropboxUploader.svelte.d.ts +19 -0
- package/dist/components/DropboxUploader.svelte.d.ts.map +1 -0
- package/dist/components/EmptyIcon.svelte +3 -0
- package/dist/components/EmptyIcon.svelte.d.ts +19 -0
- package/dist/components/EmptyIcon.svelte.d.ts.map +1 -0
- package/dist/components/FileHero.svelte +152 -0
- package/dist/components/FileHero.svelte.d.ts +8 -0
- package/dist/components/FileHero.svelte.d.ts.map +1 -0
- package/dist/components/FileIcon.svelte +23 -0
- package/dist/components/FileIcon.svelte.d.ts +8 -0
- package/dist/components/FileIcon.svelte.d.ts.map +1 -0
- package/dist/components/FileItem.svelte +95 -0
- package/dist/components/FileItem.svelte.d.ts +12 -0
- package/dist/components/FileItem.svelte.d.ts.map +1 -0
- package/dist/components/FileList.svelte +442 -0
- package/dist/components/FileList.svelte.d.ts +4 -0
- package/dist/components/FileList.svelte.d.ts.map +1 -0
- package/dist/components/FilePreview.svelte +218 -0
- package/dist/components/FilePreview.svelte.d.ts +17 -0
- package/dist/components/FilePreview.svelte.d.ts.map +1 -0
- package/dist/components/FilePreviewPortal.svelte +189 -0
- package/dist/components/FilePreviewPortal.svelte.d.ts +12 -0
- package/dist/components/FilePreviewPortal.svelte.d.ts.map +1 -0
- package/dist/components/FilePreviewThumbnail.svelte +87 -0
- package/dist/components/FilePreviewThumbnail.svelte.d.ts +17 -0
- package/dist/components/FilePreviewThumbnail.svelte.d.ts.map +1 -0
- package/dist/components/FileRow.svelte +192 -0
- package/dist/components/FileRow.svelte.d.ts +10 -0
- package/dist/components/FileRow.svelte.d.ts.map +1 -0
- package/dist/components/GoogleDriveUploader.svelte +17 -0
- package/dist/components/GoogleDriveUploader.svelte.d.ts +19 -0
- package/dist/components/GoogleDriveUploader.svelte.d.ts.map +1 -0
- package/dist/components/Icon.svelte +24 -0
- package/dist/components/Icon.svelte.d.ts +10 -0
- package/dist/components/Icon.svelte.d.ts.map +1 -0
- package/dist/components/Icons.d.ts +10 -0
- package/dist/components/Icons.d.ts.map +1 -0
- package/dist/components/Icons.js +9 -0
- package/dist/components/ImageEditorStub.svelte +3 -0
- package/dist/components/ImageEditorStub.svelte.d.ts +19 -0
- package/dist/components/ImageEditorStub.svelte.d.ts.map +1 -0
- package/dist/components/OneDriveUploader.svelte +17 -0
- package/dist/components/OneDriveUploader.svelte.d.ts +19 -0
- package/dist/components/OneDriveUploader.svelte.d.ts.map +1 -0
- package/dist/components/ScreenCaptureUploader.svelte +254 -0
- package/dist/components/ScreenCaptureUploader.svelte.d.ts +4 -0
- package/dist/components/ScreenCaptureUploader.svelte.d.ts.map +1 -0
- package/dist/components/ServerModeDriveUploader.svelte +250 -0
- package/dist/components/ServerModeDriveUploader.svelte.d.ts +10 -0
- package/dist/components/ServerModeDriveUploader.svelte.d.ts.map +1 -0
- package/dist/components/SourceSelector.svelte +292 -0
- package/dist/components/SourceSelector.svelte.d.ts +19 -0
- package/dist/components/SourceSelector.svelte.d.ts.map +1 -0
- package/dist/components/SourceView.svelte +100 -0
- package/dist/components/SourceView.svelte.d.ts +4 -0
- package/dist/components/SourceView.svelte.d.ts.map +1 -0
- package/dist/components/TrashIcon.svelte +11 -0
- package/dist/components/TrashIcon.svelte.d.ts +8 -0
- package/dist/components/TrashIcon.svelte.d.ts.map +1 -0
- package/dist/components/UploaderPanel.svelte +363 -0
- package/dist/components/UploaderPanel.svelte.d.ts +4 -0
- package/dist/components/UploaderPanel.svelte.d.ts.map +1 -0
- package/dist/components/UrlUploader.svelte +71 -0
- package/dist/components/UrlUploader.svelte.d.ts +4 -0
- package/dist/components/UrlUploader.svelte.d.ts.map +1 -0
- package/dist/components/icons/AudioIcon.svelte +5 -0
- package/dist/components/icons/AudioIcon.svelte.d.ts +7 -0
- package/dist/components/icons/AudioIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/BoxIcon.svelte +5 -0
- package/dist/components/icons/BoxIcon.svelte.d.ts +7 -0
- package/dist/components/icons/BoxIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/CameraIcon.svelte +5 -0
- package/dist/components/icons/CameraIcon.svelte.d.ts +7 -0
- package/dist/components/icons/CameraIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/DropboxIcon.svelte +5 -0
- package/dist/components/icons/DropboxIcon.svelte.d.ts +7 -0
- package/dist/components/icons/DropboxIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/GoogleDriveIcon.svelte +5 -0
- package/dist/components/icons/GoogleDriveIcon.svelte.d.ts +7 -0
- package/dist/components/icons/GoogleDriveIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/LinkIcon.svelte +5 -0
- package/dist/components/icons/LinkIcon.svelte.d.ts +7 -0
- package/dist/components/icons/LinkIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/MyDeviceIcon.svelte +5 -0
- package/dist/components/icons/MyDeviceIcon.svelte.d.ts +7 -0
- package/dist/components/icons/MyDeviceIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/OneDriveIcon.svelte +5 -0
- package/dist/components/icons/OneDriveIcon.svelte.d.ts +7 -0
- package/dist/components/icons/OneDriveIcon.svelte.d.ts.map +1 -0
- package/dist/components/icons/ScreenCaptureIcon.svelte +5 -0
- package/dist/components/icons/ScreenCaptureIcon.svelte.d.ts +7 -0
- package/dist/components/icons/ScreenCaptureIcon.svelte.d.ts.map +1 -0
- package/dist/components/shared/DriveAuthFallback.svelte +60 -0
- package/dist/components/shared/DriveAuthFallback.svelte.d.ts +12 -0
- package/dist/components/shared/DriveAuthFallback.svelte.d.ts.map +1 -0
- package/dist/components/shared/DriveBrowser.svelte +209 -0
- package/dist/components/shared/DriveBrowser.svelte.d.ts +25 -0
- package/dist/components/shared/DriveBrowser.svelte.d.ts.map +1 -0
- package/dist/components/shared/DriveBrowserHeader.svelte +191 -0
- package/dist/components/shared/DriveBrowserHeader.svelte.d.ts +14 -0
- package/dist/components/shared/DriveBrowserHeader.svelte.d.ts.map +1 -0
- package/dist/components/shared/DriveBrowserIcon.svelte +39 -0
- package/dist/components/shared/DriveBrowserIcon.svelte.d.ts +8 -0
- package/dist/components/shared/DriveBrowserIcon.svelte.d.ts.map +1 -0
- package/dist/components/shared/DriveBrowserItem.svelte +63 -0
- package/dist/components/shared/DriveBrowserItem.svelte.d.ts +10 -0
- package/dist/components/shared/DriveBrowserItem.svelte.d.ts.map +1 -0
- package/dist/components/shared/FileSuccessCheck.svelte +47 -0
- package/dist/components/shared/FileSuccessCheck.svelte.d.ts +10 -0
- package/dist/components/shared/FileSuccessCheck.svelte.d.ts.map +1 -0
- package/dist/components/shared/ProgressBar.svelte +81 -0
- package/dist/components/shared/ProgressBar.svelte.d.ts +10 -0
- package/dist/components/shared/ProgressBar.svelte.d.ts.map +1 -0
- package/dist/components/shared/SourceViewContainer.svelte +27 -0
- package/dist/components/shared/SourceViewContainer.svelte.d.ts +10 -0
- package/dist/components/shared/SourceViewContainer.svelte.d.ts.map +1 -0
- package/dist/components/shared/UploaderHeader.svelte +156 -0
- package/dist/components/shared/UploaderHeader.svelte.d.ts +13 -0
- package/dist/components/shared/UploaderHeader.svelte.d.ts.map +1 -0
- package/dist/composables/use-tiles-per-row.d.ts +19 -0
- package/dist/composables/use-tiles-per-row.d.ts.map +1 -0
- package/dist/composables/use-tiles-per-row.js +30 -0
- package/dist/composables/useBox.d.ts +28 -0
- package/dist/composables/useBox.d.ts.map +1 -0
- package/dist/composables/useBox.js +61 -0
- package/dist/composables/useCameraUploader.d.ts +23 -0
- package/dist/composables/useCameraUploader.d.ts.map +1 -0
- package/dist/composables/useCameraUploader.js +114 -0
- package/dist/composables/useDropbox.d.ts +28 -0
- package/dist/composables/useDropbox.d.ts.map +1 -0
- package/dist/composables/useDropbox.js +61 -0
- package/dist/composables/useFetchFileByUrl.d.ts +9 -0
- package/dist/composables/useFetchFileByUrl.d.ts.map +1 -0
- package/dist/composables/useFetchFileByUrl.js +33 -0
- package/dist/composables/useGoogleDrive.d.ts +28 -0
- package/dist/composables/useGoogleDrive.d.ts.map +1 -0
- package/dist/composables/useGoogleDrive.js +65 -0
- package/dist/composables/useOneDrive.d.ts +29 -0
- package/dist/composables/useOneDrive.d.ts.map +1 -0
- package/dist/composables/useOneDrive.js +64 -0
- package/dist/composables/useSSEProcessing.d.ts +20 -0
- package/dist/composables/useSSEProcessing.d.ts.map +1 -0
- package/dist/composables/useSSEProcessing.js +17 -0
- package/dist/composables/useServerModeDrive.d.ts +17 -0
- package/dist/composables/useServerModeDrive.d.ts.map +1 -0
- package/dist/composables/useServerModeDrive.js +33 -0
- package/dist/composables/useSourceSelector.d.ts +13 -0
- package/dist/composables/useSourceSelector.d.ts.map +1 -0
- package/dist/composables/useSourceSelector.js +36 -0
- package/dist/composables/useUploaderPanel.d.ts +12 -0
- package/dist/composables/useUploaderPanel.d.ts.map +1 -0
- package/dist/composables/useUploaderPanel.js +43 -0
- package/dist/context/create-uploader-context.d.ts +4 -0
- package/dist/context/create-uploader-context.d.ts.map +1 -0
- package/dist/context/create-uploader-context.js +324 -0
- package/dist/context/source-view-header-extra.d.ts +14 -0
- package/dist/context/source-view-header-extra.d.ts.map +1 -0
- package/dist/context/source-view-header-extra.js +41 -0
- package/dist/context/uploader-context.d.ts +109 -0
- package/dist/context/uploader-context.d.ts.map +1 -0
- package/dist/context/uploader-context.js +96 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/constants.d.ts +13 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +67 -0
- package/dist/lib/to-readable.d.ts +14 -0
- package/dist/lib/to-readable.d.ts.map +1 -0
- package/dist/lib/to-readable.js +15 -0
- package/dist/lib/view-mode.d.ts +24 -0
- package/dist/lib/view-mode.d.ts.map +1 -0
- package/dist/lib/view-mode.js +31 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +1 -0
- package/dist/styles.d.ts +4 -0
- package/dist/tailwind-prefixed.css +2479 -0
- package/dist/tailwind.css +74 -0
- package/dist/use-upup-upload.d.ts +38 -0
- package/dist/use-upup-upload.d.ts.map +1 -0
- package/dist/use-upup-upload.js +81 -0
- package/package.json +101 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte'
|
|
3
|
+
import { get } from 'svelte/store'
|
|
4
|
+
import { createVirtualizer } from '@tanstack/svelte-virtual'
|
|
5
|
+
import { formatUiMessage as t, pluralUiMessage as plural, UploadStatus } from '@useupup/core'
|
|
6
|
+
import { isUploadActive, cn } from '@useupup/core/internal'
|
|
7
|
+
import {
|
|
8
|
+
useUploaderFiles,
|
|
9
|
+
useUploaderI18n,
|
|
10
|
+
useUploaderOptions,
|
|
11
|
+
useUploaderSource,
|
|
12
|
+
useUploaderTheme,
|
|
13
|
+
useUploaderUploadControls,
|
|
14
|
+
useUploaderView,
|
|
15
|
+
} from '../context/uploader-context'
|
|
16
|
+
import Icon from './Icon.svelte'
|
|
17
|
+
import FileItem from './FileItem.svelte'
|
|
18
|
+
import FileHero from './FileHero.svelte'
|
|
19
|
+
import UploaderHeader from './shared/UploaderHeader.svelte'
|
|
20
|
+
import ProgressBar from './shared/ProgressBar.svelte'
|
|
21
|
+
import FileSuccessCheck from './shared/FileSuccessCheck.svelte'
|
|
22
|
+
import { isListViewForced } from '../lib/view-mode'
|
|
23
|
+
import { useTilesPerRow } from '../composables/use-tiles-per-row'
|
|
24
|
+
|
|
25
|
+
const VIRTUAL_SCROLL_THRESHOLD = 20
|
|
26
|
+
const ESTIMATED_ITEM_HEIGHT = 76
|
|
27
|
+
|
|
28
|
+
function formatBytes(bytes: number): string {
|
|
29
|
+
if (bytes === 0) return '0 B'
|
|
30
|
+
const k = 1024
|
|
31
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
32
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
33
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatEta(seconds: number): string {
|
|
37
|
+
if (seconds <= 0 || !isFinite(seconds)) return ''
|
|
38
|
+
const m = Math.floor(seconds / 60)
|
|
39
|
+
const s = seconds % 60
|
|
40
|
+
if (m > 0) return `${m}m ${s}s left`
|
|
41
|
+
return `${s}s left`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { viewMode, sourceOverlayOpen, openSourceOverlay } = useUploaderView()
|
|
45
|
+
const { activeSource } = useUploaderSource()
|
|
46
|
+
const { files, leavingFileIds } = useUploaderFiles()
|
|
47
|
+
const { translations: tr } = useUploaderI18n()
|
|
48
|
+
const {
|
|
49
|
+
upload: {
|
|
50
|
+
startUpload,
|
|
51
|
+
retryUpload,
|
|
52
|
+
uploadStatus,
|
|
53
|
+
uploadError,
|
|
54
|
+
uploadErrorCode,
|
|
55
|
+
totalProgress,
|
|
56
|
+
uploadSpeed,
|
|
57
|
+
uploadEta,
|
|
58
|
+
uploadedBytes,
|
|
59
|
+
totalBytes,
|
|
60
|
+
},
|
|
61
|
+
handleDone,
|
|
62
|
+
handleCancel,
|
|
63
|
+
handlePause,
|
|
64
|
+
handleResume,
|
|
65
|
+
} = useUploaderUploadControls()
|
|
66
|
+
const {
|
|
67
|
+
isProcessing,
|
|
68
|
+
resumable,
|
|
69
|
+
limit,
|
|
70
|
+
quietCompletion,
|
|
71
|
+
icons: { ContainerAddMoreIcon },
|
|
72
|
+
} = useUploaderOptions()
|
|
73
|
+
const { isDark: dark, slotOverrides: slotClasses, slots: themeSlots } = useUploaderTheme()
|
|
74
|
+
|
|
75
|
+
// Scroll element — null on SSR; set via bind:this after mount
|
|
76
|
+
let scrollEl: HTMLDivElement | null = $state(null)
|
|
77
|
+
let mounted = $state(false)
|
|
78
|
+
|
|
79
|
+
// Render in CORE's insertion order (FileManager's Map preserves it, stable
|
|
80
|
+
// across renders and unchanged by in-place updateFile). A file added after
|
|
81
|
+
// others must appear LAST, never re-sorted into the middle alphabetically
|
|
82
|
+
// (round-7 item 3). Folder uploads already add folder-by-folder, so grouping
|
|
83
|
+
// survives without a sort.
|
|
84
|
+
const orderedFiles = $derived(Array.from($files.values()))
|
|
85
|
+
|
|
86
|
+
const isSingle = $derived(orderedFiles.length === 1)
|
|
87
|
+
|
|
88
|
+
// Adaptive layout rule: the square-tile grid is only honored when every tile
|
|
89
|
+
// fits in ONE row of the fixed-height panel. tilesPerRow is measured from the
|
|
90
|
+
// scroll container (160px min tile + 16px gap); past it the row list is FORCED
|
|
91
|
+
// and UploaderHeader hides the grid/list toggle.
|
|
92
|
+
const { tilesPerRow, observe: observeTiles } = useTilesPerRow()
|
|
93
|
+
$effect(() => {
|
|
94
|
+
observeTiles(scrollEl)
|
|
95
|
+
})
|
|
96
|
+
const forcedList = $derived(
|
|
97
|
+
isListViewForced(orderedFiles.length, $tilesPerRow),
|
|
98
|
+
)
|
|
99
|
+
const effectiveViewMode = $derived(forcedList ? 'list' : $viewMode)
|
|
100
|
+
|
|
101
|
+
const shouldVirtualize = $derived(
|
|
102
|
+
orderedFiles.length >= VIRTUAL_SCROLL_THRESHOLD &&
|
|
103
|
+
effectiveViewMode !== 'grid',
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
const isUploading = $derived(isUploadActive($uploadStatus))
|
|
107
|
+
// When the add-more source surface is up (overlay open, or a source chosen
|
|
108
|
+
// while files exist), this list stays mounted but dimmed and inert behind it.
|
|
109
|
+
const dimmed = $derived($sourceOverlayOpen || !!$activeSource)
|
|
110
|
+
const heroLeaving = $derived(
|
|
111
|
+
isSingle && !!orderedFiles[0] && $leavingFileIds.has(orderedFiles[0].id),
|
|
112
|
+
)
|
|
113
|
+
// Quiet completion (item 7): a successful run under `quietCompletion` shows
|
|
114
|
+
// ONLY the checkmark overlay — Done/add-more/CTA are all suppressed and the
|
|
115
|
+
// host takes over via the completion callbacks. Wins over 6a's continue flow.
|
|
116
|
+
const quietDone = $derived(
|
|
117
|
+
!!quietCompletion && $uploadStatus === UploadStatus.SUCCESSFUL,
|
|
118
|
+
)
|
|
119
|
+
const canAddMore = $derived(
|
|
120
|
+
limit > 1 && $files.size < limit && !isUploading && !isProcessing && !quietDone,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
const isGrid = $derived($files.size > 1 && effectiveViewMode === 'grid')
|
|
124
|
+
const gridStyle = $derived(
|
|
125
|
+
isGrid
|
|
126
|
+
? 'grid-template-columns: repeat(auto-fit, minmax(160px, 1fr))'
|
|
127
|
+
: undefined,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Create virtualizer store — getScrollElement returns null on SSR (safe)
|
|
131
|
+
// count starts at 0; we update via setOptions whenever orderedFiles or
|
|
132
|
+
// shouldVirtualize changes (Svelte $effect runs client-side).
|
|
133
|
+
const virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
|
|
134
|
+
count: 0,
|
|
135
|
+
getScrollElement: () => scrollEl,
|
|
136
|
+
estimateSize: () => ESTIMATED_ITEM_HEIGHT,
|
|
137
|
+
overscan: 5,
|
|
138
|
+
enabled: false,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Keep virtualizer options in sync with reactive state (runs only on client).
|
|
142
|
+
// CRITICAL: depend ONLY on the inputs (count/shouldVirtualize/scrollEl) and
|
|
143
|
+
// push them through a NON-tracking get(virtualizer) read. We must NOT read
|
|
144
|
+
// $virtualizer here: setOptions() internally calls virtualizerWritable.set(),
|
|
145
|
+
// which would re-emit the derived store; if this effect were subscribed to it
|
|
146
|
+
// (via $virtualizer auto-subscription) that emission would re-run the effect →
|
|
147
|
+
// setOptions → emit → infinite loop. get(store) does a synchronous
|
|
148
|
+
// subscribe+unsubscribe and is NOT registered as an effect dependency, so
|
|
149
|
+
// writing via it cannot re-trigger this effect.
|
|
150
|
+
$effect(() => {
|
|
151
|
+
if (!mounted) return
|
|
152
|
+
const count = orderedFiles.length // dependency: file list changes
|
|
153
|
+
const sv = shouldVirtualize // dependency: virtualization gate
|
|
154
|
+
void scrollEl // dependency: element binds after mount
|
|
155
|
+
get(virtualizer).setOptions({
|
|
156
|
+
count,
|
|
157
|
+
getScrollElement: () => scrollEl,
|
|
158
|
+
estimateSize: () => ESTIMATED_ITEM_HEIGHT,
|
|
159
|
+
overscan: 5,
|
|
160
|
+
enabled: sv,
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
onMount(() => {
|
|
165
|
+
mounted = true
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
function onUploadClick() {
|
|
169
|
+
void startUpload().catch(() => undefined)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function onRetryClick() {
|
|
173
|
+
void retryUpload().catch(() => undefined)
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<div
|
|
178
|
+
data-testid="upup-file-list"
|
|
179
|
+
data-upup-slot="file-list"
|
|
180
|
+
inert={dimmed || undefined}
|
|
181
|
+
class={cn(
|
|
182
|
+
'upup-relative upup-flex upup-h-full upup-flex-col upup-rounded-lg upup-shadow',
|
|
183
|
+
{
|
|
184
|
+
'upup-hidden': !$files.size,
|
|
185
|
+
'upup-opacity-50 upup-blur-[2px] upup-pointer-events-none': dimmed,
|
|
186
|
+
},
|
|
187
|
+
$themeSlots?.fileList?.root,
|
|
188
|
+
)}
|
|
189
|
+
>
|
|
190
|
+
<div role="status" aria-live="polite" class="upup-sr-only">
|
|
191
|
+
{t(plural(tr, 'filesSelected', $files.size), { count: $files.size })}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<UploaderHeader {handleCancel} {forcedList} hideAddMore={quietDone} />
|
|
195
|
+
|
|
196
|
+
<!-- Quiet-completion overlay (item 7): checkmark only, no CTAs. The fx
|
|
197
|
+
classes on FileSuccessCheck die under data-motion='off' (it then simply
|
|
198
|
+
appears drawn), so this satisfies the static-state rule. -->
|
|
199
|
+
{#if quietDone}
|
|
200
|
+
<div
|
|
201
|
+
data-testid="upup-complete-check"
|
|
202
|
+
data-upup-slot="complete-check"
|
|
203
|
+
role="status"
|
|
204
|
+
class={cn(
|
|
205
|
+
'upup-absolute upup-inset-0 upup-z-20 upup-flex upup-flex-col upup-items-center upup-justify-center upup-gap-2 upup-rounded-lg upup-backdrop-blur-[1px]',
|
|
206
|
+
$dark ? 'upup-bg-[#04080f]/40' : 'upup-bg-white/50',
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
<FileSuccessCheck size={56} />
|
|
210
|
+
<span
|
|
211
|
+
class={cn(
|
|
212
|
+
'upup-text-sm upup-font-medium',
|
|
213
|
+
$dark ? 'upup-text-[#e2e8f0]' : 'upup-text-[#1e293b]',
|
|
214
|
+
)}
|
|
215
|
+
>
|
|
216
|
+
{tr.announceUploadComplete}
|
|
217
|
+
</span>
|
|
218
|
+
</div>
|
|
219
|
+
{/if}
|
|
220
|
+
|
|
221
|
+
<div
|
|
222
|
+
bind:this={scrollEl}
|
|
223
|
+
class={cn(
|
|
224
|
+
'upup-preview-scroll upup-flex upup-flex-1 upup-flex-col upup-overflow-y-auto upup-p-3',
|
|
225
|
+
$dark ? 'upup-bg-transparent' : 'upup-bg-black/[0.075]',
|
|
226
|
+
$slotClasses.fileListContainer,
|
|
227
|
+
)}
|
|
228
|
+
>
|
|
229
|
+
{#if isSingle}
|
|
230
|
+
<!-- Single-file HERO: one visual fills the content area. -->
|
|
231
|
+
<div
|
|
232
|
+
role="list"
|
|
233
|
+
class={cn(
|
|
234
|
+
'upup-animate-fx-enter upup-flex upup-min-h-0 upup-flex-1 upup-flex-col',
|
|
235
|
+
heroLeaving && 'upup-animate-fx-exit upup-overflow-hidden',
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
<FileHero file={orderedFiles[0]!} />
|
|
239
|
+
</div>
|
|
240
|
+
{:else if shouldVirtualize && mounted}
|
|
241
|
+
<!-- Virtualized list -->
|
|
242
|
+
<div
|
|
243
|
+
role="list"
|
|
244
|
+
data-upup-slot="file-list-virtual"
|
|
245
|
+
style={`height: ${$virtualizer.getTotalSize()}px; position: relative;`}
|
|
246
|
+
class={cn(
|
|
247
|
+
isProcessing && 'upup-pointer-events-none upup-opacity-75',
|
|
248
|
+
'upup-font-[Arial,Helvetica,sans-serif]',
|
|
249
|
+
)}
|
|
250
|
+
>
|
|
251
|
+
{#each $virtualizer.getVirtualItems() as virtualItem (String(virtualItem.key))}
|
|
252
|
+
{@const file = orderedFiles[virtualItem.index]}
|
|
253
|
+
{#if file}
|
|
254
|
+
<div
|
|
255
|
+
data-index={virtualItem.index}
|
|
256
|
+
style={`position: absolute; top: 0; left: 0; width: 100%; transform: translateY(${virtualItem.start}px); padding-bottom: 12px;`}
|
|
257
|
+
>
|
|
258
|
+
<FileItem {file} index={virtualItem.index} {forcedList} />
|
|
259
|
+
</div>
|
|
260
|
+
{/if}
|
|
261
|
+
{/each}
|
|
262
|
+
</div>
|
|
263
|
+
{:else}
|
|
264
|
+
<!-- Standard rendering for small lists and grid mode. Grid is a single row
|
|
265
|
+
of auto-fit tiles that STRETCH to fill the row (minmax(160px,1fr) — no
|
|
266
|
+
dead columns at 2/3 files). It is only reached when the tiles fit one
|
|
267
|
+
row (else forcedList swaps to the list), so the grid can never wrap
|
|
268
|
+
the fixed panel. -->
|
|
269
|
+
<div
|
|
270
|
+
role="list"
|
|
271
|
+
style={gridStyle}
|
|
272
|
+
class={cn(
|
|
273
|
+
isProcessing && 'upup-pointer-events-none upup-opacity-75',
|
|
274
|
+
'upup-font-[Arial,Helvetica,sans-serif]',
|
|
275
|
+
isGrid ? 'upup-grid upup-gap-4' : 'upup-flex upup-flex-col upup-gap-3',
|
|
276
|
+
{
|
|
277
|
+
[$slotClasses.fileListContainerInnerMultiple ?? '']:
|
|
278
|
+
!!$slotClasses.fileListContainerInnerMultiple && $files.size > 1,
|
|
279
|
+
[$slotClasses.fileListContainerInnerSingle ?? '']:
|
|
280
|
+
!!$slotClasses.fileListContainerInnerSingle && $files.size === 1,
|
|
281
|
+
},
|
|
282
|
+
)}
|
|
283
|
+
>
|
|
284
|
+
{#each orderedFiles as file, index (file.id)}
|
|
285
|
+
<FileItem {file} {index} {forcedList} />
|
|
286
|
+
{/each}
|
|
287
|
+
</div>
|
|
288
|
+
{/if}
|
|
289
|
+
|
|
290
|
+
<!-- Full-width dashed "Add more" row (spec §4 state 3): a second
|
|
291
|
+
add-more affordance beneath the list/hero, opening the overlay. -->
|
|
292
|
+
{#if canAddMore}
|
|
293
|
+
<button
|
|
294
|
+
data-testid="upup-add-more"
|
|
295
|
+
data-placement="footer"
|
|
296
|
+
data-upup-slot="add-more"
|
|
297
|
+
class={cn(
|
|
298
|
+
'upup-fx-hover-lift upup-fx-press upup-mt-2.5 upup-flex upup-flex-none upup-items-center upup-justify-center upup-gap-2 upup-whitespace-nowrap upup-rounded-xl upup-border-[1.5px] upup-border-dashed upup-px-3 upup-py-2 upup-text-[13px] upup-font-medium',
|
|
299
|
+
$dark
|
|
300
|
+
? 'upup-border-white/[0.16] upup-text-[#94a3b8]'
|
|
301
|
+
: 'upup-border-black/[0.16] upup-text-gray-500',
|
|
302
|
+
$slotClasses.containerAddMoreButton,
|
|
303
|
+
)}
|
|
304
|
+
onclick={openSourceOverlay}
|
|
305
|
+
disabled={isUploading || isProcessing}
|
|
306
|
+
>
|
|
307
|
+
<ContainerAddMoreIcon />
|
|
308
|
+
{tr.addMore}
|
|
309
|
+
</button>
|
|
310
|
+
{/if}
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<div
|
|
314
|
+
class={cn(
|
|
315
|
+
'upup-shadow-top upup-flex upup-items-center upup-gap-3 upup-rounded-b-lg upup-bg-black/[0.025] upup-px-3 upup-py-2',
|
|
316
|
+
{ 'upup-bg-white/5 dark:upup-bg-white/5': $dark },
|
|
317
|
+
$slotClasses.fileListFooter,
|
|
318
|
+
)}
|
|
319
|
+
>
|
|
320
|
+
<!-- Primary CTA only in a pre-run/idle state. Once a run is active it
|
|
321
|
+
becomes Cancel; when PAUSED it becomes Resume; SUCCESSFUL shows Done and
|
|
322
|
+
FAILED shows Retry (below). -->
|
|
323
|
+
{#if $uploadStatus !== UploadStatus.SUCCESSFUL && $uploadStatus !== UploadStatus.FAILED && $uploadStatus !== UploadStatus.PAUSED && !isUploading}
|
|
324
|
+
<button
|
|
325
|
+
data-testid="upup-upload-btn"
|
|
326
|
+
class={cn(
|
|
327
|
+
'upup-fx-sheen-sweep upup-fx-press upup-disabled:animate-pulse upup-ml-auto upup-rounded-full upup-bg-[#0ea5e9] upup-px-4 upup-py-2 upup-text-sm upup-font-medium upup-text-white',
|
|
328
|
+
{ 'upup-bg-[#38bdf8] dark:upup-bg-[#38bdf8]': $dark },
|
|
329
|
+
$slotClasses.uploadButton,
|
|
330
|
+
)}
|
|
331
|
+
onclick={onUploadClick}
|
|
332
|
+
disabled={isUploadActive($uploadStatus) || isProcessing}
|
|
333
|
+
>
|
|
334
|
+
{t(plural(tr, 'uploadFiles', $files.size), { count: $files.size })}
|
|
335
|
+
</button>
|
|
336
|
+
{/if}
|
|
337
|
+
|
|
338
|
+
{#if $uploadStatus === UploadStatus.FAILED && $uploadError}
|
|
339
|
+
<p
|
|
340
|
+
data-testid="upup-upload-error"
|
|
341
|
+
data-upup-slot="upload-error"
|
|
342
|
+
title={$uploadErrorCode}
|
|
343
|
+
class="upup-mr-auto upup-text-sm upup-text-red-600 dark:upup-text-red-400"
|
|
344
|
+
>
|
|
345
|
+
{$uploadErrorCode ? t(tr.uploadFailedWithCode, { code: $uploadErrorCode }) : t(tr.uploadFailed, { message: $uploadError })}
|
|
346
|
+
</p>
|
|
347
|
+
{/if}
|
|
348
|
+
|
|
349
|
+
{#if $uploadStatus === UploadStatus.FAILED}
|
|
350
|
+
<button
|
|
351
|
+
data-testid="upup-retry-btn"
|
|
352
|
+
class={cn(
|
|
353
|
+
'upup-fx-press upup-disabled:animate-pulse upup-ml-auto upup-rounded-full upup-bg-red-600 upup-px-4 upup-py-2 upup-text-sm upup-font-medium upup-text-white',
|
|
354
|
+
{ 'upup-bg-red-500 dark:upup-bg-red-500': $dark },
|
|
355
|
+
$slotClasses.uploadButton,
|
|
356
|
+
)}
|
|
357
|
+
onclick={onRetryClick}
|
|
358
|
+
>
|
|
359
|
+
{resumable?.protocol === 'multipart' ? tr.resumeUpload : tr.retryUpload}
|
|
360
|
+
</button>
|
|
361
|
+
{/if}
|
|
362
|
+
|
|
363
|
+
{#if $uploadStatus === UploadStatus.SUCCESSFUL && !quietCompletion}
|
|
364
|
+
<button
|
|
365
|
+
class={cn(
|
|
366
|
+
'upup-fx-sheen-sweep upup-fx-press upup-disabled:animate-pulse upup-ml-auto upup-rounded-lg upup-bg-[#0ea5e9] upup-px-3 upup-py-2 upup-text-sm upup-font-medium upup-text-white',
|
|
367
|
+
{ 'upup-bg-[#38bdf8] dark:upup-bg-[#38bdf8]': $dark },
|
|
368
|
+
$slotClasses.uploadDoneButton,
|
|
369
|
+
)}
|
|
370
|
+
onclick={handleDone}
|
|
371
|
+
>
|
|
372
|
+
{tr.done}
|
|
373
|
+
</button>
|
|
374
|
+
{/if}
|
|
375
|
+
|
|
376
|
+
<div class="upup-flex upup-flex-1 upup-flex-col upup-gap-1">
|
|
377
|
+
<div class="upup-flex upup-items-center upup-gap-2">
|
|
378
|
+
<!-- Cancel an in-flight run: aborts the active request(s) but KEEPS the
|
|
379
|
+
files (status → PAUSED) so the run can be resumed. A true discard is
|
|
380
|
+
the header's "remove all". -->
|
|
381
|
+
{#if $uploadStatus === UploadStatus.UPLOADING}
|
|
382
|
+
<button
|
|
383
|
+
data-testid="upup-upload-cancel"
|
|
384
|
+
class={cn(
|
|
385
|
+
'upup-fx-press upup-flex upup-h-7 upup-items-center upup-gap-1 upup-whitespace-nowrap upup-rounded-full upup-bg-red-100 upup-px-3 upup-text-xs upup-font-medium upup-text-red-700 upup-transition-colors hover:upup-bg-red-200',
|
|
386
|
+
{ 'upup-bg-red-500/20 upup-text-red-100 hover:upup-bg-red-500/30': $dark },
|
|
387
|
+
)}
|
|
388
|
+
onclick={handlePause}
|
|
389
|
+
aria-label={tr.cancel}
|
|
390
|
+
title={tr.cancel}
|
|
391
|
+
>
|
|
392
|
+
<Icon name="x" size={14} />
|
|
393
|
+
{tr.cancel}
|
|
394
|
+
</button>
|
|
395
|
+
{/if}
|
|
396
|
+
{#if $uploadStatus === UploadStatus.PAUSED}
|
|
397
|
+
<button
|
|
398
|
+
data-testid="upup-upload-resume"
|
|
399
|
+
class={cn(
|
|
400
|
+
'upup-fx-press upup-flex upup-h-7 upup-items-center upup-gap-1 upup-whitespace-nowrap upup-rounded-full upup-bg-[#0ea5e9] upup-px-3 upup-text-xs upup-font-medium upup-text-white upup-transition-colors',
|
|
401
|
+
{ 'upup-bg-[#38bdf8] dark:upup-bg-[#38bdf8]': $dark },
|
|
402
|
+
)}
|
|
403
|
+
onclick={handleResume}
|
|
404
|
+
aria-label={tr.resumeUpload}
|
|
405
|
+
title={tr.resumeUpload}
|
|
406
|
+
>
|
|
407
|
+
<Icon name="player-play" size={14} />
|
|
408
|
+
{tr.resumeUpload}
|
|
409
|
+
</button>
|
|
410
|
+
{/if}
|
|
411
|
+
<ProgressBar
|
|
412
|
+
class="upup-flex-1"
|
|
413
|
+
progressBarClassName="upup-rounded"
|
|
414
|
+
progress={$totalProgress}
|
|
415
|
+
showValue={true}
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
{#if (isUploadActive($uploadStatus) || $uploadStatus === UploadStatus.PAUSED) && $totalBytes > 0}
|
|
420
|
+
<div
|
|
421
|
+
class={cn(
|
|
422
|
+
'upup-flex upup-items-center upup-justify-between upup-text-[11px] upup-text-gray-500',
|
|
423
|
+
{ 'upup-text-gray-400': $dark },
|
|
424
|
+
)}
|
|
425
|
+
>
|
|
426
|
+
<span>
|
|
427
|
+
{formatBytes($uploadedBytes)} of {formatBytes($totalBytes)}
|
|
428
|
+
{#if $uploadSpeed > 0}
|
|
429
|
+
· {formatBytes($uploadSpeed)}/s
|
|
430
|
+
{/if}
|
|
431
|
+
</span>
|
|
432
|
+
{#if isUploadActive($uploadStatus) && $uploadEta > 0}
|
|
433
|
+
<span>{formatEta($uploadEta)}</span>
|
|
434
|
+
{/if}
|
|
435
|
+
{#if $uploadStatus === UploadStatus.PAUSED}
|
|
436
|
+
<span>{tr.paused}</span>
|
|
437
|
+
{/if}
|
|
438
|
+
</div>
|
|
439
|
+
{/if}
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileList.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FileList.svelte.ts"],"names":[],"mappings":"AAuXA,QAAA,MAAM,QAAQ,2DAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { UploadStatus, type Translations } from '@useupup/core'
|
|
3
|
+
import { fileCanPreviewText, fileGetIsImage, fileGetIsPdf, fileGetIsText, cn, isFileRemovalLocked } from '@useupup/core/internal'
|
|
4
|
+
import {
|
|
5
|
+
useUploaderFiles,
|
|
6
|
+
useUploaderI18n,
|
|
7
|
+
useUploaderOptions,
|
|
8
|
+
useUploaderTheme,
|
|
9
|
+
useUploaderUploadControls,
|
|
10
|
+
} from '../context/uploader-context'
|
|
11
|
+
import FilePreviewThumbnail from './FilePreviewThumbnail.svelte'
|
|
12
|
+
import ProgressBar from './shared/ProgressBar.svelte'
|
|
13
|
+
import FileSuccessCheck from './shared/FileSuccessCheck.svelte'
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
fileName,
|
|
17
|
+
fileType,
|
|
18
|
+
fileId,
|
|
19
|
+
fileUrl,
|
|
20
|
+
fileSize,
|
|
21
|
+
canPreview,
|
|
22
|
+
onRequestPreview,
|
|
23
|
+
onUpdateCanPreview,
|
|
24
|
+
onclick,
|
|
25
|
+
index = 0,
|
|
26
|
+
}: {
|
|
27
|
+
fileName: string
|
|
28
|
+
fileType: string
|
|
29
|
+
fileId: string
|
|
30
|
+
fileUrl: string
|
|
31
|
+
fileSize?: number
|
|
32
|
+
canPreview: boolean
|
|
33
|
+
onRequestPreview?: () => void
|
|
34
|
+
onUpdateCanPreview?: (val: boolean) => void
|
|
35
|
+
onclick?: () => void
|
|
36
|
+
/** Position in the sorted list — drives the completion-check stagger. */
|
|
37
|
+
index?: number
|
|
38
|
+
} = $props()
|
|
39
|
+
|
|
40
|
+
const { handleFileRemove, files } = useUploaderFiles()
|
|
41
|
+
const { translations: tr } = useUploaderI18n()
|
|
42
|
+
const { upload: { filesProgressMap } } = useUploaderUploadControls()
|
|
43
|
+
const {
|
|
44
|
+
icons: { FileDeleteIcon },
|
|
45
|
+
allowPreview,
|
|
46
|
+
} = useUploaderOptions()
|
|
47
|
+
const {
|
|
48
|
+
isDark: isDarkTheme,
|
|
49
|
+
slotOverrides: slotClasses,
|
|
50
|
+
slots: themeSlots,
|
|
51
|
+
} = useUploaderTheme()
|
|
52
|
+
|
|
53
|
+
const isImage = $derived(fileGetIsImage(fileType))
|
|
54
|
+
const isVideo = $derived(fileType.startsWith('video/'))
|
|
55
|
+
const isPdf = $derived(fileGetIsPdf(fileType, fileName))
|
|
56
|
+
const isText = $derived(fileGetIsText(fileType, fileName))
|
|
57
|
+
const canPreviewText = $derived(fileCanPreviewText(fileType, fileName, fileSize))
|
|
58
|
+
|
|
59
|
+
// Auto-signal canPreview when applicable
|
|
60
|
+
$effect(() => {
|
|
61
|
+
if ((isImage || isPdf || (isText && canPreviewText)) && !canPreview) {
|
|
62
|
+
onUpdateCanPreview?.(true)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const progress = $derived.by(() => {
|
|
67
|
+
const fileProgress = $filesProgressMap[fileId]
|
|
68
|
+
const loaded = fileProgress?.loaded ?? NaN
|
|
69
|
+
const total = fileProgress?.total ?? NaN
|
|
70
|
+
const pct = Math.floor((loaded / total) * 100)
|
|
71
|
+
// No progress entry ⇒ NaN; total === 0 ⇒ Infinity. Either would render
|
|
72
|
+
// width:NaN%/aria-valuenow=NaN in ProgressBar while an upload is active.
|
|
73
|
+
return Number.isFinite(pct) ? pct : 0
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const fileStatus = $derived($files.get(fileId)?.status)
|
|
77
|
+
|
|
78
|
+
const isSuccessful = $derived(fileStatus === UploadStatus.SUCCESSFUL)
|
|
79
|
+
|
|
80
|
+
function formatFileSize(bytes: number | undefined, tr: Translations) {
|
|
81
|
+
if (!bytes || bytes === 0) return tr.zeroBytes
|
|
82
|
+
const k = 1024
|
|
83
|
+
const sizes = [tr.bytes, tr.kb, tr.mb, tr.gb]
|
|
84
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
85
|
+
return Math.round((bytes / Math.pow(k, i)) * 10) / 10 + ' ' + sizes[i]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onHandleFileRemove(e: MouseEvent) {
|
|
89
|
+
e.stopPropagation()
|
|
90
|
+
handleFileRemove(fileId)
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<div
|
|
95
|
+
class={cn('upup-block upup-w-full', $themeSlots?.filePreview?.root)}
|
|
96
|
+
data-testid="upup-file-preview"
|
|
97
|
+
data-upup-slot="file-preview"
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
class={cn(
|
|
101
|
+
'upup-fx-hover-lift upup-relative upup-h-[160px] upup-w-full upup-overflow-hidden upup-rounded-xl upup-ring-1',
|
|
102
|
+
'upup-bg-contain upup-bg-center upup-bg-no-repeat',
|
|
103
|
+
$isDarkTheme
|
|
104
|
+
? 'upup-bg-white/[0.055] upup-ring-white/[0.08]'
|
|
105
|
+
: 'upup-bg-black/[0.04] upup-ring-black/[0.06]',
|
|
106
|
+
{
|
|
107
|
+
[$slotClasses.fileThumbnailMultiple ?? '']: !!$slotClasses.fileThumbnailMultiple && $files.size > 1,
|
|
108
|
+
[$slotClasses.fileThumbnailSingle ?? '']: !!$slotClasses.fileThumbnailSingle && $files.size === 1,
|
|
109
|
+
},
|
|
110
|
+
$themeSlots?.filePreview?.thumbnail,
|
|
111
|
+
)}
|
|
112
|
+
style={isImage ? `background-image: url(${fileUrl})` : undefined}
|
|
113
|
+
>
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
aria-label={fileName}
|
|
117
|
+
class="upup-absolute upup-inset-0 upup-z-0 upup-cursor-pointer"
|
|
118
|
+
{onclick}
|
|
119
|
+
></button>
|
|
120
|
+
{#if isVideo}
|
|
121
|
+
<!-- First-frame video thumbnail — no controls chrome (the native
|
|
122
|
+
player pill read as broken CSS in a 145px tile). -->
|
|
123
|
+
<video
|
|
124
|
+
src={fileUrl}
|
|
125
|
+
muted
|
|
126
|
+
playsinline
|
|
127
|
+
preload="metadata"
|
|
128
|
+
class="upup-pointer-events-none upup-absolute upup-inset-0 upup-h-full upup-w-full upup-object-cover"
|
|
129
|
+
></video>
|
|
130
|
+
{/if}
|
|
131
|
+
{#if !isImage && !isVideo}
|
|
132
|
+
<div class="upup-flex upup-h-full upup-items-center upup-justify-center upup-p-6">
|
|
133
|
+
<FilePreviewThumbnail
|
|
134
|
+
{canPreview}
|
|
135
|
+
{fileType}
|
|
136
|
+
{fileName}
|
|
137
|
+
{fileUrl}
|
|
138
|
+
{fileSize}
|
|
139
|
+
{allowPreview}
|
|
140
|
+
slotClasses={$slotClasses}
|
|
141
|
+
labels={tr}
|
|
142
|
+
onUpdateCanPreview={onUpdateCanPreview}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
<!-- Delete disappears once the file has uploaded successfully — the
|
|
148
|
+
completion check is then the only overlay affordance. -->
|
|
149
|
+
{#if !isSuccessful}
|
|
150
|
+
<button
|
|
151
|
+
class={cn(
|
|
152
|
+
'upup-fx-remove upup-fx-press upup-absolute upup-right-1.5 upup-top-1.5 upup-z-10',
|
|
153
|
+
'upup-flex upup-h-[30px] upup-w-[30px] upup-items-center upup-justify-center',
|
|
154
|
+
'upup-rounded-[8px] upup-bg-[#04080f]/40 upup-text-[#e2e8f0]',
|
|
155
|
+
'hover:upup-bg-[#04080f]/65',
|
|
156
|
+
'disabled:upup-cursor-not-allowed disabled:upup-opacity-50',
|
|
157
|
+
$slotClasses.fileDeleteButton,
|
|
158
|
+
$themeSlots?.filePreview?.deleteButton,
|
|
159
|
+
)}
|
|
160
|
+
onclick={onHandleFileRemove}
|
|
161
|
+
type="button"
|
|
162
|
+
disabled={isFileRemovalLocked(progress, fileStatus ?? UploadStatus.IDLE)}
|
|
163
|
+
aria-label={tr.removeFile}
|
|
164
|
+
data-testid="upup-file-remove"
|
|
165
|
+
>
|
|
166
|
+
<FileDeleteIcon class="upup-h-5 upup-w-5" />
|
|
167
|
+
</button>
|
|
168
|
+
{/if}
|
|
169
|
+
|
|
170
|
+
{#if isSuccessful}
|
|
171
|
+
<FileSuccessCheck
|
|
172
|
+
{index}
|
|
173
|
+
size={20}
|
|
174
|
+
class="upup-absolute upup-left-1.5 upup-top-1.5 upup-z-10"
|
|
175
|
+
/>
|
|
176
|
+
{/if}
|
|
177
|
+
|
|
178
|
+
<ProgressBar
|
|
179
|
+
class="upup-absolute upup-bottom-0 upup-left-0 upup-right-0"
|
|
180
|
+
progressBarClassName="upup-rounded-t-none upup-rounded-b-md"
|
|
181
|
+
{progress}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="upup-mt-1 upup-px-0.5">
|
|
186
|
+
<div
|
|
187
|
+
class={cn(
|
|
188
|
+
'upup-truncate upup-text-[13px] upup-font-normal upup-leading-tight upup-text-gray-900',
|
|
189
|
+
{ 'upup-text-white': $isDarkTheme },
|
|
190
|
+
$themeSlots?.filePreview?.name,
|
|
191
|
+
)}
|
|
192
|
+
>
|
|
193
|
+
{fileName}
|
|
194
|
+
</div>
|
|
195
|
+
<div
|
|
196
|
+
class={cn(
|
|
197
|
+
'upup-mt-0.5 upup-text-[11px] upup-leading-tight upup-text-gray-500',
|
|
198
|
+
{ 'upup-text-gray-400': $isDarkTheme },
|
|
199
|
+
$themeSlots?.filePreview?.size,
|
|
200
|
+
)}
|
|
201
|
+
>
|
|
202
|
+
{formatFileSize(fileSize, tr)}
|
|
203
|
+
</div>
|
|
204
|
+
{#if allowPreview && canPreview}
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
class={cn(
|
|
208
|
+
'upup-mt-1 upup-text-[11px] upup-font-normal upup-leading-tight upup-text-[#0284c7] upup-transition-all hover:upup-text-[#0284c7] hover:upup-underline',
|
|
209
|
+
{ 'upup-text-[#38bdf8] hover:upup-text-[#7dd3fc]': $isDarkTheme },
|
|
210
|
+
$themeSlots?.filePreview?.previewButton,
|
|
211
|
+
)}
|
|
212
|
+
onclick={() => onRequestPreview?.()}
|
|
213
|
+
>
|
|
214
|
+
{tr.clickToPreview}
|
|
215
|
+
</button>
|
|
216
|
+
{/if}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
fileName: string;
|
|
3
|
+
fileType: string;
|
|
4
|
+
fileId: string;
|
|
5
|
+
fileUrl: string;
|
|
6
|
+
fileSize?: number;
|
|
7
|
+
canPreview: boolean;
|
|
8
|
+
onRequestPreview?: () => void;
|
|
9
|
+
onUpdateCanPreview?: (val: boolean) => void;
|
|
10
|
+
onclick?: () => void;
|
|
11
|
+
/** Position in the sorted list — drives the completion-check stagger. */
|
|
12
|
+
index?: number;
|
|
13
|
+
};
|
|
14
|
+
declare const FilePreview: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
15
|
+
type FilePreview = ReturnType<typeof FilePreview>;
|
|
16
|
+
export default FilePreview;
|
|
17
|
+
//# sourceMappingURL=FilePreview.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilePreview.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FilePreview.svelte.ts"],"names":[],"mappings":"AAgBC,KAAK,gBAAgB,GAAI;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC7B,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC;AAuJJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|