import * as React from 'react' ; import { superdeskApi } from '../apis' ; import { Button , ButtonGroup } from 'superdesk-ui-framework/react' ; import { PanelContent , PanelContentBlock , PanelContentBlockInner } from '../ui' ; import { IModalSize , Modal , ModalHeader , ModalFooter } from '../ui/modal' ; import { GridList } from '../ui/grid/GridList' ; import { PageLayout } from './PageLayout' ; export interface IUploadItem { id : string ; binary : File ; uploadProgress : number ; error : boolean ; completed : boolean ; } export interface IUploadFileListItemProps < T = any > { item : IUploadItem ; asset : T ; selected : boolean ; selectFile ( ) : void ; removeFile ? ( ) : void ; } export interface IContentPanelProps { item : IUploadItem ; submitting ? : boolean ; } interface IProps < T > { theme ? : 'dark-ui' ; modalSize ? : IModalSize ; multiple ? : boolean ; accept ? : Array < string > ; initialFiles ? : Array < { id : string ; file : File ; } > ; closeModal ( ) : void ; title : string ; onFileAdded ( id , file ) : void ; onFileRemoved ( id ) : void ; uploadFile ( item , onProgress ) : Promise < T > ; assets : Dictionary < string , T > ; ListItemComponent : React . ComponentType < IUploadFileListItemProps < T > > ; RightPanelComponent : React . ComponentType < IContentPanelProps > ; } interface IState { selectedIndex : number ; submitting : boolean ; items : Array < IUploadItem > ; } export class FileUploadModal < T > extends React . Component < IProps < T > , IState > { fileInputNode : React . RefObject < HTMLInputElement > ; constructor ( props ) { super ( props ) ; this . state = { selectedIndex : 0 , submitting : false , items : this . getInitialItems ( ) , } ; this . showFileUploadDialog = this . showFileUploadDialog . bind ( this ) ; this . addFiles = this . addFiles . bind ( this ) ; this . removeFile = this . removeFile . bind ( this ) ; this . selectFile = this . selectFile . bind ( this ) ; this . onSubmit = this . onSubmit . bind ( this ) ; this . fileInputNode = React . createRef < HTMLInputElement > ( ) ; } componentDidMount ( ) { if ( this . state . items . length === 0 ) { this . showFileUploadDialog ( ) ; } } showFileUploadDialog ( ) { if ( this . fileInputNode . current != null ) { this . fileInputNode . current . click ( ) ; } } getInitialItems ( ) { const items = [ ] ; if ( this . props . initialFiles != null && this . props . initialFiles ?. length > 0 ) { this . props . initialFiles . forEach ( ( item ) => { items . push ( { id : item . id , binary : item . file , uploadProgress : 0 , error : false , completed : false , } ) ; } ) ; } return items ; } addFiles ( event ) { const newItems = [ ] ; Array . from ( event . target . files ?? [ ] ) . forEach ( ( file ) => { const id = Math . random ( ) . toString ( 36 ) . substr ( 1 ) ; this . props . onFileAdded ( id , file ) ; newItems . push ( { id : id , binary : file , uploadProgress : 0 , error : false , completed : false , } ) ; } ) ; this . setState ( ( state ) => ( { items : [ ... state . items , ... newItems , ] , } ) ) ; event . target . value = '' ; } getItemIndexById ( id ) { return this . state . items . findIndex ( ( item ) => item . id === id ) ; } removeFile ( index ) { this . props . onFileRemoved ( this . state . items [ index ] . id ) ; this . setState ( ( state ) => { const updates = { ... state } ; updates . items . splice ( index , 1 ) ; if ( updates . items . length <= 1 ) { updates . selectedIndex = 0 ; } else if ( index >= updates . items . length ) { updates . selectedIndex = index - 1 ; } return updates ; } ) ; } selectFile ( index ) { this . setState ( { selectedIndex : index } ) ; } updateAssetState ( index , updates ) { this . setState ( ( state ) => { const items = [ ... state . items ] ; items [ index ] = { ... items [ index ] , ... updates , } ; return { items : items } ; } ) ; } onSubmit ( ) { this . setState ( { submitting : true } ) ; let requestsCompleted = 0 ; let failed = false ; this . state . items . forEach ( ( item , index ) => { if ( item . completed ) { if ( item . error === false ) { requestsCompleted += 1 ; return ; } else { this . updateAssetState ( index , { error : false , completed : false , uploadProgress : 0 , } ) ; } } const onSuccess = ( ) => { this . updateAssetState ( index , { error : false , completed : true , uploadProgress : 100 , } ) ; } ; const onFail = ( ) => { failed = true ; this . updateAssetState ( index , { error : true , completed : true , uploadProgress : 100 , } ) ; } ; const onCompleted = ( ) => { const { notify } = superdeskApi . ui ; const { gettext , gettextPlural } = superdeskApi . localization ; requestsCompleted += 1 ; if ( requestsCompleted === this . state . items . length ) { if ( failed === false ) { setTimeout ( this . props . closeModal , 500 ) ; notify . success ( gettextPlural ( requestsCompleted , 'File uploaded successfully' , '{{count}} files uploaded successfully' , { count : requestsCompleted } ) ) ; } else { this . setState ( { submitting : false } ) ; notify . error ( gettext ( 'Failed to upload files' ) ) ; } } } ; const onProgress = ( progressEvent ) => { let uploadProgress = Math . min ( Math . round ( progressEvent . loaded / progressEvent . total * 100.0 ) , 90 ) ; this . updateAssetState ( index , { uploadProgress : uploadProgress } ) ; } ; this . props . uploadFile ( item , onProgress ) . then ( onSuccess , onFail ) . finally ( onCompleted ) ; } ) ; } render ( ) { const { gettext } = superdeskApi . localization ; const { ListItemComponent , RightPanelComponent } = this . props ; const currentItem = this . state . items [ this . state . selectedIndex ] ; return ( < Modal id = "UploadFileModal" size = { this . props . modalSize } closeModal = { this . props . closeModal } closeOnEsc = { true } theme = { this . props . theme } > < ModalHeader text = { this . props . title } flex = { true } > < ButtonGroup align = "right" > < Button text = { gettext ( 'Close' ) } onClick = { this . props . closeModal } disabled = { this . state . submitting } / > < Button text = { gettext ( 'Add File' ) } onClick = { this . showFileUploadDialog } icon = "plus-sign" type = "sd-green" disabled = { this . state . submitting } / > < Button text = { gettext ( 'Upload' ) } onClick = { this . onSubmit } type = "primary" disabled = { this . state . submitting || this . state . items . length === 0 } / > < /ButtonGroup> < /ModalHeader> < PageLayout mainClassName = "sd-padding--2" main = { ( < GridList > { this . state . items . map ( ( item , index ) => ( < ListItemComponent key = { item . id } item = { item } asset = { this . props . assets [ item . id ] } selected = { this . state . selectedIndex === index } selectFile = { ( ) => this . selectFile ( index ) } removeFile = { this . state . submitting === true ? undefined : ( ) => this . removeFile ( index ) } / > ) ) } < /GridList>)} rightPanelOpen={currentItem != null} rightPanel={currentItem == null ? (<div / > ) : ( < PanelContent > < PanelContentBlock flex = { true } > < PanelContentBlockInner grow = { true } > < RightPanelComponent key = { currentItem . id } item = { currentItem } submitting = { this . state . submitting } / > < /PanelContentBlockInner> < /PanelContentBlock> < /PanelContent>)}/ > < ModalFooter > < input type = "file" ref = { this . fileInputNode } onChange = { this . addFiles } multiple = { this . props . multiple } accept = { ( this . props . accept ?? [ ] ) . join ( ',' ) } style = { { visibility : 'hidden' } } /> < /ModalFooter> < /Modal>); } }