├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── images ├── sort-asc.gif └── sort-desc.gif ├── keymaps └── bottom-dock.cson ├── lib ├── bottom-dock-service-v0.coffee ├── bottom-dock-service-v1.coffee ├── bottom-dock-service.coffee ├── main.coffee └── views │ ├── bottom-dock.coffee │ ├── dock-pane-manager.coffee │ ├── header.coffee │ ├── status.coffee │ ├── tab-button.coffee │ └── tab-manager.coffee ├── menus └── bottom-dock.cson ├── package.json ├── spec ├── bottom-dock-spec.coffee └── bottom-dock-view-spec.coffee └── styles ├── bottom-dock.less ├── filter-selector.less ├── grid.less └── toolbar.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.4.4 2 | * Fixed option text in bottom-dock settings 3 | 4 | ## 0.4.3 5 | * Removed console.log statements 6 | 7 | ## 0.4.2 8 | * Removed parantheses and icon from status bar tile 9 | 10 | ## 0.4.1 11 | * Removed close button. It was cauisng more confusion than helping. 12 | * Fixed bottom-dock not showing after adding pane. (Added isInitial) 13 | 14 | ## 0.4.0 15 | * Added status bar element to display status of bottom-dock. 16 | * Added paneCount and onDidAddPane to bottom-dock-service 17 | * Fixed bug with deleting pane not calling toggle function 18 | 19 | ## 0.3.7 20 | * Fixed isActive being incorrect after closing all panes 21 | 22 | ## 0.3.6 23 | * Added isActive property and onDidToggle to bottom dock service 24 | 25 | ## 0.3.5 26 | * Fixed height for toolbar 27 | 28 | ## 0.3.4 29 | * Updated stylings 30 | 31 | ## 0.3.3 32 | * Fixed bug where bottom-dock wasn't starting closed when startOpen was false 33 | 34 | ## 0.3.2 35 | * Added onDidFinishResizing event 36 | * Minor styling changes 37 | * Replaced tablesorter with slickgrid 38 | 39 | ## 0.3.1 40 | * Fixed button heights for some atom themes 41 | 42 | ## 0.3.0 43 | * Major styling changes 44 | * Removed refresh functionality 45 | 46 | ## 0.2.2 47 | * Added option to start panel open or closed 48 | 49 | ## 0.2.1 50 | * Added option to move tabs to bottom 51 | * Added onDidDeletePane and onDidChangePane events 52 | * Fixed resizableHandle to adjust to correct height 53 | 54 | ## 0.2.0 55 | * Added TabButton and a basicTabButton 56 | * Changed bottom-dock to open on startup 57 | 58 | ## 0.1.0 - First Release 59 | * Every feature added 60 | * Every bug fixed 61 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bottom-dock package 2 | 3 | bottom-dock is an extendable panel which helps simplify creating panels. 4 | 5 | Features: 6 | * Hide/Show panel 7 | * Resize panel 8 | * Multiple panes 9 | * Manages active tab and pane 10 | * Easy add/delete pane 11 | 12 | Commands: 13 | * ctrl-k ctrl-t: toggles panel 14 | * ctrl-k ctrl-r: refreshes window 15 | * ctrl-k ctrl-c: closes window 16 | 17 | ![image](https://cloud.githubusercontent.com/assets/9221137/13841672/4fcc555e-ebf8-11e5-8cd3-e99571e3de9d.png) 18 | 19 | #### BottomDockService API 20 | 21 | ```js 22 | class BottomDockService { 23 | isActive(): boolean 24 | toggle(): void 25 | changePane(id: string): void 26 | deletePane(id: string): void 27 | getPane(id: string): Pane //Where Pane extends DockPaneView 28 | addPane(pane: Pane, tab: TabButton, isInitial?: boolean): void 29 | getCurrentPane(): Pane 30 | deleteCurrentPane: void 31 | onDidChangePane(callback: (id: string) => void): Disposable 32 | onDidDeletePane(callback: (id: string) => void): Disposable 33 | onDidAddPane(callback: (id: string) => void): Disposable 34 | onDidFinishResizing(callback: () => void): Disposable 35 | onDidToggle(callback: () => void): Disposable 36 | paneCount(): number 37 | } 38 | ``` 39 | 40 | ##### How to use 41 | Create a new package that consumes the BottomDockService 42 | 43 | Extend the DockPaneView from the [atom-bottom-dock](https://www.npmjs.com/package/atom-bottom-dock) npm package 44 | 45 | Look at [Gulp Manager](https://github.com/benjaminRomano/gulp-manager) for an example on how to use the api 46 | -------------------------------------------------------------------------------- /images/sort-asc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminRomano/bottom-dock/859217707deca25e84c8aec8a7aee4d8e3728f55/images/sort-asc.gif -------------------------------------------------------------------------------- /images/sort-desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminRomano/bottom-dock/859217707deca25e84c8aec8a7aee4d8e3728f55/images/sort-desc.gif -------------------------------------------------------------------------------- /keymaps/bottom-dock.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/behind-atom-keymaps-in-depth 10 | 'atom-workspace': 11 | 'ctrl-k ctrl-t': 'bottom-dock:toggle' 12 | 'ctrl-k ctrl-c': 'bottom-dock:delete' 13 | -------------------------------------------------------------------------------- /lib/bottom-dock-service-v0.coffee: -------------------------------------------------------------------------------- 1 | class BottomDockServiceV0 2 | constructor: (@bottomDock) -> 3 | 4 | toggle: -> 5 | @bottomDock.toggle() 6 | 7 | changePane: (id) -> 8 | @bottomDock.changePane id 9 | 10 | refreshPane: (id) -> 11 | 12 | deletePane: (id) -> 13 | @bottomDock.deletePane id 14 | 15 | getPane: (id) -> 16 | @bottomDock.getPane id 17 | 18 | addPane: (pane, title) -> 19 | tabConfig = 20 | title: title 21 | active: true 22 | id: pane.getId() 23 | 24 | @bottomDock.addPane pane, tabConfig 25 | 26 | getCurrentPane: -> 27 | @bottomDock.getCurrentPane() 28 | 29 | refreshCurrentPane: -> 30 | 31 | deleteCurrentPane: -> 32 | @bottomDock.deleteCurrentPane() 33 | 34 | module.exports = BottomDockServiceV0 35 | -------------------------------------------------------------------------------- /lib/bottom-dock-service-v1.coffee: -------------------------------------------------------------------------------- 1 | class BottomDockServiceV1 2 | constructor: (@bottomDock) -> 3 | 4 | toggle: -> 5 | @bottomDock.toggle() 6 | 7 | changePane: (id) -> 8 | @bottomDock.changePane id 9 | 10 | refreshPane: (id) -> 11 | 12 | deletePane: (id) -> 13 | @bottomDock.deletePane id 14 | 15 | getPane: (id) -> 16 | @bottomDock.getPane id 17 | 18 | addPane: (pane, tabButton) -> 19 | # Cannot extract title from tabButton using id instead 20 | @bottomDock.addPane pane, pane.getId() 21 | 22 | getCurrentPane: -> 23 | @bottomDock.getCurrentPane() 24 | 25 | refreshCurrentPane: -> 26 | 27 | deleteCurrentPane: -> 28 | @bottomDock.deleteCurrentPane() 29 | 30 | onDidDeletePane: (callback) -> 31 | @bottomDock.onDidDeletePane callback 32 | 33 | onDidChangePane: (callback) -> 34 | @bottomDock.onDidChangePane callback 35 | 36 | module.exports = BottomDockServiceV1 37 | -------------------------------------------------------------------------------- /lib/bottom-dock-service.coffee: -------------------------------------------------------------------------------- 1 | class BottomDockService 2 | constructor: (@bottomDock) -> 3 | 4 | isActive: -> 5 | @bottomDock.active 6 | 7 | toggle: -> 8 | @bottomDock.toggle() 9 | 10 | changePane: (id) -> 11 | @bottomDock.changePane id 12 | 13 | deletePane: (id) -> 14 | @bottomDock.deletePane id 15 | 16 | getPane: (id) -> 17 | @bottomDock.getPane id 18 | 19 | addPane: (pane, title, isInitial) -> 20 | @bottomDock.addPane pane, title, isInitial 21 | 22 | getCurrentPane: -> 23 | @bottomDock.getCurrentPane() 24 | 25 | deleteCurrentPane: -> 26 | @bottomDock.deleteCurrentPane() 27 | 28 | onDidDeletePane: (callback) -> 29 | @bottomDock.onDidDeletePane callback 30 | 31 | onDidAddPane: (callback) -> 32 | @bottomDock.onDidAddPane callback 33 | 34 | onDidChangePane: (callback) -> 35 | @bottomDock.onDidChangePane callback 36 | 37 | onDidFinishResizing: (callback) -> 38 | @bottomDock.onDidFinishResizing callback 39 | 40 | onDidToggle: (callback) -> 41 | @bottomDock.onDidToggle callback 42 | 43 | paneCount: -> 44 | @bottomDock.paneCount() 45 | 46 | module.exports = BottomDockService 47 | -------------------------------------------------------------------------------- /lib/main.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | BottomDockService = require './bottom-dock-service' 3 | BottomDockServiceV1 = require './bottom-dock-service-v1' 4 | BottomDockServiceV0 = require './bottom-dock-service-v0' 5 | BottomDock = require './views/bottom-dock' 6 | Status = require './views/status' 7 | 8 | module.exports = 9 | config: 10 | showStatus: 11 | title: 'Show bottom-dock toggle in status bar' 12 | type: 'boolean' 13 | default: true 14 | startOpen: 15 | title: 'Start with panel open' 16 | type: 'boolean' 17 | default: false 18 | 19 | activate: -> 20 | config = 21 | startOpen: atom.config.get 'bottom-dock.startOpen' 22 | 23 | @bottomDock = new BottomDock config 24 | @subscriptions = new CompositeDisposable() 25 | 26 | # Subscribe to keybindings 27 | @subscriptions.add atom.commands.add 'atom-workspace', 28 | 'bottom-dock:toggle': => @bottomDock.toggle() 29 | @subscriptions.add atom.commands.add 'atom-workspace', 30 | 'bottom-dock:delete': => @bottomDock.deleteCurrentPane() 31 | 32 | # Subscribe to config for status visiblity 33 | @subscriptions.add atom.config.onDidChange 'bottom-dock.showStatus', ({newValue}) => 34 | @statusBarTile?.item.setVisiblity newValue 35 | 36 | consumeStatusBar: (statusBar) -> 37 | 38 | config = 39 | visible: atom.config.get 'bottom-dock.showStatus' 40 | 41 | @statusBarTile = statusBar.addRightTile item: new Status config , priority: 2 42 | 43 | @subscriptions.add @statusBarTile.item.onDidToggle => @bottomDock.toggle() 44 | 45 | provideBottomDockService: -> 46 | @bottomDockService = new BottomDockService @bottomDock 47 | 48 | provideBottomDockServiceV1: -> 49 | @bottomDockServiceV1 = new BottomDockServiceV1 @bottomDock 50 | 51 | provideBottomDockServiceV0: -> 52 | @bottomDockServiceV0 = new BottomDockServiceV0 @bottomDock 53 | 54 | deactivate: -> 55 | @subscriptions.dispose() 56 | @bottomDock.destroy() 57 | @statusBarTile?.destroy() 58 | @statusBarTile = null 59 | -------------------------------------------------------------------------------- /lib/views/bottom-dock.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable, Emitter} = require 'atom' 2 | {View} = require 'space-pen' 3 | TabManager = require './tab-manager' 4 | DockPaneManager = require './dock-pane-manager' 5 | Header = require './header' 6 | 7 | class BottomDock extends View 8 | @content: (config) -> 9 | @div class: 'bottom-dock', => 10 | @subview 'header', new Header() 11 | @subview 'dockPaneManager', new DockPaneManager() 12 | @subview 'tabManager', new TabManager() 13 | 14 | initialize: (config) -> 15 | config = config ? {} 16 | @subscriptions = new CompositeDisposable() 17 | @active = false 18 | 19 | @panel = @createPanel startOpen: config.startOpen 20 | @emitter = new Emitter() 21 | 22 | @subscriptions.add @tabManager.onTabClicked @changePane 23 | 24 | onDidFinishResizing: (callback) -> 25 | @header.onDidFinishResizing callback 26 | 27 | onDidChangePane: (callback) -> 28 | @emitter.on 'pane:changed', callback 29 | 30 | onDidDeletePane: (callback) -> 31 | @emitter.on 'pane:deleted', callback 32 | 33 | onDidAddPane: (callback) -> 34 | @emitter.on 'pane:added', callback 35 | 36 | onDidToggle: (callback) -> 37 | @emitter.on 'pane:toggled', callback 38 | 39 | addPane: (pane, title, isInitial) -> 40 | @dockPaneManager.addPane pane 41 | 42 | config = 43 | title: title 44 | id: pane.getId() 45 | 46 | if not isInitial 47 | @panel.show() 48 | 49 | @tabManager.addTab config 50 | 51 | if pane.isActive() 52 | @changePane pane.getId() 53 | else 54 | tabButton.setActive false 55 | 56 | @emitter.emit 'pane:added', pane.getId() 57 | 58 | getPane: (id) -> 59 | @dockPaneManager.getPane id 60 | 61 | getCurrentPane: -> 62 | @dockPaneManager.getCurrentPane() 63 | 64 | changePane: (id) => 65 | @dockPaneManager.changePane id 66 | @tabManager.changeTab id 67 | @header.setTitle @tabManager.getCurrentTabTitle() 68 | @emitter.emit 'pane:changed', id 69 | 70 | deletePane: (id) => 71 | success = @dockPaneManager.deletePane id 72 | return unless success 73 | 74 | @tabManager.deleteTab id 75 | 76 | if @dockPaneManager.getCurrentPane() 77 | @header.setTitle @tabManager.getCurrentTabTitle() 78 | else 79 | @active = false 80 | @toggle() 81 | 82 | @emitter.emit 'pane:deleted', id 83 | 84 | deleteCurrentPane: => 85 | currentPane = @dockPaneManager.getCurrentPane() 86 | return unless currentPane 87 | 88 | @deletePane currentPane.getId() 89 | 90 | createPanel: ({startOpen}) -> 91 | @active = startOpen 92 | 93 | options = 94 | item: this, 95 | visible: startOpen 96 | priority: 1000 97 | 98 | return atom.workspace.addBottomPanel options 99 | 100 | toggle: -> 101 | if not @panel.isVisible() and @dockPaneManager.getCurrentPane() 102 | @active = true 103 | @panel.show() 104 | else 105 | @active = false 106 | @panel.hide() 107 | 108 | @emitter.emit 'pane:toggled', @active 109 | 110 | paneCount: -> @dockPaneManager.paneCount() 111 | 112 | destroy: -> 113 | @subscriptions.dispose() 114 | @panel.destroy() 115 | @header.destroy() 116 | @tabManager.destroy() 117 | @dockPaneManager.destroy() 118 | 119 | module.exports = BottomDock 120 | -------------------------------------------------------------------------------- /lib/views/dock-pane-manager.coffee: -------------------------------------------------------------------------------- 1 | {View, $} = require 'space-pen' 2 | {Emitter, CompositeDisposable} = require 'atom' 3 | 4 | class DockPaneManager extends View 5 | @content: (params) -> 6 | @div class: 'pane-manager' 7 | 8 | initialize: (params) -> 9 | @panes = [] 10 | @currPane = null 11 | @subscriptions = new CompositeDisposable() 12 | @emitter = new Emitter() 13 | 14 | if params?.panes?.length 15 | @addPane pane for pane in params.panes 16 | 17 | addPane: (pane) -> 18 | @panes.push pane 19 | @append pane 20 | 21 | # Adjust height when tab-manager is shown again 22 | if @panes.length == 2 23 | @height(@height() - $('.tab-manager').height()) 24 | 25 | return pane 26 | 27 | changePane: (id) -> 28 | for pane in @panes 29 | if pane.getId() == String(id) 30 | pane.setActive true 31 | @currPane = pane 32 | else 33 | pane.setActive false 34 | 35 | getPane: (id) -> 36 | (pane for pane in @panes when pane.getId() == String(id))[0] 37 | 38 | getCurrentPane: -> 39 | return @currPane 40 | 41 | deletePane: (id) -> 42 | pane = (pane for pane in @panes when pane.getId() == String(id))[0] 43 | 44 | return false unless pane 45 | 46 | @currPane = null if @currPane.getId() == String(id) 47 | 48 | pane.destroy() 49 | 50 | @panes = (pane for pane in @panes when pane.getId() != String(id)) 51 | 52 | if not @currPane and @panes.length 53 | @changePane @panes[@panes.length - 1].getId() 54 | 55 | 56 | # Adjust height when tab-manager is hidden 57 | if @panes.length == 1 58 | @height(@height() + $('.tab-manager').height()) 59 | 60 | return true 61 | 62 | paneCount: -> 63 | @panes.length 64 | 65 | destroy: -> 66 | @subscriptions.dispose() 67 | pane.destroy() for pane in @panes 68 | 69 | module.exports = DockPaneManager 70 | -------------------------------------------------------------------------------- /lib/views/header.coffee: -------------------------------------------------------------------------------- 1 | {Emitter} = require 'atom' 2 | {View, $} = require 'space-pen' 3 | 4 | class Header extends View 5 | @content: (title) -> 6 | @div class: 'bottom-dock-header', => 7 | @div outlet: 'resizeHandle', class: 'dock-resize-handle' 8 | @span outlet: 'title', class: 'title' 9 | 10 | setTitle: (title) => 11 | @title.text title 12 | 13 | initialize: (title) -> 14 | @setTitle title if title? 15 | @handleEvents() 16 | @emitter = new Emitter() 17 | 18 | resizeStarted: => 19 | $(document).on 'mousemove', @resizePane 20 | $(document).on 'mouseup', @resizeStopped 21 | 22 | resizeStopped: => 23 | $(document).off 'mousemove', @resizePane 24 | $(document).off 'mouseup', @resizeStopped 25 | @emitter.emit 'header:resize:finished' 26 | 27 | onDidFinishResizing: (callback) => 28 | @emitter.on 'header:resize:finished', callback 29 | 30 | resizePane: ({pageY, which}) -> 31 | height = $(document.body).height() - pageY - $('.tab-manager').height() - $('.bottom-dock-header').height() 32 | height -= $('.status-bar').height() if $('.status-bar') 33 | 34 | $('.pane-manager').height(height) 35 | $('.pane-manager').trigger 'update' 36 | $('.pane-manager').on 'update', -> 37 | 38 | handleEvents: -> 39 | @on 'mousedown', '.dock-resize-handle', (e) => @resizeStarted e 40 | 41 | destroy: -> 42 | @resizeStopped() 43 | @remove() 44 | 45 | module.exports = Header 46 | -------------------------------------------------------------------------------- /lib/views/status.coffee: -------------------------------------------------------------------------------- 1 | {Emitter} = require 'atom' 2 | {View, $} = require 'space-pen' 3 | 4 | class Status extends View 5 | @content: (config) -> 6 | @div class: 'bottom-dock-status-container inline-block', style: 'display: inline-block', => 7 | @span 'Bottom Dock' 8 | 9 | initialize: (config) -> 10 | @emitter = new Emitter 11 | 12 | @setVisiblity config.visible 13 | 14 | @on 'click', @toggleClicked 15 | 16 | 17 | setVisiblity: (value) => 18 | if value 19 | @show() 20 | else 21 | @hide() 22 | 23 | toggleClicked: => 24 | @emitter.emit 'status:toggled' 25 | 26 | onDidToggle: (callback) -> 27 | @emitter.on 'status:toggled', callback 28 | 29 | module.exports = Status 30 | -------------------------------------------------------------------------------- /lib/views/tab-button.coffee: -------------------------------------------------------------------------------- 1 | {Emitter} = require 'atom' 2 | {View} = require 'space-pen' 3 | 4 | class TabButton extends View 5 | @content: (config) -> 6 | @button click: 'clicked', class: 'tab-button', config.title 7 | 8 | clicked: => 9 | @emitter.emit 'tab:button:clicked', @getId() 10 | 11 | initialize: (config) -> 12 | @title = config.title 13 | @id = "tab-button-#{config.id}" 14 | @setActive config.active 15 | 16 | @emitter = new Emitter() 17 | 18 | getId: -> 19 | @id.split('tab-button-')[1] 20 | 21 | isActive: -> 22 | @active 23 | 24 | setActive: (value) -> 25 | @active = value 26 | if @active then @addClass 'selected' else @removeClass 'selected' 27 | 28 | onDidClick: (callback) -> 29 | @emitter.on 'tab:button:clicked', callback 30 | 31 | destroy: -> 32 | @remove() 33 | 34 | module.exports = TabButton 35 | -------------------------------------------------------------------------------- /lib/views/tab-manager.coffee: -------------------------------------------------------------------------------- 1 | {View, $} = require 'space-pen' 2 | {Emitter, CompositeDisposable} = require 'atom' 3 | TabButton = require './tab-button' 4 | 5 | class TabManager extends View 6 | @content: () -> 7 | @div class: 'tab-manager', => 8 | @div outlet: 'tabContainer', class: 'tab-container' 9 | 10 | initialize: () -> 11 | @tabs = [] 12 | @subscriptions = new CompositeDisposable() 13 | @emitter = new Emitter() 14 | @currTab = null 15 | 16 | getCurrentTabTitle: -> 17 | @currTab?.title ? "" 18 | 19 | addTab: (config) -> 20 | tab = new TabButton config 21 | @tabContainer.append tab 22 | @tabs.push tab 23 | 24 | if @tabs.length == 1 25 | @hide() 26 | else 27 | @show() 28 | 29 | @subscriptions.add tab.onDidClick (id) => 30 | @emitter.emit 'tab:clicked', id 31 | 32 | deleteTab: (id) -> 33 | matchedTabs = (tab for tab in @tabs when tab.getId() == String(id)) 34 | matchedTab = matchedTabs[0] 35 | 36 | return unless matchedTab 37 | 38 | if @currTab.getId() == String(id) 39 | @currTab = null 40 | 41 | matchedTab.destroy() 42 | 43 | @tabs = (tab for tab in @tabs when tab.getId() != String(id)) 44 | 45 | if not @currTab && @tabs.length 46 | @changeTab @tabs[@tabs.length - 1].getId() 47 | 48 | if @tabs.length == 1 then @hide() else @show() 49 | 50 | changeTab: (id) -> 51 | for tab in @tabs 52 | if tab.getId() == String(id) 53 | tab.setActive true 54 | @currTab = tab 55 | else 56 | tab.setActive false 57 | 58 | deleteClicked: => 59 | if @currTab 60 | @emitter.emit 'delete:clicked', @currTab.getId() 61 | 62 | onTabClicked: (callback) -> 63 | @emitter.on 'tab:clicked', callback 64 | 65 | onDeleteClicked: (callback) -> 66 | @emitter.on 'delete:clicked', callback 67 | 68 | deleteCurrentTab: -> 69 | @removeTab @currTab.getId() 70 | 71 | destroy: -> 72 | @subscriptions.dispose() 73 | tab.destroy for tab in @tabs 74 | 75 | module.exports = TabManager 76 | -------------------------------------------------------------------------------- /menus/bottom-dock.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | { 3 | 'label': 'Packages' 4 | 'submenu': [ 5 | 'label': 'bottom-dock' 6 | 'submenu': [ 7 | { 8 | 'label': 'Toggle' 9 | 'command': 'bottom-dock:toggle' 10 | }, 11 | { 12 | 'label': 'Remove Current Pane' 13 | 'command': 'bottom-dock:delete' 14 | } 15 | ] 16 | ] 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bottom-dock", 3 | "main": "./lib/main", 4 | "version": "0.4.4", 5 | "description": "Generic bottom dock for quick panel development", 6 | "keywords": [ 7 | "generic", 8 | "bottom", 9 | "dock", 10 | "panel" 11 | ], 12 | "repository": "https://github.com/benjaminRomano/bottom-dock", 13 | "license": "MIT", 14 | "engines": { 15 | "atom": ">=1.0.0 <2.0.0" 16 | }, 17 | "dependencies": { 18 | "atom-bottom-dock": "^0.5.0", 19 | "space-pen": "^5.1.1" 20 | }, 21 | "consumedServices": { 22 | "status-bar": { 23 | "versions": { 24 | "^1.0.0": "consumeStatusBar" 25 | } 26 | } 27 | }, 28 | "providedServices": { 29 | "bottom-dock": { 30 | "description": "Simple service to add window to bottom dock", 31 | "versions": { 32 | "0.0.0": "provideBottomDockServiceV0", 33 | "0.1.1": "provideBottomDockServiceV1", 34 | "0.2.0": "provideBottomDockService" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spec/bottom-dock-spec.coffee: -------------------------------------------------------------------------------- 1 | BottomDock = require '../lib/bottom-dock' 2 | 3 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs. 4 | # 5 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` 6 | # or `fdescribe`). Remove the `f` to unfocus the block. 7 | 8 | describe "BottomDock", -> 9 | [workspaceElement, activationPromise] = [] 10 | 11 | beforeEach -> 12 | workspaceElement = atom.views.getView(atom.workspace) 13 | activationPromise = atom.packages.activatePackage('bottom-dock') 14 | 15 | describe "when the bottom-dock:toggle event is triggered", -> 16 | it "hides and shows the modal panel", -> 17 | # Before the activation event the view is not on the DOM, and no panel 18 | # has been created 19 | expect(workspaceElement.querySelector('.bottom-dock')).not.toExist() 20 | 21 | # This is an activation event, triggering it will cause the package to be 22 | # activated. 23 | atom.commands.dispatch workspaceElement, 'bottom-dock:toggle' 24 | 25 | waitsForPromise -> 26 | activationPromise 27 | 28 | runs -> 29 | expect(workspaceElement.querySelector('.bottom-dock')).toExist() 30 | 31 | bottomDockElement = workspaceElement.querySelector('.bottom-dock') 32 | expect(bottomDockElement).toExist() 33 | 34 | bottomDockPanel = atom.workspace.panelForItem(bottomDockElement) 35 | expect(bottomDockPanel.isVisible()).toBe true 36 | atom.commands.dispatch workspaceElement, 'bottom-dock:toggle' 37 | expect(bottomDockPanel.isVisible()).toBe false 38 | 39 | it "hides and shows the view", -> 40 | # This test shows you an integration test testing at the view level. 41 | 42 | # Attaching the workspaceElement to the DOM is required to allow the 43 | # `toBeVisible()` matchers to work. Anything testing visibility or focus 44 | # requires that the workspaceElement is on the DOM. Tests that attach the 45 | # workspaceElement to the DOM are generally slower than those off DOM. 46 | jasmine.attachToDOM(workspaceElement) 47 | 48 | expect(workspaceElement.querySelector('.bottom-dock')).not.toExist() 49 | 50 | # This is an activation event, triggering it causes the package to be 51 | # activated. 52 | atom.commands.dispatch workspaceElement, 'bottom-dock:toggle' 53 | 54 | waitsForPromise -> 55 | activationPromise 56 | 57 | runs -> 58 | # Now we can test for view visibility 59 | bottomDockElement = workspaceElement.querySelector('.bottom-dock') 60 | expect(bottomDockElement).toBeVisible() 61 | atom.commands.dispatch workspaceElement, 'bottom-dock:toggle' 62 | expect(bottomDockElement).not.toBeVisible() 63 | -------------------------------------------------------------------------------- /spec/bottom-dock-view-spec.coffee: -------------------------------------------------------------------------------- 1 | BottomDockView = require '../lib/bottom-dock-view' 2 | 3 | describe "BottomDockView", -> 4 | it "has one valid test", -> 5 | expect("life").toBe "easy" 6 | -------------------------------------------------------------------------------- /styles/bottom-dock.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .bottom-dock { 8 | 9 | .tab-manager { 10 | .tab-button { 11 | background: @button-background-color; 12 | text-shadow: none; 13 | border: 1px solid @button-border-color; 14 | border-left: 0; 15 | border-radius: 0; 16 | height: initial; 17 | line-height: 1.8em; 18 | 19 | &:first-child { 20 | border-left: 1px solid #181818; 21 | } 22 | 23 | &:hover { 24 | .delete-button { 25 | visibility: visible; 26 | } 27 | } 28 | 29 | &.selected { 30 | background-color: @button-background-color-selected; 31 | color: @text-color-selected; 32 | } 33 | } 34 | } 35 | 36 | .bottom-dock-header { 37 | background-color: @panel-heading-background-color; 38 | border: 1px solid @base-border-color; 39 | border-top: 0; 40 | border-bottom: 0; 41 | padding-bottom: 4px; 42 | 43 | .dock-resize-handle { 44 | height: 4px; 45 | cursor: ns-resize; 46 | } 47 | 48 | .title { 49 | color: @text-color-highlight; 50 | padding-left: 10px; 51 | vertical-align: middle; 52 | } 53 | 54 | .button-container { 55 | float: right; 56 | 57 | span { 58 | padding-left: 10px; 59 | vertical-align: middle; 60 | padding: 0px; 61 | cursor: pointer; 62 | } 63 | 64 | .delete-button { 65 | color: @text-color-error; 66 | 67 | &:hover { 68 | color: @background-color-error; 69 | } 70 | 71 | &:active { 72 | color: @text-color-selected; 73 | } 74 | } 75 | } 76 | } 77 | 78 | .pane-manager { 79 | height: 250px; 80 | display: block; 81 | overflow: hidden; 82 | background-color:@base-background-color; 83 | border: 1px solid @base-border-color; 84 | border-bottom: 0px; 85 | } 86 | 87 | 88 | .table-container { 89 | overflow: auto; 90 | flex: 1 1; 91 | 92 | table { 93 | table-layout:fixed; 94 | 95 | td { 96 | cursor: pointer; 97 | overflow: hidden; 98 | text-overflow: ellipsis; 99 | white-space: nowrap; 100 | min-width: 20px; 101 | } 102 | th { 103 | overflow: hidden; 104 | text-overflow: ellipsis; 105 | min-width: 20px; 106 | font-weight: normal; 107 | } 108 | } 109 | } 110 | } 111 | 112 | .bottom-dock-status-container { 113 | margin-left: 10px; 114 | cursor: pointer; 115 | 116 | &:hover { 117 | text-decoration: underline; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /styles/filter-selector.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .filter-selector { 4 | padding-right: 5px; 5 | 6 | .filter-label { 7 | padding-right: 5px; 8 | vertical-align: middle; 9 | line-height: 1.8em; 10 | 11 | } 12 | 13 | .filter-button { 14 | border-radius: 0; 15 | height: initial; 16 | background: @button-background-color; 17 | text-shadow: none; 18 | border: 1px solid @button-border-color; 19 | border-left: 0; 20 | padding-top: initial; 21 | line-height: 1.8em; 22 | 23 | &:first-child { 24 | border-left: 1px solid #181818; 25 | border-top-left-radius: @component-border-radius; 26 | border-bottom-left-radius: @component-border-radius; 27 | } 28 | 29 | &:last-child { 30 | border-top-right-radius: @component-border-radius; 31 | border-bottom-right-radius: @component-border-radius; 32 | 33 | } 34 | 35 | &:hover { 36 | .delete-button { 37 | visibility: visible; 38 | } 39 | } 40 | 41 | &.selected { 42 | background-color: @button-background-color-selected; 43 | color: @text-color-selected; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /styles/grid.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | @headerBackground: @inset-panel-background-color; 4 | @overallBorder: @tool-panel-border-color solid 1px; 5 | @bodyBackground: @base-background-color; 6 | @packageName: 'bottom-dock'; 7 | 8 | .slick-header.ui-state-default, .slick-headerrow.ui-state-default { 9 | width: 100%; 10 | overflow: hidden; 11 | border-left: 0px !important; 12 | } 13 | 14 | .slick-header-columns, .slick-headerrow-columns { 15 | position: relative; 16 | white-space: nowrap; 17 | cursor: default; 18 | overflow: hidden; 19 | } 20 | 21 | .slick-header-column.ui-state-default { 22 | position: relative; 23 | display: inline-block; 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | height: 16px; 27 | line-height: .8em; 28 | font-size: @font-size; 29 | margin: 0; 30 | padding: 4px; 31 | border: @overallBorder; 32 | border-left: 0px; 33 | border-top: 0px; 34 | border-bottom: 0px; 35 | float: left; 36 | } 37 | 38 | .slick-headerrow-column.ui-state-default { 39 | padding: 4px; 40 | } 41 | 42 | .slick-header-column-sorted { 43 | } 44 | 45 | .slick-sort-indicator { 46 | display: inline-block; 47 | width: 8px; 48 | height: 5px; 49 | margin-left: 4px; 50 | } 51 | 52 | .slick-sort-indicator-desc { 53 | background: url("atom://@{packageName}/images/sort-desc.gif"); 54 | } 55 | 56 | .slick-sort-indicator-asc { 57 | background: url("atom://@{packageName}/images/sort-asc.gif"); 58 | } 59 | 60 | .slick-resizable-handle { 61 | cursor: ew-resize; 62 | display: block; 63 | font-size: 0.1px; 64 | height: 100%; 65 | position: absolute; 66 | right: 0; 67 | top: 0; 68 | width: 4px; 69 | } 70 | 71 | .slick-sortable-placeholder { 72 | } 73 | 74 | .grid-canvas { 75 | position: relative; 76 | outline: 0; 77 | } 78 | 79 | .slick-row.ui-widget-content, .slick-row.ui-state-active { 80 | position: absolute; 81 | border: 0; 82 | width: 100%; 83 | 84 | &:hover { 85 | background: lighten( desaturate(@headerBackground, 60%), 10% ); 86 | } 87 | } 88 | 89 | 90 | .slick-cell, .slick-headerrow-column { 91 | position: absolute; 92 | overflow: hidden; 93 | text-overflow: ellipsis; 94 | white-space: nowrap; 95 | vertical-align: middle; 96 | z-index: 1; 97 | padding: 1px 2px 2px 1px; 98 | margin: 0; 99 | white-space: nowrap; 100 | cursor: pointer; 101 | } 102 | 103 | .slick-group { 104 | } 105 | 106 | .slick-group-toggle { 107 | display: inline-block; 108 | } 109 | 110 | .slick-cell.highlighted { 111 | -webkit-transition: all 0.5s; 112 | -moz-transition: all 0.5s; 113 | transition: all 0.5s; 114 | } 115 | 116 | .slick-cell.flashing { 117 | border: 1px solid red !important; 118 | } 119 | 120 | .slick-cell.editable { 121 | z-index: 11; 122 | overflow: visible; 123 | border-color: black; 124 | border-style: solid; 125 | } 126 | 127 | .slick-cell:focus { 128 | outline: none; 129 | } 130 | 131 | .slick-reorder-proxy { 132 | display: inline-block; 133 | opacity: 0.15; 134 | cursor: move; 135 | } 136 | 137 | .slick-reorder-guide { 138 | display: inline-block; 139 | height: 2px; 140 | opacity: 0.7; 141 | } 142 | 143 | .slick-selection { 144 | z-index: 10; 145 | position: absolute; 146 | border: 2px dashed black; 147 | } 148 | 149 | 150 | .ui-widget-content { 151 | border: @overallBorder; 152 | background: @bodyBackground 50% 50% repeat-x; 153 | } 154 | .ui-widget-header { 155 | border: @overallBorder; 156 | background: @headerBackground 50% 50% repeat-x; 157 | } 158 | 159 | .ui-state-default, 160 | .ui-widget-content .ui-state-default, 161 | .ui-widget-header .ui-state-default { 162 | border: @overallBorder; 163 | background: @headerBackground 50% 50% repeat-x; 164 | } 165 | .ui-state-hover, 166 | .ui-widget-content .ui-state-hover, 167 | .ui-widget-header .ui-state-hover, 168 | .ui-state-focus, 169 | .ui-widget-content .ui-state-focus, 170 | .ui-widget-header .ui-state-focus { 171 | border: 1px solid #999999; 172 | background: @headerBackground 50% 50% repeat-x; 173 | } 174 | -------------------------------------------------------------------------------- /styles/toolbar.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "octicon-mixins"; 3 | 4 | // Based off of status-bar styling 5 | 6 | .bottom-dock { 7 | 8 | .toolbar { 9 | flex: none; 10 | display: block; 11 | font-size: 11px; 12 | line-height: @component-line-height - 3px; 13 | height: @component-line-height + 3px; 14 | position: relative; 15 | -webkit-user-select: none; 16 | cursor: default; 17 | overflow: hidden; 18 | white-space: nowrap; 19 | min-width: -webkit-min-content; 20 | background-color: @tool-panel-background-color; 21 | border-bottom: @base-border-color 1px solid; 22 | 23 | a, a:hover { 24 | color: @text-color; 25 | } 26 | 27 | .flexbox{ 28 | padding: 0 @component-line-height/2; 29 | display: flex; 30 | justify-content: space-between; 31 | } 32 | 33 | // Use 1/3 of space -> will get cut-off first when narrow 34 | .toolbar-left { 35 | padding-left: @component-padding; 36 | flex: 1 1 33%; 37 | } 38 | 39 | // Use 2/3 of space 40 | .toolbar-right { 41 | float: right; 42 | padding-right: @component-padding; 43 | .inline-block{ 44 | margin-right: 0; 45 | margin-left: @component-padding; 46 | & > .inline-block { 47 | margin-left: 0; 48 | } 49 | } 50 | } 51 | 52 | // Add horizontal overflow scrolling 53 | .toolbar-left, 54 | .toolbar-right { 55 | overflow-x: auto; 56 | &::-webkit-scrollbar { 57 | display: none; 58 | } 59 | } 60 | 61 | // Limit inline-blocks from getting too long 62 | .inline-block { 63 | max-width: 20vw; 64 | overflow: hidden; 65 | text-overflow: ellipsis; 66 | white-space: nowrap; 67 | } 68 | 69 | // All icons are smaller than normal (normal is 16px) 70 | .icon:before { 71 | .icon-size(14px); 72 | position: relative; 73 | top: 1px; 74 | margin-right: 0px; 75 | } 76 | } 77 | } 78 | --------------------------------------------------------------------------------