11import { ConsultMessage , User , UserRole } from '@idea2app/data-server' ;
2- import { Avatar , Button , Container , Paper , TextField , Typography } from '@mui/material' ;
2+ import { Avatar , Button , Container , IconButton , Paper , TextField , Tooltip , Typography } from '@mui/material' ;
33import { marked } from 'marked' ;
44import { observer } from 'mobx-react' ;
55import { ObservedComponent , reaction } from 'mobx-react-helper' ;
66import { compose , JWTProps , jwtVerifier , RouteProps , router } from 'next-ssr-middleware' ;
7- import { FormEvent , KeyboardEventHandler } from 'react' ;
7+ import { ChangeEvent , ClipboardEvent , createRef , DragEvent , FormEvent , KeyboardEventHandler } from 'react' ;
88import { formToJSON , scrollTo , sleep } from 'web-utility' ;
99
10+ import { SymbolIcon } from '../../../components/Icon' ;
1011import { PageHead } from '../../../components/PageHead' ;
1112import { EvaluationDisplay } from '../../../components/Project/EvaluationDisplay' ;
1213import { ScrollList } from '../../../components/ScrollList' ;
1314import { SessionBox } from '../../../components/User/SessionBox' ;
15+ import fileStore from '../../../models/File' ;
1416import { ConsultMessageModel , ProjectModel } from '../../../models/ProjectEvaluation' ;
1517import { i18n , I18nContext } from '../../../models/Translation' ;
1618
@@ -31,6 +33,8 @@ export default class ProjectEvaluationPage extends ObservedComponent<
3133
3234 messageStore = new ConsultMessageModel ( this . projectId ) ;
3335
36+ fileInputRef = createRef < HTMLInputElement > ( ) ;
37+
3438 get menu ( ) {
3539 const { t } = this . observedContext ;
3640
@@ -76,6 +80,43 @@ export default class ProjectEvaluationPage extends ObservedComponent<
7680 ) ;
7781 } ;
7882
83+ handleFiles = async ( files : File [ ] ) => {
84+ for ( const file of files ) {
85+ const url = await fileStore . upload ( file ) ;
86+ const content = file . type . startsWith ( 'image/' )
87+ ? ``
88+ : `[${ file . name } ](${ url } )` ;
89+
90+ await this . messageStore . updateOne ( { content } ) ;
91+ }
92+ } ;
93+
94+ handleFileInputChange = async ( event : ChangeEvent < HTMLInputElement > ) => {
95+ const files = [ ...( event . target . files || [ ] ) ] ;
96+
97+ event . target . value = '' ;
98+
99+ if ( files . length > 0 ) await this . handleFiles ( files ) ;
100+ } ;
101+
102+ handlePasteDrop = async ( event : ClipboardEvent | DragEvent ) => {
103+ const items =
104+ event . type === 'paste'
105+ ? [ ...( event as ClipboardEvent ) . clipboardData . items ]
106+ : [ ...( event as DragEvent ) . dataTransfer . items ] ;
107+
108+ const files = items
109+ . filter ( item => item . kind === 'file' )
110+ . map ( item => item . getAsFile ( ) )
111+ . filter ( ( file ) : file is File => file !== null ) ;
112+
113+ if ( files . length > 0 ) {
114+ event . preventDefault ( ) ;
115+
116+ await this . handleFiles ( files ) ;
117+ }
118+ } ;
119+
79120 renderChatMessage = (
80121 { id, content, evaluation, prototypes, createdAt, createdBy } : ConsultMessage ,
81122 index = 0 ,
@@ -175,6 +216,22 @@ export default class ProjectEvaluationPage extends ObservedComponent<
175216 className = "sticky bottom-0 mx-1 mt-auto mb-1 flex items-end gap-2 p-1.5 sm:mx-0 sm:mb-0 sm:p-2"
176217 onSubmit = { this . handleMessageSubmit }
177218 >
219+ < input
220+ ref = { this . fileInputRef }
221+ type = "file"
222+ multiple
223+ className = "hidden"
224+ onChange = { this . handleFileInputChange }
225+ />
226+ < Tooltip title = { t ( 'attach_files' ) } >
227+ < IconButton
228+ size = "small"
229+ disabled = { fileStore . uploading > 0 || messageStore . uploading > 0 }
230+ onClick = { ( ) => this . fileInputRef . current ?. click ( ) }
231+ >
232+ < SymbolIcon name = "attach_file" />
233+ </ IconButton >
234+ </ Tooltip >
178235 < TextField
179236 name = "content"
180237 placeholder = { t ( 'type_your_message' ) }
@@ -185,12 +242,15 @@ export default class ProjectEvaluationPage extends ObservedComponent<
185242 size = "small"
186243 required
187244 onKeyUp = { this . handleQuickSubmit }
245+ onPaste = { this . handlePasteDrop }
246+ onDragOver = { e => e . preventDefault ( ) }
247+ onDrop = { this . handlePasteDrop }
188248 />
189249 < Button
190250 type = "submit"
191251 variant = "contained"
192252 className = "min-w-full px-2 whitespace-nowrap sm:min-w-0"
193- disabled = { messageStore . uploading > 0 }
253+ disabled = { fileStore . uploading > 0 || messageStore . uploading > 0 }
194254 >
195255 { t ( 'send' ) }
196256 </ Button >
0 commit comments