@@ -11,23 +11,33 @@ import type {
1111 InitializedEvent ,
1212} from '@js/ui/button' ;
1313import type Button from '@js/ui/button' ;
14- import type { Properties as FileUploaderProperties } from '@js/ui/file_uploader' ;
14+ import type { Attachment } from '@js/ui/chat' ;
15+ import type { UploadedEvent , UploadStartedEvent , ValueChangedEvent } from '@js/ui/file_uploader' ;
1516import { current , isMaterial } from '@js/ui/themes' ;
1617import type { Item as ToolbarItem } from '@js/ui/toolbar' ;
1718import Toolbar from '@js/ui/toolbar' ;
1819import type { OptionChanged } from '@ts/core/widget/types' ;
1920import type { SupportedKeys } from '@ts/core/widget/widget' ;
21+ import Widget from '@ts/core/widget/widget' ;
22+ import FileUploader from '@ts/ui/file_uploader/file_uploader' ;
23+ import type { CancelButtonClickEvent , Properties as FileUploaderProperties } from '@ts/ui/file_uploader/file_uploader.types' ;
2024import type { TextAreaProperties } from '@ts/ui/m_text_area' ;
2125import TextArea from '@ts/ui/m_text_area' ;
2226
2327export const TEXT_AREA_TOOLBAR = 'dx-textarea-toolbar' ;
28+ const TEXT_AREA_ATTACHMENTS = 'dx-textarea-attachments' ;
29+ const TEXT_AREA_ATTACH_BUTTON = 'dx-textarea-attach-button' ;
2430
2531const isMobile = ( ) : boolean => devices . current ( ) . deviceType !== 'desktop' ;
2632
2733type EnterKeyEvent = NativeEventInfo < ChatTextArea , KeyboardEvent > ;
2834
2935export type SendEvent = ClickEvent | EnterKeyEvent ;
3036
37+ type FileToSend = Attachment & {
38+ readyToSend : boolean ;
39+ } ;
40+
3141export type Properties = TextAreaProperties & {
3242 fileUploaderOptions ?: FileUploaderProperties ;
3343
@@ -39,18 +49,34 @@ class ChatTextArea extends TextArea<Properties> {
3949
4050 _toolbar ?: Toolbar | null ;
4151
52+ _$fileUploader ?: dxElementWrapper | null ;
53+
54+ _fileUploader ?: FileUploader | null ;
55+
56+ _filesToSend ?: Map < File , FileToSend > ;
57+
4258 _sendButton ?: Button ;
4359
4460 _sendAction ?: ( e : SendEvent ) => void ;
4561
62+ getAttachments ( ) : Attachment [ ] | undefined {
63+ if ( ! this . _filesToSend ?. size ) {
64+ return undefined ;
65+ }
66+
67+ return Array
68+ . from ( this . _filesToSend . values ( ) )
69+ . map ( ( { name, size } ) => ( { name, size } ) ) ;
70+ }
71+
4672 _getDefaultOptions ( ) : Properties {
4773 return {
4874 ...super . _getDefaultOptions ( ) ,
4975 stylingMode : 'outlined' ,
5076 placeholder : messageLocalization . format ( 'dxChat-textareaPlaceholder' ) ,
5177 autoResizeEnabled : true ,
52- valueChangeEvent : 'input' ,
5378 maxHeight : '8em' ,
79+ valueChangeEvent : 'input' ,
5480 fileUploaderOptions : undefined ,
5581 } ;
5682 }
@@ -112,6 +138,7 @@ class ChatTextArea extends TextArea<Properties> {
112138 _initMarkup ( ) : void {
113139 super . _initMarkup ( ) ;
114140 this . _renderToolbar ( ) ;
141+ this . _initFileUploader ( ) ;
115142 }
116143
117144 _renderToolbar ( ) : void {
@@ -160,6 +187,7 @@ class ChatTextArea extends TextArea<Properties> {
160187 activeStateEnabled,
161188 focusStateEnabled,
162189 hoverStateEnabled,
190+ elementAttr : { class : TEXT_AREA_ATTACH_BUTTON } ,
163191 icon : 'attach' ,
164192 } ,
165193 } as ToolbarItem ;
@@ -200,35 +228,136 @@ class ChatTextArea extends TextArea<Properties> {
200228 return configuration ;
201229 }
202230
203- _toggleButtonDisableState ( state : boolean ) : void {
204- this . _sendButton ?. option ( 'disabled' , state ) ;
231+ _initFileUploader ( ) : void {
232+ const { fileUploaderOptions } = this . option ( ) ;
233+
234+ if ( ! fileUploaderOptions ) {
235+ return ;
236+ }
237+
238+ this . _renderFileUploader ( ) ;
239+ this . _filesToSend = new Map < File , FileToSend > ( ) ;
240+ }
241+
242+ _renderFileUploader ( ) : void {
243+ this . _$fileUploader = $ ( '<div>' )
244+ . addClass ( TEXT_AREA_ATTACHMENTS )
245+ . insertBefore ( this . _$textEditorContainer ) ;
246+
247+ this . _fileUploader = this . _createComponent (
248+ this . _$fileUploader ,
249+ FileUploader ,
250+ this . _getFileUploaderOptions ( ) ,
251+ ) ;
252+ }
253+
254+ _shouldHideFileUploader ( value : File [ ] = [ ] ) : boolean {
255+ return value . length !== 0 ;
256+ }
257+
258+ _getFileUploaderOptions ( ) : FileUploaderProperties {
259+ const { fileUploaderOptions = { } } = this . option ( ) ;
260+ const multiple = fileUploaderOptions . multiple ?? true ;
261+ const visible = this . _shouldHideFileUploader ( fileUploaderOptions . value ) ;
262+
263+ return {
264+ ...fileUploaderOptions ,
265+ multiple,
266+ visible,
267+ uploadMode : 'instantly' ,
268+ dialogTrigger : this . $element ( ) . find ( `.${ TEXT_AREA_ATTACH_BUTTON } ` ) . get ( 0 ) ,
269+ _hideCancelButtonOnUpload : false ,
270+ _showFileIcon : true ,
271+ _cancelButtonPosition : 'end' ,
272+ onValueChanged : ( e ) => this . _fileUploaderOnValueChanged ( e ) ,
273+ onUploadStarted : ( e ) => this . _fileUploaderOnUploadStarted ( e ) ,
274+ onUploaded : ( e ) => this . _fileUploaderOnUploaded ( e ) ,
275+ onCancelButtonClick : ( e ) => this . _fileUploaderOnCancelButtonClick ( e ) ,
276+ } ;
277+ }
278+
279+ _fileUploaderOnValueChanged ( e : ValueChangedEvent ) : void {
280+ const { value, component } = e ;
281+ const { fileUploaderOptions = { } } = this . option ( ) ;
282+
283+ component . option ( 'visible' , this . _shouldHideFileUploader ( value ) ) ;
284+ this . _updateInputHeight ( ) ;
285+ fileUploaderOptions . onValueChanged ?.( e ) ;
286+ }
287+
288+ _fileUploaderOnUploadStarted ( e : UploadStartedEvent ) : void {
289+ const { file } = e ;
290+ const { fileUploaderOptions = { } } = this . option ( ) ;
291+
292+ this . _filesToSend ?. set ( file , {
293+ readyToSend : false ,
294+ name : file . name ,
295+ size : file . size ,
296+ } ) ;
297+ this . _toggleButtonDisableState ( ) ;
298+
299+ fileUploaderOptions . onUploadStarted ?.( e ) ;
300+ }
301+
302+ _fileUploaderOnUploaded ( e : UploadedEvent ) : void {
303+ const { file } = e ;
304+ const { fileUploaderOptions = { } } = this . option ( ) ;
305+ const fileInfo = this . _filesToSend ?. get ( file ) ;
306+
307+ if ( this . _filesToSend && fileInfo ) {
308+ this . _filesToSend . set ( file , {
309+ ...fileInfo ,
310+ readyToSend : true ,
311+ } ) ;
312+ }
313+
314+ this . _toggleButtonDisableState ( ) ;
315+
316+ fileUploaderOptions . onUploaded ?.( e ) ;
317+ }
318+
319+ _fileUploaderOnCancelButtonClick = ( e : CancelButtonClickEvent ) : void => {
320+ const { file } = e ;
321+
322+ if ( file ) {
323+ this . _filesToSend ?. delete ( file ) ;
324+ }
325+
326+ this . _toggleButtonDisableState ( ) ;
327+ } ;
328+
329+ _toggleButtonDisableState ( state ?: boolean ) : void {
330+ const shouldDisable = state ?? ! this . _isMessageCanBeSent ( ) ;
331+ this . _sendButton ?. option ( 'disabled' , shouldDisable ) ;
205332 }
206333
207334 _renderButtonContainers ( ) : void { }
208335
209336 _getHeightDifference ( $input : dxElementWrapper ) : number {
210337 const superResult = super . _getHeightDifference ( $input ) ;
211338 const toolbarHeight = getOuterHeight ( this . _$toolbar ) ;
212- const sum : number = superResult + toolbarHeight ;
339+ const fileUploaderHeight = getOuterHeight ( this . _$fileUploader ) ;
340+ const sum : number = superResult + toolbarHeight + fileUploaderHeight ;
213341
214342 return sum ;
215343 }
216344
217345 _keyPressHandler ( e : InputEvent ) : void {
218346 super . _keyPressHandler ( e ) ;
219347
220- const shouldButtonBeDisabled = ! this . _isValuableTextEntered ( ) ;
221- this . _toggleButtonDisableState ( shouldButtonBeDisabled ) ;
348+ this . _toggleButtonDisableState ( ) ;
222349 }
223350
224351 _processSendButtonActivation ( e : SendEvent ) : void {
225352 this . _sendAction ?.( e ) ;
226353 this . reset ( ) ;
354+ this . _fileUploader ?. reset ( ) ;
355+ this . _filesToSend ?. clear ( ) ;
227356 this . _toggleButtonDisableState ( true ) ;
228357 }
229358
230359 _shouldSendMessageOnEnter ( e : DxEvent < KeyboardEvent > ) : boolean {
231- return ! e ?. shiftKey && this . _isValuableTextEntered ( ) && ! isMobile ( ) ;
360+ return ! e ?. shiftKey && this . _isMessageCanBeSent ( ) && ! isMobile ( ) ;
232361 }
233362
234363 _optionChanged ( args : OptionChanged < Properties > ) : void {
@@ -242,8 +371,7 @@ class ChatTextArea extends TextArea<Properties> {
242371 break ;
243372
244373 case 'text' : {
245- const shouldButtonBeDisabled = ! this . _isValuableTextEntered ( ) ;
246- this . _toggleButtonDisableState ( shouldButtonBeDisabled ) ;
374+ this . _toggleButtonDisableState ( ) ;
247375 break ;
248376 }
249377
@@ -252,22 +380,73 @@ class ChatTextArea extends TextArea<Properties> {
252380 break ;
253381
254382 case 'fileUploaderOptions' :
383+ this . _handleFileUploaderOptionsChange ( args ) ;
384+ break ;
255385 default :
256386 super . _optionChanged ( args ) ;
257387 }
258388 }
259389
390+ _handleFileUploaderOptionsChange ( args : OptionChanged < Properties > ) : void {
391+ const { fullName, value, previousValue } = args ;
392+
393+ if ( fullName === 'fileUploaderOptions' && ( ! value || ! previousValue ) ) {
394+ this . _cleanToolbar ( ) ;
395+ this . _renderToolbar ( ) ;
396+ this . _cleanFileUploader ( ) ;
397+ this . _initFileUploader ( ) ;
398+
399+ return ;
400+ }
401+
402+ const options = Widget . getOptionsFromContainer ( args ) ;
403+ this . _fileUploader ?. option ( options ) ;
404+ }
405+
260406 _isValuableTextEntered ( ) : boolean {
261407 const { text } = this . option ( ) ;
262408
263409 return Boolean ( text ?. trim ( ) ) ;
264410 }
265411
266- _dispose ( ) : void {
412+ _getFilesArray ( ) : FileToSend [ ] {
413+ return this . _filesToSend ? Array . from ( this . _filesToSend . values ( ) ) : [ ] ;
414+ }
415+
416+ _areFilesReadyToSend ( ) : boolean {
417+ if ( ! this . _filesToSend ?. size ) {
418+ return false ;
419+ }
420+
421+ return this . _getFilesArray ( ) . every ( ( file ) => file . readyToSend ) ;
422+ }
423+
424+ _isMessageCanBeSent ( ) : boolean {
425+ const hasText = this . _isValuableTextEntered ( ) ;
426+ const hasReadyFiles = this . _areFilesReadyToSend ( ) ;
427+ const hasUnreadyFiles = this . _filesToSend && this . _getFilesArray ( )
428+ . some ( ( file ) => ! file . readyToSend ) ;
429+
430+ return ! hasUnreadyFiles && ( hasText || hasReadyFiles ) ;
431+ }
432+
433+ _cleanFileUploader ( ) : void {
434+ this . _fileUploader ?. dispose ( ) ;
435+ this . _$fileUploader ?. remove ( ) ;
436+ this . _fileUploader = null ;
437+ this . _$fileUploader = null ;
438+ }
439+
440+ _cleanToolbar ( ) : void {
267441 this . _toolbar ?. dispose ( ) ;
268442 this . _$toolbar ?. remove ( ) ;
269443 this . _toolbar = null ;
270444 this . _$toolbar = null ;
445+ }
446+
447+ _dispose ( ) : void {
448+ this . _cleanFileUploader ( ) ;
449+ this . _cleanToolbar ( ) ;
271450 super . _dispose ( ) ;
272451 }
273452}
0 commit comments