@@ -7,6 +7,7 @@ import React, {
77 useMemo ,
88 useRef ,
99 useState ,
10+ ClipboardEvent ,
1011} from 'react' ;
1112import { CopilotTextarea } from '@copilotkit/react-textarea' ;
1213import clsx from 'clsx' ;
@@ -32,6 +33,10 @@ import {
3233 LinkedinCompanyPop ,
3334 ShowLinkedinCompany ,
3435} from '@gitroom/frontend/components/launches/helpers/linkedin.component' ;
36+ import { DropEvent , FileRejection , useDropzone } from 'react-dropzone' ;
37+ import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader' ;
38+ import { UploadResult } from '@uppy/core' ;
39+ import { ProgressBar } from '@uppy/react' ;
3540export const EditorWrapper : FC < {
3641 totalPosts : number ;
3742 value : string ;
@@ -46,6 +51,8 @@ export const EditorWrapper: FC<{
4651 addInternalValue,
4752 addGlobalValue,
4853 setInternalValueMedia,
54+ appendInternalValueMedia,
55+ appendGlobalValueMedia,
4956 setGlobalValueMedia,
5057 changeOrderGlobal,
5158 changeOrderInternal,
@@ -77,6 +84,8 @@ export const EditorWrapper: FC<{
7784 setGlobalValue : state . setGlobalValue ,
7885 setInternalValue : state . setInternalValue ,
7986 totalChars : state . totalChars ,
87+ appendInternalValueMedia : state . appendInternalValueMedia ,
88+ appendGlobalValueMedia : state . appendGlobalValueMedia ,
8089 } ) )
8190 ) ;
8291
@@ -101,7 +110,7 @@ export const EditorWrapper: FC<{
101110 }
102111
103112 return global ;
104- } , [ current , internal , global ] ) ;
113+ } , [ internal , global ] ) ;
105114
106115 const setValue = useCallback (
107116 ( value : string [ ] ) => {
@@ -165,6 +174,17 @@ export const EditorWrapper: FC<{
165174 [ current , global , internal ]
166175 ) ;
167176
177+ const appendImages = useCallback (
178+ ( index : number ) => ( value : any [ ] ) => {
179+ if ( internal ) {
180+ return appendInternalValueMedia ( current , index , value ) ;
181+ }
182+
183+ return appendGlobalValueMedia ( index , value ) ;
184+ } ,
185+ [ current , global , internal ]
186+ ) ;
187+
168188 const changeOrder = useCallback (
169189 ( index : number ) => ( direction : 'up' | 'down' ) => {
170190 if ( internal ) {
@@ -274,6 +294,7 @@ export const EditorWrapper: FC<{
274294 validateChars = { true }
275295 identifier = { internalFromAll ?. identifier || 'global' }
276296 totalChars = { totalChars }
297+ appendImages = { appendImages ( index ) }
277298 />
278299 </ div >
279300 < div className = "flex flex-col items-center gap-[10px]" >
@@ -337,6 +358,7 @@ export const Editor: FC<{
337358 allValues ?: any [ ] ;
338359 onChange : ( value : string ) => void ;
339360 setImages ?: ( value : any [ ] ) => void ;
361+ appendImages ?: ( value : any [ ] ) => void ;
340362 autoComplete ?: boolean ;
341363 validateChars ?: boolean ;
342364 identifier ?: string ;
@@ -350,13 +372,60 @@ export const Editor: FC<{
350372 autoComplete,
351373 validateChars,
352374 identifier,
375+ appendImages,
353376 } = props ;
354377 const user = useUser ( ) ;
355378 const [ id ] = useState ( makeId ( 10 ) ) ;
356379 const newRef = useRef < any > ( null ) ;
357380 const [ emojiPickerOpen , setEmojiPickerOpen ] = useState ( false ) ;
381+ const [ isUploading , setIsUploading ] = useState ( false ) ;
358382 const t = useT ( ) ;
359383
384+ const uppy = useUppyUploader ( {
385+ onUploadSuccess : ( result : UploadResult < any , any > ) => {
386+ appendImages ( [
387+ ...result . successful . map ( ( p ) => ( {
388+ id : p . response . body . saved . id ,
389+ path : p . response . body . saved . path ,
390+ } ) ) ,
391+ ] ) ;
392+ uppy . clear ( ) ;
393+ } ,
394+ allowedFileTypes : 'image/*,video/mp4' ,
395+ } ) ;
396+
397+ const onDrop = useCallback (
398+ ( acceptedFiles : File [ ] ) => {
399+ for ( const file of acceptedFiles ) {
400+ uppy . addFile ( file ) ;
401+ }
402+ } ,
403+ [ uppy ]
404+ ) ;
405+
406+ const paste = useCallback (
407+ async ( event : ClipboardEvent < HTMLDivElement > | File [ ] ) => {
408+ // @ts -ignore
409+ const clipboardItems = event . clipboardData ?. items ;
410+ if ( ! clipboardItems ) {
411+ return ;
412+ }
413+
414+ // @ts -ignore
415+ for ( const item of clipboardItems ) {
416+ if ( item . kind === 'file' ) {
417+ const file = item . getAsFile ( ) ;
418+ if ( file ) {
419+ uppy . addFile ( file ) ;
420+ }
421+ }
422+ }
423+ } ,
424+ [ uppy ]
425+ ) ;
426+
427+ const { getRootProps, getInputProps, isDragActive } = useDropzone ( { onDrop } ) ;
428+
360429 const addText = useCallback (
361430 ( emoji : string ) => {
362431 setTimeout ( ( ) => {
@@ -367,8 +436,16 @@ export const Editor: FC<{
367436 [ props . value , id ]
368437 ) ;
369438 return (
370- < >
439+ < div { ... getRootProps ( ) } >
371440 < div className = "relative bg-customColor2" id = { id } >
441+ < div
442+ className = { clsx (
443+ 'absolute left-0 top-0 w-full h-full bg-black/70 z-[300] transition-all items-center justify-center flex text-white text-sm' ,
444+ ! isDragActive ? 'pointer-events-none opacity-0' : 'opacity-100'
445+ ) }
446+ >
447+ Drop your files here to upload
448+ </ div >
372449 < div className = "flex gap-[5px] bg-customColor55 border-b border-t border-customColor3 justify-center items-center p-[5px]" >
373450 < SignatureBox editor = { newRef ?. current ?. editor ! } />
374451 < UText
@@ -401,30 +478,35 @@ export const Editor: FC<{
401478 </ div >
402479 </ div >
403480 </ div >
404- < CopilotTextarea
405- disableBranding = { true }
406- ref = { newRef }
407- className = { clsx (
408- '!min-h-40 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-customColor2 outline-none' ,
409- props . totalPosts > 1 && '!max-h-80'
410- ) }
411- value = { props . value }
412- onChange = { ( e ) => {
413- props ?. onChange ?.( e . target . value ) ;
414- } }
415- // onPaste={props.onPaste}
416- placeholder = { t ( 'write_your_reply' , 'Write your post...' ) }
417- autosuggestionsConfig = { {
418- textareaPurpose : `Assist me in writing social media posts.` ,
419- chatApiConfigs : {
420- suggestionsApiConfig : {
421- maxTokens : 20 ,
422- stop : [ '.' , '?' , '!' ] ,
481+ < div className = "relative" >
482+ < div className = "pointer-events-none absolute end-0 bottom-0 z-[200]" >
483+ < ProgressBar id = { `prog-${ num } ` } uppy = { uppy } />
484+ </ div >
485+ < CopilotTextarea
486+ disableBranding = { true }
487+ ref = { newRef }
488+ className = { clsx (
489+ '!min-h-40 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-customColor2 outline-none' ,
490+ props . totalPosts > 1 && '!max-h-80'
491+ ) }
492+ value = { props . value }
493+ onChange = { ( e ) => {
494+ props ?. onChange ?.( e . target . value ) ;
495+ } }
496+ onPaste = { paste }
497+ placeholder = { t ( 'write_your_reply' , 'Write your post...' ) }
498+ autosuggestionsConfig = { {
499+ textareaPurpose : `Assist me in writing social media posts.` ,
500+ chatApiConfigs : {
501+ suggestionsApiConfig : {
502+ maxTokens : 20 ,
503+ stop : [ '.' , '?' , '!' ] ,
504+ } ,
423505 } ,
424- } ,
425- disabled : user ?. tier ?. ai ? ! autoComplete : true ,
426- } }
427- / >
506+ disabled : user ?. tier ?. ai ? ! autoComplete : true ,
507+ } }
508+ />
509+ </ div >
428510 { validateChars && props . value . length < 6 && (
429511 < div className = "px-3 text-sm bg-red-600 !text-white mb-[4px]" >
430512 { t (
@@ -461,6 +543,6 @@ export const Editor: FC<{
461543 { props ?. value ?. length } /{ props . totalChars }
462544 </ div >
463545 ) }
464- </ >
546+ </ div >
465547 ) ;
466548} ;
0 commit comments