UNPKG

jsreport-studio

Version:
457 lines (381 loc) 11.9 kB
import Promise from 'bluebird' import PropTypes from 'prop-types' import React, { Component } from 'react' import ReactDOM from 'react-dom' import assign from 'lodash/assign' import Pane from './Pane' import Resizer from './Resizer' class SplitPane extends Component { constructor (props) { super(props) this.windows = {} this.splitPaneRef = React.createRef() this.resizerRef = React.createRef() this.pane1Ref = React.createRef() this.pane2Ref = React.createRef() this.state = { active: false, resized: false } this.collapse = this.collapse.bind(this) this.cancel = this.cancel.bind(this) this.onMouseDown = this.onMouseDown.bind(this) this.onMouseMove = this.onMouseMove.bind(this) this.onMouseUp = this.onMouseUp.bind(this) } static propTypes = { primary: PropTypes.oneOf(['first', 'second']), minSize: PropTypes.number, defaultSize: PropTypes.string, size: PropTypes.number, allowResize: PropTypes.bool, resizerClassName: PropTypes.string, split: PropTypes.oneOf(['vertical', 'horizontal']), onDragStarted: PropTypes.func, onDragFinished: PropTypes.func, onCollapsing: PropTypes.func, onDocking: PropTypes.func, onUndocking: PropTypes.func, onUndocked: PropTypes.func } static defaultProps = { split: 'vertical', minSize: 50, allowResize: true, rimary: 'first', collapsable: 'second', undockeable: false, defaultSize: '50%' } componentDidMount () { this.setSize(this.props, this.state, (newSize) => { if (typeof this.props.onChange === 'function') { this.props.onChange(newSize) } if (typeof this.props.onDragFinished === 'function') { this.props.onDragFinished() } }) document.addEventListener('mouseup', this.onMouseUp) this.windows = {} } setSize (props, state, cb) { const ref = this.props.primary === 'first' ? this.pane1Ref.current : this.pane2Ref.current let newSize if (ref) { newSize = props.size || (state && state.draggedSize) || props.defaultSize || props.minSize ref.setState({ size: newSize }, () => cb(newSize)) } } componentWillUnmount () { document.removeEventListener('mouseup', this.onMouseUp) this.windows = {} } onMouseDown (event) { if (this.props.allowResize && !this.props.size) { this.unFocus() let position = this.props.split === 'vertical' ? event.clientX : event.clientY if (typeof this.props.onDragStarted === 'function') { this.props.onDragStarted() } this.setState({ active: true, position: position }) document.addEventListener('mousemove', this.onMouseMove) } } onMouseMove (event, force) { if (this.props.allowResize && !this.props.size && !this.state.collapsed) { if (this.state.active || force) { this.unFocus() const ref = this.props.primary === 'first' ? this.pane1Ref.current : this.pane2Ref.current if (ref) { const node = ref.node if (node.getBoundingClientRect) { const width = node.getBoundingClientRect().width const height = node.getBoundingClientRect().height const current = this.props.split === 'vertical' ? event.clientX : event.clientY const size = this.props.split === 'vertical' ? width : height const position = this.state.position const newPosition = this.props.primary === 'first' ? (position - current) : (current - position) let newSize = size - newPosition if (newSize < this.props.minSize) { newSize = this.props.minSize } else { this.setState({ position: current, resized: true }) } if (this.props.onChange) { this.props.onChange(newSize) } this.setState({ draggedSize: newSize }) ref.setState({ size: newSize }) } } } } } onMouseUp () { document.removeEventListener('mousemove', this.onMouseMove) if (this.props.allowResize && !this.props.size) { if (this.state.active) { if (typeof this.props.onDragFinished === 'function') { this.props.onDragFinished() } this.setState({ active: false }) } } } unFocus () { if (document.selection) { document.selection.empty() } else { window.getSelection().removeAllRanges() } } merge (into, obj) { for (let attr in obj) { into[attr] = obj[attr] } } openWindow (opts) { let defaultWindowOpts = { directories: false, toolbar: false, titlebar: false, location: false, copyhistory: false, status: false, menubar: false, scrollbars: true, resizable: true } let windowOptsStr if (this.windows[opts.id] != null) { if (this.windows[opts.id].closed === true) { delete this.windows[opts.id] } else { return this.windows[opts.id] } } if (!opts.tab) { let windowOpts = assign({}, defaultWindowOpts, opts.windowOpts) let dualScreenLeft = window.screenLeft != null ? window.screenLeft : window.screen.left let dualScreenTop = window.screenTop != null ? window.screenTop : window.screen.top let width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : window.screen.width let height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : window.screen.height let windowWidth = width / 2 let windowHeight = height / 1.3 let left = ((width / 2) - (windowWidth / 2)) + dualScreenLeft let top = ((height / 2) - (windowHeight / 2)) + dualScreenTop windowOpts.top = top windowOpts.left = left windowOpts.width = windowWidth windowOpts.height = windowHeight windowOptsStr = ( Object.keys(windowOpts) .map((opt) => `${opt}=${typeof windowOpts[opt] === 'boolean' ? (windowOpts[opt] ? 'yes' : 'no') : windowOpts[opt]}`) .join(',') ) } let nWindow = window.open( opts.url || '', opts.name || '_blank', opts.tab ? undefined : windowOptsStr ) this.windows[opts.id] = nWindow if (nWindow.focus) { nWindow.focus() } return nWindow } collapse (v, undockeable, undocked) { let shouldCollapseAsync shouldCollapseAsync = (this.props.onCollapsing != null) ? this.props.onCollapsing(v) : true Promise.resolve(shouldCollapseAsync).then((shouldCollapse) => { let ref1 = this.props.collapsable === 'first' ? this.pane2Ref.current : this.pane1Ref.current const ref2 = this.props.collapsable === 'first' ? this.pane1Ref.current : this.pane2Ref.current let stateToUpdate if (!shouldCollapse) { return } if (!v) { if (ref1) { ref1.setState({ size: this.lastSize }) } if (ref2) { ref2.setState({ size: this.lastSize2 }) } stateToUpdate = { resized: true, collapsed: v, draggedSize: this.lastSize, position: this.lastSize, undocked: undocked } if (undockeable && undocked === false) { this.props.onDocking && this.props.onDocking() } this.setState(stateToUpdate, () => { this.props.onCollapseChange && this.props.onCollapseChange() }) } else { if (ref1) { this.lastSize = ref1.state.size } if (ref2) { this.lastSize2 = ref2.state.size } stateToUpdate = { collapsed: v, resized: true, draggedSize: undefined, position: undefined, undocked: undocked } if (undockeable && undocked === true) { let windowOpts = (this.props.onUndocking != null) ? this.props.onUndocking() : null if (!windowOpts) { return } if (ref1) { ref1.setState({ size: undefined }) } if (ref2) { ref2.setState({ size: 0 }) } this.setState(stateToUpdate, () => { // opening the window when setState is done.. // giving it the chance to clear the previous iframe const nWindow = this.openWindow(windowOpts) this.props.onUndocked && this.props.onUndocked(windowOpts.id, nWindow) this.props.onCollapseChange && this.props.onCollapseChange() }) } else { if (ref1) { ref1.setState({ size: undefined }) } if (ref2) { ref2.setState({ size: 0 }) } this.setState(stateToUpdate, () => { this.props.onCollapseChange && this.props.onCollapseChange() }) } } if (typeof this.props.onDragFinished === 'function') { this.props.onDragFinished() } }) } cancel () { if (this.props.onCancel) { this.props.onCancel() } } renderPane (type, undockeable, pane) { const { collapsable } = this.props const { undocked } = this.state if (collapsable === type) { if (!undockeable) { return pane } if (undocked) { return null } return pane } else { return pane } } render () { const { split, allowResize, resizerClassName, collapsedText, collapsable, undockeable, cancellable } = this.props const { collapsed, undocked } = this.state let disabledClass = allowResize ? '' : 'disabled' let style = { display: 'flex', flex: 1, outline: 'none', overflow: 'hidden', MozUserSelect: 'text', WebkitUserSelect: 'text', msUserSelect: 'text', userSelect: 'text' } if (split === 'vertical') { this.merge(style, { flexDirection: 'row' }) } else { this.merge(style, { flexDirection: 'column', width: '100%' }) } const children = this.props.children const classes = ['SplitPane', this.props.className, split, disabledClass] const undockSupported = (typeof undockeable === 'function' ? undockeable() : undockeable) const cancelSupported = (typeof cancellable === 'function' ? cancellable() : cancellable) return ( <div className={classes.join(' ')} style={style} ref={this.splitPaneRef}> {this.renderPane( 'first', undockSupported, <Pane ref={this.pane1Ref} key='pane1' className='Pane1' split={split}>{children[0]}</Pane> )} <Resizer ref={this.resizerRef} key='resizer' collapsable={collapsable} collapsedText={collapsedText} className={disabledClass + ' ' + resizerClassName} onMouseDown={this.onMouseDown} collapsed={collapsed} split={split} collapse={this.collapse} undockeable={undockSupported} cancellable={cancelSupported} cancel={this.cancel} undocked={undocked} /> {this.renderPane( 'second', undockSupported, <Pane ref={this.pane2Ref} key='pane2' className='Pane2' split={split}>{children[1]}</Pane> )} </div> ) } } export default SplitPane