Merge pull request #16827 from atom/fb-mdt-pane-element-to-js

Convert PaneContainerElement and PaneElement to JS
This commit is contained in:
Max Brunsfeld
2018-02-23 16:04:47 -08:00
committed by GitHub
4 changed files with 258 additions and 167 deletions

View File

@@ -1,28 +0,0 @@
{CompositeDisposable} = require 'event-kit'
_ = require 'underscore-plus'
module.exports =
class PaneContainerElement extends HTMLElement
createdCallback: ->
@subscriptions = new CompositeDisposable
@classList.add 'panes'
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing PaneContainerElements") unless @views?
@subscriptions.add @model.observeRoot(@rootChanged.bind(this))
this
rootChanged: (root) ->
focusedElement = document.activeElement if @hasFocus()
@firstChild?.remove()
if root?
view = @views.getView(root)
@appendChild(view)
focusedElement?.focus()
hasFocus: ->
this is document.activeElement or @contains(document.activeElement)
module.exports = PaneContainerElement = document.registerElement 'atom-pane-container', prototype: PaneContainerElement.prototype

View File

@@ -0,0 +1,40 @@
const {CompositeDisposable} = require('event-kit')
class PaneContainerElement extends HTMLElement {
createdCallback () {
this.subscriptions = new CompositeDisposable()
this.classList.add('panes')
}
initialize (model, {views}) {
this.model = model
this.views = views
if (this.views == null) {
throw new Error('Must pass a views parameter when initializing PaneContainerElements')
}
this.subscriptions.add(this.model.observeRoot(this.rootChanged.bind(this)))
return this
}
rootChanged (root) {
const focusedElement = this.hasFocus() ? document.activeElement : null
if (this.firstChild != null) {
this.firstChild.remove()
}
if (root != null) {
const view = this.views.getView(root)
this.appendChild(view)
if (focusedElement != null) {
focusedElement.focus()
}
}
}
hasFocus () {
return this === document.activeElement || this.contains(document.activeElement)
}
}
module.exports = document.registerElement('atom-pane-container', {
prototype: PaneContainerElement.prototype
})

View File

