blob: 116a7b7194c3e8ea911adb542f46a1e6795160cc [file] [log] [blame]
define([
'../../../core/api',
'../../../app',
'react',
'react-dom',
'./actions',
'./stores',
'../../fauxton/components.react',
'../../components/react-components.react',
'react-bootstrap',
'../../../helpers',
], function (FauxtonAPI, app, React, ReactDOM, Actions, Stores, FauxtonComponents, GeneralComponents, ReactBootstrap, Helpers) {
var store = Stores.docEditorStore;
var Modal = ReactBootstrap.Modal;
var DocEditorController = React.createClass({
getInitialState: function () {
return this.getStoreState();
},
getStoreState: function () {
return {
isLoading: store.isLoading(),
doc: store.getDoc(),
cloneDocModalVisible: store.isCloneDocModalVisible(),
uploadModalVisible: store.isUploadModalVisible(),
deleteDocModalVisible: store.isDeleteDocModalVisible(),
numFilesUploaded: store.getNumFilesUploaded()
};
},
getDefaultProps: function () {
return {
database: {},
previousPage: '',
isNewDoc: false
};
},
getCodeEditor: function () {
if (this.state.isLoading) {
return (<GeneralComponents.LoadLines />);
}
var code = JSON.stringify(this.state.doc.attributes, null, ' ');
var editorCommands = [{
name: 'save',
bindKey: { win: 'Ctrl-S', mac: 'Ctrl-S' },
exec: this.saveDoc
}];
return (
<GeneralComponents.CodeEditor
id="doc-editor"
ref="docEditor"
defaultCode={code}
mode="json"
autoFocus={true}
editorCommands={editorCommands}
notifyUnsavedChanges={true}
stringEditModalEnabled={true} />
);
},
componentDidMount: function () {
store.on('change', this.onChange, this);
},
componentWillUnmount: function () {
store.off('change', this.onChange);
},
// whenever a file is uploaded, reset the editor
componentWillUpdate: function (nextProps, nextState) {
if (this.state.numFilesUploaded !== nextState.numFilesUploaded) {
this.getEditor().setValue(JSON.stringify(nextState.doc.attributes, null, ' '));
}
},
onChange: function () {
if (this.isMounted()) {
this.setState(this.getStoreState());
}
},
saveDoc: function () {
Actions.saveDoc(this.state.doc, this.checkDocIsValid(), this.onSaveComplete);
},
onSaveComplete: function (json) {
this.getEditor().clearChanges();
},
hideDeleteDocModal: function () {
Actions.hideDeleteDocModal();
},
deleteDoc: function () {
Actions.hideDeleteDocModal();
Actions.deleteDoc(this.state.doc);
},
getEditor: function () {
return (this.refs.docEditor) ? this.refs.docEditor.getEditor() : null;
},
checkDocIsValid: function () {
if (this.getEditor().hasErrors()) {
return false;
}
var json = JSON.parse(this.getEditor().getValue());
this.state.doc.clear().set(json, { validate: true });
return !this.state.doc.validationError;
},
clearChanges: function () {
this.refs.docEditor.clearChanges();
},
getExtensionIcons: function () {
var extensions = FauxtonAPI.getExtensions('DocEditor:icons');
return _.map(extensions, function (Extension, i) {
return (<Extension doc={this.state.doc} key={i} database={this.props.database} />);
}, this);
},
getButtonRow: function () {
if (this.props.isNewDoc) {
return false;
}
return (
<div>
<AttachmentsPanelButton doc={this.state.doc} isLoading={this.state.isLoading} />
<div className="doc-editor-extension-icons">{this.getExtensionIcons()}</div>
<PanelButton title="Upload Attachment" iconClass="icon-circle-arrow-up" onClick={Actions.showUploadModal} />
<PanelButton title="Clone Document" iconClass="icon-repeat" onClick={Actions.showCloneDocModal} />
<PanelButton title="Delete" iconClass="icon-trash" onClick={Actions.showDeleteDocModal} />
</div>
);
},
render: function () {
var saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
return (
<div>
<div id="doc-editor-actions-panel">
<div className="doc-actions-left">
<button className="save-doc btn btn-success save" type="button" onClick={this.saveDoc}>
<i className="icon fonticon-ok-circled"></i> {saveButtonLabel}
</button>
<div>
<a href={this.props.previousPage} className="js-back cancel-button">Cancel</a>
</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="uploadModal"
visible={this.state.uploadModalVisible}
doc={this.state.doc} />
<CloneDocModal
visible={this.state.cloneDocModalVisible}
onSubmit={this.clearChanges} />
<FauxtonComponents.ConfirmationModal
title="Confirm Deletion"
visible={this.state.deleteDocModalVisible}
text="Are you sure you want to delete this document?"
onClose={this.hideDeleteDocModal}
onSubmit={this.deleteDoc}
successButtonLabel="Delete Document" />
</div>
);
}
});
var AttachmentsPanelButton = React.createClass({
propTypes: {
isLoading: React.PropTypes.bool.isRequired,
doc: React.PropTypes.object
},
getDefaultProps: function () {
return {
isLoading: true,
doc: {}
};
},
getAttachmentList: function () {
var db = this.props.doc.database.get('id');
var doc = this.props.doc.get('_id');
return _.map(this.props.doc.get('_attachments'), function (item, filename) {
var url = FauxtonAPI.urls('document', 'attachment', db, doc, app.utils.safeURLName(filename));
return (
<li key={filename}>
<a href={url} target="_blank" data-bypass="true"> <strong>{filename}</strong>
<span className="attachment-delimiter">-</span>
<span>{item.content_type}, {Helpers.formatSize(item.length)}</span>
</a>
</li>
);
});
},
render: function () {
if (this.props.isLoading || !this.props.doc.get('_attachments')) {
return false;
}
return (
<div className="panel-section view-attachments-section btn-group">
<button className="panel-button dropdown-toggle btn" data-bypass="true" data-toggle="dropdown" title="View Attachments"
id="view-attachments-menu">
<i className="icon fonticon-picture"></i>
<span>View Attachments</span>{' '}
<span className="caret"></span>
</button>
<ul className="dropdown-menu" role="menu" aria-labelledby="view-attachments-menu">
{this.getAttachmentList()}
</ul>
</div>
);
}
});
var PanelButton = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired
},
getDefaultProps: function () {
return {
title: '',
iconClass: '',
onClick: function () { }
};
},
render: function () {
var iconClasses = 'icon ' + this.props.iconClass;
return (
<div className="panel-section">
<button className="panel-button upload" title={this.props.title} onClick={this.props.onClick}>
<i className={iconClasses}></i>
<span>{this.props.title}</span>
</button>
</div>
);
}
});
var UploadModal = React.createClass({
propTypes: {
visible: React.PropTypes.bool.isRequired,
doc: React.PropTypes.object
},
getInitialState: function () {
return this.getStoreState();
},
getStoreState: function () {
return {
inProgress: store.isUploadInProgress(),
loadPercentage: store.getUploadLoadPercentage(),
errorMessage: store.getFileUploadErrorMsg()
};
},
closeModal: function (e) {
if (e) {
e.preventDefault();
}
if (this.state.inProgress) {
Actions.cancelUpload();
}
Actions.hideUploadModal();
Actions.resetUploadModal();
},
upload: function () {
Actions.uploadAttachment({
doc: this.props.doc,
rev: this.props.doc.get('_rev'),
files: $(ReactDOM.findDOMNode(this.refs.attachments))[0].files
});
},
render: function () {
var errorClasses = 'alert alert-error';
if (this.state.errorMessage === '') {
errorClasses += ' hide';
}
var loadIndicatorClasses = 'progress progress-info';
if (!this.state.inProgress) {
loadIndicatorClasses += ' hide';
}
return (
<Modal dialogClassName="upload-file-modal" show={this.props.visible} onHide={this.closeModal}>
<Modal.Header closeButton={true}>
<Modal.Title>Upload Attachment</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className={errorClasses}>{this.state.errorMessage}</div>
<div>
<form ref="uploadForm" className="form" method="post">
<p>
Please select the file you want to upload as an attachment to this document. This creates a new
revision of the document, so it's not necessary to save after uploading.
</p>
<input ref="attachments" type="file" name="_attachments" />
<br />
</form>
<div className={loadIndicatorClasses}>
<div className="bar" style={{ width: this.state.loadPercentage + '%'}}></div>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button href="#" id="upload-btn" data-bypass="true" className="btn btn-success save" onClick={this.upload}>
Upload Attachment
</button>
<a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
</Modal.Footer>
</Modal>
);
}
});
var CloneDocModal = React.createClass({
propTypes: {
visible: React.PropTypes.bool.isRequired
},
getInitialState: function () {
return {
uuid: null
};
},
cloneDoc: function () {
if (this.props.onSubmit) {
this.props.onSubmit();
}
Actions.cloneDoc(this.state.uuid);
},
componentDidUpdate: function () {
if (this.state.uuid === null) {
var uuid = new FauxtonAPI.UUID();
uuid.fetch().then(function () {
this.setState({ uuid: uuid.next() });
}.bind(this));
}
},
closeModal: function (e) {
if (e) {
e.preventDefault();
}
Actions.hideCloneDocModal();
},
docIDChange: function (e) {
this.setState({ uuid: e.target.value });
},
render: function () {
if (this.state.uuid === null) {
return false;
}
return (
<Modal dialogClassName="clone-doc-modal" show={this.props.visible} onHide={this.closeModal}>
<Modal.Header closeButton={true}>
<Modal.Title>Clone Document</Modal.Title>
</Modal.Header>
<Modal.Body>
<form className="form" method="post">
<p>
Set new document's ID:
</p>
<input ref="newDocId" type="text" autoFocus={true} className="input-block-level"
onChange={this.docIDChange} value={this.state.uuid} />
</form>
</Modal.Body>
<Modal.Footer>
<button className="btn btn-success save" onClick={this.cloneDoc}>
<i className="fonticon-ok-circled"></i> Clone Document
</button>
<a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
</Modal.Footer>
</Modal>
);
}
});
return {
DocEditorController: DocEditorController,
AttachmentsPanelButton: AttachmentsPanelButton,
UploadModal: UploadModal,
CloneDocModal: CloneDocModal
};
});