diff --git a/lib/build-view.js b/lib/build-view.js index f743f3e4..69382a8c 100644 --- a/lib/build-view.js +++ b/lib/build-view.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var View = require('atom-space-pen-views').View; +var $ = require('atom-space-pen-views').$; var ansi_up = require('ansi_up'); var cscompatability = require('./cscompatability'); var GoogleAnalytics = require('./google-analytics'); @@ -21,9 +22,12 @@ module.exports = (function() { this.buffer = new Buffer(0); this.links = []; + this._setMonocleIcon(); + atom.config.observe('build.panelVisibility', this.visibleFromConfig.bind(this)); - atom.config.observe('build.monocleHeight', this.heightFromConfig.bind(this)); - atom.config.observe('build.minimizedHeight', this.heightFromConfig.bind(this)); + atom.config.observe('build.panelOrientation', this.orientationFromConfig.bind(this)); + atom.config.observe('build.monocleHeight', this.sizeFromConfig.bind(this)); + atom.config.observe('build.minimizedHeight', this.sizeFromConfig.bind(this)); atom.commands.add('atom-workspace', 'build:toggle-panel', this.toggle.bind(this)); } @@ -41,7 +45,7 @@ module.exports = (function() { BuildView.div({ class: 'output panel-body', outlet: 'output' }); - BuildView.div(function() { + BuildView.div({ class: 'status' }, function() { BuildView.h1({ class: 'title panel-heading', outlet: 'title' }, function () { BuildView.span({ class: 'build-timer', outlet: 'buildTimer' }, '0.0 s'); BuildView.span({ class: 'title-text', outlet: 'titleText' }, 'Ready'); @@ -62,9 +66,15 @@ module.exports = (function() { if (this.panel) { this.panel.destroy(); } - this.panel = atom.workspace.addBottomPanel({ item: this }); - this.height = this.output.offset().top + this.output.height(); - this.heightFromConfig(); + let addfn = { + Top: atom.workspace.addTopPanel, + Bottom: atom.workspace.addBottomPanel, + Left: atom.workspace.addLeftPanel, + Right: atom.workspace.addRightPanel + }; + let orientation = atom.config.get('build.panelOrientation') || 'Bottom'; + this.panel = addfn[orientation].call(atom.workspace, { item: this }); + this.sizeFromConfig(); }; BuildView.prototype.detach = function(force) { @@ -82,12 +92,8 @@ module.exports = (function() { return !!this.panel; }; - BuildView.prototype.heightFromConfig = function() { - if (this.monocle) { - this.setHeightPercent(atom.config.get('build.monocleHeight')); - } else { - this.setHeightPercent(atom.config.get('build.minimizedHeight')); - } + BuildView.prototype.sizeFromConfig = function() { + this.setSizePercent(atom.config.get(this.monocle ? 'build.monocleHeight' : 'build.minimizedHeight')); }; BuildView.prototype.visibleFromConfig = function(val) { @@ -101,6 +107,15 @@ module.exports = (function() { } }; + BuildView.prototype.orientationFromConfig = function (val) { + let isVisible = this.isVisible(); + this.detach(true); + if (isVisible) { + this.attach(); + this._setMonocleIcon(); + } + }; + BuildView.prototype.reset = function() { clearTimeout(this.titleTimer); this.buffer = new Buffer(0); @@ -137,21 +152,49 @@ module.exports = (function() { atom.commands.dispatch(atom.views.getView(atom.workspace), 'build:trigger'); }; - BuildView.prototype.setHeightPercent = function(percent) { - var newHeight = percent * this.height; - this.output.css('height', newHeight + 'px'); + BuildView.prototype.setSizePercent = function(percent) { + let size = 0, cssKey = 'height'; + switch (atom.config.get('build.panelOrientation')) { + case 'Top': + case 'Bottom': + size = $('atom-workspace-axis.vertical').height(); + cssKey = 'height'; + break; + + case 'Left': + case 'Right': + size = $('atom-workspace-axis.vertical').width(); + if ($('.build').length) { + size += $('.build').get(0).clientWidth; + } + cssKey = 'width'; + break; + } + this.output.css('width', 'auto'); + this.output.css('height', '100%'); + this.output.css(cssKey, percent * size + 'px'); + }; + + BuildView.prototype._setMonocleIcon = function () { + let iconName = () => { + switch (atom.config.get('build.panelOrientation')) { + case 'Top': return this.monocle ? 'icon-chevron-up' : 'icon-chevron-down'; + case 'Bottom': return this.monocle ? 'icon-chevron-down' : 'icon-chevron-up'; + case 'Right': return this.monocle ? 'icon-chevron-right' : 'icon-chevron-left'; + case 'Left': return this.monocle ? 'icon-chevron-left' : 'icon-chevron-right'; + } + }; + + this.monocleButton + .removeClass('icon-chevron-down icon-chevron-up icon-chevron-left icon-chevron-right') + .addClass(iconName()); }; BuildView.prototype.toggleMonocle = function(event, element) { GoogleAnalytics.sendEvent('view', 'monocle toggled'); - if (!this.monocle) { - this.setHeightPercent(atom.config.get('build.monocleHeight')); - this.monocleButton.removeClass('icon-chevron-up').addClass('icon-chevron-down'); - } else { - this.setHeightPercent(atom.config.get('build.minimizedHeight')); - this.monocleButton.removeClass('icon-chevron-down').addClass('icon-chevron-up'); - } this.monocle = !this.monocle; + this.setSizePercent(atom.config.get(this.monocle ? 'build.monocleHeight' : 'build.minimizedHeight')); + this._setMonocleIcon(); }; BuildView.prototype.buildStarted = function() { diff --git a/lib/build.js b/lib/build.js index 149c42ad..69c59bb5 100644 --- a/lib/build.js +++ b/lib/build.js @@ -80,6 +80,14 @@ module.exports = { minimum: 0.1, maximum: 0.9, order: 7 + }, + panelOrientation: { + title: 'Panel Orientation', + description: 'Where to attach the build panel', + type: 'string', + default: 'Bottom', + enum: [ 'Bottom', 'Top', 'Left', 'Right' ], + order: 8 } }, diff --git a/spec/build-error-match-spec.js b/spec/build-error-match-spec.js index 48362318..5a208db9 100644 --- a/spec/build-error-match-spec.js +++ b/spec/build-error-match-spec.js @@ -1,4 +1,3 @@ -'use babel'; 'use strict'; var fs = require('fs-extra'); @@ -349,7 +348,7 @@ describe('Error Match', function() { waits(100); runs(function() { - expect(workspaceElement.querySelector('.build .output').scrollTop).toEqual(135); + expect(workspaceElement.querySelector('.build .output').scrollTop).toEqual(168); atom.commands.dispatch(workspaceElement, 'build:error-match'); }); @@ -382,7 +381,7 @@ describe('Error Match', function() { waits(100); runs(function() { - expect(workspaceElement.querySelector('.build .output').scrollTop).toEqual(135); + expect(workspaceElement.querySelector('.build .output').scrollTop).toEqual(168); atom.commands.dispatch(workspaceElement, 'build:error-match-first'); }); diff --git a/spec/build-view-spec.js b/spec/build-view-spec.js index 4b1bc8fa..d78d85b8 100644 --- a/spec/build-view-spec.js +++ b/spec/build-view-spec.js @@ -5,7 +5,7 @@ var fs = require('fs-extra'); var temp = require('temp'); var specHelpers = require('./spec-helpers'); -describe('Visible', function() { +describe('BuildView', function() { var directory = null; var workspaceElement = null; @@ -160,4 +160,90 @@ describe('Visible', function() { }); }); }); + + describe('when panel orientation is altered', function () { + it('should show the panel at the bottom spot', function () { + expect(workspaceElement.querySelector('.build')).not.toExist(); + atom.config.set('build.panelOrientation', 'Bottom'); + + fs.writeFileSync(directory + '.atom-build.json', JSON.stringify({ + cmd: 'echo this will fail && exit 1', + })); + atom.commands.dispatch(workspaceElement, 'build:trigger'); + + waitsFor(function() { + return workspaceElement.querySelector('.build .title') && + workspaceElement.querySelector('.build .title').classList.contains('error'); + }); + + runs(function() { + var bottomPanels = atom.workspace.getBottomPanels(); + expect(bottomPanels.length).toEqual(1); + expect(bottomPanels[0].item.constructor.name).toEqual('BuildView'); + }); + }); + + it('should show the panel at the bottom spot', function () { + expect(workspaceElement.querySelector('.build')).not.toExist(); + atom.config.set('build.panelOrientation', 'Left'); + + fs.writeFileSync(directory + '.atom-build.json', JSON.stringify({ + cmd: 'echo this will fail && exit 1', + })); + atom.commands.dispatch(workspaceElement, 'build:trigger'); + + waitsFor(function() { + return workspaceElement.querySelector('.build .title') && + workspaceElement.querySelector('.build .title').classList.contains('error'); + }); + + runs(function() { + var panels = atom.workspace.getLeftPanels(); + expect(panels.length).toEqual(1); + expect(panels[0].item.constructor.name).toEqual('BuildView'); + }); + }); + + it('should show the panel at the bottom spot', function () { + expect(workspaceElement.querySelector('.build')).not.toExist(); + atom.config.set('build.panelOrientation', 'Top'); + + fs.writeFileSync(directory + '.atom-build.json', JSON.stringify({ + cmd: 'echo this will fail && exit 1', + })); + atom.commands.dispatch(workspaceElement, 'build:trigger'); + + waitsFor(function() { + return workspaceElement.querySelector('.build .title') && + workspaceElement.querySelector('.build .title').classList.contains('error'); + }); + + runs(function() { + var panels = atom.workspace.getTopPanels(); + expect(panels.length).toEqual(1); + expect(panels[0].item.constructor.name).toEqual('BuildView'); + }); + }); + + it('should show the panel at the bottom spot', function () { + expect(workspaceElement.querySelector('.build')).not.toExist(); + atom.config.set('build.panelOrientation', 'Right'); + + fs.writeFileSync(directory + '.atom-build.json', JSON.stringify({ + cmd: 'echo this will fail && exit 1', + })); + atom.commands.dispatch(workspaceElement, 'build:trigger'); + + waitsFor(function() { + return workspaceElement.querySelector('.build .title') && + workspaceElement.querySelector('.build .title').classList.contains('error'); + }); + + runs(function() { + var panels = atom.workspace.getRightPanels(); + expect(panels.length).toEqual(1); + expect(panels[0].item.constructor.name).toEqual('BuildView'); + }); + }); + }); }); diff --git a/styles/build.less b/styles/build.less index 9011589c..7b267720 100644 --- a/styles/build.less +++ b/styles/build.less @@ -1,6 +1,10 @@ @import "ui-variables"; +@status-panel-height: 3em; .build { + + height: 100%; + .title { transition: background-color 0.2s ease-in; } @@ -30,16 +34,27 @@ } .output { - transition: height 0.5s ease-in-out; - padding: 10px 0 0 10px; + transition: 0.5s ease-in-out; + transition-property: width, height; + padding: 10px 0 @status-panel-height 10px; min-height: 100px; - overflow-y: auto; + overflow: auto; word-wrap: break-word; white-space: pre-wrap; - list-style: none; font-family: monospace; } + .status { + position: absolute; + bottom: 0; + height: @status-panel-height; + width: 100%; + + h1 { + height: 100%; + } + } + a { text-decoration: underline; }