@@ -1,139 +0,0 @@
path = require 'path'
{CompositeDisposable} = require 'event-kit'
class PaneElement extends HTMLElement
attached: false
createdCallback: ->
@attached = false
@subscriptions = new CompositeDisposable
@inlineDisplayStyles = new WeakMap
@initializeContent()
@subscribeToDOMEvents()
attachedCallback: ->
@attached = true
@focus() if @model.isFocused()
detachedCallback: ->
@attached = false
initializeContent: ->
@setAttribute 'class', 'pane'
@setAttribute 'tabindex', -1
@appendChild @itemViews = document.createElement('div')
@itemViews.setAttribute 'class', 'item-views'
subscribeToDOMEvents: ->
handleFocus = (event) =>
@model.focus() unless @isActivating or @model.isDestroyed() or @contains(event.relatedTarget)
if event.target is this and view = @getActiveView()
view.focus()
event.stopPropagation()
handleBlur = (event) =>
@model.blur() unless @contains(event.relatedTarget)
handleDragOver = (event) ->
event.preventDefault()
event.stopPropagation()
handleDrop = (event) =>
event.preventDefault()
event.stopPropagation()
@getModel().activate()
pathsToOpen = Array::map.call event.dataTransfer.files, (file) -> file.path
@applicationDelegate.open({pathsToOpen}) if pathsToOpen.length > 0
@addEventListener 'focus', handleFocus, true
@addEventListener 'blur', handleBlur, true
@addEventListener 'dragover', handleDragOver
@addEventListener 'drop', handleDrop
initialize: (@model, {@views, @applicationDelegate}) ->
throw new Error("Must pass a views parameter when initializing PaneElements") unless @views?
throw new Error("Must pass an applicationDelegate parameter when initializing PaneElements") unless @applicationDelegate?
@subscriptions.add @model.onDidActivate(@activated.bind(this))
@subscriptions.add @model.observeActive(@activeStatusChanged.bind(this))
@subscriptions.add @model.observeActiveItem(@activeItemChanged.bind(this))
@subscriptions.add @model.onDidRemoveItem(@itemRemoved.bind(this))
@subscriptions.add @model.onDidDestroy(@paneDestroyed.bind(this))
@subscriptions.add @model.observeFlexScale(@flexScaleChanged.bind(this))
this
getModel: -> @model
activated: ->
@isActivating = true
@focus() unless @hasFocus() # Don't steal focus from children.
@isActivating = false
activeStatusChanged: (active) ->
if active
@classList.add('active')
else
@classList.remove('active')
activeItemChanged: (item) ->
delete @dataset.activeItemName
delete @dataset.activeItemPath
@changePathDisposable?.dispose()
return unless item?
hasFocus = @hasFocus()
itemView = @views.getView(item)
if itemPath = item.getPath?()
@dataset.activeItemName = path.basename(itemPath)
@dataset.activeItemPath = itemPath
if item.onDidChangePath?
@changePathDisposable = item.onDidChangePath =>
itemPath = item.getPath()
@dataset.activeItemName = path.basename(itemPath)
@dataset.activeItemPath = itemPath
unless @itemViews.contains(itemView)
@itemViews.appendChild(itemView)
for child in @itemViews.children
if child is itemView
@showItemView(child) if @attached
else
@hideItemView(child)
itemView.focus() if hasFocus
showItemView: (itemView) ->
inlineDisplayStyle = @inlineDisplayStyles.get(itemView)
if inlineDisplayStyle?
itemView.style.display = inlineDisplayStyle
else
itemView.style.display = ''
hideItemView: (itemView) ->
inlineDisplayStyle = itemView.style.display
unless inlineDisplayStyle is 'none'
@inlineDisplayStyles.set(itemView, inlineDisplayStyle) if inlineDisplayStyle?
itemView.style.display = 'none'
itemRemoved: ({item, index, destroyed}) ->
if viewToRemove = @views.getView(item)
viewToRemove.remove()
paneDestroyed: ->
@subscriptions.dispose()
@changePathDisposable?.dispose()
flexScaleChanged: (flexScale) ->
@style.flexGrow = flexScale
getActiveView: -> @views.getView(@model.getActiveItem())
hasFocus: ->
this is document.activeElement or @contains(document.activeElement)
module.exports = PaneElement = document.registerElement 'atom-pane', prototype: PaneElement.prototype

218
src/pane-element.js Normal file
View File

