From 9b1d5e186459a64b8f2ae7701539294b810c4e04 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 11:43:51 -0800 Subject: [PATCH 1/7] Add addModalPanel to atom.workspace. --- spec/panel-element-spec.coffee | 9 +++++++++ spec/workspace-spec.coffee | 9 +++++++++ spec/workspace-view-spec.coffee | 3 +++ src/panel-element.coffee | 1 + src/panel.coffee | 4 +++- src/workspace-element.coffee | 3 +++ src/workspace.coffee | 17 +++++++++++++++++ 7 files changed, 45 insertions(+), 1 deletion(-) diff --git a/spec/panel-element-spec.coffee b/spec/panel-element-spec.coffee index 7cf7f8d8b..c283e158d 100644 --- a/spec/panel-element-spec.coffee +++ b/spec/panel-element-spec.coffee @@ -54,3 +54,12 @@ describe "PanelElement", -> panel.show() expect(element.style.display).not.toBe 'none' + + describe "when a class name is specified", -> + it 'initially renders panel created with visibile: false', -> + panel = new Panel({viewRegistry, className: 'some classes', item: new TestPanelItem}) + element = panel.getView() + jasmineContent.appendChild(element) + + expect(element).toHaveClass 'some' + expect(element).toHaveClass 'classes' diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index e844acecd..edd954acc 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -492,3 +492,12 @@ describe "Workspace", -> expect(panel).toBeDefined() expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) + + describe '::addModalPanel(model)', -> + it 'adds a panel to the correct panel container', -> + atom.workspace.panelContainers.modal.onDidAddPanel addPanelSpy = jasmine.createSpy() + panel = atom.workspace.addModalPanel(item: new TestPanel()) + + expect(panel).toBeDefined() + expect(addPanelSpy).toHaveBeenCalledWith({panel, index: 0}) + expect(panel.getClassName()).toBe 'overlay from-top' # the default diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index ff0eff660..4b40b20c4 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -286,3 +286,6 @@ describe "WorkspaceView", -> bottomContainer = workspaceElement.querySelector('atom-panel-container[location="bottom"]') expect(topContainer.nextSibling).toBe workspaceElement.paneContainer expect(bottomContainer.previousSibling).toBe workspaceElement.paneContainer + + modalContainer = workspaceElement.querySelector('atom-panel-container[location="modal"]') + expect(modalContainer.parentNode).toBe workspaceElement diff --git a/src/panel-element.coffee b/src/panel-element.coffee index 87d0fdafc..eec1d91b5 100644 --- a/src/panel-element.coffee +++ b/src/panel-element.coffee @@ -12,6 +12,7 @@ class PanelElement extends HTMLElement @appendChild(view) callAttachHooks(view) # for backward compatibility with SpacePen views + @classList.add(@model.getClassName().split(' ')...) if @model.getClassName()? @subscriptions.add @model.onDidChangeVisible(@visibleChanged.bind(this)) @subscriptions.add @model.onDidDestroy(@destroyed.bind(this)) diff --git a/src/panel.coffee b/src/panel.coffee index 048d6840e..dace72e16 100644 --- a/src/panel.coffee +++ b/src/panel.coffee @@ -14,7 +14,7 @@ class Panel Section: Construction and Destruction ### - constructor: ({@viewRegistry, @item, @visible, @priority}) -> + constructor: ({@viewRegistry, @item, @visible, @priority, @className}) -> @emitter = new Emitter @visible ?= true @priority ?= 100 @@ -62,6 +62,8 @@ class Panel # Public: Returns a {Number} indicating this panel's priority. getPriority: -> @priority + getClassName: -> @className + # Public: Returns a {Boolean} true when the panel is visible. isVisible: -> @visible diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee index 73a9438af..3994ccfe9 100644 --- a/src/workspace-element.coffee +++ b/src/workspace-element.coffee @@ -77,6 +77,7 @@ class WorkspaceElement extends HTMLElement left: @model.panelContainers.left.getView() right: @model.panelContainers.right.getView() bottom: @model.panelContainers.bottom.getView() + modal: @model.panelContainers.modal.getView() @horizontalAxis.insertBefore(@panelContainers.left, @verticalAxis) @horizontalAxis.appendChild(@panelContainers.right) @@ -84,6 +85,8 @@ class WorkspaceElement extends HTMLElement @verticalAxis.insertBefore(@panelContainers.top, @paneContainer) @verticalAxis.appendChild(@panelContainers.bottom) + @appendChild(@panelContainers.modal) + @__spacePenView.setModel(@model) setTextEditorFontSize: (fontSize) -> diff --git a/src/workspace.coffee b/src/workspace.coffee index e4fa0ffb9..944634575 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -54,6 +54,7 @@ class Workspace extends Model left: new PanelContainer({viewRegistry, location: 'left'}) right: new PanelContainer({viewRegistry, location: 'right'}) bottom: new PanelContainer({viewRegistry, location: 'bottom'}) + modal: new PanelContainer({viewRegistry, location: 'modal'}) @subscribeToActiveItem() @@ -660,6 +661,22 @@ class Workspace extends Model addTopPanel: (options) -> @addPanel('top', options) + # Essential: Adds a panel item as a modal dialog. + # + # * `options` {Object} + # * `item` Your panel content. It can be DOM element, a jQuery element, or + # a model with a view registered via {ViewRegistry::addViewProvider}. We recommend the + # latter. See {ViewRegistry::addViewProvider} for more information. + # * `visible` (optional) {Boolean} false if you want the panel to initially be hidden + # (default: true) + # * `priority` (optional) {Number} Determines stacking order. Lower priority items are + # forced closer to the edges of the window. (default: 100) + # + # Returns a {Panel} + addModalPanel: (options={}) -> + options.className ?= 'overlay from-top' + @addPanel('modal', options) + addPanel: (location, options) -> options ?= {} options.viewRegistry = atom.views From 3da11bf47898befb6d61eee1f415c381788e4f75 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 14:04:15 -0800 Subject: [PATCH 2/7] Update styles for modal panels. --- src/panel-container-element.coffee | 8 +++--- src/workspace.coffee | 1 + static/overlay.less | 41 +++++++++++++++++------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/panel-container-element.coffee b/src/panel-container-element.coffee index fb5c6684e..5f83a6a81 100644 --- a/src/panel-container-element.coffee +++ b/src/panel-container-element.coffee @@ -11,14 +11,14 @@ class PanelContainerElement extends HTMLElement @subscriptions.add @model.onDidRemovePanel(@panelRemoved.bind(this)) @subscriptions.add @model.onDidDestroy(@destroyed.bind(this)) - @setAttribute('location', @model.getLocation()) - panelAdded: ({panel, index}) -> + panelElement = panel.getView() + panelElement.setAttribute('location', @model.getLocation()) if index >= @childNodes.length - @appendChild(panel.getView()) + @appendChild(panelElement) else referenceItem = @childNodes[index + 1] - @insertBefore(panel.getView(), referenceItem) + @insertBefore(panelElement, referenceItem) panelRemoved: ({panel, index}) -> @removeChild(@childNodes[index]) diff --git a/src/workspace.coffee b/src/workspace.coffee index 944634575..d88f9196a 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -674,6 +674,7 @@ class Workspace extends Model # # Returns a {Panel} addModalPanel: (options={}) -> + # TODO: remove these default classes. They are to supoprt existing themes. options.className ?= 'overlay from-top' @addPanel('modal', options) diff --git a/static/overlay.less b/static/overlay.less index ee6f983ac..6bcd85ea7 100644 --- a/static/overlay.less +++ b/static/overlay.less @@ -1,6 +1,7 @@ @import "ui-variables"; -.overlay { +.overlay, +atom-panel[location="modal"] { position: absolute; left: 50%; width: 500px; @@ -8,7 +9,9 @@ z-index: 9999; box-sizing: border-box; - background-color: #fff; + color: @text-color; + background-color: @overlay-background-color; + padding: 10px; h1 { @@ -21,30 +24,32 @@ h2 { font-size: 1.3em; } + + atom-text-editor.mini { + margin-bottom: 10px; + } + + .message { + padding-top: 5px; + font-size: 11px; + } + + &.mini { + width: 200px; + margin-left: -100px; + font-size: 12px; + } } -.overlay atom-text-editor.mini { - margin-bottom: 10px; -} - -.overlay .message { - padding-top: 5px; - font-size: 11px; -} - -.overlay.mini { - width: 200px; - margin-left: -100px; - font-size: 12px; -} - -.overlay.from-top { +.overlay.from-top, // TODO: Remove the .from-* classes +atom-panel[location="modal"] { top: 0; border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; } +// TODO: Remove these! .overlay.from-bottom { bottom: 0; border-bottom: none; From da30b66ffada8bfcd0241e869d48d47f800bc16c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 14:05:16 -0800 Subject: [PATCH 3/7] Fix tests --- spec/panel-container-element-spec.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/panel-container-element-spec.coffee b/spec/panel-container-element-spec.coffee index 3dd6b2b18..bb08c5409 100644 --- a/spec/panel-container-element-spec.coffee +++ b/spec/panel-container-element-spec.coffee @@ -34,9 +34,6 @@ describe "PanelContainerElement", -> element = container.getView() jasmineContent.appendChild(element) - it 'has a location attribute with value from the model', -> - expect(element.getAttribute('location')).toBe 'left' - it 'removes the element when the container is destroyed', -> expect(element.parentNode).toBe jasmineContent container.destroy() @@ -49,6 +46,7 @@ describe "PanelContainerElement", -> panel1 = new Panel({viewRegistry, item: new TestPanelContainerItem(), location: 'left'}) container.addPanel(panel1) expect(element.childNodes.length).toBe 1 + expect(element.childNodes[0].getAttribute('location')).toBe 'left' expect(element.childNodes[0].tagName).toBe 'ATOM-PANEL' From 8485831f40e91341916889e9bfd4c430c862c9ac Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 14:22:15 -0800 Subject: [PATCH 4/7] Add the location attr. Need it for styling! --- spec/panel-container-element-spec.coffee | 3 +++ src/panel-container-element.coffee | 2 ++ 2 files changed, 5 insertions(+) diff --git a/spec/panel-container-element-spec.coffee b/spec/panel-container-element-spec.coffee index bb08c5409..6dd6a0183 100644 --- a/spec/panel-container-element-spec.coffee +++ b/spec/panel-container-element-spec.coffee @@ -34,6 +34,9 @@ describe "PanelContainerElement", -> element = container.getView() jasmineContent.appendChild(element) + it 'has a location attribute with value from the model', -> + expect(element.getAttribute('location')).toBe 'left' + it 'removes the element when the container is destroyed', -> expect(element.parentNode).toBe jasmineContent container.destroy() diff --git a/src/panel-container-element.coffee b/src/panel-container-element.coffee index 5f83a6a81..d6dccdcbb 100644 --- a/src/panel-container-element.coffee +++ b/src/panel-container-element.coffee @@ -11,6 +11,8 @@ class PanelContainerElement extends HTMLElement @subscriptions.add @model.onDidRemovePanel(@panelRemoved.bind(this)) @subscriptions.add @model.onDidDestroy(@destroyed.bind(this)) + @setAttribute('location', @model.getLocation()) + panelAdded: ({panel, index}) -> panelElement = panel.getView() panelElement.setAttribute('location', @model.getLocation()) From 5bd028b24e17afd00cd5dbe1184b1bd8907b6956 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 14:58:00 -0800 Subject: [PATCH 5/7] Enforce modal --- spec/panel-container-element-spec.coffee | 26 ++++++++++++++++++++++++ src/panel-container-element.coffee | 10 +++++++++ src/panel-container.coffee | 2 ++ 3 files changed, 38 insertions(+) diff --git a/spec/panel-container-element-spec.coffee b/spec/panel-container-element-spec.coffee index 6dd6a0183..12e28e04d 100644 --- a/spec/panel-container-element-spec.coffee +++ b/spec/panel-container-element-spec.coffee @@ -57,8 +57,34 @@ describe "PanelContainerElement", -> container.addPanel(panel2) expect(element.childNodes.length).toBe 2 + expect(panel1.getView().style.display).not.toBe 'none' + expect(panel2.getView().style.display).not.toBe 'none' + panel1.destroy() expect(element.childNodes.length).toBe 1 panel2.destroy() expect(element.childNodes.length).toBe 0 + + describe "when the container is modal", -> + beforeEach -> + container = new PanelContainer({viewRegistry, location: 'modal'}) + element = container.getView() + jasmineContent.appendChild(element) + + it "allows only one panel to be visible at a time", -> + panel1 = new Panel({viewRegistry, item: new TestPanelContainerItem()}) + container.addPanel(panel1) + + expect(panel1.getView().style.display).not.toBe 'none' + + panel2 = new Panel({viewRegistry, item: new TestPanelContainerItem()}) + container.addPanel(panel2) + + expect(panel1.getView().style.display).toBe 'none' + expect(panel2.getView().style.display).not.toBe 'none' + + panel1.show() + + expect(panel1.getView().style.display).not.toBe 'none' + expect(panel2.getView().style.display).toBe 'none' diff --git a/src/panel-container-element.coffee b/src/panel-container-element.coffee index d6dccdcbb..0b07c1ecc 100644 --- a/src/panel-container-element.coffee +++ b/src/panel-container-element.coffee @@ -22,6 +22,11 @@ class PanelContainerElement extends HTMLElement referenceItem = @childNodes[index + 1] @insertBefore(panelElement, referenceItem) + if @model.isModal() + @enforceModalityFor(panel) + @subscriptions.add panel.onDidChangeVisible (visible) => + @enforceModalityFor(panel) if visible + panelRemoved: ({panel, index}) -> @removeChild(@childNodes[index]) @@ -29,4 +34,9 @@ class PanelContainerElement extends HTMLElement @subscriptions.dispose() @parentNode?.removeChild(this) + enforceModalityFor: (excludedPanel) -> + for panel in @model.getPanels() + panel.hide() unless panel is excludedPanel + return + module.exports = PanelContainerElement = document.registerElement 'atom-panel-container', prototype: PanelContainerElement.prototype diff --git a/src/panel-container.coffee b/src/panel-container.coffee index 4bf7e30ba..24d48eac1 100644 --- a/src/panel-container.coffee +++ b/src/panel-container.coffee @@ -34,6 +34,8 @@ class PanelContainer getLocation: -> @location + isModal: -> @location is 'modal' + getPanels: -> @panels addPanel: (panel) -> From 52511834108abb28aa5c108a009fec68789412a2 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 15:21:32 -0800 Subject: [PATCH 6/7] Dispose panel emitter on destroy() --- src/panel.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panel.coffee b/src/panel.coffee index dace72e16..365e4ad5e 100644 --- a/src/panel.coffee +++ b/src/panel.coffee @@ -22,6 +22,7 @@ class Panel # Public: Destroy and remove this panel from the UI. destroy: -> @emitter.emit 'did-destroy', this + @emitter.dispose() ### Section: Event Subscription From df9a0dc7a39bf3b3be4e0d91fd788c0bf7cfff8d Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 4 Nov 2014 15:22:32 -0800 Subject: [PATCH 7/7] hideAllPanelsExcept --- src/panel-container-element.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panel-container-element.coffee b/src/panel-container-element.coffee index 0b07c1ecc..d162686cf 100644 --- a/src/panel-container-element.coffee +++ b/src/panel-container-element.coffee @@ -23,9 +23,9 @@ class PanelContainerElement extends HTMLElement @insertBefore(panelElement, referenceItem) if @model.isModal() - @enforceModalityFor(panel) + @hideAllPanelsExcept(panel) @subscriptions.add panel.onDidChangeVisible (visible) => - @enforceModalityFor(panel) if visible + @hideAllPanelsExcept(panel) if visible panelRemoved: ({panel, index}) -> @removeChild(@childNodes[index]) @@ -34,7 +34,7 @@ class PanelContainerElement extends HTMLElement @subscriptions.dispose() @parentNode?.removeChild(this) - enforceModalityFor: (excludedPanel) -> + hideAllPanelsExcept: (excludedPanel) -> for panel in @model.getPanels() panel.hide() unless panel is excludedPanel return