app/addons/documents/doc-editor/components/DocEditorScreen.js (177 lines of code) (raw):

// Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. import PropTypes from 'prop-types'; import React from 'react'; import { Button } from 'react-bootstrap'; import FauxtonAPI from '../../../../core/api'; import FauxtonComponents from '../../../fauxton/components'; import GeneralComponents from '../../../components/react-components'; import AttachmentsPanelButton from './AttachmentsPanelButton'; import CloneDocModal from './CloneDocModal'; import PanelButton from './PanelButton'; import UploadModal from './UploadModal'; import PreferencesButton from './PreferencesButton'; export default class DocEditorScreen extends React.Component { static defaultProps = { database: {}, isNewDoc: false }; static propTypes = { isLoading: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired, isNewDoc: PropTypes.bool.isRequired, isDbPartitioned: PropTypes.bool.isRequired, doc: PropTypes.object, conflictCount: PropTypes.number.isRequired, previousUrl: PropTypes.string, saveDoc: PropTypes.func.isRequired, isCloneDocModalVisible: PropTypes.bool.isRequired, database: PropTypes.object, showCloneDocModal: PropTypes.func.isRequired, hideCloneDocModal: PropTypes.func.isRequired, cloneDoc: PropTypes.func.isRequired, isDeleteDocModalVisible: PropTypes.bool.isRequired, showDeleteDocModal: PropTypes.func.isRequired, hideDeleteDocModal: PropTypes.func.isRequired, deleteDoc: PropTypes.func.isRequired, isUploadModalVisible: PropTypes.bool.isRequired, uploadInProgress: PropTypes.bool.isRequired, uploadPercentage: PropTypes.number.isRequired, uploadErrorMessage: PropTypes.string, numFilesUploaded: PropTypes.number.isRequired, showUploadModal: PropTypes.func.isRequired, hideUploadModal: PropTypes.func.isRequired, cancelUpload: PropTypes.func.isRequired, resetUploadModal: PropTypes.func.isRequired, uploadAttachment: PropTypes.func.isRequired, wordWrapEnabled: PropTypes.bool.isRequired, toggleWordWrap: PropTypes.func.isRequired, }; getCodeEditor = () => { if (this.props.isLoading) { return (<GeneralComponents.LoadLines />); } const docContent = this.props.doc.attributes; if (this.props.isDbPartitioned) { if (!docContent._id.includes(':') && !docContent._id.startsWith('_design')) { docContent._id = ':' + docContent._id; } } const code = JSON.stringify(docContent, null, ' '); const editorCommands = [{ name: 'save', bindKey: { win: 'Ctrl-S', mac: 'Ctrl-S' }, exec: this.saveDoc }]; return ( <GeneralComponents.CodeEditor id="doc-editor" ref={node => this.docEditor = node} defaultCode={code} disabled={this.props.isSaving} mode="json" autoFocus={true} editorCommands={editorCommands} notifyUnsavedChanges={true} stringEditModalEnabled={true} wordWrapEnabled={this.props.wordWrapEnabled}/> ); }; UNSAFE_componentWillUpdate(nextProps) { // Update the editor whenever a file is uploaded, a doc is cloned, or a new doc is loaded if (this.props.numFilesUploaded !== nextProps.numFilesUploaded || this.props.doc && this.props.doc.hasChanged() || (this.props.doc && nextProps.doc && this.props.doc.id !== nextProps.doc.id)) { const editor = this.getEditor(); if (editor && nextProps.doc) { this.getEditor().setValue(JSON.stringify(nextProps.doc.attributes, null, ' ')); } this.onSaveComplete(); } } saveDoc = () => { this.props.saveDoc(this.props.doc, this.checkDocIsValid(), this.onSaveComplete, this.props.previousUrl); }; onSaveComplete = () => { if (this.getEditor()) { this.getEditor().clearChanges(); } }; hideDeleteDocModal = () => { this.props.hideDeleteDocModal(); }; deleteDoc = () => { this.props.hideDeleteDocModal(); this.props.deleteDoc(this.props.doc); }; getEditor = () => { return (this.docEditor) ? this.docEditor.getEditor() : null; }; checkDocIsValid = () => { if (this.getEditor().hasErrors()) { return false; } const json = JSON.parse(this.getEditor().getValue()); this.props.doc.clear().set(json, { validate: true }); return !this.props.doc.validationError; }; clearChanges = () => { if (this.docEditor) { this.docEditor.clearChanges(); } }; getExtensionIcons = () => { var extensions = FauxtonAPI.getExtensions('DocEditor:icons'); return _.map(extensions, (Extension, i) => { return (<Extension doc={this.props.doc} key={i} database={this.props.database} />); }); }; getButtonRow = () => { if (this.props.isNewDoc || this.props.doc === null) { return false; } return ( <div> <PreferencesButton wordWrapEnabled={this.props.wordWrapEnabled} toggleWordWrap={this.props.toggleWordWrap} /> <AttachmentsPanelButton doc={this.props.doc} isLoading={this.props.isLoading} disabled={this.props.isSaving} /> <div className="doc-editor-extension-icons">{this.getExtensionIcons()}</div> {this.props.conflictCount ? <PanelButton title={`Conflicts (${this.props.conflictCount})`} iconClass="fonticon-columns" className="conflicts" disabled={this.props.isSaving} onClick={() => { FauxtonAPI.navigate(FauxtonAPI.urls('revision-browser', 'app', this.props.database.safeID(), this.props.doc.id));}}/> : null} <PanelButton className="upload" title="Upload Attachment" iconClass="fonticon-up-circled" disabled={this.props.isSaving} onClick={this.props.showUploadModal} /> <PanelButton title="Clone Document" iconClass="fonticon-cw" disabled={this.props.isSaving} onClick={this.props.showCloneDocModal} /> <PanelButton title="Delete" iconClass="fonticon-trash" disabled={this.props.isSaving} onClick={this.props.showDeleteDocModal} /> </div> ); }; render() { const saveButtonLabel = this.props.isSaving ? 'Saving...' : (this.props.isNewDoc ? 'Create Document' : 'Save Changes'); const endpoint = this.props.previousUrl ? this.props.previousUrl : FauxtonAPI.urls('allDocs', 'app', FauxtonAPI.url.encode(this.props.database.id)); return ( <div> <div id="doc-editor-actions-panel"> <div className="doc-actions-left"> <Button id="save-doc-btn" disabled={this.props.isSaving} variant="cf-primary" onClick={this.saveDoc}> <i className="fonticon-ok-circled"></i> {saveButtonLabel} </Button> <div> <Button href={this.props.isSaving ? undefined : `#/${endpoint}`} variant="cf-cancel" className="js-back cancel-button" disabled={this.props.isSaving}> Cancel </Button> </div> </div> <div className="alignRight"> {this.getButtonRow()} </div> </div> <div className="code-region"> <div className="bgEditorGutter"></div> <div id="editor-container" className="doc-code">{this.getCodeEditor()}</div> </div> <UploadModal ref={node => this.uploadModal = node} visible={this.props.isUploadModalVisible} doc={this.props.doc} inProgress={this.props.uploadInProgress} uploadPercentage={this.props.uploadPercentage} errorMessage={this.props.uploadErrorMessage} cancelUpload={this.props.cancelUpload} hideUploadModal={this.props.hideUploadModal} resetUploadModal={this.props.resetUploadModal} uploadAttachment={this.props.uploadAttachment}/> <CloneDocModal doc={this.props.doc} database={this.props.database} visible={this.props.isCloneDocModalVisible} onSubmit={this.clearChanges} hideCloneDocModal={this.props.hideCloneDocModal} cloneDoc={this.props.cloneDoc}/> <span id='hey'>bb</span> <FauxtonComponents.ConfirmationModal title="Confirm Deletion" visible={this.props.isDeleteDocModalVisible} text="Are you sure you want to delete this document?" onClose={this.hideDeleteDocModal} onSubmit={this.deleteDoc} successButtonLabel="Delete Document" /> </div> ); } }