@@ -0,0 +1,218 @@
const path = require('path')
const {CompositeDisposable} = require('event-kit')
class PaneElement extends HTMLElement {
createdCallback () {
this.attached = false
this.subscriptions = new CompositeDisposable()
this.inlineDisplayStyles = new WeakMap()
this.initializeContent()
this.subscribeToDOMEvents()
}
attachedCallback () {
this.attached = true
if (this.model.isFocused()) {
this.focus()
}
}
detachedCallback () {
this.attached = false
}
initializeContent () {
this.setAttribute('class', 'pane')
this.setAttribute('tabindex', -1)
this.itemViews = document.createElement('div')
this.appendChild(this.itemViews)
this.itemViews.setAttribute('class', 'item-views')
}
subscribeToDOMEvents () {
const handleFocus = event => {
if (
!(
this.isActivating ||
this.model.isDestroyed() ||
this.contains(event.relatedTarget)
)
) {
this.model.focus()
}
if (event.target !== this) return
const view = this.getActiveView()
if (view) {
view.focus()
event.stopPropagation()
}
}
const handleBlur = event => {
if (!this.contains(event.relatedTarget)) {
this.model.blur()
}
}
const handleDragOver = event => {
event.preventDefault()
event.stopPropagation()
}
const handleDrop = event => {
event.preventDefault()
event.stopPropagation()
this.getModel().activate()
const pathsToOpen = [...event.dataTransfer.files].map(file => file.path)
if (pathsToOpen.length > 0) {
this.applicationDelegate.open({pathsToOpen})
}
}
this.addEventListener('focus', handleFocus, true)
this.addEventListener('blur', handleBlur, true)
this.addEventListener('dragover', handleDragOver)
this.addEventListener('drop', handleDrop)
}
initialize (model, {views, applicationDelegate}) {
this.model = model
this.views = views
this.applicationDelegate = applicationDelegate
if (this.views == null) {
throw new Error(
'Must pass a views parameter when initializing PaneElements'
)
}
if (this.applicationDelegate == null) {
throw new Error(
'Must pass an applicationDelegate parameter when initializing PaneElements'
)
}
this.subscriptions.add(this.model.onDidActivate(this.activated.bind(this)))
this.subscriptions.add(
this.model.observeActive(this.activeStatusChanged.bind(this))
)
this.subscriptions.add(
this.model.observeActiveItem(this.activeItemChanged.bind(this))
)
this.subscriptions.add(
this.model.onDidRemoveItem(this.itemRemoved.bind(this))
)
this.subscriptions.add(
this.model.onDidDestroy(this.paneDestroyed.bind(this))
)
this.subscriptions.add(
this.model.observeFlexScale(this.flexScaleChanged.bind(this))
)
return this
}
getModel () {
return this.model
}
activated () {
this.isActivating = true
if (!this.hasFocus()) {
// Don't steal focus from children.
this.focus()
}
this.isActivating = false
}
activeStatusChanged (active) {
if (active) {
this.classList.add('active')
} else {
this.classList.remove('active')
}
}
activeItemChanged (item) {
delete this.dataset.activeItemName
delete this.dataset.activeItemPath
if (this.changePathDisposable != null) {
this.changePathDisposable.dispose()
}
if (item == null) {
return
}
const hasFocus = this.hasFocus()
const itemView = this.views.getView(item)
const itemPath = typeof item.getPath === 'function' ? item.getPath() : null
if (itemPath) {
this.dataset.activeItemName = path.basename(itemPath)
this.dataset.activeItemPath = itemPath
if (item.onDidChangePath != null) {
this.changePathDisposable = item.onDidChangePath(() => {
const itemPath = item.getPath()
this.dataset.activeItemName = path.basename(itemPath)
this.dataset.activeItemPath = itemPath
})
}
}
if (!this.itemViews.contains(itemView)) {
this.itemViews.appendChild(itemView)
}
for (const child of this.itemViews.children) {
if (child === itemView) {
if (this.attached) {
this.showItemView(child)
}
} else {
this.hideItemView(child)
}
}
if (hasFocus) {
itemView.focus()
}
}
showItemView (itemView) {
const inlineDisplayStyle = this.inlineDisplayStyles.get(itemView)
if (inlineDisplayStyle != null) {
itemView.style.display = inlineDisplayStyle
} else {
itemView.style.display = ''
}
}
hideItemView (itemView) {
const inlineDisplayStyle = itemView.style.display
if (inlineDisplayStyle !== 'none') {
if (inlineDisplayStyle != null) {
this.inlineDisplayStyles.set(itemView, inlineDisplayStyle)
}
itemView.style.display = 'none'
}
}
itemRemoved ({item, index, destroyed}) {
const viewToRemove = this.views.getView(item)
if (viewToRemove) {
viewToRemove.remove()
}
}
paneDestroyed () {
this.subscriptions.dispose()
if (this.changePathDisposable != null) {
this.changePathDisposable.dispose()
}
}
flexScaleChanged (flexScale) {
this.style.flexGrow = flexScale
}
getActiveView () {
return this.views.getView(this.model.getActiveItem())
}
hasFocus () {
return (
this === document.activeElement || this.contains(document.activeElement)
)
}
}
module.exports = document.registerElement('atom-pane', {
prototype: PaneElement.prototype
})