UNPKG

@darwino/darwino-react-bootstrap

Version:

A set of Javascript classes and utilities

225 lines (206 loc) 7.69 kB
/* * (c) Copyright Darwino Inc. 2014-2017. */ import React, {Component} from "react"; import PropTypes from 'prop-types'; import { Button, FormControl, FormGroup, ControlLabel } from 'react-bootstrap'; import Dialog from "./Dialog" import { Richtext } from '@darwino/darwino'; const { renderAttachmentUrl, cleanAttachmentName } = Richtext; import DocumentForm from './DocumentForm'; const OP_NOOP = 0 const OP_CREATED = 1 const OP_UPDATED = 2 const OP_DELETED = 3 class FieldAttachments extends Component { // Context to read from the parent - router static contextTypes = { documentForm: PropTypes.object }; static defaultProps = { colName: true, colLength: true, colMime: true, colDelete: true }; constructor(props,context) { super(props,context); if(!(context.documentForm instanceof DocumentForm)) { throw new Error('AttachmentTable must be inside a component within a DocumentForm'); } this.dragEnter = this.dragEnter.bind(this); this.dragLeave = this.dragLeave.bind(this); this.dragOver = this.dragOver.bind(this); this.drop = this.drop.bind(this); this.state = {dropAction: false}; } onUpload(att) { return !this.props.onUpload || this.props.onUpload(att) } onDelete(att) { return !this.props.onDelete || this.props.onDelete(att) } _indexOf(name) { const {value} = this.props.input; for(let i=0; i<value.length; i++) { if(value[i].name==name) return i; } return -1; } deleteFile(att) { if(!this.onDelete(att)) { return; } const {onChange,value} = this.props.input; const newAtt = [...value] const idx = this._indexOf(att.name) if(att.op==OP_CREATED) { newAtt.splice(idx, 1); } else { newAtt[idx] = {...att, op:OP_DELETED} } onChange(newAtt) } fileSelected(_file) { const {richTextfield} = this.props; let file = _file || this.refs.upload.files[0] this.refs.upload.value = "" // Reset the value var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { let att = [...this.props.input.value] // We get the new value here... let newAttachment = { op: OP_CREATED, name: (richTextfield ? (richTextfield+"^^") : "")+file.name, // Create file attachment length: file.size, mimeType: file.type, } if(!this.onUpload(newAttachment)) { return; } // We should wait until the document is saved, performance wise let content = reader.result newAttachment.content = content.substring(content.indexOf(',')+1) const idx = this._indexOf(newAttachment.name) if(idx>=0) { att[idx] = newAttachment newAttachment.op = OP_UPDATED } else { att.push(newAttachment) } this.props.input.onChange(att) } reader.onerror = (error) => { alert("Error reading attachment "+file.name) }; } renderUpload() { const {disabled,buttonLabel} = this.props return ( <div> {<input ref="upload" type="file" id="input" onChange={(e) => this.fileSelected()} style={{display:"none"}} />} <Button disabled={disabled} onClick={() =>this.refs.upload.click()}>{buttonLabel||"Choose File..."}</Button> </div> ) } renderTable() { const {richTextfield,onChange,readOnly} = this.props; const {colName, colLength, colMime, colDelete} = this.props const attachments = this.props.input.value return ( <table className="table table-condensed table-striped table-bordered table-attachments"> <thead> <tr> {colName && <th>Name</th>} {colLength && <th>Size</th>} {colMime && <th>Type</th>} {colDelete && !readOnly &&<th></th>} </tr> </thead> <tbody>{attachments && this.renderAttachmentRows(this.filterAttachments(attachments, richTextfield), onChange)}</tbody> </table> ) } filterAttachments(attachments,richTextfield) { const inline = this.props.inline return attachments.filter((att,idx) => { if(att.op==OP_DELETED) { return false; } if(richTextfield) { const attName = att.name.toLowerCase() if( attName.indexOf(richTextfield+"^^") == 0 // File attachement || (inline && attName.indexOf(richTextfield+"||") == 0)) { // Inline attachment return true; } return false; } return true; }) } renderAttachmentRows(attachments) { const { disabled, readOnly } = this.props const { colName, colLength, colMime, colDelete } = this.props const { databaseId, storeId, unid } = this.context.documentForm.state; return attachments.map((att) => { return (<tr key={att.name}> {colName && <td> {att.op!=OP_CREATED ? <a href={renderAttachmentUrl(databaseId, storeId, unid, att.name)} target="_blank"> {cleanAttachmentName(att.name)} </a> : <a style={{pointerEvents: 'none'}} disabled="disabled"> {cleanAttachmentName(att.name)} </a> } </td>} {colLength && <td>{att.length}</td>} {colMime && <td>{att.mimeType}</td>} {colDelete && !readOnly && <td> <Button disabled={disabled} bsStyle="link" onClick={()=>{this.deleteFile(att)}}><span className="glyphicon glyphicon-trash" aria-hidden="true"></span></Button> </td> } </tr> ) }) } dragEnter(e) { e.preventDefault(); this.setState({dropAction: true}); } dragLeave(e) { e.preventDefault(); this.setState({dropAction: false}); } dragOver(e) { e.preventDefault(); e.stopPropagation() try { e.dataTransfer.dropEffect = 'copy' } catch (err) {} return false } drop(e) { const { disabled, readOnly } = this.props if(!disabled && !readOnly) { e.stopPropagation();e.preventDefault(); var dt = e.dataTransfer; var files = dt.files; for(let i=0; i<files.length; i++) { this.fileSelected(files[i]); } } this.setState({dropAction: false}); } render() { const {multiple, readOnly } = this.props return ( <div onDragEnter={this.dragEnter} onDragLeave={this.dragLeave} onDragOver={this.dragOver} onDrop={this.drop}> {!readOnly && this.renderUpload()} {this.renderTable()} </div> ) } } export default FieldAttachments