mirror of
https://github.com/atom/atom.git
synced 2026-02-17 18:11:29 -05:00
Convert Dock to an etch component
This commit is contained in:
227
src/dock.js
227
src/dock.js
@@ -1,5 +1,7 @@
|
||||
'use strict'
|
||||
/** @babel */
|
||||
/** @jsx etch.dom */
|
||||
|
||||
const etch = require('etch')
|
||||
const _ = require('underscore-plus')
|
||||
const {CompositeDisposable, Emitter} = require('event-kit')
|
||||
const PaneContainer = require('./pane-container')
|
||||
@@ -26,6 +28,8 @@ module.exports = class Dock {
|
||||
this.handleMouseUp = this.handleMouseUp.bind(this)
|
||||
this.handleDrag = _.throttle(this.handleDrag.bind(this), 30)
|
||||
this.handleDragEnd = this.handleDragEnd.bind(this)
|
||||
this.handleToggleButtonDragEnter = this.handleToggleButtonDragEnter.bind(this)
|
||||
this.toggle = this.toggle.bind(this)
|
||||
|
||||
this.location = params.location
|
||||
this.widthOrHeight = getWidthOrHeight(this.location)
|
||||
@@ -67,16 +71,17 @@ module.exports = class Dock {
|
||||
this.paneContainer.onDidChangeActivePaneItem((item) => params.didChangeActivePaneItem(this, item)),
|
||||
this.paneContainer.onDidDestroyPaneItem((item) => params.didDestroyPaneItem(item))
|
||||
)
|
||||
|
||||
etch.initialize(this)
|
||||
}
|
||||
|
||||
// This method is called explicitly by the object which adds the Dock to the document.
|
||||
elementAttached () {
|
||||
// Re-render when the dock is attached to make sure we remeasure sizes defined in CSS.
|
||||
this.render(this.state)
|
||||
etch.update(this)
|
||||
}
|
||||
|
||||
getElement () {
|
||||
if (!this.element) this.render(this.state)
|
||||
return this.element
|
||||
}
|
||||
|
||||
@@ -151,9 +156,14 @@ module.exports = class Dock {
|
||||
}
|
||||
|
||||
this.state = nextState
|
||||
this.render(this.state)
|
||||
|
||||
const {hovered, visible} = this.state
|
||||
|
||||
// Render immediately if the dock becomes visible or the size changes in case people are
|
||||
// measuring after opening, for example.
|
||||
if ((visible && !prevState.visible) || (this.state.size !== prevState.size)) etch.updateSync(this)
|
||||
else etch.update(this)
|
||||
|
||||
if (hovered !== prevState.hovered) {
|
||||
this.emitter.emit('did-change-hovered', hovered)
|
||||
}
|
||||
@@ -162,80 +172,74 @@ module.exports = class Dock {
|
||||
}
|
||||
}
|
||||
|
||||
render (state) {
|
||||
if (this.element == null) {
|
||||
this.element = document.createElement('atom-dock')
|
||||
this.element.classList.add(this.location)
|
||||
this.innerElement = document.createElement('div')
|
||||
this.innerElement.classList.add('atom-dock-inner', this.location)
|
||||
this.maskElement = document.createElement('div')
|
||||
this.maskElement.classList.add('atom-dock-mask')
|
||||
this.wrapperElement = document.createElement('div')
|
||||
this.wrapperElement.classList.add('atom-dock-content-wrapper', this.location)
|
||||
this.resizeHandle = new DockResizeHandle({
|
||||
location: this.location,
|
||||
onResizeStart: this.handleResizeHandleDragStart,
|
||||
onResizeToFit: this.handleResizeToFit
|
||||
})
|
||||
this.toggleButton = new DockToggleButton({
|
||||
onDragEnter: this.handleToggleButtonDragEnter.bind(this),
|
||||
location: this.location,
|
||||
toggle: this.toggle.bind(this)
|
||||
})
|
||||
this.cursorOverlayElement = document.createElement('div')
|
||||
this.cursorOverlayElement.classList.add('atom-dock-cursor-overlay', this.location)
|
||||
render () {
|
||||
const innerElementClassList = ['atom-dock-inner', this.location]
|
||||
if (this.state.visible) innerElementClassList.push(VISIBLE_CLASS)
|
||||
|
||||
// Add the children to the DOM tree
|
||||
this.element.appendChild(this.innerElement)
|
||||
this.innerElement.appendChild(this.maskElement)
|
||||
this.maskElement.appendChild(this.wrapperElement)
|
||||
this.wrapperElement.appendChild(this.resizeHandle.getElement())
|
||||
this.wrapperElement.appendChild(this.paneContainer.getElement())
|
||||
this.wrapperElement.appendChild(this.cursorOverlayElement)
|
||||
// The toggle button must be rendered outside the mask because (1) it shouldn't be masked and
|
||||
// (2) if we made the mask larger to avoid masking it, the mask would block mouse events.
|
||||
this.innerElement.appendChild(this.toggleButton.getElement())
|
||||
}
|
||||
const maskElementClassList = ['atom-dock-mask']
|
||||
if (this.state.shouldAnimate) maskElementClassList.push(SHOULD_ANIMATE_CLASS)
|
||||
|
||||
if (state.visible) {
|
||||
this.innerElement.classList.add(VISIBLE_CLASS)
|
||||
} else {
|
||||
this.innerElement.classList.remove(VISIBLE_CLASS)
|
||||
}
|
||||
const cursorOverlayElementClassList = ['atom-dock-cursor-overlay', this.location]
|
||||
if (this.state.resizing) cursorOverlayElementClassList.push(CURSOR_OVERLAY_VISIBLE_CLASS)
|
||||
|
||||
if (state.shouldAnimate) {
|
||||
this.maskElement.classList.add(SHOULD_ANIMATE_CLASS)
|
||||
} else {
|
||||
this.maskElement.classList.remove(SHOULD_ANIMATE_CLASS)
|
||||
}
|
||||
|
||||
if (state.resizing) {
|
||||
this.cursorOverlayElement.classList.add(CURSOR_OVERLAY_VISIBLE_CLASS)
|
||||
} else {
|
||||
this.cursorOverlayElement.classList.remove(CURSOR_OVERLAY_VISIBLE_CLASS)
|
||||
}
|
||||
|
||||
const shouldBeVisible = state.visible || state.showDropTarget
|
||||
const shouldBeVisible = this.state.visible || this.state.showDropTarget
|
||||
const size = Math.max(MINIMUM_SIZE,
|
||||
state.size ||
|
||||
(state.draggingItem && getPreferredSize(state.draggingItem, this.location)) ||
|
||||
this.state.size ||
|
||||
(this.state.draggingItem && getPreferredSize(this.state.draggingItem, this.location)) ||
|
||||
DEFAULT_INITIAL_SIZE
|
||||
)
|
||||
|
||||
// We need to change the size of the mask...
|
||||
this.maskElement.style[this.widthOrHeight] = `${shouldBeVisible ? size : 0}px`
|
||||
const maskStyle = {[this.widthOrHeight]: `${shouldBeVisible ? size : 0}px`}
|
||||
// ...but the content needs to maintain a constant size.
|
||||
this.wrapperElement.style[this.widthOrHeight] = `${size}px`
|
||||
const wrapperStyle = {[this.widthOrHeight]: `${size}px`}
|
||||
|
||||
this.resizeHandle.update({dockIsVisible: this.state.visible})
|
||||
this.toggleButton.update({
|
||||
dockIsVisible: shouldBeVisible,
|
||||
visible:
|
||||
// Don't show the toggle button if the dock is closed and empty...
|
||||
(state.hovered && (this.state.visible || this.getPaneItems().length > 0)) ||
|
||||
// ...or if the item can't be dropped in that dock.
|
||||
(!shouldBeVisible && state.draggingItem && isItemAllowed(state.draggingItem, this.location))
|
||||
})
|
||||
return (
|
||||
<atom-dock className={this.location}>
|
||||
<div ref='innerElement' className={innerElementClassList.join(' ')}>
|
||||
<div
|
||||
className={maskElementClassList.join(' ')}
|
||||
style={maskStyle}>
|
||||
<div
|
||||
ref='wrapperElement'
|
||||
className={`atom-dock-content-wrapper ${this.location}`}
|
||||
style={wrapperStyle}>
|
||||
<DockResizeHandle
|
||||
location={this.location}
|
||||
onResizeStart={this.handleResizeHandleDragStart}
|
||||
onResizeToFit={this.handleResizeToFit}
|
||||
dockIsVisible={this.state.visible}
|
||||
/>
|
||||
<ElementComponent element={this.paneContainer.getElement()} />
|
||||
<div className={cursorOverlayElementClassList.join(' ')} />
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
The toggle button must be rendered outside the mask because (1) it shouldn't be masked
|
||||
and (2) if we made the mask larger to avoid masking it, the mask would block mouse
|
||||
events.
|
||||
*/}
|
||||
<DockToggleButton
|
||||
ref='toggleButton'
|
||||
onDragEnter={this.handleToggleButtonDragEnter}
|
||||
location={this.location}
|
||||
toggle={this.toggle}
|
||||
dockIsVisible={shouldBeVisible}
|
||||
visible={
|
||||
// Don't show the toggle button if the dock is closed and empty...
|
||||
(this.state.hovered && (this.state.visible || this.getPaneItems().length > 0)) ||
|
||||
// ...or if the item can't be dropped in that dock.
|
||||
(!shouldBeVisible && this.state.draggingItem && isItemAllowed(this.state.draggingItem, this.location))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</atom-dock>
|
||||
)
|
||||
}
|
||||
|
||||
update (props) {
|
||||
// Since we're interopping with non-etch stuff, this method's actually never called.
|
||||
return etch.update(this)
|
||||
}
|
||||
|
||||
handleDidAddPaneItem () {
|
||||
@@ -321,7 +325,7 @@ module.exports = class Dock {
|
||||
// area considered when detecting exit MUST fully encompass the area considered when detecting
|
||||
// entry.
|
||||
pointWithinHoverArea (point, detectingExit) {
|
||||
const dockBounds = this.innerElement.getBoundingClientRect()
|
||||
const dockBounds = this.refs.innerElement.getBoundingClientRect()
|
||||
|
||||
// Copy the bounds object since we can't mutate it.
|
||||
const bounds = {
|
||||
@@ -370,7 +374,7 @@ module.exports = class Dock {
|
||||
// remove it as an argument and determine whether we're inside the toggle button using
|
||||
// mouseenter/leave events on it. This class would still need to keep track of the mouse
|
||||
// position (via a mousemove listener) for the other measurements, though.
|
||||
const toggleButtonBounds = this.toggleButton.getBounds()
|
||||
const toggleButtonBounds = this.refs.toggleButton.getBounds()
|
||||
if (rectContainsPoint(toggleButtonBounds, point)) return true
|
||||
|
||||
// The area used when detecting exit is actually larger than when detecting entrances. Expand
|
||||
@@ -707,13 +711,20 @@ module.exports = class Dock {
|
||||
|
||||
class DockResizeHandle {
|
||||
constructor (props) {
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this)
|
||||
|
||||
this.element = document.createElement('div')
|
||||
this.element.classList.add('atom-dock-resize-handle', props.location)
|
||||
this.element.addEventListener('mousedown', this.handleMouseDown)
|
||||
this.props = props
|
||||
this.update(props)
|
||||
etch.initialize(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
const classList = ['atom-dock-resize-handle', this.props.location]
|
||||
if (this.props.dockIsVisible) classList.push(RESIZE_HANDLE_RESIZABLE_CLASS)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classList.join(' ')}
|
||||
on={{mousedown: this.handleMouseDown}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
getElement () {
|
||||
@@ -729,12 +740,7 @@ class DockResizeHandle {
|
||||
|
||||
update (newProps) {
|
||||
this.props = Object.assign({}, this.props, newProps)
|
||||
|
||||
if (this.props.dockIsVisible) {
|
||||
this.element.classList.add(RESIZE_HANDLE_RESIZABLE_CLASS)
|
||||
} else {
|
||||
this.element.classList.remove(RESIZE_HANDLE_RESIZABLE_CLASS)
|
||||
}
|
||||
return etch.update(this)
|
||||
}
|
||||
|
||||
handleMouseDown (event) {
|
||||
@@ -748,22 +754,26 @@ class DockResizeHandle {
|
||||
|
||||
class DockToggleButton {
|
||||
constructor (props) {
|
||||
this.handleClick = this.handleClick.bind(this)
|
||||
this.handleDragEnter = this.handleDragEnter.bind(this)
|
||||
|
||||
this.element = document.createElement('div')
|
||||
this.element.classList.add('atom-dock-toggle-button', props.location)
|
||||
this.element.classList.add(props.location)
|
||||
this.innerElement = document.createElement('div')
|
||||
this.innerElement.classList.add('atom-dock-toggle-button-inner', props.location)
|
||||
this.innerElement.addEventListener('click', this.handleClick)
|
||||
this.innerElement.addEventListener('dragenter', this.handleDragEnter)
|
||||
this.iconElement = document.createElement('span')
|
||||
this.innerElement.appendChild(this.iconElement)
|
||||
this.element.appendChild(this.innerElement)
|
||||
|
||||
this.props = props
|
||||
this.update(props)
|
||||
etch.initialize(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
const classList = ['atom-dock-toggle-button', this.props.location]
|
||||
if (this.props.visible) classList.push(TOGGLE_BUTTON_VISIBLE_CLASS)
|
||||
|
||||
return (
|
||||
<div className={classList.join(' ')}>
|
||||
<div
|
||||
ref='innerElement'
|
||||
className={`atom-dock-toggle-button-inner ${this.props.location}`}
|
||||
on={{click: this.handleClick, dragenter: this.handleDragEnter}}>
|
||||
<span
|
||||
ref='iconElement'
|
||||
className={`icon ${getIconName(this.props.location, this.props.dockIsVisible)}`} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getElement () {
|
||||
@@ -771,19 +781,12 @@ class DockToggleButton {
|
||||
}
|
||||
|
||||
getBounds () {
|
||||
return this.innerElement.getBoundingClientRect()
|
||||
return this.refs.innerElement.getBoundingClientRect()
|
||||
}
|
||||
|
||||
update (newProps) {
|
||||
this.props = Object.assign({}, this.props, newProps)
|
||||
|
||||
if (this.props.visible) {
|
||||
this.element.classList.add(TOGGLE_BUTTON_VISIBLE_CLASS)
|
||||
} else {
|
||||
this.element.classList.remove(TOGGLE_BUTTON_VISIBLE_CLASS)
|
||||
}
|
||||
|
||||
this.iconElement.className = 'icon ' + getIconName(this.props.location, this.props.dockIsVisible)
|
||||
return etch.update(this)
|
||||
}
|
||||
|
||||
handleClick () {
|
||||
@@ -795,6 +798,18 @@ class DockToggleButton {
|
||||
}
|
||||
}
|
||||
|
||||
// An etch component that doesn't use etch, this component provides a gateway from JSX back into
|
||||
// the mutable DOM world.
|
||||
class ElementComponent {
|
||||
constructor (props) {
|
||||
this.element = props.element
|
||||
}
|
||||
|
||||
update (props) {
|
||||
this.element = props.element
|
||||
}
|
||||
}
|
||||
|
||||
function getWidthOrHeight (location) {
|
||||
return location === 'left' || location === 'right' ? 'width' : 'height'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user