diff --git a/.gitignore b/.gitignore index 3cd3b8edff..d35cfedc57 100644 --- a/.gitignore +++ b/.gitignore @@ -5,16 +5,10 @@ node_modules npm-debug.log # Signing Identity -script/identity +identity # Resources -resources/virtualbox-*.pkg resources/boot2docker* -resources/mongod -resources/MONGOD_LICENSE.txt -resources/node -resources/NODE_LICENSE.txt -resources/settings.json # Cache cache diff --git a/README.md b/README.md index ca6f0dca08..ae738c4c2b 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![bitHound Score](https://app.bithound.io/kitematic/kitematic/badges/score.svg)](http://app.bithound.io/kitematic/kitematic) + ![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png) Kitematic is a simple application for managing Docker containers on Mac OS X. diff --git a/app/ContainerDetails.react.js b/app/ContainerDetails.react.js index 7dfd520322..a126de1f9c 100644 --- a/app/ContainerDetails.react.js +++ b/app/ContainerDetails.react.js @@ -2,8 +2,6 @@ var _ = require('underscore'); var $ = require('jquery'); var React = require('react/addons'); var Router = require('react-router'); -var Convert = require('ansi-to-html'); -var convert = new Convert(); var ContainerStore = require('./ContainerStore'); var docker = require('./docker'); var exec = require('exec'); @@ -19,67 +17,29 @@ var RouteHandler = Router.RouteHandler; var ContainerDetails = React.createClass({ mixins: [Router.State], _oldHeight: 0, + PAGE_LOGS: 'logs', + PAGE_SETTINGS: 'settings', getInitialState: function () { return { - logs: [] + logs: [], + page: this.PAGE_LOGS }; }, - logs: function () { - this.updateProgress(this.getParams().name); - /*var self = this; - var logs = []; - var index = 0; - docker.client().getContainer(this.getParams().name).logs({ - follow: false, - stdout: true, - stderr: true, - timestamps: true - }, function (err, stream) { - stream.setEncoding('utf8'); - stream.on('data', function (buf) { - // Every other message is a header - if (index % 2 === 1) { - var time = buf.substr(0,buf.indexOf(' ')); - var msg = buf.substr(buf.indexOf(' ')+1); - logs.push(convert.toHtml(self._escapeHTML(msg))); - } - index += 1; - }); - stream.on('end', function (buf) { - self.setState({logs: logs}); - docker.client().getContainer(self.getParams().name).logs({ - follow: true, - stdout: true, - stderr: true, - timestamps: true, - tail: 0 - }, function (err, stream) { - stream.setEncoding('utf8'); - stream.on('data', function (buf) { - // Every other message is a header - if (index % 2 === 1) { - var time = buf.substr(0,buf.indexOf(' ')); - var msg = buf.substr(buf.indexOf(' ')+1); - logs.push(convert.toHtml(self._escapeHTML(msg))); - self.setState({logs: logs}); - } - index += 1; - }); - }); - }); - });*/ - }, componentWillReceiveProps: function () { - this.logs(); - }, - componentWillMount: function () { - this.logs(); + this.setState({ + page: this.PAGE_LOGS + }); + ContainerStore.fetchLogs(this.getParams().name, function () { + this.updateLogs(); + }.bind(this)); }, componentDidMount: function () { ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress); + ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs); }, componentWillUnmount: function () { ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress); + ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs); }, componentDidUpdate: function () { var parent = $('.details-logs'); @@ -92,18 +52,31 @@ var ContainerDetails = React.createClass({ } this._oldHeight = parent[0].scrollHeight - parent.height(); }, + updateLogs: function (name) { + if (name && name !== this.getParams().name) { + return; + } + this.setState({ + logs: ContainerStore.logs(this.getParams().name) + }); + }, updateProgress: function (name) { + console.log('progress', name, ContainerStore.progress(name)); if (name === this.getParams().name) { this.setState({ progress: ContainerStore.progress(name) }); } }, - _escapeHTML: function (html) { - var text = document.createTextNode(html); - var div = document.createElement('div'); - div.appendChild(text); - return div.innerHTML; + showLogs: function () { + this.setState({ + page: this.PAGE_LOGS + }); + }, + showSettings: function () { + this.setState({ + page: this.PAGE_SETTINGS + }); }, handleClick: function (name) { var container = this.props.container; @@ -146,20 +119,13 @@ var ContainerDetails = React.createClass({ var state; if (this.props.container.State.Running) { - state =

