diff --git a/src/pane-axis-element.coffee b/src/pane-axis-element.coffee deleted file mode 100644 index 86615b2c6..000000000 --- a/src/pane-axis-element.coffee +++ /dev/null @@ -1,71 +0,0 @@ -{CompositeDisposable} = require 'event-kit' -PaneResizeHandleElement = require './pane-resize-handle-element' - -class PaneAxisElement extends HTMLElement - attachedCallback: -> - @subscriptions ?= @subscribeToModel() - @childAdded({child, index}) for child, index in @model.getChildren() - - detachedCallback: -> - @subscriptions.dispose() - @subscriptions = null - @childRemoved({child}) for child in @model.getChildren() - - initialize: (@model, @viewRegistry) -> - @subscriptions ?= @subscribeToModel() - @childAdded({child, index}) for child, index in @model.getChildren() - - switch @model.getOrientation() - when 'horizontal' - @classList.add('horizontal', 'pane-row') - when 'vertical' - @classList.add('vertical', 'pane-column') - this - - subscribeToModel: -> - subscriptions = new CompositeDisposable - subscriptions.add @model.onDidAddChild(@childAdded.bind(this)) - subscriptions.add @model.onDidRemoveChild(@childRemoved.bind(this)) - subscriptions.add @model.onDidReplaceChild(@childReplaced.bind(this)) - subscriptions.add @model.observeFlexScale(@flexScaleChanged.bind(this)) - subscriptions - - isPaneResizeHandleElement: (element) -> - element?.nodeName.toLowerCase() is 'atom-pane-resize-handle' - - childAdded: ({child, index}) -> - view = @viewRegistry.getView(child) - @insertBefore(view, @children[index * 2]) - - prevElement = view.previousSibling - # if previous element is not pane resize element, then insert new resize element - if prevElement? and not @isPaneResizeHandleElement(prevElement) - resizeHandle = document.createElement('atom-pane-resize-handle') - @insertBefore(resizeHandle, view) - - nextElement = view.nextSibling - # if next element isnot resize element, then insert new resize element - if nextElement? and not @isPaneResizeHandleElement(nextElement) - resizeHandle = document.createElement('atom-pane-resize-handle') - @insertBefore(resizeHandle, nextElement) - - childRemoved: ({child}) -> - view = @viewRegistry.getView(child) - siblingView = view.previousSibling - # make sure next sibling view is pane resize view - if siblingView? and @isPaneResizeHandleElement(siblingView) - siblingView.remove() - view.remove() - - childReplaced: ({index, oldChild, newChild}) -> - focusedElement = document.activeElement if @hasFocus() - @childRemoved({child: oldChild, index}) - @childAdded({child: newChild, index}) - focusedElement?.focus() if document.activeElement is document.body - - flexScaleChanged: (flexScale) -> @style.flexGrow = flexScale - - hasFocus: -> - this is document.activeElement or @contains(document.activeElement) - -module.exports = PaneAxisElement = document.registerElement 'atom-pane-axis', prototype: PaneAxisElement.prototype diff --git a/src/pane-axis-element.js b/src/pane-axis-element.js new file mode 100644 index 000000000..fd66d07b9 --- /dev/null +++ b/src/pane-axis-element.js @@ -0,0 +1,121 @@ +const { CompositeDisposable } = require('event-kit'); +/* eslint-disable-next-line no-unused-vars */ +const PaneResizeHandleElement = require('./pane-resize-handle-element'); + +class PaneAxisElement extends HTMLElement { + attachedCallback() { + if (this.subscriptions == null) { + this.subscriptions = this.subscribeToModel(); + } + this.model + .getChildren() + .map((child, index) => this.childAdded({ child, index })); + } + + detachedCallback() { + this.subscriptions.dispose(); + this.subscriptions = null; + this.model.getChildren().map(child => this.childRemoved({ child })); + } + + initialize(model, viewRegistry) { + this.model = model; + this.viewRegistry = viewRegistry; + if (this.subscriptions == null) { + this.subscriptions = this.subscribeToModel(); + } + const iterable = this.model.getChildren(); + for (let index = 0; index < iterable.length; index++) { + const child = iterable[index]; + this.childAdded({ child, index }); + } + + switch (this.model.getOrientation()) { + case 'horizontal': + this.classList.add('horizontal', 'pane-row'); + break; + case 'vertical': + this.classList.add('vertical', 'pane-column'); + break; + } + return this; + } + + subscribeToModel() { + const subscriptions = new CompositeDisposable(); + subscriptions.add(this.model.onDidAddChild(this.childAdded.bind(this))); + subscriptions.add( + this.model.onDidRemoveChild(this.childRemoved.bind(this)) + ); + subscriptions.add( + this.model.onDidReplaceChild(this.childReplaced.bind(this)) + ); + subscriptions.add( + this.model.observeFlexScale(this.flexScaleChanged.bind(this)) + ); + return subscriptions; + } + + isPaneResizeHandleElement(element) { + return ( + (element != null ? element.nodeName.toLowerCase() : undefined) === + 'atom-pane-resize-handle' + ); + } + + childAdded({ child, index }) { + let resizeHandle; + const view = this.viewRegistry.getView(child); + this.insertBefore(view, this.children[index * 2]); + + const prevElement = view.previousSibling; + // if previous element is not pane resize element, then insert new resize element + if (prevElement != null && !this.isPaneResizeHandleElement(prevElement)) { + resizeHandle = document.createElement('atom-pane-resize-handle'); + this.insertBefore(resizeHandle, view); + } + + const nextElement = view.nextSibling; + // if next element isnot resize element, then insert new resize element + if (nextElement != null && !this.isPaneResizeHandleElement(nextElement)) { + resizeHandle = document.createElement('atom-pane-resize-handle'); + return this.insertBefore(resizeHandle, nextElement); + } + } + + childRemoved({ child }) { + const view = this.viewRegistry.getView(child); + const siblingView = view.previousSibling; + // make sure next sibling view is pane resize view + if (siblingView != null && this.isPaneResizeHandleElement(siblingView)) { + siblingView.remove(); + } + return view.remove(); + } + + childReplaced({ index, oldChild, newChild }) { + let focusedElement; + if (this.hasFocus()) { + focusedElement = document.activeElement; + } + this.childRemoved({ child: oldChild, index }); + this.childAdded({ child: newChild, index }); + if (document.activeElement === document.body) { + return focusedElement != null ? focusedElement.focus() : undefined; + } + } + + flexScaleChanged(flexScale) { + this.style.flexGrow = flexScale; + } + + hasFocus() { + return ( + this === document.activeElement || this.contains(document.activeElement) + ); + } +} + +module.exports = document.registerElement('atom-pane-axis', { + prototype: PaneAxisElement.prototype +}); diff --git a/src/pane-resize-handle-element.coffee b/src/pane-resize-handle-element.coffee deleted file mode 100644 index e7ea5480a..000000000 --- a/src/pane-resize-handle-element.coffee +++ /dev/null @@ -1,80 +0,0 @@ -class PaneResizeHandleElement extends HTMLElement - createdCallback: -> - @resizePane = @resizePane.bind(this) - @resizeStopped = @resizeStopped.bind(this) - @subscribeToDOMEvents() - - subscribeToDOMEvents: -> - @addEventListener 'dblclick', @resizeToFitContent.bind(this) - @addEventListener 'mousedown', @resizeStarted.bind(this) - - attachedCallback: -> - # For some reason Chromium 58 is firing the attached callback after the - # element has been detached, so we ignore the callback when a parent element - # can't be found. - if @parentElement - @isHorizontal = @parentElement.classList.contains("horizontal") - @classList.add if @isHorizontal then 'horizontal' else 'vertical' - - detachedCallback: -> - @resizeStopped() - - resizeToFitContent: -> - # clear flex-grow css style of both pane - @previousSibling?.model.setFlexScale(1) - @nextSibling?.model.setFlexScale(1) - - resizeStarted: (e) -> - e.stopPropagation() - if not @overlay - @overlay = document.createElement('div') - @overlay.classList.add('atom-pane-cursor-overlay') - @overlay.classList.add(if @isHorizontal then 'horizontal' else 'vertical') - @appendChild @overlay - document.addEventListener 'mousemove', @resizePane - document.addEventListener 'mouseup', @resizeStopped - - resizeStopped: -> - document.removeEventListener 'mousemove', @resizePane - document.removeEventListener 'mouseup', @resizeStopped - if @overlay - @removeChild @overlay - @overlay = undefined - - calcRatio: (ratio1, ratio2, total) -> - allRatio = ratio1 + ratio2 - [total * ratio1 / allRatio, total * ratio2 / allRatio] - - setFlexGrow: (prevSize, nextSize) -> - @prevModel = @previousSibling.model - @nextModel = @nextSibling.model - totalScale = @prevModel.getFlexScale() + @nextModel.getFlexScale() - flexGrows = @calcRatio(prevSize, nextSize, totalScale) - @prevModel.setFlexScale flexGrows[0] - @nextModel.setFlexScale flexGrows[1] - - fixInRange: (val, minValue, maxValue) -> - Math.min(Math.max(val, minValue), maxValue) - - resizePane: ({clientX, clientY, which}) -> - return @resizeStopped() unless which is 1 - return @resizeStopped() unless @previousSibling? and @nextSibling? - - if @isHorizontal - totalWidth = @previousSibling.clientWidth + @nextSibling.clientWidth - #get the left and right width after move the resize view - leftWidth = clientX - @previousSibling.getBoundingClientRect().left - leftWidth = @fixInRange(leftWidth, 0, totalWidth) - rightWidth = totalWidth - leftWidth - # set the flex grow by the ratio of left width and right width - # to change pane width - @setFlexGrow(leftWidth, rightWidth) - else - totalHeight = @previousSibling.clientHeight + @nextSibling.clientHeight - topHeight = clientY - @previousSibling.getBoundingClientRect().top - topHeight = @fixInRange(topHeight, 0, totalHeight) - bottomHeight = totalHeight - topHeight - @setFlexGrow(topHeight, bottomHeight) - -module.exports = PaneResizeHandleElement = -document.registerElement 'atom-pane-resize-handle', prototype: PaneResizeHandleElement.prototype diff --git a/src/pane-resize-handle-element.js b/src/pane-resize-handle-element.js new file mode 100644 index 000000000..f41146312 --- /dev/null +++ b/src/pane-resize-handle-element.js @@ -0,0 +1,110 @@ +class PaneResizeHandleElement extends HTMLElement { + createdCallback() { + this.resizePane = this.resizePane.bind(this); + this.resizeStopped = this.resizeStopped.bind(this); + this.subscribeToDOMEvents(); + } + + subscribeToDOMEvents() { + this.addEventListener('dblclick', this.resizeToFitContent.bind(this)); + this.addEventListener('mousedown', this.resizeStarted.bind(this)); + } + + attachedCallback() { + // For some reason Chromium 58 is firing the attached callback after the + // element has been detached, so we ignore the callback when a parent element + // can't be found. + if (this.parentElement) { + this.isHorizontal = this.parentElement.classList.contains('horizontal'); + this.classList.add(this.isHorizontal ? 'horizontal' : 'vertical'); + } + } + + detachedCallback() { + this.resizeStopped(); + } + + resizeToFitContent() { + // clear flex-grow css style of both pane + if (this.previousSibling != null) { + this.previousSibling.model.setFlexScale(1); + } + return this.nextSibling != null + ? this.nextSibling.model.setFlexScale(1) + : undefined; + } + + resizeStarted(e) { + e.stopPropagation(); + if (!this.overlay) { + this.overlay = document.createElement('div'); + this.overlay.classList.add('atom-pane-cursor-overlay'); + this.overlay.classList.add(this.isHorizontal ? 'horizontal' : 'vertical'); + this.appendChild(this.overlay); + } + document.addEventListener('mousemove', this.resizePane); + document.addEventListener('mouseup', this.resizeStopped); + } + + resizeStopped() { + document.removeEventListener('mousemove', this.resizePane); + document.removeEventListener('mouseup', this.resizeStopped); + if (this.overlay) { + this.removeChild(this.overlay); + this.overlay = undefined; + } + } + + calcRatio(ratio1, ratio2, total) { + const allRatio = ratio1 + ratio2; + return [(total * ratio1) / allRatio, (total * ratio2) / allRatio]; + } + + setFlexGrow(prevSize, nextSize) { + this.prevModel = this.previousSibling.model; + this.nextModel = this.nextSibling.model; + const totalScale = + this.prevModel.getFlexScale() + this.nextModel.getFlexScale(); + const flexGrows = this.calcRatio(prevSize, nextSize, totalScale); + this.prevModel.setFlexScale(flexGrows[0]); + this.nextModel.setFlexScale(flexGrows[1]); + } + + fixInRange(val, minValue, maxValue) { + return Math.min(Math.max(val, minValue), maxValue); + } + + resizePane({ clientX, clientY, which }) { + if (which !== 1) { + return this.resizeStopped(); + } + if (this.previousSibling == null || this.nextSibling == null) { + return this.resizeStopped(); + } + + if (this.isHorizontal) { + const totalWidth = + this.previousSibling.clientWidth + this.nextSibling.clientWidth; + // get the left and right width after move the resize view + let leftWidth = + clientX - this.previousSibling.getBoundingClientRect().left; + leftWidth = this.fixInRange(leftWidth, 0, totalWidth); + const rightWidth = totalWidth - leftWidth; + // set the flex grow by the ratio of left width and right width + // to change pane width + this.setFlexGrow(leftWidth, rightWidth); + } else { + const totalHeight = + this.previousSibling.clientHeight + this.nextSibling.clientHeight; + let topHeight = + clientY - this.previousSibling.getBoundingClientRect().top; + topHeight = this.fixInRange(topHeight, 0, totalHeight); + const bottomHeight = totalHeight - topHeight; + this.setFlexGrow(topHeight, bottomHeight); + } + } +} + +module.exports = document.registerElement('atom-pane-resize-handle', { + prototype: PaneResizeHandleElement.prototype +}); diff --git a/src/styles-element.coffee b/src/styles-element.coffee deleted file mode 100644 index 2c53300c2..000000000 --- a/src/styles-element.coffee +++ /dev/null @@ -1,82 +0,0 @@ -{Emitter, CompositeDisposable} = require 'event-kit' - -class StylesElement extends HTMLElement - subscriptions: null - context: null - - onDidAddStyleElement: (callback) -> - @emitter.on 'did-add-style-element', callback - - onDidRemoveStyleElement: (callback) -> - @emitter.on 'did-remove-style-element', callback - - onDidUpdateStyleElement: (callback) -> - @emitter.on 'did-update-style-element', callback - - createdCallback: -> - @subscriptions = new CompositeDisposable - @emitter = new Emitter - @styleElementClonesByOriginalElement = new WeakMap - - attachedCallback: -> - @context = @getAttribute('context') ? undefined - - detachedCallback: -> - @subscriptions.dispose() - @subscriptions = new CompositeDisposable - - attributeChangedCallback: (attrName, oldVal, newVal) -> - @contextChanged() if attrName is 'context' - - initialize: (@styleManager) -> - throw new Error("Must pass a styleManager parameter when initializing a StylesElement") unless @styleManager? - - @subscriptions.add @styleManager.observeStyleElements(@styleElementAdded.bind(this)) - @subscriptions.add @styleManager.onDidRemoveStyleElement(@styleElementRemoved.bind(this)) - @subscriptions.add @styleManager.onDidUpdateStyleElement(@styleElementUpdated.bind(this)) - - contextChanged: -> - return unless @subscriptions? - - @styleElementRemoved(child) for child in Array::slice.call(@children) - @context = @getAttribute('context') - @styleElementAdded(styleElement) for styleElement in @styleManager.getStyleElements() - return - - styleElementAdded: (styleElement) -> - return unless @styleElementMatchesContext(styleElement) - - styleElementClone = styleElement.cloneNode(true) - styleElementClone.sourcePath = styleElement.sourcePath - styleElementClone.context = styleElement.context - styleElementClone.priority = styleElement.priority - @styleElementClonesByOriginalElement.set(styleElement, styleElementClone) - - priority = styleElement.priority - if priority? - for child in @children - if child.priority > priority - insertBefore = child - break - - @insertBefore(styleElementClone, insertBefore) - @emitter.emit 'did-add-style-element', styleElementClone - - styleElementRemoved: (styleElement) -> - return unless @styleElementMatchesContext(styleElement) - - styleElementClone = @styleElementClonesByOriginalElement.get(styleElement) ? styleElement - styleElementClone.remove() - @emitter.emit 'did-remove-style-element', styleElementClone - - styleElementUpdated: (styleElement) -> - return unless @styleElementMatchesContext(styleElement) - - styleElementClone = @styleElementClonesByOriginalElement.get(styleElement) - styleElementClone.textContent = styleElement.textContent - @emitter.emit 'did-update-style-element', styleElementClone - - styleElementMatchesContext: (styleElement) -> - not @context? or styleElement.context is @context - -module.exports = StylesElement = document.registerElement 'atom-styles', prototype: StylesElement.prototype diff --git a/src/styles-element.js b/src/styles-element.js new file mode 100644 index 000000000..6073717ba --- /dev/null +++ b/src/styles-element.js @@ -0,0 +1,145 @@ +const { Emitter, CompositeDisposable } = require('event-kit'); + +class StylesElement extends HTMLElement { + constructor() { + super(); + this.subscriptions = null; + this.context = null; + } + + onDidAddStyleElement(callback) { + this.emitter.on('did-add-style-element', callback); + } + + onDidRemoveStyleElement(callback) { + this.emitter.on('did-remove-style-element', callback); + } + + onDidUpdateStyleElement(callback) { + this.emitter.on('did-update-style-element', callback); + } + + createdCallback() { + this.subscriptions = new CompositeDisposable(); + this.emitter = new Emitter(); + this.styleElementClonesByOriginalElement = new WeakMap(); + } + + attachedCallback() { + let left; + this.context = + (left = this.getAttribute('context')) != null ? left : undefined; + } + + detachedCallback() { + this.subscriptions.dispose(); + this.subscriptions = new CompositeDisposable(); + } + + attributeChangedCallback(attrName) { + if (attrName === 'context') { + return this.contextChanged(); + } + } + + initialize(styleManager) { + this.styleManager = styleManager; + if (this.styleManager == null) { + throw new Error( + 'Must pass a styleManager parameter when initializing a StylesElement' + ); + } + + this.subscriptions.add( + this.styleManager.observeStyleElements(this.styleElementAdded.bind(this)) + ); + this.subscriptions.add( + this.styleManager.onDidRemoveStyleElement( + this.styleElementRemoved.bind(this) + ) + ); + return this.subscriptions.add( + this.styleManager.onDidUpdateStyleElement( + this.styleElementUpdated.bind(this) + ) + ); + } + + contextChanged() { + if (this.subscriptions == null) { + return; + } + + for (let child of Array.from(Array.prototype.slice.call(this.children))) { + this.styleElementRemoved(child); + } + this.context = this.getAttribute('context'); + for (let styleElement of Array.from(this.styleManager.getStyleElements())) { + this.styleElementAdded(styleElement); + } + } + + styleElementAdded(styleElement) { + let insertBefore; + if (!this.styleElementMatchesContext(styleElement)) { + return; + } + + const styleElementClone = styleElement.cloneNode(true); + styleElementClone.sourcePath = styleElement.sourcePath; + styleElementClone.context = styleElement.context; + styleElementClone.priority = styleElement.priority; + this.styleElementClonesByOriginalElement.set( + styleElement, + styleElementClone + ); + + const { priority } = styleElement; + if (priority != null) { + for (let child of this.children) { + if (child.priority > priority) { + insertBefore = child; + break; + } + } + } + + this.insertBefore(styleElementClone, insertBefore); + this.emitter.emit('did-add-style-element', styleElementClone); + } + + styleElementRemoved(styleElement) { + let left; + if (!this.styleElementMatchesContext(styleElement)) { + return; + } + + const styleElementClone = + (left = this.styleElementClonesByOriginalElement.get(styleElement)) != + null + ? left + : styleElement; + styleElementClone.remove(); + this.emitter.emit('did-remove-style-element', styleElementClone); + } + + styleElementUpdated(styleElement) { + if (!this.styleElementMatchesContext(styleElement)) { + return; + } + + const styleElementClone = this.styleElementClonesByOriginalElement.get( + styleElement + ); + styleElementClone.textContent = styleElement.textContent; + this.emitter.emit('did-update-style-element', styleElementClone); + } + + styleElementMatchesContext(styleElement) { + return this.context == null || styleElement.context === this.context; + } +} + +module.exports = document.registerElement('atom-styles', { + prototype: StylesElement.prototype +});