ldx-widgets
Version:
widgets
405 lines (371 loc) • 13.7 kB
JavaScript
(function() {
var ConfirmSave, ESCAPE, KEY_S, Modal, PropTypes, React, Spinner, StyleSheet, assign, button, createClass, css, dialogueMixin, div, enterKeyFrames, header, ref, ref1, ref2, span, styles;
React = require('react');
createClass = require('create-react-class');
PropTypes = require('prop-types');
ref = require('aphrodite/no-important'), StyleSheet = ref.StyleSheet, css = ref.css;
assign = require('lodash/assign');
dialogueMixin = require('../mixins/dialogue_mixin');
ref1 = require('../constants/keyboard'), ESCAPE = ref1.ESCAPE, KEY_S = ref1.KEY_S;
ConfirmSave = React.createFactory(require('./confirm_save'));
Spinner = React.createFactory(require('./spinner'));
ref2 = require('react-dom-factories'), div = ref2.div, header = ref2.header, span = ref2.span, button = ref2.button;
/*&
@props.title - OPTIONAL - [String]
title for the modal header
@props.buttons - OPTIONAL - [Array]
Array of button objects with name, handler to be called on click, and disabled boolean, eg...
```
[
{
name: 'Save'
handler: @save
disabled: no
}
]
```
@props.children - REQUIRED - [Element]
React element (or array of elements) to inserted as the modal body
@props.styleOverride - OPTIONAL - [Object]
aphrodite style object, optionally can contain .modal, .header, .title, .actionButton class defs, eg...
```
{
modalBase: {}
modalEnter: {}
modal: {}
header: {}
title: {}
actionButton: {}
}
```
@props.close - OPTIONAL - [Function]
Function that closes the overlay, passed automatically by the overlay framework
@props.onClose - OPTIONAL - [Function]
Function that is called right before the modal closes
@props.showClose - OPTIONAL - [Boolean]
Defaults to yes, set it to no to not show the close button
@props.closeAfterSave - OPTIONAL - [Boolean]
Defaults to yes, set it to no to prevent the modal from calling @props.close after a save is complete
Note: in this case you MUST pass an onSaveComplete handler that sets the saveState to null after a save
@props.onSaveComplete - OPTIONAL - [Function]
Function that is called right after the saveState is set to complete and the success indicator finishes animating
@props.closeBtnText - OPTIONAL - [String]
Defaults to 'Cancel', text to display in the close button
@props.animateIn - OPTIONAL - [Boolean]
Defaults to yes, whether or not to bouce the modal on enter
@props.loading - OPTIONAL - [Boolean]
Defaults to no, whether or not to show the spinner instead of the children
@props.draggable - OPTIONAL - [Boolean]
Defaults to yes, whether or not the modal can be dragged from it's header
@props.stopPropagation - OPTIONAL - [Boolean]
Defaults to yes, whether or not the modal stop click from bubbling above it
@props.displayProgressBar - OPTIONAL - [Boolean]
Defaults to no, whether or not the confirm/save show the progress bar instead of the spinner
@props.uploadProgress -OPTIONAL
Progress of a file being uploaded
&
*/
Modal = createClass({
displayName: 'Modal',
mixins: [dialogueMixin],
propTypes: {
styleOverride: PropTypes.shape({
modal: PropTypes.object,
header: PropTypes.object,
title: PropTypes.object,
actionButton: PropTypes.object
}),
title: PropTypes.string,
buttons: PropTypes.array,
close: PropTypes.func.isRequired,
onClose: PropTypes.func,
onSaveComplete: PropTypes.func,
showClose: PropTypes.bool,
closeAfterSave: PropTypes.bool,
closeBtnText: PropTypes.string,
unSavedMessage: PropTypes.string,
unSavedDialogueHeight: PropTypes.number,
unSavedChanges: PropTypes.bool,
onSaveFail: PropTypes.func,
inLineStyle: PropTypes.object,
animateIn: PropTypes.bool,
loading: PropTypes.bool,
draggable: PropTypes.bool,
stopPropagation: PropTypes.bool,
spinnerProps: PropTypes.object,
displayProgressBar: PropTypes.bool,
uploadProgress: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
},
getDefaultProps: function() {
return {
styleOverride: {},
inLineStyle: {},
buttons: [],
showClose: true,
closeAfterSave: true,
unSavedDialogueHeight: 100,
saveState: null,
saveMessage: null,
unSavedChanges: false,
animateIn: true,
loading: false,
draggable: true,
stopPropagation: true,
spinnerProps: {
length: 7,
radius: 7,
lines: 12,
width: 2
},
displayProgressBar: false,
uploadProgress: ''
};
},
getInitialState: function() {
return {
dragX: 0,
dragY: 0
};
},
componentWillMount: function() {
this.closeBtnText = t('Cancel');
return document.addEventListener('keydown', this.handleKeyPress);
},
componentDidMount: function() {
setTimeout((function(_this) {
return function() {
return _this.calculateMaxDrags();
};
})(this), 0);
return window.addEventListener('resize', this.calculateMaxDrags);
},
componentWillUnmount: function() {
document.removeEventListener('keydown', this.handleKeyPress);
return window.removeEventListener('resize', this.calculateMaxDrags);
},
render: function() {
var animateIn, animateStyle, b, buttons, children, closeBtnText, displayProgressBar, dragX, dragY, draggable, headerChildren, i, inLineStyle, loading, onSaveFail, ref3, ref4, saveMessage, saveState, showClose, spinnerProps, stopPropagation, styleOverride, title, uploadProgress;
ref3 = this.props, styleOverride = ref3.styleOverride, title = ref3.title, buttons = ref3.buttons, closeBtnText = ref3.closeBtnText, showClose = ref3.showClose, children = ref3.children, saveState = ref3.saveState, saveMessage = ref3.saveMessage, onSaveFail = ref3.onSaveFail, inLineStyle = ref3.inLineStyle, animateIn = ref3.animateIn, loading = ref3.loading, spinnerProps = ref3.spinnerProps, draggable = ref3.draggable, stopPropagation = ref3.stopPropagation, displayProgressBar = ref3.displayProgressBar, uploadProgress = ref3.uploadProgress;
ref4 = this.state, dragX = ref4.dragX, dragY = ref4.dragY;
closeBtnText = closeBtnText || this.closeBtnText;
assign(styles, styleOverride);
inLineStyle = assign({}, inLineStyle, {
transform: "translate(" + dragX + "px, " + dragY + "px) translateZ(0px)",
WebkitTransform: "translate(" + dragX + "px, " + dragY + "px) translateZ(0px)",
msTransform: "translate(" + dragX + "px, " + dragY + "px)"
});
assign(spinnerProps, {
key: 'spinner'
});
headerChildren = [];
if (title != null) {
headerChildren.push(span({
key: 'title',
className: css(styles.title)
}, title));
}
if (showClose) {
headerChildren.push(button({
key: 'close',
className: css(styles.actionButton),
onClick: this.closeWithCheck
}, closeBtnText));
}
for (i = buttons.length - 1; i >= 0; i += -1) {
b = buttons[i];
headerChildren.push(button({
key: b.name,
className: css(styles.actionButton),
onClick: b.handler,
disabled: b.disabled
}, b.name));
}
animateStyle = animateIn ? styles.modalEnter : null;
return div({
className: css(styles.modalBase, animateStyle, styles.modal),
style: inLineStyle,
onClick: stopPropagation ? this.handleClick : null
}, [
header({
key: 'header',
className: css(styles.header),
onMouseDown: draggable ? this.handleMouseDown : null,
ref: (function(_this) {
return function(header1) {
_this.header = header1;
};
})(this)
}, headerChildren), this.dialogueBox(), saveState != null ? ConfirmSave({
key: 'confirm',
done: this.saveComplete,
fail: function() {
return typeof onSaveFail === "function" ? onSaveFail() : void 0;
},
saveMessage: saveMessage,
saveState: saveState,
displayProgressBar: displayProgressBar,
uploadProgress: uploadProgress
}) : void 0, loading ? Spinner(spinnerProps) : children
]);
},
handleClick: function(e) {
return e.stopPropagation();
},
closeWithCheck: function() {
var ref3, unSavedChanges, unSavedDialogueHeight, unSavedMessage;
ref3 = this.props, unSavedMessage = ref3.unSavedMessage, unSavedDialogueHeight = ref3.unSavedDialogueHeight, unSavedChanges = ref3.unSavedChanges;
if (unSavedChanges) {
return this.showDialogue({
message: unSavedMessage || t('There are unsaved changes. How do you want to proceed?'),
confirmText: t('Discard Changes'),
height: unSavedDialogueHeight,
confirmCallback: this.close
});
} else {
return this.close();
}
},
close: function() {
var close, onClose, ref3;
ref3 = this.props, close = ref3.close, onClose = ref3.onClose;
if (typeof onClose === "function") {
onClose();
}
return close();
},
saveComplete: function() {
var close, closeAfterSave, onSaveComplete, ref3;
ref3 = this.props, close = ref3.close, onSaveComplete = ref3.onSaveComplete, closeAfterSave = ref3.closeAfterSave;
if (typeof onSaveComplete === "function") {
onSaveComplete();
}
if (closeAfterSave) {
return close();
}
},
handleKeyPress: function(e) {
var buttons, keyCode, metaKey, ref3;
keyCode = e.keyCode, metaKey = e.metaKey;
if (keyCode === ESCAPE) {
this.closeWithCheck();
}
if (keyCode === KEY_S && metaKey) {
e.preventDefault();
buttons = this.props.buttons;
if (((ref3 = buttons[0]) != null ? ref3.name : void 0) === t('Save')) {
return buttons[0].handler();
}
}
},
handleMouseDown: function(e) {
var dragX, dragY, ref3;
e.preventDefault();
ref3 = this.state, dragX = ref3.dragX, dragY = ref3.dragY;
this.startDragX = dragX;
this.startDragY = dragY;
this.startX = e.clientX;
this.startY = e.clientY;
document.addEventListener('mousemove', this.handleMouseMove);
return document.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseMove: function(e) {
var dragX, dragY;
e.preventDefault();
dragX = this.startDragX + (e.clientX - this.startX);
dragY = this.startDragY + (e.clientY - this.startY);
dragY = dragY < this.minDragY ? this.minDragY : dragY;
dragY = dragY > this.maxDragY ? this.maxDragY : dragY;
dragX = dragX < this.minDragX ? this.minDragX : dragX;
dragX = dragX > this.maxDragX ? this.maxDragX : dragX;
return this.setState({
dragX: dragX,
dragY: dragY
});
},
handleMouseUp: function() {
document.removeEventListener('mousemove', this.handleMouseMove);
return document.removeEventListener('mouseup', this.handleMouseUp);
},
calculateMaxDrags: function() {
var innerHeight, innerWidth, offsetHeight, offsetLeft, offsetTop, offsetWidth, ref3, ref4;
ref3 = this.header.parentNode, offsetTop = ref3.offsetTop, offsetLeft = ref3.offsetLeft;
ref4 = this.header, offsetHeight = ref4.offsetHeight, offsetWidth = ref4.offsetWidth;
innerHeight = window.innerHeight, innerWidth = window.innerWidth;
this.maxDragX = innerWidth - (offsetLeft + offsetWidth);
this.maxDragY = innerHeight - (offsetTop + offsetHeight);
this.minDragX = 0 - offsetLeft;
return this.minDragY = 0 - offsetTop;
}
});
enterKeyFrames = {
'0%': {
transform: 'scale(0.9)',
opacity: '0'
},
'40%': {
opacity: '1',
transform: 'scale(1.05)'
},
'100%': {
transform: 'scale(1)'
}
};
styles = StyleSheet.create({
modalBase: {
position: 'absolute',
backgroundColor: 'white',
borderRadius: '8px',
overflow: 'hidden'
},
modalEnter: {
animationName: [enterKeyFrames],
animationDuration: '.15s',
animationIterationCount: '1',
animationTimingFunction: 'ease-out'
},
modal: {
top: '10%',
left: '50%',
width: '600px',
height: '500px',
marginLeft: '-300px'
},
header: {
position: 'relative',
width: '100%',
height: '44px',
lineHeight: '44px',
backgroundColor: 'rgb(246,246,246)',
borderBottom: '1px solid rgb(190,190,190)',
borderTopLeftRadius: '8px',
borderTopRightRadius: '8px',
margin: '0px',
cursor: 'pointer'
},
title: {
display: 'inline-block',
fontSize: '16px',
fontWeight: 'normal',
textAlign: 'left',
color: 'rgb(113,113,113)',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
marginLeft: '15px'
},
actionButton: {
float: 'right',
color: 'rgb(0,127,255)',
height: '28px',
textAlign: 'center',
lineHeight: '26px',
marginTop: '8px',
marginRight: '15px',
fontSize: '13px',
':disabled': {
color: 'rgb(204,204,204)'
}
}
});
module.exports = Modal;
}).call(this);