@@ -18,6 +18,7 @@ import { Media } from '@prisma/client';
1818import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory' ;
1919import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values' ;
2020import EventEmitter from 'events' ;
21+ import { useToaster } from '@gitroom/react/toaster/toaster' ;
2122import clsx from 'clsx' ;
2223import { VideoFrame } from '@gitroom/react/helpers/video.frame' ;
2324import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader' ;
@@ -48,6 +49,7 @@ import {
4849} from '@gitroom/frontend/components/ui/icons' ;
4950import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store' ;
5051import { useShallow } from 'zustand/react/shallow' ;
52+ import { LoadingComponent } from '@gitroom/frontend/components/layout/loading' ;
5153const Polonto = dynamic (
5254 ( ) => import ( '@gitroom/frontend/components/launches/polonto' )
5355) ;
@@ -194,6 +196,7 @@ export const showMediaBox = (
194196 showModalEmitter . emit ( 'show-modal' , callback ) ;
195197} ;
196198const CHUNK_SIZE = 1024 * 1024 ;
199+ const MAX_UPLOAD_SIZE = 1024 * 1024 * 1024 ; // 1 GB
197200export const MediaBox : FC < {
198201 setMedia : ( params : { id : string ; path : string } [ ] ) => void ;
199202 standalone ?: boolean ;
@@ -203,6 +206,7 @@ export const MediaBox: FC<{
203206 const [ page , setPage ] = useState ( 0 ) ;
204207 const fetch = useFetch ( ) ;
205208 const modals = useModals ( ) ;
209+ const toaster = useToaster ( ) ;
206210 const loadMedia = useCallback ( async ( ) => {
207211 return ( await fetch ( `/media?page=${ page + 1 } ` ) ) . json ( ) ;
208212 } , [ page ] ) ;
@@ -211,6 +215,7 @@ export const MediaBox: FC<{
211215 const t = useT ( ) ;
212216 const uploaderRef = useRef < any > ( null ) ;
213217 const mediaDirectory = useMediaDirectory ( ) ;
218+ const [ loading , setLoading ] = useState ( false ) ;
214219
215220 const uppy = useUppyUploader ( {
216221 allowedFileTypes :
@@ -220,7 +225,6 @@ export const MediaBox: FC<{
220225 ? 'video/mp4'
221226 : 'image/*,video/mp4' ,
222227 onUploadSuccess : async ( arr ) => {
223- uppy . clear ( ) ;
224228 await mutate ( ) ;
225229 if ( standalone ) {
226230 return ;
@@ -229,6 +233,8 @@ export const MediaBox: FC<{
229233 return [ ...prevSelected , ...arr ] ;
230234 } ) ;
231235 } ,
236+ onStart : ( ) => setLoading ( true ) ,
237+ onEnd : ( ) => setLoading ( false ) ,
232238 } ) ;
233239
234240 const addRemoveSelected = useCallback (
@@ -255,11 +261,29 @@ export const MediaBox: FC<{
255261 modals . closeCurrent ( ) ;
256262 } , [ selected ] ) ;
257263
258- const addToUpload = useCallback ( async ( e : ChangeEvent < HTMLInputElement > ) => {
259- const files = Array . from ( e . target . files ) . slice ( 0 , 5 ) ;
260- // @ts -ignore
261- uppy . addFiles ( files ) ;
262- } , [ ] ) ;
264+ const addToUpload = useCallback (
265+ async ( e : ChangeEvent < HTMLInputElement > ) => {
266+ const files = Array . from ( e . target . files || [ ] ) ;
267+ const totalSize = files . reduce ( ( acc , file ) => acc + file . size , 0 ) ;
268+
269+ if ( totalSize > MAX_UPLOAD_SIZE ) {
270+ toaster . show (
271+ t (
272+ 'upload_size_limit_exceeded' ,
273+ 'Upload size limit exceeded. Maximum 1 GB per upload session.'
274+ ) ,
275+ 'warning'
276+ ) ;
277+ return ;
278+ }
279+
280+ setLoading ( true ) ;
281+
282+ // @ts -ignore
283+ uppy . addFiles ( files ) ;
284+ } ,
285+ [ toaster , t ]
286+ ) ;
263287
264288 const dragAndDrop = useCallback (
265289 async ( event : ClipboardEvent < HTMLDivElement > | File [ ] ) => {
@@ -272,7 +296,7 @@ export const MediaBox: FC<{
272296 return ;
273297 }
274298
275- const files = [ ] ;
299+ const files : File [ ] = [ ] ;
276300 // @ts -ignore
277301 for ( const item of clipboardItems ) {
278302 if ( item . kind === 'file' ) {
@@ -283,11 +307,26 @@ export const MediaBox: FC<{
283307 }
284308 }
285309
286- for ( const file of files . slice ( 0 , 5 ) ) {
310+ const totalSize = files . reduce ( ( acc , file ) => acc + file . size , 0 ) ;
311+
312+ if ( totalSize > MAX_UPLOAD_SIZE ) {
313+ toaster . show (
314+ t (
315+ 'upload_size_limit_exceeded' ,
316+ 'Upload size limit exceeded. Maximum 1 GB per upload session.'
317+ ) ,
318+ 'warning'
319+ ) ;
320+ return ;
321+ }
322+
323+ setLoading ( true ) ;
324+
325+ for ( const file of files ) {
287326 uppy . addFile ( file ) ;
288327 }
289328 } ,
290- [ ]
329+ [ toaster , t ]
291330 ) ;
292331
293332 const maximize = useCallback (
@@ -299,7 +338,10 @@ export const MediaBox: FC<{
299338 children : (
300339 < div className = "w-full h-full p-[50px]" >
301340 { media . path . indexOf ( 'mp4' ) > - 1 ? (
302- < VideoFrame autoplay = { true } url = { mediaDirectory . set ( media . path ) } />
341+ < VideoFrame
342+ autoplay = { true }
343+ url = { mediaDirectory . set ( media . path ) }
344+ />
303345 ) : (
304346 < img
305347 width = "100%"
@@ -340,17 +382,24 @@ export const MediaBox: FC<{
340382 const btn = useMemo ( ( ) => {
341383 return (
342384 < button
385+ disabled = { loading }
343386 onClick = { ( ) => uploaderRef ?. current ?. click ( ) }
344- className = "cursor-pointer bg-btnSimple changeColor flex gap-[8px] h-[44px] px-[18px] justify-center items-center rounded-[8px]"
387+ className = "relative cursor-pointer bg-btnSimple changeColor flex gap-[8px] h-[44px] px-[18px] justify-center items-center rounded-[8px]"
345388 >
346- < PlusIcon size = { 14 } />
347- < div > { t ( 'upload' , 'Upload' ) } </ div >
389+ { loading ? (
390+ < div className = "absolute left-[50%] top-[50%] -translate-y-[50%] -translate-x-[50%]" >
391+ < div className = "animate-spin h-[20px] w-[20px] border-4 border-white border-t-transparent rounded-full" />
392+ </ div >
393+ ) : (
394+ < PlusIcon size = { 14 } />
395+ ) }
396+ < div className = { loading && 'invisible' } > { t ( 'upload' , 'Upload' ) } </ div >
348397 </ button >
349398 ) ;
350- } , [ t ] ) ;
399+ } , [ t , loading ] ) ;
351400
352401 return (
353- < DropFiles className = "flex flex-col flex-1" onDrop = { dragAndDrop } >
402+ < DropFiles disabled = { loading } className = "flex flex-col flex-1" onDrop = { dragAndDrop } >
354403 < div className = "flex flex-col flex-1" >
355404 < div
356405 className = { clsx (
@@ -361,8 +410,8 @@ export const MediaBox: FC<{
361410 { ! isLoading && ! ! data ?. results ?. length && (
362411 < div className = "flex-1 text-[14px] font-[600] whitespace-pre-line" >
363412 { t (
364- 'select_or_upload_pictures_max_5 ' ,
365- 'Select or upload pictures (maximum 5 at a time ).'
413+ 'select_or_upload_pictures_max_1gb ' ,
414+ 'Select or upload pictures (maximum 1 GB per upload ).'
366415 ) }
367416 { '\n' }
368417 { t (
@@ -423,8 +472,8 @@ export const MediaBox: FC<{
423472 </ div >
424473 < div className = "whitespace-pre-line text-newTextColor/[0.6] text-center" >
425474 { t (
426- 'select_or_upload_pictures_max_5 ' ,
427- 'Select or upload pictures (maximum 5 at a time ).'
475+ 'select_or_upload_pictures_max_1gb ' ,
476+ 'Select or upload pictures (maximum 1 GB per upload ).'
428477 ) } { ' ' }
429478 { '\n' }
430479 { t (
@@ -476,7 +525,7 @@ export const MediaBox: FC<{
476525 onClick = { addRemoveSelected ( media ) }
477526 >
478527 { ! ! selected . find ( ( p : any ) => p . id === media . id ) ? (
479- < div className = "text-white flex justify-center items-center text-[14px] font-[500] w-[24px] h-[24px] rounded-full bg-[#612BD3] absolute -bottom-[10px] -end-[10px]" >
528+ < div className = "text-white flex z-[101] justify-center items-center text-[14px] font-[500] w-[24px] h-[24px] rounded-full bg-[#612BD3] absolute -bottom-[10px] -end-[10px]" >
480529 { selected . findIndex ( ( z : any ) => z . id === media . id ) + 1 }
481530 </ div >
482531 ) : (
0 commit comments