@linktr.ee/messaging-react 1.28.0-rc-1776231821 → 1.28.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/dist/{Creator-BFpHsh2u.js → Creator-VyMyIk2b.js} +126 -114
- package/dist/Creator-VyMyIk2b.js.map +1 -0
- package/dist/{MediaPlayer-DXz4IBLx.js → MediaPlayer-Bf-xPB8Z.js} +99 -94
- package/dist/MediaPlayer-Bf-xPB8Z.js.map +1 -0
- package/dist/Visitor-C4WqnN8H.js +218 -0
- package/dist/Visitor-C4WqnN8H.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +640 -636
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +24 -7
- package/src/components/LockedAttachment/components/Creator.tsx +25 -5
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +10 -2
- package/src/components/LockedAttachment/components/Visitor.tsx +75 -33
- package/src/components/MessagingShell/index.tsx +4 -0
- package/dist/Creator-BFpHsh2u.js.map +0 -1
- package/dist/MediaPlayer-DXz4IBLx.js.map +0 -1
- package/dist/Visitor-C1Fcrgd6.js +0 -199
- package/dist/Visitor-C1Fcrgd6.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
-
import React from 'react'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
3
3
|
|
|
4
|
-
import LockedAttachment, { type VisitorCardProps } from '.'
|
|
4
|
+
import LockedAttachment, { type CreatorCardProps, type VisitorCardProps } from '.'
|
|
5
5
|
|
|
6
6
|
const VIDEO_THUMBNAIL_BLURRED = '/video-thumbnail-blurred.jpg'
|
|
7
7
|
const VIDEO_SOURCE = '/video-source.mp4'
|
|
@@ -26,6 +26,25 @@ type InteractiveProps = Omit<VisitorCardProps, 'onUnlock' | 'onDownload'> & {
|
|
|
26
26
|
unlockedSource: string
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Simulates a slow network by withholding the source for 2s, then loading via blob URL. */
|
|
30
|
+
type SlowCreatorPreviewProps = Omit<CreatorCardProps, 'isPreview'> & { sourcePath: string }
|
|
31
|
+
const SlowCreatorPreview = ({ sourcePath, ...props }: SlowCreatorPreviewProps) => {
|
|
32
|
+
const [source, setSource] = useState<string | undefined>(undefined)
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setSource(undefined)
|
|
35
|
+
let cancelled = false
|
|
36
|
+
const load = async () => {
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
38
|
+
const res = await fetch(sourcePath)
|
|
39
|
+
const blob = await res.blob()
|
|
40
|
+
if (!cancelled) setSource(URL.createObjectURL(blob))
|
|
41
|
+
}
|
|
42
|
+
void load()
|
|
43
|
+
return () => { cancelled = true }
|
|
44
|
+
}, [sourcePath])
|
|
45
|
+
return <LockedAttachment isCreator isPreview source={source} {...props} />
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
const Interactive = ({ unlockedSource, ...props }: InteractiveProps) => (
|
|
30
49
|
<LockedAttachment
|
|
31
50
|
{...props}
|
|
@@ -192,7 +211,7 @@ export const Visitor: StoryFn = () => (
|
|
|
192
211
|
detail={detail}
|
|
193
212
|
amountText="AU$9.99"
|
|
194
213
|
paymentStatus="paid"
|
|
195
|
-
onDownload={() =>
|
|
214
|
+
onDownload={() => new Promise((resolve) => setTimeout(resolve, 2000))}
|
|
196
215
|
/>
|
|
197
216
|
</td>
|
|
198
217
|
))}
|
|
@@ -211,11 +230,9 @@ export const Creator: StoryFn = () => (
|
|
|
211
230
|
</td>
|
|
212
231
|
{VARIANTS.map(({ mimeType, detail, thumbnail, source }) => (
|
|
213
232
|
<td key={mimeType} className="align-top">
|
|
214
|
-
<
|
|
215
|
-
isCreator
|
|
216
|
-
isPreview
|
|
233
|
+
<SlowCreatorPreview
|
|
217
234
|
thumbnail={thumbnail}
|
|
218
|
-
|
|
235
|
+
sourcePath={source}
|
|
219
236
|
mimeType={mimeType}
|
|
220
237
|
detail={detail}
|
|
221
238
|
placeholderAmountText="A$0.00"
|
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
CheckCircleIcon,
|
|
3
3
|
EyeIcon,
|
|
4
4
|
EyeSlashIcon,
|
|
5
|
+
LockIcon,
|
|
6
|
+
LockOpenIcon,
|
|
5
7
|
PauseIcon,
|
|
6
8
|
PlayIcon,
|
|
7
9
|
XIcon,
|
|
@@ -44,11 +46,12 @@ interface CollapsedThumbnailProps {
|
|
|
44
46
|
thumbnail?: string
|
|
45
47
|
mimeType: string
|
|
46
48
|
overlayIcon?: React.ElementType
|
|
49
|
+
darkOverlay?: boolean
|
|
47
50
|
onClick?: () => void
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {
|
|
51
|
-
const { thumbnail, mimeType, overlayIcon: OverlayIcon, onClick } = props
|
|
54
|
+
const { thumbnail, mimeType, overlayIcon: OverlayIcon, darkOverlay, onClick } = props
|
|
52
55
|
return (
|
|
53
56
|
<button
|
|
54
57
|
type="button"
|
|
@@ -74,6 +77,9 @@ const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {
|
|
|
74
77
|
})}
|
|
75
78
|
</div>
|
|
76
79
|
)}
|
|
80
|
+
{darkOverlay && (
|
|
81
|
+
<div className="pointer-events-none absolute inset-0 bg-black/30" />
|
|
82
|
+
)}
|
|
77
83
|
{OverlayIcon && (
|
|
78
84
|
<div className="pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white">
|
|
79
85
|
<OverlayIcon className="size-4" weight="fill" />
|
|
@@ -125,9 +131,12 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
|
125
131
|
}
|
|
126
132
|
}, [playing, seeking])
|
|
127
133
|
|
|
134
|
+
const [audioReady, setAudioReady] = useState(false)
|
|
135
|
+
|
|
128
136
|
useEffect(() => {
|
|
129
137
|
setPlaying(false)
|
|
130
138
|
setPlayed(0)
|
|
139
|
+
setAudioReady(false)
|
|
131
140
|
}, [source])
|
|
132
141
|
|
|
133
142
|
const getFraction = useCallback(
|
|
@@ -181,6 +190,7 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
|
181
190
|
ref={audioRef}
|
|
182
191
|
src={source}
|
|
183
192
|
loop
|
|
193
|
+
onCanPlay={() => setAudioReady(true)}
|
|
184
194
|
onEnded={() => {
|
|
185
195
|
setPlaying(false)
|
|
186
196
|
setPlayed(0)
|
|
@@ -192,10 +202,10 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
|
192
202
|
<CollapsedThumbnail
|
|
193
203
|
thumbnail={thumbnail}
|
|
194
204
|
mimeType={mimeType}
|
|
195
|
-
overlayIcon={source ? (playing ? PauseIcon : PlayIcon) : undefined}
|
|
196
|
-
onClick={source ? toggle : undefined}
|
|
205
|
+
overlayIcon={source && audioReady ? (playing ? PauseIcon : PlayIcon) : undefined}
|
|
206
|
+
onClick={source && audioReady ? toggle : undefined}
|
|
197
207
|
/>
|
|
198
|
-
{source && (
|
|
208
|
+
{source && audioReady && (
|
|
199
209
|
<div className="absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent">
|
|
200
210
|
<div
|
|
201
211
|
ref={trackRef}
|
|
@@ -386,8 +396,18 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
|
|
|
386
396
|
/>
|
|
387
397
|
)
|
|
388
398
|
} else {
|
|
399
|
+
const lockedOverlayIcon = onDismiss
|
|
400
|
+
? undefined
|
|
401
|
+
: paymentStatus === 'paid'
|
|
402
|
+
? LockOpenIcon
|
|
403
|
+
: LockIcon
|
|
389
404
|
mediaPreview = (
|
|
390
|
-
<CollapsedThumbnail
|
|
405
|
+
<CollapsedThumbnail
|
|
406
|
+
thumbnail={thumbnail}
|
|
407
|
+
mimeType={mimeType}
|
|
408
|
+
overlayIcon={lockedOverlayIcon}
|
|
409
|
+
darkOverlay
|
|
410
|
+
/>
|
|
391
411
|
)
|
|
392
412
|
}
|
|
393
413
|
|
|
@@ -63,6 +63,8 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
63
63
|
const [scrubberHovered, setScrubberHovered] = useState(false)
|
|
64
64
|
const [videoAspect, setVideoAspect] = useState<number | null>(null)
|
|
65
65
|
const [buffering, setBuffering] = useState(false)
|
|
66
|
+
/** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */
|
|
67
|
+
const [initialLoad, setInitialLoad] = useState(true)
|
|
66
68
|
/** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */
|
|
67
69
|
const [manualPlayRequired, setManualPlayRequired] = useState(false)
|
|
68
70
|
const playerRef = useRef<HTMLMediaElement>(null)
|
|
@@ -71,6 +73,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
71
73
|
|
|
72
74
|
useEffect(() => {
|
|
73
75
|
setManualPlayRequired(false)
|
|
76
|
+
setInitialLoad(true)
|
|
74
77
|
}, [source])
|
|
75
78
|
|
|
76
79
|
useEffect(() => {
|
|
@@ -209,6 +212,11 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
209
212
|
})}
|
|
210
213
|
</div>
|
|
211
214
|
)}
|
|
215
|
+
{initialLoad && poster && (
|
|
216
|
+
<div className="absolute inset-0 z-20">
|
|
217
|
+
<img src={poster} alt="" className="h-full w-full object-cover" />
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
212
220
|
<div className="absolute inset-0">
|
|
213
221
|
{sourceType === 'audio' ? (
|
|
214
222
|
<audio
|
|
@@ -218,7 +226,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
218
226
|
muted={muted}
|
|
219
227
|
style={{ width: '100%', height: '100%' }}
|
|
220
228
|
onLoadStart={() => setBuffering(true)}
|
|
221
|
-
onCanPlay={() => setBuffering(false)}
|
|
229
|
+
onCanPlay={() => { setBuffering(false); setInitialLoad(false) }}
|
|
222
230
|
onWaiting={() => setBuffering(true)}
|
|
223
231
|
onPlay={() => setManualPlayRequired(false)}
|
|
224
232
|
onEnded={() => {
|
|
@@ -240,7 +248,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
240
248
|
playsInline
|
|
241
249
|
style={{ width: '100%', height: '100%' }}
|
|
242
250
|
onLoadStart={() => setBuffering(true)}
|
|
243
|
-
onCanPlay={() => setBuffering(false)}
|
|
251
|
+
onCanPlay={() => { setBuffering(false); setInitialLoad(false) }}
|
|
244
252
|
onWaiting={() => setBuffering(true)}
|
|
245
253
|
onPlay={() => setManualPlayRequired(false)}
|
|
246
254
|
onLoadedMetadata={() => {
|
|
@@ -24,10 +24,11 @@ export interface VisitorCardProps extends LockedAttachmentBaseProps {
|
|
|
24
24
|
* Omit to hide the Unlock button.
|
|
25
25
|
*/
|
|
26
26
|
onUnlock?: () => Promise<LockedAttachmentSource>
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
28
|
* Called when the visitor clicks Download on an unlocked card.
|
|
29
|
+
* May return a Promise — if so, the component shows a loader until it resolves.
|
|
29
30
|
*/
|
|
30
|
-
onDownload?: () => void
|
|
31
|
+
onDownload?: () => void | Promise<void>
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
@@ -36,14 +37,23 @@ const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
|
36
37
|
|
|
37
38
|
interface LockOverlayProps {
|
|
38
39
|
icon: React.ElementType
|
|
40
|
+
loading?: boolean
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const LockOverlay: React.FC<LockOverlayProps> = (props) => {
|
|
42
|
-
const { icon: Icon } = props
|
|
44
|
+
const { icon: Icon, loading } = props
|
|
43
45
|
return (
|
|
44
46
|
<div className="absolute inset-0 bg-black/30">
|
|
45
47
|
<div className="absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60">
|
|
46
|
-
|
|
48
|
+
{loading ? (
|
|
49
|
+
<span className="flex items-center gap-[2px]">
|
|
50
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
51
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
52
|
+
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
53
|
+
</span>
|
|
54
|
+
) : (
|
|
55
|
+
<Icon className="size-4 text-white" weight="fill" />
|
|
56
|
+
)}
|
|
47
57
|
</div>
|
|
48
58
|
</div>
|
|
49
59
|
)
|
|
@@ -53,10 +63,11 @@ interface LockedPreviewProps {
|
|
|
53
63
|
thumbnail?: string
|
|
54
64
|
mimeType: string
|
|
55
65
|
LockIcon: React.ElementType
|
|
66
|
+
loading?: boolean
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
const LockedPreview: React.FC<LockedPreviewProps> = (props) => {
|
|
59
|
-
const { thumbnail, mimeType, LockIcon } = props
|
|
70
|
+
const { thumbnail, mimeType, LockIcon, loading } = props
|
|
60
71
|
return (
|
|
61
72
|
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
62
73
|
{thumbnail ? (
|
|
@@ -73,7 +84,7 @@ const LockedPreview: React.FC<LockedPreviewProps> = (props) => {
|
|
|
73
84
|
})}
|
|
74
85
|
</div>
|
|
75
86
|
)}
|
|
76
|
-
<LockOverlay icon={LockIcon} />
|
|
87
|
+
<LockOverlay icon={LockIcon} loading={loading} />
|
|
77
88
|
</div>
|
|
78
89
|
)
|
|
79
90
|
}
|
|
@@ -86,10 +97,11 @@ interface ImagePreviewProps {
|
|
|
86
97
|
title?: string
|
|
87
98
|
paymentStatus?: PaymentStatus
|
|
88
99
|
isLocked: boolean
|
|
100
|
+
loading?: boolean
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
92
|
-
const { source, thumbnail, mimeType, title, paymentStatus, isLocked } = props
|
|
104
|
+
const { source, thumbnail, mimeType, title, paymentStatus, isLocked, loading } = props
|
|
93
105
|
const [sourceReady, setSourceReady] = useState(false)
|
|
94
106
|
|
|
95
107
|
useEffect(() => {
|
|
@@ -102,6 +114,7 @@ const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
|
102
114
|
thumbnail={thumbnail}
|
|
103
115
|
mimeType={mimeType}
|
|
104
116
|
LockIcon={getLockIcon(paymentStatus)}
|
|
117
|
+
loading={loading}
|
|
105
118
|
/>
|
|
106
119
|
)
|
|
107
120
|
}
|
|
@@ -123,10 +136,11 @@ interface DocumentPreviewProps {
|
|
|
123
136
|
mimeType: string
|
|
124
137
|
paymentStatus?: PaymentStatus
|
|
125
138
|
isLocked: boolean
|
|
139
|
+
loading?: boolean
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
const DocumentPreview: React.FC<DocumentPreviewProps> = (props) => {
|
|
129
|
-
const { thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
143
|
+
const { thumbnail, mimeType, paymentStatus, isLocked, loading } = props
|
|
130
144
|
return (
|
|
131
145
|
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
132
146
|
{thumbnail ? (
|
|
@@ -143,7 +157,7 @@ const DocumentPreview: React.FC<DocumentPreviewProps> = (props) => {
|
|
|
143
157
|
})}
|
|
144
158
|
</div>
|
|
145
159
|
)}
|
|
146
|
-
{isLocked && <LockOverlay icon={getLockIcon(paymentStatus)} />}
|
|
160
|
+
{isLocked && <LockOverlay icon={getLockIcon(paymentStatus)} loading={loading} />}
|
|
147
161
|
</div>
|
|
148
162
|
)
|
|
149
163
|
}
|
|
@@ -154,16 +168,18 @@ interface MediaPreviewProps {
|
|
|
154
168
|
mimeType: string
|
|
155
169
|
paymentStatus?: PaymentStatus
|
|
156
170
|
isLocked: boolean
|
|
171
|
+
loading?: boolean
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
const MediaPreview: React.FC<MediaPreviewProps> = (props) => {
|
|
160
|
-
const { source, thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
175
|
+
const { source, thumbnail, mimeType, paymentStatus, isLocked, loading } = props
|
|
161
176
|
if (isLocked) {
|
|
162
177
|
return (
|
|
163
178
|
<LockedPreview
|
|
164
179
|
thumbnail={thumbnail}
|
|
165
180
|
mimeType={mimeType}
|
|
166
181
|
LockIcon={getLockIcon(paymentStatus)}
|
|
182
|
+
loading={loading}
|
|
167
183
|
/>
|
|
168
184
|
)
|
|
169
185
|
}
|
|
@@ -171,9 +187,18 @@ const MediaPreview: React.FC<MediaPreviewProps> = (props) => {
|
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
|
|
190
|
+
const LoadingDots = () => (
|
|
191
|
+
<span className="flex items-center gap-1">
|
|
192
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
193
|
+
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
194
|
+
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
195
|
+
</span>
|
|
196
|
+
)
|
|
197
|
+
|
|
174
198
|
interface CardActionsProps {
|
|
175
199
|
isLocked: boolean
|
|
176
|
-
|
|
200
|
+
unlockLoading: boolean
|
|
201
|
+
downloadLoading: boolean
|
|
177
202
|
paymentStatus?: PaymentStatus
|
|
178
203
|
source?: string
|
|
179
204
|
onUnlock?: () => void
|
|
@@ -181,8 +206,7 @@ interface CardActionsProps {
|
|
|
181
206
|
}
|
|
182
207
|
|
|
183
208
|
const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
184
|
-
const { isLocked,
|
|
185
|
-
props
|
|
209
|
+
const { isLocked, unlockLoading, downloadLoading, paymentStatus, source, onUnlock, onDownload } = props
|
|
186
210
|
const LockIcon = getLockIcon(paymentStatus)
|
|
187
211
|
|
|
188
212
|
if (isLocked && onUnlock) {
|
|
@@ -190,15 +214,11 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
190
214
|
<button
|
|
191
215
|
type="button"
|
|
192
216
|
onClick={onUnlock}
|
|
193
|
-
disabled={
|
|
217
|
+
disabled={unlockLoading}
|
|
194
218
|
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-[#121110] px-4 text-sm font-medium leading-none text-white disabled:opacity-70"
|
|
195
219
|
>
|
|
196
|
-
{
|
|
197
|
-
<
|
|
198
|
-
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.3s]" />
|
|
199
|
-
<span className="size-1 rounded-full bg-white animate-bounce [animation-delay:-0.15s]" />
|
|
200
|
-
<span className="size-1 rounded-full bg-white animate-bounce" />
|
|
201
|
-
</span>
|
|
220
|
+
{unlockLoading ? (
|
|
221
|
+
<LoadingDots />
|
|
202
222
|
) : (
|
|
203
223
|
<>
|
|
204
224
|
{paymentStatus === 'paid' ? (
|
|
@@ -215,16 +235,21 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
215
235
|
|
|
216
236
|
if (!isLocked && onDownload && source) {
|
|
217
237
|
return (
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
target="_blank"
|
|
221
|
-
rel="noopener noreferrer"
|
|
238
|
+
<button
|
|
239
|
+
type="button"
|
|
222
240
|
onClick={onDownload}
|
|
223
|
-
|
|
241
|
+
disabled={downloadLoading}
|
|
242
|
+
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-[#121110] px-4 text-sm font-medium leading-none text-white disabled:opacity-70"
|
|
224
243
|
>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
244
|
+
{downloadLoading ? (
|
|
245
|
+
<LoadingDots />
|
|
246
|
+
) : (
|
|
247
|
+
<>
|
|
248
|
+
<DownloadSimpleIcon className="size-4" weight="bold" />
|
|
249
|
+
Download
|
|
250
|
+
</>
|
|
251
|
+
)}
|
|
252
|
+
</button>
|
|
228
253
|
)
|
|
229
254
|
}
|
|
230
255
|
|
|
@@ -245,7 +270,8 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
245
270
|
paymentStatus,
|
|
246
271
|
} = props
|
|
247
272
|
const [source, setSource] = useState(sourceProp)
|
|
248
|
-
const [
|
|
273
|
+
const [unlockLoading, setUnlockLoading] = useState(false)
|
|
274
|
+
const [downloadLoading, setDownloadLoading] = useState(false)
|
|
249
275
|
|
|
250
276
|
useEffect(() => {
|
|
251
277
|
if (sourceProp !== undefined) setSource(sourceProp)
|
|
@@ -256,14 +282,26 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
256
282
|
|
|
257
283
|
const handleUnlock = async () => {
|
|
258
284
|
if (!onUnlock) return
|
|
259
|
-
|
|
285
|
+
setUnlockLoading(true)
|
|
260
286
|
try {
|
|
261
287
|
const result = await onUnlock()
|
|
262
288
|
setSource(result.source)
|
|
263
289
|
} catch (err) {
|
|
264
290
|
if (isDevBuild()) console.debug('[LockedAttachment] onUnlock failed', err)
|
|
265
291
|
} finally {
|
|
266
|
-
|
|
292
|
+
setUnlockLoading(false)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const handleDownload = async () => {
|
|
297
|
+
if (!onDownload) return
|
|
298
|
+
setDownloadLoading(true)
|
|
299
|
+
try {
|
|
300
|
+
await onDownload()
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (isDevBuild()) console.debug('[LockedAttachment] onDownload failed', err)
|
|
303
|
+
} finally {
|
|
304
|
+
setDownloadLoading(false)
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
|
|
@@ -277,6 +315,7 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
277
315
|
title={title}
|
|
278
316
|
paymentStatus={paymentStatus}
|
|
279
317
|
isLocked={isLocked}
|
|
318
|
+
loading={unlockLoading}
|
|
280
319
|
/>
|
|
281
320
|
)
|
|
282
321
|
} else if (sourceType === 'document') {
|
|
@@ -286,6 +325,7 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
286
325
|
mimeType={mimeType}
|
|
287
326
|
paymentStatus={paymentStatus}
|
|
288
327
|
isLocked={isLocked}
|
|
328
|
+
loading={unlockLoading}
|
|
289
329
|
/>
|
|
290
330
|
)
|
|
291
331
|
} else {
|
|
@@ -296,6 +336,7 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
296
336
|
mimeType={mimeType}
|
|
297
337
|
paymentStatus={paymentStatus}
|
|
298
338
|
isLocked={isLocked}
|
|
339
|
+
loading={unlockLoading}
|
|
299
340
|
/>
|
|
300
341
|
)
|
|
301
342
|
}
|
|
@@ -339,11 +380,12 @@ const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
|
339
380
|
</div>
|
|
340
381
|
<CardActions
|
|
341
382
|
isLocked={isLocked}
|
|
342
|
-
|
|
383
|
+
unlockLoading={unlockLoading}
|
|
384
|
+
downloadLoading={downloadLoading}
|
|
343
385
|
paymentStatus={paymentStatus}
|
|
344
386
|
source={source}
|
|
345
387
|
onUnlock={onUnlock ? handleUnlock : undefined}
|
|
346
|
-
onDownload={onDownload}
|
|
388
|
+
onDownload={onDownload ? handleDownload : undefined}
|
|
347
389
|
/>
|
|
348
390
|
</div>
|
|
349
391
|
</div>
|
|
@@ -42,6 +42,8 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
42
42
|
customProfileContent,
|
|
43
43
|
customChannelActions,
|
|
44
44
|
renderMessage,
|
|
45
|
+
onAttachmentUnlock,
|
|
46
|
+
onAttachmentDownload,
|
|
45
47
|
}) => {
|
|
46
48
|
const {
|
|
47
49
|
service,
|
|
@@ -505,6 +507,8 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
505
507
|
customProfileContent={customProfileContent}
|
|
506
508
|
customChannelActions={customChannelActions}
|
|
507
509
|
renderMessage={renderMessage}
|
|
510
|
+
onAttachmentUnlock={onAttachmentUnlock}
|
|
511
|
+
onAttachmentDownload={onAttachmentDownload}
|
|
508
512
|
/>
|
|
509
513
|
</div>
|
|
510
514
|
) : initialParticipantFilter ? (
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Creator-BFpHsh2u.js","sources":["../src/components/LockedAttachment/components/Creator.tsx"],"sourcesContent":["import {\n CheckCircleIcon,\n EyeIcon,\n EyeSlashIcon,\n PauseIcon,\n PlayIcon,\n XIcon,\n} from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { LockedAttachmentBaseProps } from '../types'\nimport { renderTypeIcon } from '../utils/icons'\nimport { getSourceType } from '../utils/mimeType'\n\nimport MediaPlayer from './MediaPlayer'\n\nexport interface CreatorCardProps extends LockedAttachmentBaseProps {\n isPreview?: boolean\n placeholderTitle?: string\n placeholderAmountText?: string\n onDismiss?: () => void\n}\n\ninterface CloseButtonProps {\n onClose: () => void\n}\n\nconst CloseButton: React.FC<CloseButtonProps> = (props) => {\n const { onClose } = props\n return (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"absolute left-3 top-3 z-40 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\"\n aria-label=\"Close preview\"\n >\n <EyeIcon className=\"size-4\" weight=\"fill\" />\n </button>\n )\n}\n\ninterface CollapsedThumbnailProps {\n thumbnail?: string\n mimeType: string\n overlayIcon?: React.ElementType\n onClick?: () => void\n}\n\nconst CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {\n const { thumbnail, mimeType, overlayIcon: OverlayIcon, onClick } = props\n return (\n <button\n type=\"button\"\n disabled={!onClick}\n className={classNames(\n 'relative aspect-video block w-full overflow-hidden border-0 bg-black/5 p-0 text-left appearance-none',\n { 'cursor-pointer': !!onClick, 'cursor-default': !onClick }\n )}\n onClick={onClick}\n aria-label={OverlayIcon ? 'Toggle preview' : undefined}\n >\n {thumbnail ? (\n <img\n src={thumbnail}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n {OverlayIcon && (\n <div className=\"pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\">\n <OverlayIcon className=\"size-4\" weight=\"fill\" />\n </div>\n )}\n </button>\n )\n}\n\n\ninterface AudioPreviewProps {\n source?: string\n thumbnail?: string\n mimeType: string\n}\n\nconst AudioPreview: React.FC<AudioPreviewProps> = (props) => {\n const { source, thumbnail, mimeType } = props\n const [playing, setPlaying] = useState(false)\n const [played, setPlayed] = useState(0)\n const [seeking, setSeeking] = useState(false)\n const audioRef = useRef<HTMLAudioElement>(null)\n const trackRef = useRef<HTMLDivElement>(null)\n const rafRef = useRef<number | null>(null)\n\n useEffect(() => {\n const el = audioRef.current\n if (!el) return\n if (playing) {\n void el.play().catch(() => setPlaying(false))\n } else {\n el.pause()\n }\n }, [playing])\n\n useEffect(() => {\n if (!playing) {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n return\n }\n const tick = () => {\n const el = audioRef.current\n if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n }\n }, [playing, seeking])\n\n useEffect(() => {\n setPlaying(false)\n setPlayed(0)\n }, [source])\n\n const getFraction = useCallback(\n (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {\n const track = trackRef.current\n if (!track) return 0\n const clientX =\n 'touches' in e\n ? (e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0)\n : e.clientX\n const rect = track.getBoundingClientRect()\n return Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))\n },\n []\n )\n\n const seekTo = useCallback((fraction: number) => {\n const el = audioRef.current\n if (el && el.duration) el.currentTime = fraction * el.duration\n }, [])\n\n useEffect(() => {\n if (!seeking) return\n const onMove = (e: MouseEvent | TouchEvent) => {\n const f = getFraction(e)\n setPlayed(f)\n seekTo(f)\n }\n const onUp = (e: MouseEvent | TouchEvent) => {\n setSeeking(false)\n seekTo(getFraction(e))\n }\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', onUp)\n window.addEventListener('touchmove', onMove, { passive: true })\n window.addEventListener('touchend', onUp)\n return () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', onUp)\n window.removeEventListener('touchmove', onMove)\n window.removeEventListener('touchend', onUp)\n }\n }, [seeking, getFraction, seekTo])\n\n const toggle = useCallback(() => setPlaying((p) => !p), [])\n\n return (\n <div className=\"relative\">\n {source && (\n <audio\n ref={audioRef}\n src={source}\n loop\n onEnded={() => {\n setPlaying(false)\n setPlayed(0)\n }}\n >\n <track kind=\"captions\" />\n </audio>\n )}\n <CollapsedThumbnail\n thumbnail={thumbnail}\n mimeType={mimeType}\n overlayIcon={source ? (playing ? PauseIcon : PlayIcon) : undefined}\n onClick={source ? toggle : undefined}\n />\n {source && (\n <div className=\"absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent\">\n <div\n ref={trackRef}\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={Math.round(played * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={(e) => {\n e.stopPropagation()\n setSeeking(true)\n const f = getFraction(e)\n setPlayed(f)\n seekTo(f)\n }}\n onTouchStart={(e) => {\n e.stopPropagation()\n setSeeking(true)\n const f = getFraction(e)\n setPlayed(f)\n seekTo(f)\n }}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n }}\n >\n <div className=\"w-full overflow-hidden rounded-full bg-white/30 h-1\">\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${Math.round(played * 100)}%` }}\n />\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n\ninterface VideoPreviewProps {\n source?: string\n thumbnail?: string\n mimeType: string\n}\n\nconst VideoPreview: React.FC<VideoPreviewProps> = (props) => {\n const { source, thumbnail, mimeType } = props\n const [expanded, setExpanded] = useState(false)\n const collapse = () => setExpanded(false)\n\n useEffect(() => {\n setExpanded(false)\n }, [source])\n\n if (!source) {\n return <CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} />\n }\n\n return (\n <div\n className={classNames('relative overflow-hidden', {\n 'aspect-video': !expanded,\n })}\n >\n <MediaPlayer\n source={source}\n mimeType={mimeType}\n poster={thumbnail}\n playing={expanded}\n loop\n controls={false}\n showProgress\n onContainerClick={collapse}\n muted\n />\n {!expanded && (\n <button\n type=\"button\"\n className=\"absolute inset-0 block cursor-pointer border-0 p-0 text-left appearance-none\"\n onClick={() => setExpanded(true)}\n aria-label=\"Expand video preview\"\n >\n {thumbnail ? (\n <img\n src={thumbnail}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n <div className=\"pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\">\n <EyeSlashIcon className=\"size-4\" weight=\"fill\" />\n </div>\n </button>\n )}\n {expanded && <CloseButton onClose={collapse} />}\n </div>\n )\n}\n\ninterface ImagePreviewProps {\n source?: string\n thumbnail?: string\n mimeType: string\n title?: string\n}\n\nconst ImagePreview: React.FC<ImagePreviewProps> = (props) => {\n const { source, thumbnail, mimeType, title } = props\n const [expanded, setExpanded] = useState(false)\n const collapse = () => setExpanded(false)\n\n useEffect(() => {\n setExpanded(false)\n }, [source])\n\n if (expanded && source) {\n return (\n <div className=\"relative\">\n <button\n type=\"button\"\n className=\"block w-full cursor-pointer border-0 p-0 text-left appearance-none\"\n onClick={collapse}\n aria-label=\"Close preview\"\n >\n <img src={source} alt={title ?? ''} className=\"block w-full\" />\n </button>\n <CloseButton onClose={collapse} />\n </div>\n )\n }\n\n return (\n <CollapsedThumbnail\n thumbnail={thumbnail}\n mimeType={mimeType}\n overlayIcon={source ? EyeSlashIcon : undefined}\n onClick={source ? () => setExpanded(true) : undefined}\n />\n )\n}\n\n\nconst CreatorCard: React.FC<CreatorCardProps> = (props) => {\n const {\n title,\n mimeType = 'application/octet-stream',\n thumbnail,\n source,\n detail,\n amountText,\n placeholderTitle = 'Attachment title',\n placeholderAmountText,\n paymentStatus,\n onDismiss,\n isPreview = false,\n } = props\n const sourceType = getSourceType(mimeType)\n const displayAmountText = amountText ?? placeholderAmountText\n const isPlaceholderAmount = !amountText && !!placeholderAmountText\n\n let mediaPreview: React.ReactNode\n if (isPreview && sourceType === 'audio') {\n mediaPreview = (\n <AudioPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />\n )\n } else if (isPreview && sourceType === 'video') {\n mediaPreview = (\n <VideoPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />\n )\n } else if (isPreview && sourceType === 'image') {\n mediaPreview = (\n <ImagePreview\n source={source}\n thumbnail={thumbnail}\n mimeType={mimeType}\n title={title}\n />\n )\n } else {\n mediaPreview = (\n <CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} />\n )\n }\n\n return (\n <div className=\"relative w-[280px] select-none overflow-hidden rounded-3xl bg-white shadow-card\">\n {onDismiss && (\n <button\n type=\"button\"\n onClick={onDismiss}\n className=\"absolute right-3 top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white\"\n aria-label=\"Dismiss attachment\"\n >\n <XIcon className=\"size-4\" weight=\"bold\" />\n </button>\n )}\n {mediaPreview}\n <div className=\"px-4 pb-3 pt-3\">\n <p\n className={classNames('mb-1.5 truncate text-base font-medium', {\n 'text-black/30': !title,\n 'text-black': !!title,\n })}\n >\n {title || placeholderTitle}\n </p>\n <div className=\"flex items-center gap-1\">\n {renderTypeIcon(mimeType, {\n className: 'size-5 shrink-0 text-black/55',\n weight: 'regular',\n })}\n {detail && (\n <span className=\"text-xs font-medium text-black/55\">{detail}</span>\n )}\n {paymentStatus === 'paid' ? (\n <>\n <span className=\"text-xs font-medium text-black/55\">•</span>\n <span className=\"text-xs font-medium text-[#008236]\">\n Purchased\n </span>\n <CheckCircleIcon\n className=\"size-4 text-[#008236]\"\n weight=\"bold\"\n />\n </>\n ) : (\n displayAmountText && (\n <>\n <span\n className={classNames('text-xs font-medium', {\n 'text-black/30': isPlaceholderAmount,\n 'text-black/55': !isPlaceholderAmount,\n })}\n >\n •\n </span>\n <span\n className={classNames('text-xs font-medium', {\n 'text-black/30': isPlaceholderAmount,\n 'text-black/55': !isPlaceholderAmount,\n })}\n >\n {displayAmountText}\n </span>\n </>\n )\n )}\n </div>\n </div>\n </div>\n )\n}\n\nexport default CreatorCard\n"],"names":["CloseButton","props","onClose","jsx","EyeIcon","CollapsedThumbnail","thumbnail","mimeType","OverlayIcon","onClick","jsxs","classNames","AudioPreview","source","playing","setPlaying","useState","played","setPlayed","seeking","setSeeking","audioRef","useRef","trackRef","rafRef","useEffect","el","tick","getFraction","useCallback","e","track","clientX","_a","_b","rect","seekTo","fraction","onMove","f","onUp","toggle","p","PauseIcon","PlayIcon","VideoPreview","expanded","setExpanded","collapse","MediaPlayer","EyeSlashIcon","ImagePreview","title","CreatorCard","detail","amountText","placeholderTitle","placeholderAmountText","paymentStatus","onDismiss","isPreview","sourceType","getSourceType","displayAmountText","isPlaceholderAmount","mediaPreview","XIcon","renderTypeIcon","Fragment","CheckCircleIcon"],"mappings":";;;;;AA4BA,MAAMA,IAA0C,CAACC,MAAU;AACzD,QAAM,EAAE,SAAAC,MAAYD;AACpB,SACE,gBAAAE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,SAASD;AAAA,MACT,WAAU;AAAA,MACV,cAAW;AAAA,MAEX,UAAA,gBAAAC,EAACC,GAAA,EAAQ,WAAU,UAAS,QAAO,OAAA,CAAO;AAAA,IAAA;AAAA,EAAA;AAGhD,GASMC,IAAwD,CAACJ,MAAU;AACvE,QAAM,EAAE,WAAAK,GAAW,UAAAC,GAAU,aAAaC,GAAa,SAAAC,MAAYR;AACnE,SACE,gBAAAS;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,UAAU,CAACD;AAAA,MACX,WAAWE;AAAA,QACT;AAAA,QACA,EAAE,kBAAkB,CAAC,CAACF,GAAS,kBAAkB,CAACA,EAAA;AAAA,MAAQ;AAAA,MAE5D,SAAAA;AAAA,MACA,cAAYD,IAAc,mBAAmB;AAAA,MAE5C,UAAA;AAAA,QAAAF,IACC,gBAAAH;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAKG;AAAA,YACL,KAAI;AAAA,YACJ,WAAU;AAAA,UAAA;AAAA,QAAA,IAGZ,gBAAAH,EAAC,OAAA,EAAI,WAAU,qDACZ,YAAeI,GAAU;AAAA,UACxB,WAAW;AAAA,UACX,QAAQ;AAAA,QAAA,CACT,GACH;AAAA,QAEDC,KACC,gBAAAL,EAAC,OAAA,EAAI,WAAU,yHACb,UAAA,gBAAAA,EAACK,GAAA,EAAY,WAAU,UAAS,QAAO,OAAA,CAAO,EAAA,CAChD;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,GASMI,IAA4C,CAACX,MAAU;AAC3D,QAAM,EAAE,QAAAY,GAAQ,WAAAP,GAAW,UAAAC,EAAA,IAAaN,GAClC,CAACa,GAASC,CAAU,IAAIC,EAAS,EAAK,GACtC,CAACC,GAAQC,CAAS,IAAIF,EAAS,CAAC,GAChC,CAACG,GAASC,CAAU,IAAIJ,EAAS,EAAK,GACtCK,IAAWC,EAAyB,IAAI,GACxCC,IAAWD,EAAuB,IAAI,GACtCE,IAASF,EAAsB,IAAI;AAEzC,EAAAG,EAAU,MAAM;AACd,UAAMC,IAAKL,EAAS;AACpB,IAAKK,MACDZ,IACGY,EAAG,KAAA,EAAO,MAAM,MAAMX,EAAW,EAAK,CAAC,IAE5CW,EAAG,MAAA;AAAA,EAEP,GAAG,CAACZ,CAAO,CAAC,GAEZW,EAAU,MAAM;AACd,QAAI,CAACX,GAAS;AACZ,MAAIU,EAAO,YAAY,QAAM,qBAAqBA,EAAO,OAAO;AAChE;AAAA,IACF;AACA,UAAMG,IAAO,MAAM;AACjB,YAAMD,IAAKL,EAAS;AACpB,MAAIK,KAAMA,EAAG,YAAY,CAACP,KAASD,EAAUQ,EAAG,cAAcA,EAAG,QAAQ,GACzEF,EAAO,UAAU,sBAAsBG,CAAI;AAAA,IAC7C;AACA,WAAAH,EAAO,UAAU,sBAAsBG,CAAI,GACpC,MAAM;AACX,MAAIH,EAAO,YAAY,QAAM,qBAAqBA,EAAO,OAAO;AAAA,IAClE;AAAA,EACF,GAAG,CAACV,GAASK,CAAO,CAAC,GAErBM,EAAU,MAAM;AACd,IAAAV,EAAW,EAAK,GAChBG,EAAU,CAAC;AAAA,EACb,GAAG,CAACL,CAAM,CAAC;AAEX,QAAMe,IAAcC;AAAA,IAClB,CAACC,MAAqE;;AACpE,YAAMC,IAAQR,EAAS;AACvB,UAAI,CAACQ,EAAO,QAAO;AACnB,YAAMC,IACJ,aAAaF,MACRG,IAAAH,EAAE,QAAQ,CAAC,MAAX,gBAAAG,EAAc,cAAWC,IAAAJ,EAAE,eAAe,CAAC,MAAlB,gBAAAI,EAAqB,YAAW,IAC1DJ,EAAE,SACFK,IAAOJ,EAAM,sBAAA;AACnB,aAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAIC,IAAUG,EAAK,QAAQA,EAAK,KAAK,CAAC;AAAA,IACpE;AAAA,IACA,CAAA;AAAA,EAAC,GAGGC,IAASP,EAAY,CAACQ,MAAqB;AAC/C,UAAMX,IAAKL,EAAS;AACpB,IAAIK,KAAMA,EAAG,aAAUA,EAAG,cAAcW,IAAWX,EAAG;AAAA,EACxD,GAAG,CAAA,CAAE;AAEL,EAAAD,EAAU,MAAM;AACd,QAAI,CAACN,EAAS;AACd,UAAMmB,IAAS,CAACR,MAA+B;AAC7C,YAAMS,IAAIX,EAAYE,CAAC;AACvB,MAAAZ,EAAUqB,CAAC,GACXH,EAAOG,CAAC;AAAA,IACV,GACMC,IAAO,CAACV,MAA+B;AAC3C,MAAAV,EAAW,EAAK,GAChBgB,EAAOR,EAAYE,CAAC,CAAC;AAAA,IACvB;AACA,kBAAO,iBAAiB,aAAaQ,CAAM,GAC3C,OAAO,iBAAiB,WAAWE,CAAI,GACvC,OAAO,iBAAiB,aAAaF,GAAQ,EAAE,SAAS,IAAM,GAC9D,OAAO,iBAAiB,YAAYE,CAAI,GACjC,MAAM;AACX,aAAO,oBAAoB,aAAaF,CAAM,GAC9C,OAAO,oBAAoB,WAAWE,CAAI,GAC1C,OAAO,oBAAoB,aAAaF,CAAM,GAC9C,OAAO,oBAAoB,YAAYE,CAAI;AAAA,IAC7C;AAAA,EACF,GAAG,CAACrB,GAASS,GAAaQ,CAAM,CAAC;AAEjC,QAAMK,IAASZ,EAAY,MAAMd,EAAW,CAAC2B,MAAM,CAACA,CAAC,GAAG,EAAE;AAE1D,SACE,gBAAAhC,EAAC,OAAA,EAAI,WAAU,YACZ,UAAA;AAAA,IAAAG,KACC,gBAAAV;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKkB;AAAA,QACL,KAAKR;AAAA,QACL,MAAI;AAAA,QACJ,SAAS,MAAM;AACb,UAAAE,EAAW,EAAK,GAChBG,EAAU,CAAC;AAAA,QACb;AAAA,QAEA,UAAA,gBAAAf,EAAC,SAAA,EAAM,MAAK,WAAA,CAAW;AAAA,MAAA;AAAA,IAAA;AAAA,IAG3B,gBAAAA;AAAA,MAACE;AAAA,MAAA;AAAA,QACC,WAAAC;AAAA,QACA,UAAAC;AAAA,QACA,aAAaM,IAAUC,IAAU6B,IAAYC,IAAY;AAAA,QACzD,SAAS/B,IAAS4B,IAAS;AAAA,MAAA;AAAA,IAAA;AAAA,IAE5B5B,KACC,gBAAAV,EAAC,OAAA,EAAI,WAAU,8FACb,UAAA,gBAAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKoB;AAAA,QACL,MAAK;AAAA,QACL,cAAW;AAAA,QACX,iBAAe,KAAK,MAAMN,IAAS,GAAG;AAAA,QACtC,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,UAAU;AAAA,QACV,WAAU;AAAA,QACV,aAAa,CAACa,MAAM;AAClB,UAAAA,EAAE,gBAAA,GACFV,EAAW,EAAI;AACf,gBAAMmB,IAAIX,EAAYE,CAAC;AACvB,UAAAZ,EAAUqB,CAAC,GACXH,EAAOG,CAAC;AAAA,QACV;AAAA,QACA,cAAc,CAACT,MAAM;AACnB,UAAAA,EAAE,gBAAA,GACFV,EAAW,EAAI;AACf,gBAAMmB,IAAIX,EAAYE,CAAC;AACvB,UAAAZ,EAAUqB,CAAC,GACXH,EAAOG,CAAC;AAAA,QACV;AAAA,QACA,SAAS,CAACT,MAAMA,EAAE,gBAAA;AAAA,QAClB,WAAW,CAACA,MAAM;AAChB,UAAIA,EAAE,QAAQ,gBAAcM,EAAO,KAAK,IAAI,GAAGnB,IAAS,IAAI,CAAC,GACzDa,EAAE,QAAQ,eAAaM,EAAO,KAAK,IAAI,GAAGnB,IAAS,IAAI,CAAC;AAAA,QAC9D;AAAA,QAEA,UAAA,gBAAAd,EAAC,OAAA,EAAI,WAAU,uDACb,UAAA,gBAAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,OAAO,GAAG,KAAK,MAAMc,IAAS,GAAG,CAAC,IAAA;AAAA,UAAI;AAAA,QAAA,EACjD,CACF;AAAA,MAAA;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GAEJ;AAEJ,GAQM4B,IAA4C,CAAC5C,MAAU;AAC3D,QAAM,EAAE,QAAAY,GAAQ,WAAAP,GAAW,UAAAC,EAAA,IAAaN,GAClC,CAAC6C,GAAUC,CAAW,IAAI/B,EAAS,EAAK,GACxCgC,IAAW,MAAMD,EAAY,EAAK;AAMxC,SAJAtB,EAAU,MAAM;AACd,IAAAsB,EAAY,EAAK;AAAA,EACnB,GAAG,CAAClC,CAAM,CAAC,GAENA,IAKH,gBAAAH;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAW,4BAA4B;AAAA,QAChD,gBAAgB,CAACmC;AAAA,MAAA,CAClB;AAAA,MAED,UAAA;AAAA,QAAA,gBAAA3C;AAAA,UAAC8C;AAAA,UAAA;AAAA,YACC,QAAApC;AAAA,YACA,UAAAN;AAAA,YACA,QAAQD;AAAA,YACR,SAASwC;AAAA,YACT,MAAI;AAAA,YACJ,UAAU;AAAA,YACV,cAAY;AAAA,YACZ,kBAAkBE;AAAA,YAClB,OAAK;AAAA,UAAA;AAAA,QAAA;AAAA,QAEN,CAACF,KACA,gBAAApC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS,MAAMqC,EAAY,EAAI;AAAA,YAC/B,cAAW;AAAA,YAEV,UAAA;AAAA,cAAAzC,IACC,gBAAAH;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,KAAKG;AAAA,kBACL,KAAI;AAAA,kBACJ,WAAU;AAAA,gBAAA;AAAA,cAAA,IAGZ,gBAAAH,EAAC,OAAA,EAAI,WAAU,qDACZ,YAAeI,GAAU;AAAA,gBACxB,WAAW;AAAA,gBACX,QAAQ;AAAA,cAAA,CACT,GACH;AAAA,cAEF,gBAAAJ,EAAC,OAAA,EAAI,WAAU,yHACb,UAAA,gBAAAA,EAAC+C,KAAa,WAAU,UAAS,QAAO,OAAA,CAAO,EAAA,CACjD;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGHJ,KAAY,gBAAA3C,EAACH,GAAA,EAAY,SAASgD,EAAA,CAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,IA9CxC,gBAAA7C,EAACE,GAAA,EAAmB,WAAAC,GAAsB,UAAAC,EAAA,CAAoB;AAiDzE,GASM4C,IAA4C,CAAClD,MAAU;AAC3D,QAAM,EAAE,QAAAY,GAAQ,WAAAP,GAAW,UAAAC,GAAU,OAAA6C,MAAUnD,GACzC,CAAC6C,GAAUC,CAAW,IAAI/B,EAAS,EAAK,GACxCgC,IAAW,MAAMD,EAAY,EAAK;AAMxC,SAJAtB,EAAU,MAAM;AACd,IAAAsB,EAAY,EAAK;AAAA,EACnB,GAAG,CAAClC,CAAM,CAAC,GAEPiC,KAAYjC,IAEZ,gBAAAH,EAAC,OAAA,EAAI,WAAU,YACb,UAAA;AAAA,IAAA,gBAAAP;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,SAAS6C;AAAA,QACT,cAAW;AAAA,QAEX,UAAA,gBAAA7C,EAAC,SAAI,KAAKU,GAAQ,KAAKuC,KAAS,IAAI,WAAU,eAAA,CAAe;AAAA,MAAA;AAAA,IAAA;AAAA,IAE/D,gBAAAjD,EAACH,GAAA,EAAY,SAASgD,EAAA,CAAU;AAAA,EAAA,GAClC,IAKF,gBAAA7C;AAAA,IAACE;AAAA,IAAA;AAAA,MACC,WAAAC;AAAA,MACA,UAAAC;AAAA,MACA,aAAaM,IAASqC,IAAe;AAAA,MACrC,SAASrC,IAAS,MAAMkC,EAAY,EAAI,IAAI;AAAA,IAAA;AAAA,EAAA;AAGlD,GAGMM,IAA0C,CAACpD,MAAU;AACzD,QAAM;AAAA,IACJ,OAAAmD;AAAA,IACA,UAAA7C,IAAW;AAAA,IACX,WAAAD;AAAA,IACA,QAAAO;AAAA,IACA,QAAAyC;AAAA,IACA,YAAAC;AAAA,IACA,kBAAAC,IAAmB;AAAA,IACnB,uBAAAC;AAAA,IACA,eAAAC;AAAA,IACA,WAAAC;AAAA,IACA,WAAAC,IAAY;AAAA,EAAA,IACV3D,GACE4D,IAAaC,EAAcvD,CAAQ,GACnCwD,IAAoBR,KAAcE,GAClCO,IAAsB,CAACT,KAAc,CAAC,CAACE;AAE7C,MAAIQ;AACJ,SAAIL,KAAaC,MAAe,UAC9BI,IACE,gBAAA9D,EAACS,GAAA,EAAa,QAAAC,GAAgB,WAAAP,GAAsB,UAAAC,GAAoB,IAEjEqD,KAAaC,MAAe,UACrCI,IACE,gBAAA9D,EAAC0C,GAAA,EAAa,QAAAhC,GAAgB,WAAAP,GAAsB,UAAAC,GAAoB,IAEjEqD,KAAaC,MAAe,UACrCI,IACE,gBAAA9D;AAAA,IAACgD;AAAA,IAAA;AAAA,MACC,QAAAtC;AAAA,MACA,WAAAP;AAAA,MACA,UAAAC;AAAA,MACA,OAAA6C;AAAA,IAAA;AAAA,EAAA,IAIJa,IACE,gBAAA9D,EAACE,GAAA,EAAmB,WAAAC,GAAsB,UAAAC,EAAA,CAAoB,GAKhE,gBAAAG,EAAC,OAAA,EAAI,WAAU,mFACZ,UAAA;AAAA,IAAAiD,KACC,gBAAAxD;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAASwD;AAAA,QACT,WAAU;AAAA,QACV,cAAW;AAAA,QAEX,UAAA,gBAAAxD,EAAC+D,GAAA,EAAM,WAAU,UAAS,QAAO,OAAA,CAAO;AAAA,MAAA;AAAA,IAAA;AAAA,IAG3CD;AAAA,IACD,gBAAAvD,EAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,MAAA,gBAAAP;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWQ,EAAW,yCAAyC;AAAA,YAC7D,iBAAiB,CAACyC;AAAA,YAClB,cAAc,CAAC,CAACA;AAAA,UAAA,CACjB;AAAA,UAEA,UAAAA,KAASI;AAAA,QAAA;AAAA,MAAA;AAAA,MAEZ,gBAAA9C,EAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,QAAAyD,EAAe5D,GAAU;AAAA,UACxB,WAAW;AAAA,UACX,QAAQ;AAAA,QAAA,CACT;AAAA,QACA+C,KACC,gBAAAnD,EAAC,QAAA,EAAK,WAAU,qCAAqC,UAAAmD,GAAO;AAAA,QAE7DI,MAAkB,SACjB,gBAAAhD,EAAA0D,GAAA,EACE,UAAA;AAAA,UAAA,gBAAAjE,EAAC,QAAA,EAAK,WAAU,qCAAoC,UAAA,KAAC;AAAA,UACrD,gBAAAA,EAAC,QAAA,EAAK,WAAU,sCAAqC,UAAA,aAErD;AAAA,UACA,gBAAAA;AAAA,YAACkE;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,QAAO;AAAA,YAAA;AAAA,UAAA;AAAA,QACT,EAAA,CACF,IAEAN,KACE,gBAAArD,EAAA0D,GAAA,EACE,UAAA;AAAA,UAAA,gBAAAjE;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWQ,EAAW,uBAAuB;AAAA,gBAC3C,iBAAiBqD;AAAA,gBACjB,iBAAiB,CAACA;AAAA,cAAA,CACnB;AAAA,cACF,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGD,gBAAA7D;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAWQ,EAAW,uBAAuB;AAAA,gBAC3C,iBAAiBqD;AAAA,gBACjB,iBAAiB,CAACA;AAAA,cAAA,CACnB;AAAA,cAEA,UAAAD;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,EAAA,CACF;AAAA,MAAA,EAAA,CAGN;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MediaPlayer-DXz4IBLx.js","sources":["../src/components/LockedAttachment/utils/mimeType.ts","../src/components/LockedAttachment/utils/icons.ts","../src/components/LockedAttachment/components/MediaPlayer.tsx"],"sourcesContent":["export type AttachmentSourceType = 'image' | 'audio' | 'video' | 'document'\n\nexport type DocumentIconType =\n | 'pdf'\n | 'doc'\n | 'xls'\n | 'csv'\n | 'ppt'\n | 'zip'\n | 'text'\n | 'markdown'\n | 'generic'\n\nconst DOCUMENT_ICON_PATTERNS: Array<[RegExp, DocumentIconType]> = [\n [/pdf/, 'pdf'],\n [/wordprocessingml|msword|\\.doc/, 'doc'],\n [/spreadsheetml|ms-excel|\\.xls/, 'xls'],\n [/csv/, 'csv'],\n [/presentationml|ms-powerpoint|\\.ppt/, 'ppt'],\n [/zip|x-rar|x-7z|x-tar|x-gzip/, 'zip'],\n [/plain|rtf/, 'text'],\n [/markdown/, 'markdown'],\n]\n\nexport function getSourceType(mimeType: string): AttachmentSourceType {\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n if (mimeType.startsWith('image/')) return 'image'\n return 'document'\n}\n\nexport function getDocumentIconType(mimeType: string): DocumentIconType {\n const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>\n pattern.test(mimeType)\n )\n return match ? match[1] : 'generic'\n}\n","import {\n FileIcon,\n FileCsvIcon,\n FileDocIcon,\n FileMdIcon,\n FilePdfIcon,\n FilePptIcon,\n FileTextIcon,\n FileXlsIcon,\n FileZipIcon,\n ImageIcon,\n SpeakerHighIcon,\n VideoCameraIcon,\n IconProps,\n} from '@phosphor-icons/react'\nimport React from 'react'\n\nimport { getDocumentIconType, getSourceType } from './mimeType'\nimport type { AttachmentSourceType } from './mimeType'\n\nexport const MEDIA_TYPE_ICON: Record<AttachmentSourceType, React.ElementType> =\n {\n video: VideoCameraIcon,\n audio: SpeakerHighIcon,\n image: ImageIcon,\n document: FileIcon,\n }\n\nconst DOCUMENT_ICON_COMPONENT = {\n pdf: FilePdfIcon,\n doc: FileDocIcon,\n xls: FileXlsIcon,\n csv: FileCsvIcon,\n ppt: FilePptIcon,\n zip: FileZipIcon,\n text: FileTextIcon,\n markdown: FileMdIcon,\n generic: FileIcon,\n} as const\n\nexport function getTypeIcon(mimeType: string): React.ElementType {\n const sourceType = getSourceType(mimeType)\n if (sourceType !== 'document') return MEDIA_TYPE_ICON[sourceType]\n return DOCUMENT_ICON_COMPONENT[getDocumentIconType(mimeType)]\n}\n\n/** Use instead of `<TypeIcon />` where TypeIcon = getTypeIcon(mime) to satisfy react-hooks/static-components. */\nexport function renderTypeIcon(\n mimeType: string,\n props: IconProps\n): React.ReactElement {\n return React.createElement(getTypeIcon(mimeType), props)\n}\n","import { CircleNotchIcon, PauseIcon, PlayIcon } from '@phosphor-icons/react'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { isDevBuild } from '../../../utils/isDevBuild'\nimport { renderTypeIcon } from '../utils/icons'\nimport { getSourceType, type AttachmentSourceType } from '../utils/mimeType'\n\nconst getPlayerBg = (sourceType: AttachmentSourceType, poster?: string) =>\n sourceType === 'audio' && !poster ? 'bg-black/5' : 'bg-black'\n\nfunction getClientXFromEvent(\n e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent\n): number {\n if ('touches' in e) {\n return e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0\n }\n return e.clientX\n}\n\nexport interface MediaPlayerProps {\n source: string\n mimeType: string\n poster?: string\n autoPlay?: boolean\n /** Controlled playing state. When provided, syncs to internal play/pause. */\n playing?: boolean\n loop?: boolean\n controls?: boolean\n showProgress?: boolean\n onContainerClick?: () => void\n /** When true, requests muted playback (helps autoplay policies on video). */\n muted?: boolean\n}\n\nconst MediaPlayer: React.FC<MediaPlayerProps> = ({\n source,\n mimeType,\n poster,\n autoPlay = false,\n playing: playingProp,\n loop = false,\n controls = true,\n showProgress = false,\n onContainerClick,\n muted = false,\n}) => {\n const sourceType = getSourceType(mimeType)\n const [playing, setPlaying] = useState(autoPlay)\n\n // Sync controlled playing prop to internal state\n const prevPlayingPropRef = useRef(playingProp)\n useEffect(() => {\n if (\n playingProp !== undefined &&\n playingProp !== prevPlayingPropRef.current\n ) {\n prevPlayingPropRef.current = playingProp\n setPlaying(playingProp)\n }\n }, [playingProp])\n const [played, setPlayed] = useState(0)\n const [seeking, setSeeking] = useState(false)\n const [scrubberHovered, setScrubberHovered] = useState(false)\n const [videoAspect, setVideoAspect] = useState<number | null>(null)\n const [buffering, setBuffering] = useState(false)\n /** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */\n const [manualPlayRequired, setManualPlayRequired] = useState(false)\n const playerRef = useRef<HTMLMediaElement>(null)\n const trackRef = useRef<HTMLDivElement>(null)\n const rafRef = useRef<number | null>(null)\n\n useEffect(() => {\n setManualPlayRequired(false)\n }, [source])\n\n useEffect(() => {\n if (!playing) {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = null\n }\n return\n }\n const tick = () => {\n const el = playerRef.current\n if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n }\n }, [playing, seeking])\n\n // ReactPlayer v3 uses native HTML media elements and does not support a\n // declarative `playing` prop — playback must be driven imperatively.\n useEffect(() => {\n const el = playerRef.current\n if (!el) return\n if (playing) {\n void el.play().catch((err) => {\n setPlaying(false)\n setManualPlayRequired(true)\n if (isDevBuild()) {\n console.debug('[MediaPlayer] play() failed', err)\n }\n })\n } else {\n el.pause()\n }\n }, [playing])\n\n const startPlaybackFromGesture = useCallback(() => {\n setManualPlayRequired(false)\n setPlaying(true)\n }, [])\n\n const getFraction = useCallback(\n (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {\n const track = trackRef.current\n if (!track) return 0\n const rect = track.getBoundingClientRect()\n return Math.max(\n 0,\n Math.min(1, (getClientXFromEvent(e) - rect.left) / rect.width)\n )\n },\n []\n )\n\n const seekTo = useCallback((fraction: number) => {\n const el = playerRef.current\n if (el && el.duration) el.currentTime = fraction * el.duration\n }, [])\n\n const handleTrackPointerDown = (\n e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>\n ) => {\n e.stopPropagation()\n setSeeking(true)\n const fraction = getFraction(e)\n setPlayed(fraction)\n seekTo(fraction)\n }\n\n useEffect(() => {\n if (!seeking) return\n const onMove = (e: MouseEvent | TouchEvent) => setPlayed(getFraction(e))\n const onUp = (e: MouseEvent | TouchEvent) => {\n setSeeking(false)\n seekTo(getFraction(e))\n }\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', onUp)\n window.addEventListener('touchmove', onMove, { passive: true })\n window.addEventListener('touchend', onUp)\n return () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', onUp)\n window.removeEventListener('touchmove', onMove)\n window.removeEventListener('touchend', onUp)\n }\n }, [seeking, getFraction, seekTo])\n\n // Use natural aspect ratio once metadata loads, fall back to 16:9 before then.\n const aspectStyle = videoAspect\n ? { aspectRatio: String(videoAspect) }\n : undefined\n const aspectClass = !videoAspect ? ' aspect-video' : ''\n const scrubberPercent = Math.round(played * 100)\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={`relative cursor-pointer overflow-hidden ${getPlayerBg(sourceType, poster)}${aspectClass}`}\n style={aspectStyle}\n onClick={() => {\n if (manualPlayRequired) return\n if (onContainerClick) {\n onContainerClick()\n return\n }\n if (controls) setPlaying((p) => !p)\n }}\n onKeyDown={(e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return\n e.preventDefault()\n if (manualPlayRequired) return\n if (onContainerClick) {\n onContainerClick()\n return\n }\n if (controls) setPlaying((p) => !p)\n }}\n >\n {poster && (\n <img\n src={poster}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n )}\n {!poster && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n <div className=\"absolute inset-0\">\n {sourceType === 'audio' ? (\n <audio\n ref={playerRef as React.RefObject<HTMLAudioElement>}\n src={source}\n loop={loop}\n muted={muted}\n style={{ width: '100%', height: '100%' }}\n onLoadStart={() => setBuffering(true)}\n onCanPlay={() => setBuffering(false)}\n onWaiting={() => setBuffering(true)}\n onPlay={() => setManualPlayRequired(false)}\n onEnded={() => {\n if (!loop) {\n setPlaying(false)\n setPlayed(0)\n }\n }}\n >\n <track kind=\"captions\" />\n </audio>\n ) : (\n <video\n ref={playerRef as React.RefObject<HTMLVideoElement>}\n src={source}\n poster={poster}\n loop={loop}\n muted={muted}\n playsInline\n style={{ width: '100%', height: '100%' }}\n onLoadStart={() => setBuffering(true)}\n onCanPlay={() => setBuffering(false)}\n onWaiting={() => setBuffering(true)}\n onPlay={() => setManualPlayRequired(false)}\n onLoadedMetadata={() => {\n const el = playerRef.current\n if (el instanceof HTMLVideoElement && el.videoWidth && el.videoHeight) {\n setVideoAspect(el.videoWidth / el.videoHeight)\n }\n }}\n onEnded={() => {\n if (!loop) {\n setPlaying(false)\n setPlayed(0)\n }\n }}\n >\n <track kind=\"captions\" />\n </video>\n )}\n </div>\n\n {buffering && !manualPlayRequired && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center\">\n <CircleNotchIcon\n className=\"size-8 animate-spin text-white/80\"\n weight=\"bold\"\n />\n </div>\n )}\n\n {manualPlayRequired && !controls && (\n <div\n className=\"absolute inset-0 z-30 flex cursor-pointer items-center justify-center bg-black/35\"\n role=\"button\"\n tabIndex={0}\n aria-label=\"Play preview\"\n onClick={(e) => {\n e.stopPropagation()\n startPlaybackFromGesture()\n }}\n onKeyDown={(e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return\n e.preventDefault()\n e.stopPropagation()\n startPlaybackFromGesture()\n }}\n >\n <span className=\"flex size-16 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm\">\n <PlayIcon className=\"size-9 translate-x-0.5\" weight=\"fill\" />\n </span>\n </div>\n )}\n\n {showProgress && !controls && (\n <div className=\"absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent\">\n <div\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={scrubberPercent}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n ref={trackRef}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={handleTrackPointerDown}\n onTouchStart={handleTrackPointerDown}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n }}\n >\n <div className=\"w-full overflow-hidden rounded-full bg-white/30 h-1\">\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${scrubberPercent}%` }}\n />\n </div>\n </div>\n </div>\n )}\n\n {controls && (\n <div className=\"absolute inset-x-0 bottom-0 flex items-center gap-2 bg-gradient-to-t from-black/60 to-transparent px-3 pb-2.5 pt-6 transition-all duration-200\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setPlaying((p) => !p)\n }}\n className=\"shrink-0 text-white\"\n aria-label={playing ? 'Pause' : 'Play'}\n >\n {playing ? (\n <PauseIcon className=\"size-5\" weight=\"fill\" />\n ) : (\n <PlayIcon className=\"size-5 translate-x-px\" weight=\"fill\" />\n )}\n </button>\n\n <div\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={scrubberPercent}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n ref={trackRef}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={handleTrackPointerDown}\n onTouchStart={handleTrackPointerDown}\n onClick={(e) => e.stopPropagation()}\n onMouseEnter={() => setScrubberHovered(true)}\n onMouseLeave={() => setScrubberHovered(false)}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n }}\n >\n <div\n className={`w-full overflow-hidden rounded-full bg-white/30 transition-all duration-200 ${scrubberHovered || seeking ? 'h-1.5' : 'h-1'}`}\n >\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${scrubberPercent}%` }}\n />\n </div>\n <div\n className={`absolute size-3 -translate-x-1/2 rounded-full bg-white shadow transition-[opacity,transform] duration-200 ${scrubberHovered || seeking ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}\n style={{ left: `${scrubberPercent}%` }}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default MediaPlayer\n"],"names":["DOCUMENT_ICON_PATTERNS","getSourceType","mimeType","getDocumentIconType","match","pattern","MEDIA_TYPE_ICON","VideoCameraIcon","SpeakerHighIcon","ImageIcon","FileIcon","DOCUMENT_ICON_COMPONENT","FilePdfIcon","FileDocIcon","FileXlsIcon","FileCsvIcon","FilePptIcon","FileZipIcon","FileTextIcon","FileMdIcon","getTypeIcon","sourceType","renderTypeIcon","props","React","getPlayerBg","poster","getClientXFromEvent","e","_a","_b","MediaPlayer","source","autoPlay","playingProp","loop","controls","showProgress","onContainerClick","muted","playing","setPlaying","useState","prevPlayingPropRef","useRef","useEffect","played","setPlayed","seeking","setSeeking","scrubberHovered","setScrubberHovered","videoAspect","setVideoAspect","buffering","setBuffering","manualPlayRequired","setManualPlayRequired","playerRef","trackRef","rafRef","tick","el","err","startPlaybackFromGesture","useCallback","getFraction","track","rect","seekTo","fraction","handleTrackPointerDown","onMove","onUp","aspectStyle","aspectClass","scrubberPercent","jsxs","p","jsx","CircleNotchIcon","PlayIcon","PauseIcon"],"mappings":"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"}
|