running

; + state =

running

; } else if (this.props.container.State.Restarting) { - state =

restarting

; - } - - var progress; - if (this.state.progress > 0 && this.state.progress != 1) { - progress = ( -
- -
- ); - } else { - progress =
; + state =

restarting

; + } else if (this.props.container.State.Paused) { + state =

paused

; + } else if (this.props.container.State.Downloading) { + state =

downloading

; } var button; @@ -169,17 +135,75 @@ var ContainerDetails = React.createClass({ button = View; } + var body; + if (this.props.container.State.Downloading) { + body = ( +
+ +
+ ); + } else { + if (this.state.page === this.PAGE_LOGS) { + body = ( +
+
+ {logs} +
+
+ ); + } else { + body = ( +
+
+
+
+ ); + } + } + + var textButtonClasses = React.addons.classSet({ + 'btn': true, + 'btn-action': true, + 'only-icon': true, + 'active': this.state.page === this.PAGE_LOGS + }); + + var gearButtonClass = React.addons.classSet({ + 'btn': true, + 'btn-action': true, + 'only-icon': true, + 'active': this.state.page === this.PAGE_SETTINGS + }); + + var name = this.props.container.Name; + var image = this.props.container.Config.Image; + return (
-

{this.getParams().name}

View -
- {progress} -
-
- {logs} +
+

{name}

{state}

Image

{image}

+
+
+
+ View +
+
+ Volumes +
+
+ Restart +
+
+ Terminal +
+
+ + +
+ {body}
); } diff --git a/app/ContainerModal.react.js b/app/ContainerModal.react.js index c3e0e614be..383ccf0191 100644 --- a/app/ContainerModal.react.js +++ b/app/ContainerModal.react.js @@ -16,6 +16,14 @@ var ContainerModal = React.createClass({ }, componentDidMount: function () { this.refs.searchInput.getDOMNode().focus(); + ContainerStore.on(ContainerStore.SERVER_RECOMMENDED_EVENT, this.update); + }, + update: function () { + if (!this.state.query.length) { + this.setState({ + results: ContainerStore.recommended() + }); + } }, search: function (query) { if (this._searchRequest) { @@ -91,7 +99,10 @@ var ContainerModal = React.createClass({
- +
+ Create + +
); @@ -119,6 +130,12 @@ var ContainerModal = React.createClass({ hidden: !this.state.loading, loading: true }); + var magnifierClasses = React.addons.classSet({ + hidden: this.state.loading, + icon: true, + 'icon-magnifier': true, + 'search-icon': true + }); return ( @@ -126,6 +143,7 @@ var ContainerModal = React.createClass({
+
@@ -137,7 +155,7 @@ var ContainerModal = React.createClass({
diff --git a/app/ContainerStore.js b/app/ContainerStore.js index 97997080d3..05a9c84998 100644 --- a/app/ContainerStore.js +++ b/app/ContainerStore.js @@ -1,6 +1,9 @@ var EventEmitter = require('events').EventEmitter; var async = require('async'); var assign = require('object-assign'); +var Stream = require('stream'); +var Convert = require('ansi-to-html'); +var convert = new Convert(); var docker = require('./docker'); var registry = require('./registry'); var $ = require('jquery'); @@ -9,6 +12,7 @@ var _ = require('underscore'); var _recommended = []; var _containers = {}; var _progress = {}; +var _logs = {}; var ContainerStore = assign(EventEmitter.prototype, { CLIENT_CONTAINER_EVENT: 'client_container', @@ -19,10 +23,6 @@ var ContainerStore = assign(EventEmitter.prototype, { _pullScratchImage: function (callback) { var image = docker.client().getImage('scratch:latest'); image.inspect(function (err, data) { - if (err) { - callback(err); - return; - } if (!data) { docker.client().pull('scratch:latest', function (err, stream) { if (err) { @@ -69,6 +69,7 @@ var ContainerStore = assign(EventEmitter.prototype, { stream.on('data', function (str) { var data = JSON.parse(str); + console.log(data); if (data.status === 'Already exists') { layerProgress[data.id] = 1; @@ -97,6 +98,12 @@ var ContainerStore = assign(EventEmitter.prototype, { }); }); }, + _escapeHTML: function (html) { + var text = document.createTextNode(html); + var div = document.createElement('div'); + div.appendChild(text); + return div.innerHTML; + }, _createContainer: function (image, name, callback) { var existing = docker.client().getContainer(name); var self = this; @@ -124,7 +131,6 @@ var ContainerStore = assign(EventEmitter.prototype, { }); }, _createPlaceholderContainer: function (imageName, name, callback) { - console.log('_createPlaceholderContainer', imageName, name); var self = this; this._pullScratchImage(function (err) { if (err) { @@ -194,7 +200,6 @@ var ContainerStore = assign(EventEmitter.prototype, { // If the event is delete, remove the container if (data.status === 'destroy') { - console.log('destroy'); var container = _.findWhere(_.values(_containers), {Id: data.id}); delete _containers[container.Name]; this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status); @@ -278,6 +283,54 @@ var ContainerStore = assign(EventEmitter.prototype, { } }); }, + fetchLogs: function (name, callback) { + if (_logs[name]) { + callback(); + } + _logs[name] = []; + var index = 0; + var self = this; + docker.client().getContainer(name).logs({ + follow: false, + stdout: true, + stderr: true, + timestamps: true + }, function (err, stream) { + stream.setEncoding('utf8'); + stream.on('data', function (buf) { + // Every other message is a header + if (index % 2 === 1) { + var time = buf.substr(0,buf.indexOf(' ')); + var msg = buf.substr(buf.indexOf(' ')+1); + _logs[name].push(convert.toHtml(self._escapeHTML(msg))); + self.emit(self.SERVER_LOGS_EVENT, name); + } + index += 1; + }); + stream.on('end', function (buf) { + callback(); + docker.client().getContainer(name).logs({ + follow: true, + stdout: true, + stderr: true, + timestamps: true, + tail: 0 + }, function (err, stream) { + stream.setEncoding('utf8'); + stream.on('data', function (buf) { + // Every other message is a header + if (index % 2 === 1) { + var time = buf.substr(0,buf.indexOf(' ')); + var msg = buf.substr(buf.indexOf(' ')+1); + _logs[name].push(convert.toHtml(self._escapeHTML(msg))); + self.emit(self.SERVER_LOGS_EVENT, name); + } + index += 1; + }); + }); + }); + }); + }, create: function (repository, tag, callback) { tag = tag || 'latest'; var self = this; @@ -338,7 +391,7 @@ var ContainerStore = assign(EventEmitter.prototype, { return _progress[name]; }, logs: function (name) { - return logs[name]; + return _logs[name] || []; } }); diff --git a/app/Containers.react.js b/app/Containers.react.js index 6127a402fd..30a640273b 100644 --- a/app/Containers.react.js +++ b/app/Containers.react.js @@ -80,12 +80,10 @@ var Containers = React.createClass({
-

containers

+

My Containers

}> -
- -
+
diff --git a/app/NoContainers.react.js b/app/NoContainers.react.js index 84bfc13dda..4d15223be6 100644 --- a/app/NoContainers.react.js +++ b/app/NoContainers.react.js @@ -5,7 +5,6 @@ var NoContainers = React.createClass({ render: function () { return (
-

No Containers

); diff --git a/app/images/downloading-arrow-white.png b/app/images/downloading-arrow-white.png new file mode 100644 index 0000000000..a986393f10 Binary files /dev/null and b/app/images/downloading-arrow-white.png differ diff --git a/app/images/downloading-arrow-white@2x.png b/app/images/downloading-arrow-white@2x.png new file mode 100644 index 0000000000..a2de93b9ec Binary files /dev/null and b/app/images/downloading-arrow-white@2x.png differ diff --git a/app/images/downloading-white.png b/app/images/downloading-white.png new file mode 100644 index 0000000000..3344a6dbd5 Binary files /dev/null and b/app/images/downloading-white.png differ diff --git a/app/images/downloading-white@2x.png b/app/images/downloading-white@2x.png new file mode 100644 index 0000000000..21a062a211 Binary files /dev/null and b/app/images/downloading-white@2x.png differ diff --git a/app/images/downloading.png b/app/images/downloading.png deleted file mode 100644 index 2dd4acfa27..0000000000 Binary files a/app/images/downloading.png and /dev/null differ diff --git a/app/images/downloading@2x.png b/app/images/downloading@2x.png index b3d8b3a16d..d974261408 100644 Binary files a/app/images/downloading@2x.png and b/app/images/downloading@2x.png differ diff --git a/app/images/loading.png b/app/images/loading.png index b8d6f5b1e2..6217c07530 100644 Binary files a/app/images/loading.png and b/app/images/loading.png differ diff --git a/app/images/loading@2x.png b/app/images/loading@2x.png index 3f6434a0e6..102eae7805 100644 Binary files a/app/images/loading@2x.png and b/app/images/loading@2x.png differ diff --git a/app/images/roundedcontainer.png b/app/images/roundedcontainer.png index c32e2d6b0b..897e09e792 100644 Binary files a/app/images/roundedcontainer.png and b/app/images/roundedcontainer.png differ diff --git a/app/images/roundedcontainer@2x.png b/app/images/roundedcontainer@2x.png index 1acea98153..a220e3d093 100644 Binary files a/app/images/roundedcontainer@2x.png and b/app/images/roundedcontainer@2x.png differ diff --git a/app/images/running-white.png b/app/images/running-white.png new file mode 100644 index 0000000000..682d7b4685 Binary files /dev/null and b/app/images/running-white.png differ diff --git a/app/images/running-white@2x.png b/app/images/running-white@2x.png new file mode 100644 index 0000000000..a83f28aee8 Binary files /dev/null and b/app/images/running-white@2x.png differ diff --git a/app/images/running.png b/app/images/running.png index 15ccd11b6b..ea129def4f 100644 Binary files a/app/images/running.png and b/app/images/running.png differ diff --git a/app/images/running@2x.png b/app/images/running@2x.png index 406bfc05f4..b2777164bf 100644 Binary files a/app/images/running@2x.png and b/app/images/running@2x.png differ diff --git a/app/images/runningwave-white.png b/app/images/runningwave-white.png new file mode 100644 index 0000000000..b564bea7b6 Binary files /dev/null and b/app/images/runningwave-white.png differ diff --git a/app/images/runningwave-white@2x.png b/app/images/runningwave-white@2x.png new file mode 100644 index 0000000000..013a1247fc Binary files /dev/null and b/app/images/runningwave-white@2x.png differ diff --git a/app/images/runningwave.png b/app/images/runningwave.png index 2934116bf3..9833ff0ac1 100644 Binary files a/app/images/runningwave.png and b/app/images/runningwave.png differ diff --git a/app/images/runningwave@2x.png b/app/images/runningwave@2x.png index d20f942fce..cfda2c62a4 100644 Binary files a/app/images/runningwave@2x.png and b/app/images/runningwave@2x.png differ diff --git a/app/images/stopped-white.png b/app/images/stopped-white.png new file mode 100644 index 0000000000..ec950d5c24 Binary files /dev/null and b/app/images/stopped-white.png differ diff --git a/app/images/stopped-white@2x.png b/app/images/stopped-white@2x.png new file mode 100644 index 0000000000..64110715fe Binary files /dev/null and b/app/images/stopped-white@2x.png differ diff --git a/app/styles/containers.less b/app/styles/containers.less index c55773efc4..a2719b89aa 100644 --- a/app/styles/containers.less +++ b/app/styles/containers.less @@ -10,7 +10,7 @@ .sidebar { display: flex; flex-direction: column; - min-width: 240px; + min-width: 280px; margin: 0; box-sizing: border-box; border-right: 1px solid #eee; @@ -22,49 +22,36 @@ display: flex; border-bottom: 1px solid transparent; transition: border-bottom 0.25s; + padding: 0px 10px 0px 10px; &.sep { border-bottom: 1px solid #eee; box-shadow: 0px 2px 3px 0px rgba(0,0,0,0.03); } - h3 { + h4 { align-self: flex-start; - color: #CCD3D5; - font-size: 18px; - font-weight: 400; padding: 0 24px; - margin: 10px 0 0; - font-variant: small-caps; + margin: 14px 0 0; display: inline-block; + font-size: 14px; position: relative; } .create { flex: 1 auto; text-align: right; - - .wrapper { - text-align: center; - display: inline-block; - width: 50px; - - span.icon { - margin-top: 5px; - margin-left: auto; - display: inline-block; - border-radius: 20px; - font-size: 26px; - color: @brand-primary; - } - - &:hover { - span.icon { - color: darken(@brand-primary, 20%); - } + .btn { + margin-top: 4px; + padding: 4px 7px; + font-size: 16px; + position: relative; + .icon { + position: relative; + top: 3px; + left: 1px; } } - } } @@ -83,6 +70,7 @@ margin: 0; min-width: 240px; padding: 0; + margin-top: 4px; display: flex; flex-direction: column; @@ -91,12 +79,41 @@ color: inherit; flex-shrink: 0; cursor: default; + margin: 0px 3px 0px 8px; + outline: none; + padding: 4px 5px; &.active { - background: #eee; + li { border-bottom: none; + border-radius: 40px; + background: @brand-primary; + .name { + color: white; + } + .image { + color: white; + opacity: 0.9; + } - &:hover { + .state-running { + .at2x('running-white.png', 20px, 20px); + + .runningwave { + .at2x('runningwave-white.png', 20px, 20px); + } + } + .state-stopped { + .at2x('stopped-white.png', 20px, 20px); + } + + .state-downloading { + .at2x('downloading-white.png', 20px, 20px); + + .downloading-arrow { + .at2x('downloading-arrow-white.png', 20px, 20px); + } + } } } @@ -111,8 +128,7 @@ li { vertical-align: middle; - padding-bottom: 14px; - margin: 16px 24px 0px; + padding: 10px 16px 10px 16px; display: flex; flex-direction: row; @@ -127,10 +143,10 @@ overflow: hidden; font-size: 14px; font-weight: 400; - color: #555; + color: @gray-darkest; } .image { - color: #999; + color: @gray-lighter; font-size: 12px; font-weight: 400; text-overflow: ellipsis; @@ -210,12 +226,6 @@ } } } - - .status { - font-size: 12px; - font-variant: small-caps; - color: @brand-primary; - } } .no-containers { @@ -224,9 +234,12 @@ align-items: center; justify-content: center; flex-direction: column; + position: relative; h3 { - font-size: 16px; + position: relative; + top: -44px; + font-size: 18px; color: #C7D7D7; } } @@ -244,29 +257,69 @@ .details-header { flex: 0 auto; display: flex; - flex-direction: row; - padding: 0px 45px 14px; + flex-direction: column; + padding: 4px 40px 10px 40px; position: relative; - a { - position: absolute; - right: 30px; - top: -4px; - } - h1 { - font-size: 24px; - margin: 0; - } - h2 { - margin-left: 18px; - font-size: 14px; - font-variant: small-caps; + border-bottom: 1px solid #eee; - &.status { - color: @brand-success; + .details-header-actions { + flex: 0 auto; + display: flex; + flex-direction: row; + margin-top: 24px; + margin-bottom: 6px; + position: relative; + border-bottom: 1px solid transparent; + transition: border-bottom 0.25s; + .action { + flex: 0 auto; + margin-right: 24px; } + .details-header-actions-rhs { + flex: 1 auto; + display: flex; + align-items: right; + justify-content: flex-end; + a.btn { + z-index: 0; + } + } + } - &.image { - + .details-header-info { + display: flex; + flex-direction: row; + a { + position: absolute; + right: 30px; + top: -4px; + } + h1 { + margin: 0; + font-size: 20px; + margin: 0; + color: @gray-darkest; + } + h2 { + &.status { + margin: 8px 0px 0px 16px; + text-transform: uppercase; + font-weight: bold; + font-size: 10px; + &.running { + color: @brand-positive; + } + } + &.image-label { + margin: 8px 0px 0px 30px; + font-size: 10px; + color: @gray-lighter; + } + &.image { + margin: 5px 0px 0px 16px; + font-size: 14px; + color: @gray-normal; + } } } } @@ -279,12 +332,17 @@ .details-logs { flex: 1; overflow: auto; + h4 { + font-size: 14px; + margin-top: 16px; + margin-left: 40px; + } .logs { -webkit-user-select: text; font-family: Menlo; font-size: 12px; - padding: 44px 45px; - color: #595D5E; + padding: 18px 45px; + color: lighten(@gray-normal, 6%); white-space: pre-wrap; p { margin: 0 6px; diff --git a/app/styles/header.less b/app/styles/header.less index 63f8f0d92f..4731611def 100644 --- a/app/styles/header.less +++ b/app/styles/header.less @@ -3,9 +3,10 @@ .header { min-width: 100%; flex: 0; - min-height: 48px; + min-height: 50px; -webkit-app-region: drag; -webkit-user-select: none; + // border-bottom: 1px solid #efefef; &.no-drag { -webkit-app-region: no-drag; diff --git a/app/styles/main.less b/app/styles/main.less index c3861fa157..6c5055a63a 100644 --- a/app/styles/main.less +++ b/app/styles/main.less @@ -15,6 +15,11 @@ html, body { -webkit-font-smoothing: antialiased; -webkit-user-select: none; font-family: 'Clear Sans', sans-serif; + + cursor: default; + img { + pointer-events: none; + } } ::-webkit-scrollbar { @@ -57,12 +62,6 @@ html, body { flex-direction: row; padding: 32px 32px; - .title { - color: #CCD3D5; - font-weight: 400; - font-size: 13px; - } - aside.custom { flex: 0 auto; padding-left: 32px; @@ -76,7 +75,10 @@ html, body { .question { a { - color: #CCD3D5; + color: @gray-lightest; + &:hover { + color: darken(@gray-lightest, 10%); + } } font-size: 10px; text-align: right; @@ -86,27 +88,35 @@ html, body { position: relative; .loading { position: absolute; - right: 9px; - top: 7px; - width: 24px; - height: 24px; + left: 13px; + top: 10px; + width: 20px; + height: 20px; -webkit-animation-name: spin; -webkit-animation-duration: 1.8s; -webkit-animation-iteration-count: infinite; -webkit-animation-timing-function: linear; } - + .search-icon { + font-size: 20px; + color: @gray-lighter; + position: absolute; + top: 9px; + left: 14px; + } input { border-radius: 20px; font-size: 13px; height: 38px; - padding: 8px 16px; - font-weight: 400; - color: #666; + padding: 8px 16px 8px 40px; + color: @gray-darkest; + margin-bottom: 3px; + border-color: @gray-lightest; + box-shadow: none; &:focus { box-shadow: none; - border-color: #bbb; + border-color: @gray-lighter; } &::-webkit-input-placeholder { @@ -134,17 +144,25 @@ html, body { } ul { + margin-top: 10px; list-style: none; - color: #555; padding: 0; li { + &:hover { + background-color: lighten(@gray-lightest, 17.5%); + } display: flex; flex-direction: row; - margin: 12px; + padding: 8px 14px 5px 14px; + //margin: 12px; border-bottom: 1px solid #eee; + &:last-child { + border-bottom: 0; + } .info { .name { + color: @gray-darkest; max-width: 278px; img { margin-right: 6px; @@ -156,7 +174,7 @@ html, body { text-overflow: ellipsis; } .properties { - color: #A7A7A7; + color: @gray-lighter; margin-top: 2px; .star-count { @@ -178,6 +196,8 @@ html, body { flex: 0 auto; } .action { + position: relative; + top: 5px; text-align: right; flex: 1 auto; } diff --git a/app/styles/theme.less b/app/styles/theme.less index 41d5e83de2..fd87da3215 100644 --- a/app/styles/theme.less +++ b/app/styles/theme.less @@ -6,6 +6,12 @@ @import "bootstrap/mixins.less"; +h4 { + font-size: 13px; + color: @gray-normal; + font-weight: 400; +} + // // Buttons // -------------------------------------------------- @@ -21,32 +27,83 @@ } // Mixin for generating new styles -.btn-styles(@btn-color: #555) { +.btn-styles(@btn-color: @gray-normal) { + transition: all 0.1s; .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners - background-repeat: repeat-x; - // border-color: darken(@btn-color, 14%); + border-color: @btn-color; + color: @btn-color; &:hover, - &:focus { - background-color: darken(@btn-color, 12%); + &:focus { + border-color: darken(@btn-color, 10%); + color: darken(@btn-color, 10%); + cursor: default; + box-shadow: none; + } + + &:active { + background-color: lighten(@btn-color, 45%); + border-color: darken(@btn-color, 10%); + color: darken(@btn-color, 10%); + box-shadow: none; + } + + &.active { + background-color: @btn-color; + color: white; + box-shadow: none; + box-shadow: none; } &:disabled, &[disabled] { - background-color: darken(@btn-color, 12%); + opacity: 0.5; + } +} + +.btn-group { + .btn { + .icon-dropdown { + &.icon:before { + top: 7px; + margin-left: 0px; + margin-right: 4px; + } + } } } // Common styles .btn { + font-size: 12px; + background-color: transparent; + color: @gray-normal; + border: 1px solid @gray-normal; border-radius: 25px; box-shadow: none; font-weight: 400; text-shadow: none; + padding: 6px 14px 6px 14px; + height: 32px; + cursor: default; + + .content { + position: relative; + top: -4px; + margin-left: 5px; + margin-right: 5px; + } + + .icon { + position: relative; + font-size: 16px; + } + // Remove the gradient for the pressed/active state &:active, &.active { background-image: none; + box-shadow: none; } &:focus, @@ -57,6 +114,9 @@ } // Apply the mixin to the buttons +.btn-action { + .btn-styles(@brand-action); +} .btn-default { .btn-styles(@btn-default-bg); } .btn-primary { .btn-styles(@btn-primary-bg); } .btn-success { .btn-styles(@btn-success-bg); } diff --git a/app/styles/variables.less b/app/styles/variables.less index e11e816a84..cff01eb030 100644 --- a/app/styles/variables.less +++ b/app/styles/variables.less @@ -1,4 +1,10 @@ @brand-primary: #24B8EB; -@brand-action: #49CEF2; -@brand-positive: #3AD86D; -@brand-negative: #F74B1F; +@brand-action: #24B8EB; +@brand-positive: #65E100; +@brand-negative: #F47A45; + +@gray-darkest: #253237; +@gray-darker: #394C51; +@gray-normal: #546C70; +@gray-lighter: #7A9999; +@gray-lightest: #C7D7D7; diff --git a/browser/main.js b/browser/main.js index 03c1328acc..92627b2b7b 100644 --- a/browser/main.js +++ b/browser/main.js @@ -27,9 +27,9 @@ app.on('activate-with-no-open-windows', function () { app.on('ready', function() { var windowOptions = { - width: 1200, - height: 800, - 'min-width': 960, + width: 1000, + height: 700, + 'min-width': 1000, 'min-height': 700, resizable: true, frame: false diff --git a/script/deps b/deps similarity index 90% rename from script/deps rename to deps index 77bcae2055..059abaf35c 100755 --- a/script/deps +++ b/deps @@ -1,7 +1,6 @@ #!/bin/bash -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -BASE=$DIR/.. +BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" export BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") export BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION diff --git a/gulpfile.js b/gulpfile.js index 6be303b775..c591d8e552 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -22,21 +22,28 @@ var downloadatomshell = require('gulp-download-atom-shell'); var packagejson = require('./package.json'); var http = require('http'); var react = require('gulp-react'); +var fs = require('fs'); var dependencies = Object.keys(packagejson.dependencies); var devDependencies = Object.keys(packagejson.devDependencies); var options = { - dev: process.argv.indexOf('release') === -1, + dev: process.argv.indexOf('release') === -1 && process.argv.indexOf('test') === -1, test: process.argv.indexOf('test') !== -1, filename: 'Kitematic.app', name: 'Kitematic', - signing_identity: process.env.XCODE_SIGNING_IDENTITY + signing_identity: fs.readFileSync('./identity') }; gulp.task('js', function () { gulp.src('./app/**/*.js') + .pipe(plumber(function(error) { + gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); + // emit the end event, to properly end the task + this.emit('end'); + })) .pipe(react()) - .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')); + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); }); gulp.task('specs', function () { @@ -74,7 +81,7 @@ gulp.task('images', function() { svgoPlugins: [{removeViewBox: false}] })) .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) - .pipe(gulpif(options.dev && !options.test, livereload())); + .pipe(gulpif(options.dev, livereload())); }); gulp.task('styles', function () { @@ -103,11 +110,11 @@ gulp.task('download', function (cb) { gulp.task('copy', function () { gulp.src('./app/index.html') .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) - .pipe(gulpif(options.dev && !options.test, livereload())); + .pipe(gulpif(options.dev, livereload())); gulp.src('./app/fonts/**') .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) - .pipe(gulpif(options.dev && !options.test, livereload())); + .pipe(gulpif(options.dev, livereload())); }); gulp.task('dist', function (cb) { diff --git a/package.json b/package.json index 8bb576cd1d..52e6f7c3f8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "bugs": "https://github.com/kitematic/kitematic/issues", "scripts": { "start": "gulp", - "preinstall": "./script/deps", + "preinstall": "./deps", "test": "gulp test", "release": ". ./script/identity && gulp release" },