Initial commit

This commit is contained in:
Nuintun 2015-11-20 12:47:35 +08:00
parent 72f3cc1ad8
commit a20d1def1b
29 changed files with 12094 additions and 0 deletions

3
.gitignore vendored
View File

@ -25,3 +25,6 @@ build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
#WebStorm
.idea

BIN
app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

341
bin/app-configure.js Normal file
View File

@ -0,0 +1,341 @@
/**
* Created by nuintun on 2015/11/18.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var join = path.join;
// module to control application life
var app = require('app');
var ipc = require('ipc-main');
var dialog = require('dialog');
var shell = require('shell');
const USERDATA = app.getPath('userData');
const USERDESKTOP = app.getPath('userDesktop');
const CONFIGURENAME = 'command-manager.config';
const CONFIGUREPATH = join(USERDATA, CONFIGURENAME);
const DEFAULTCONFIGURE = { projects: [] };
const ERRORMESSAGE = {
NONEXISTS: '不存在',
READERROR: '读取失败',
WRITEERROR: '写入失败',
PARSEERROR: '解析失败',
VALIDERROR: '校验失败'
};
/**
* ConfigureError
* @param code
* @param message
* @constructor
*/
function ConfigureError(code, message){
this.code = code;
this.message = message;
this.name = 'ConfigureError';
}
// ConfigureError prototype
ConfigureError.prototype = Object.create(Error.prototype);
ConfigureError.prototype.constructor = ConfigureError;
/**
* verify configure
* @param configure
* @returns {*}
*/
function verifyConfigure(configure){
if (!configure) {
return false;
}
if (!Array.isArray(configure.projects)) {
return false;
}
return configure.projects.every(function (project){
if (!project.name || typeof project.name !== 'string') {
return false;
}
if (Array.isArray(project.env)) {
if (
!project.env.every(function (env){
return env.name && typeof env.name === 'string'
&& env.value && typeof env.value === 'string';
})
) {
return false;
}
}
if (Array.isArray(project.command)) {
if (
project.command.every(function (command){
return command.name && typeof command.name === 'string'
&& command.value && typeof command.value === 'string';
})
) {
return false;
}
}
return true;
});
}
/**
* unique array by a track
* @param array
* @param progress
* @param track
* @returns {Array}
*/
function unique(array, progress, track){
var cache = {};
progress = typeof progress === 'function' ? progress : function (){};
track = track && typeof track === 'string' ? track : 'name';
return array.filter(function (item){
var key = item[track];
if (cache[key]) {
return false;
} else {
cache[key] = true;
progress.apply(this, arguments);
return true;
}
});
}
/**
* filter configure
* @param configure
* @returns {*}
*/
function filterConfigure(configure){
configure.projects = unique(configure.projects, function (project){
if (project.env) {
project.env = unique(project.env);
}
if (project.command) {
project.command = unique(project.command);
}
});
return configure;
}
/**
* AppConfigure
* @param window
* @param tray
* @constructor
*/
function AppConfigure(window, tray){
this.window = window;
this.tray = tray;
this.title = window.getTitle();
this.init();
}
/**
* AppConfigure prototype
*/
AppConfigure.prototype = {
init: function (){
var context = this;
this.create();
ipc.on('app-configure', function (event, command){
switch (command) {
case 'import':
context.import(function (configure){
this.showMessageBox('配置文件导入成功!', { type: 'info' });
event.sender.send('app-configure', 'refresh', configure);
}, function (error){
this.showMessageBox('配置文件' + error.message + '');
});
break;
case 'export':
context.export(function (path){
this.showMessageBox('配置文件导出成功!', { type: 'info' }, function (){
shell.showItemInFolder(path);
});
}, function (){
this.showMessageBox('配置文件导出失败!');
});
break;
case 'refresh':
context.read(function (configure){
event.sender.send('app-configure', 'refresh', configure);
}, function (error){
context.showMessageBox('配置文件' + error.message + '', function (){
context.window.close();
});
});
break;
case 'add':
context.save(function (configure){
}, function (configure){
});
break;
}
});
},
create: function (){
var context = this;
fs.stat(CONFIGUREPATH, function (error, stats){
if (error || !stats.isFile()) {
context.save(DEFAULTCONFIGURE, null, function (){
context.showMessageBox('配置文件创建失败,请用管理员模式运行重试!', function (){
context.window.close();
});
});
}
});
},
save: function (configure, done, fail){
var context = this;
done = typeof done === 'function' ? done : function (){};
fail = typeof fail === 'function' ? fail : function (){};
fs.writeFile(CONFIGUREPATH, JSON.stringify(configure), function (error){
if (error) {
var code = error.code === 'ENOENT' ? 'NONEXISTS' : 'WRITEERROR';
fail.call(context, new ConfigureError(code, ERRORMESSAGE[code]));
} else {
done.call(context, configure);
}
});
},
read: function (done, fail){
var context = this;
done = typeof done === 'function' ? done : function (){};
fail = typeof fail === 'function' ? fail : function (){};
fs.readFile(CONFIGUREPATH, function (error, configure){
if (error) {
var code = error.code === 'ENOENT' ? 'NONEXISTS' : 'READERROR';
fail.call(context, new ConfigureError(code, ERRORMESSAGE[code]));
} else {
try {
configure = JSON.parse(configure);
} catch (error) {
return fail.call(context, new ConfigureError('PARSEERROR', ERRORMESSAGE.PARSEERROR));
}
}
done.call(context, configure);
});
},
import: function (done, fail){
var context = this;
done = typeof done === 'function' ? done : function (){};
fail = typeof fail === 'function' ? fail : function (){};
// show open dialog
dialog.showOpenDialog(this.window, {
title: this.title,
defaultPath: CONFIGURENAME,
properties: ['openFile'],
filters: [{ name: 'Config Files', extensions: ['config'] }]
}, function (paths){
if (paths) {
fs.readFile(paths[0], function (error, configure){
if (error) {
var code = error.code === 'ENOENT' ? 'NONEXISTS' : 'READERROR';
fail.call(context, new ConfigureError(code, ERRORMESSAGE[code]));
} else {
try {
configure = JSON.parse(configure);
} catch (error) {
return fail.call(context, new ConfigureError('PARSEERROR', ERRORMESSAGE.PARSEERROR));
}
// verify configure
var invalid = !verifyConfigure(configure);
// invalid configure
if (invalid) {
return fail.call(context, new ConfigureError('VALIDERROR', ERRORMESSAGE.VALIDERROR));
}
// filter configure
configure = filterConfigure(configure);
// save configure
context.save(configure, done, fail);
}
});
}
});
},
export: function (done, fail){
var context = this;
done = typeof done === 'function' ? done : function (){};
fail = typeof fail === 'function' ? fail : function (){};
// show save dialog
dialog.showSaveDialog(this.window, {
title: this.title,
defaultPath: join(USERDESKTOP, CONFIGURENAME),
filters: [{ name: 'Config Files', extensions: ['config'] }]
}, function (path){
if (path) {
fs.createReadStream(CONFIGUREPATH)
.on('error', function (error){
var code = error.code === 'ENOENT' ? 'NONEXISTS' : 'READERROR';
fail.call(context, new ConfigureError(code, ERRORMESSAGE[code]));
})
.pipe(fs.createWriteStream(path))
.on('finish', function (){
done.call(context, path);
})
.on('error', function (error){
var code = error.code === 'ENOENT' ? 'NONEXISTS' : 'WRITEERROR';
fail.call(context, new ConfigureError(code, ERRORMESSAGE[code]));
});
}
});
},
showMessageBox: function (message, options, callback){
if (typeof options === 'function') {
callback = options;
options = { message: message };
}
options = options || {};
options.title = this.title;
options.message = message;
options.type = options.type || 'error';
options.buttons = options.buttons || [];
dialog.showMessageBox(this.window, options, callback);
}
};
module.exports = AppConfigure;

28
bin/open-directory.js Normal file
View File

@ -0,0 +1,28 @@
/**
* Created by nuintun on 2015/11/18.
*/
'use strict';
// module to control application life
var ipc = require('ipc-main');
var dialog = require('dialog');
/**
* open directory
* @param window
*/
module.exports = function (window){
// listen open directory ipc
ipc.on('open-directory', function (event, path, uid){
dialog.showOpenDialog(window, {
title: window.getTitle(),
properties: ['openDirectory'],
defaultPath: path || ''
}, function (directorys){
if (directorys) {
event.sender.send('select-directory', directorys, uid);
}
});
});
};

59
bin/window-control.js Normal file
View File

@ -0,0 +1,59 @@
/**
* Created by nuintun on 2015/11/18.
*/
'use strict';
// module to control application life
var ipc = require('ipc-main');
/**
* window control
* @param icon
* @param window
* @param tray
*/
module.exports = function (icon, window, tray){
// bind maximize event
window.on('maximize', function (event){
event.sender.send('is-maximized', true);
});
// bind unmaximize event
window.on('unmaximize', function (event){
event.sender.send('is-maximized', false);
});
// bind tray double-click event
tray.on('double-click', function (){
window.show();
});
// listen window ipc
ipc.on('window', function (event, command){
switch (command) {
case 'tray':
var title = window.getTitle();
window.hide();
tray.displayBalloon({
icon: icon,
title: title,
content: title + '正在后台运行!'
});
break;
case 'close':
window.close();
break;
case 'maximize':
window.maximize();
break;
case 'unmaximize':
window.unmaximize();
break;
case 'is-maximized':
event.sender.send('is-maximized', window.isMaximized());
break;
}
});
};

136
css/base.css Normal file
View File

@ -0,0 +1,136 @@
@charset "utf-8";
/* alice.base 样式模块 */
/* 防止用户自定义背景颜色对网页的影响,添加让用户可以自定义字体 */
html {
color: #000; background: #fff;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
/* 内外边距通常让各个浏览器样式的表现位置不同 */
body, div, dl, dt, dd, ul,
ol, li, h1, h2, h3, h4, h5,
h6, pre, code, form, fieldset,
legend, input, textarea, p, blockquote,
th, td, hr, button, article, aside, details,
figcaption, figure, footer, header, hgroup, menu, nav, section {
margin: 0; padding: 0;
}
/* 重设 HTML5 标签, IE 需要在 js 中 createElement(TAG) */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
display: block;
}
/* HTML5 媒体文件跟 img 保持一致 */
audio, canvas, video {
display: inline-block; *display: inline; *zoom: 1;
}
/* 要注意表单元素并不继承父级 font 的问题 */
body, button, input, select, textarea {
font: 12px/1.5 Microsoft Yahei, tahoma, arial, "Hiragino Sans GB", \5b8b\4f53;
}
input, select, textarea {
font-size: 100%;
}
/* 去掉各Table cell 的边距并让其边重合 */
table {
border-collapse: collapse; border-spacing: 0;
}
/* IE bug fixed: th 不继承 text-align*/
th {
text-align: inherit;
}
/* 去除默认边框 */
fieldset, img {
border: 0;
}
/* ie6 7 8(q) bug 显示为行内表现 */
iframe {
display: block;
}
/* 去掉 firefox 下此元素的边框 */
abbr, acronym {
border: 0; font-variant: normal;
}
/* 一致的 del 样式 */
del {
text-decoration: line-through;
}
address, caption, cite, code, dfn, em, th, var {
font-style: normal;
font-weight: 500;
}
/* 去掉列表前的标识, li 会继承 */
ol, ul {
list-style: none;
}
/* 对齐是排版最重要的因素, 别让什么都居中 */
caption, th {
text-align: left;
}
/* 来自yahoo, 让标题都自定义, 适应多个系统应用 */
h1, h2, h3, h4, h5, h6 {
font-size: 100%;
font-weight: 500;
}
q:before, q:after {
content: '';
}
/* 统一上标和下标 */
sub, sup {
font-size: 75%; line-height: 0; position: relative; vertical-align: baseline;
}
sup { top: -0.5em; }
sub { bottom: -0.25em; }
/* 正常链接 未访问 */
a:link {
color: #08c;
}
/* 鼠标悬停 */
a:hover {
color: #08c;
text-decoration: underline;
}
/* 默认不显示下划线,保持页面简洁 */
ins, a {
text-decoration: none;
}
/* 代码字体 */
code,
kbd,
pre,
samp {
font-family: monospace, serif;
font-size: 1em;
}
/* 清理浮动 */
.fn-clear:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
.fn-clear {
zoom: 1; /* for IE6 IE7 */
}
/* 隐藏, 通常用来与 JS 配合 */
body .fn-hide {
display: none;
}
/* visibility 隐藏, 通常用来与 JS 配合 */
body .fn-invisible {
visibility: hidden;
}
/* 设置内联, 减少浮动带来的bug */
.fn-left,
.fn-right {
display: inline;
}
.fn-left {
float: left;
}
.fn-right {
float: right;
}

456
css/index.css Normal file
View File

@ -0,0 +1,456 @@
@font-face {
font-family: font-icon;
src: url('../fonts/font-icon.svg#font-icon') format('svg'),
url("../fonts/font-icon.woff") format("woff"),
url("../fonts/font-icon.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-variant: normal;
}
[class^="icon-"],
[class*=" icon-"] {
speak: none;
outline: none;
line-height: 1;
font-size: 20px;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
vertical-align: middle;
display: inline-block;
font-family: font-icon, sans-serif;
/* enable ligatures */
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
/* better font rendering */
-webkit-font-smoothing: antialiased;
/* min font size */
-webkit-text-size-adjust: none;
}
.icon-play:before {
content: "\e600";
font-size: 16px;
color: #2f9c23;
vertical-align: 3px;
}
.icon-stop:before {
content: "\e601";
font-size: 15px;
color: #f00;
vertical-align: 3px;
}
.icon-folder:before {
content: "\e602";
font-size: 16px;
margin-right: 3px;
vertical-align: 5px;
}
.icon-maximize:before {
content: "\e603";
font-size: 17px;
vertical-align: 2px;
}
.icon-unmaximize:before {
content: "\e604";
font-size: 17px;
vertical-align: 2px;
}
.icon-plus:before {
content: "\e605";
font-size: 17px;
vertical-align: 2px;
}
.icon-cross:before {
content: "\e606";
font-size: 24px;
}
.icon-import:before {
content: "\e607";
font-size: 14px;
vertical-align: 3px;
}
.icon-export:before {
content: "\e608";
font-size: 14px;
vertical-align: 3px;
}
.icon-minimize-tray:before {
content: "\e609";
font-size: 24px;
}
.icon-expand:before {
content: "\e610";
font-size: 20px;
}
.icon-ellipsis:before {
content: "\e611";
font-size: 20px;
}
.icon-trash:before {
content: "\e612";
font-size: 16px;
color: #f00;
vertical-align: 2px;
}
.icon-gear:before {
content: "\e613";
font-size: 18px;
vertical-align: 1px;
}
.icon-ellipsis {
width: 40px;
height: 26px;
line-height: 26px;
text-align: center;
color: #fff !important;
background-color: #f57527;
transition: box-shadow .25s ease-in-out;
}
.icon-ellipsis:hover {
box-shadow: 0 0 rgba(34, 25, 25, 0.15) inset, 0 0 rgba(255, 255, 255, 0.8), 0 0 3px rgb(0, 120, 255);
}
::-webkit-scrollbar-track-piece {
background-color: #fff;
border-radius: 0;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
height: 50px;
background-color: #999;
border-radius: 4px;
outline: 2px solid #fff;
outline-offset: -2px;
border: 2px solid #fff;
}
::-webkit-scrollbar-thumb:hover {
height: 50px;
background-color: #9f9f9f;
border-radius: 4px;
}
input[type=text] {
resize: none;
height: 24px;
line-height: 24px;
outline: none;
padding: 0 5px;
font-size: 13px;
border: 1px solid #ccc;
background-color: #ffffcd;
box-shadow: 2px 2px 3px #ededed inset;
transition: border .25s ease-in-out, box-shadow .25s ease-in-out;
}
input[type=text]:focus {
border: 1px solid rgba(82, 162, 235, .8);
box-shadow: 0 0 rgba(34, 25, 25, 0.15) inset, 0 0 rgba(255, 255, 255, 0.8), 0 0 6px rgba(82, 162, 235, 1);
}
html,
body {
width: 100%; height: 100%; overflow: hidden;
}
body {
border: 1px solid #ccc;
box-sizing: border-box;
min-width: 1024px; !important;
min-height: 768px !important;
}
a {
outline: none;
}
a:hover {
text-decoration: none;
}
.ui-button {
color: #fff;
border: none;
outline: none;
background-color: #0e7fd4;
height: 26px;
line-height: 26px;
padding: 0 13px;
font-size: 13px;
overflow: auto;
display: inline-block;
vertical-align: middle;
cursor: pointer;
transition: box-shadow .25s ease-in-out;
}
.ui-button:hover {
box-shadow: 0 0 rgba(34, 25, 25, 0.15) inset, 0 0 rgba(255, 255, 255, 0.8), 0 0 3px rgb(0, 120, 255);
}
.ui-button-orange {
background-color: #f57527;
}
header {
padding: 3px 6px;
position: relative;
-webkit-app-region: drag;
border-bottom: 1px solid #ccc;
}
header [class^="icon-"],
header [class*=" icon-"] {
-webkit-app-region: no-drag;
}
.icon-trash,
.ui-project-add,
.ui-import-configure,
.ui-export-configure {
transition: transform .09s linear;
}
.icon-gear {
transition: transform .15s linear;
}
.ui-window-control a {
display: inline-block; color: #9c9c9c;
transition: transform .25s linear;
}
.icon-gear:hover,
.ui-window-control a:hover {
transform: rotate(360deg);
}
.icon-trash:hover,
.ui-project-add:hover,
.ui-import-configure:hover,
.ui-export-configure:hover {
transform: scale(1.2);
}
.ui-project-add,
.ui-import-configure,
.ui-export-configure {
font-size: 0;
line-height: 0;
display: inline-block;
vertical-align: middle;
}
.ui-project-add .icon-expand {
margin-left: -5px;
}
.ui-import-configure {
margin: 0 1px 0 6px;
}
.ui-popup {
position: absolute;
top: 31px;
left: 5px;
border: 1px solid #ccc;
z-index: 1;
background-color: #fff;
box-shadow: 0 0 5px 0 #a9a9a9;
}
.ui-popup input,
.ui-project-configure input {
vertical-align: middle;
}
.ui-popup-arrow {
position: absolute;
top: -8px; left: 9px;
z-index: 1;
}
.ui-popup-arrow em,
.ui-popup-arrow span {
width: 0; height: 0;
border: solid rgba(255, 255, 255, 0);
position: absolute;
overflow: hidden;
}
.ui-popup-arrow em {
border-width: 0 7px 7px;
border-bottom-color: #ccc;
}
.ui-popup-arrow span {
top: 1px;
border-width: 0 7px 7px;
border-bottom-color: #fff;
}
.ui-popup-content {
padding: 20px;
}
.ui-popup-content li,
.ui-project-configure li {
color: #333;
margin: 0 0 16px 0;
}
.ui-popup-content strong,
.ui-project-configure strong {
font-size: 13px;
display: inline-block;
vertical-align: top;
}
.ui-popup-content input[type=text],
.ui-project-configure input[type=text] {
width: 180px;
}
.ui-popup-content .ui-popup-control {
margin: 0;
text-align: center;
}
.ui-project-tree {
box-sizing: border-box;
width: 20%;
min-height: 735px !important;
height: calc(100% - 31px);
overflow: auto;
}
.ui-project-tree li:nth-child(odd) {
background-color: #eee;
}
.ui-project-tree li {
color: #333;
width: 100%;
overflow: hidden;
cursor: pointer;
}
.ui-project-tree li a {
width: 100%;
height: 28px;
line-height: 28px;
padding: 0 0 0 10px;
box-sizing: border-box;
display: inline-block;
vertical-align: middle;
}
.ui-project-main {
box-sizing: border-box;
width: 80%;
height: calc(100% - 31px);
min-height: 735px !important;
overflow: hidden;
}
.ui-project-stage,
.ui-project-setting,
.ui-project-setting form {
width: 100%;
height: 100%;
}
.ui-control-bar {
height: 39px;
line-height: 39px;
border-left: 1px solid #ccc;
}
.ui-control-bar a {
display: inline-block;
}
.ui-control-operate a {
height: 39px;
line-height: 39px;
}
.ui-control-bar .icon-trash {
margin: 0 0 0 10px;
}
.ui-command {
position: relative;
}
.ui-command li {
float: left;
font-size: 14px;
color: #333;
}
.ui-command a {
padding: 0 6px;
}
.ui-command-more .icon-expand {
font-size: 24px;
color: #08c;
margin: -4px 0 0 -4px;
}
.ui-command-popup {
position: absolute;
top: 39px; right: 0;
background-color: #eee;
}
.ui-command-popup li {
float: none;
line-height: 30px;
}
.ui-command-popup li a {
padding: 0 10px;
}
.ui-project-tree a,
.ui-command li a,
.ui-command-popup li a {
color: inherit;
}
.ui-project-tree a:hover,
.ui-project-tree .active,
.ui-command a:hover {
background-color: #0e7fd4;
color: #fff;
}
.ui-command a:hover .icon-play:before {
color: #34bd24;
}
.ui-project-tree a:hover .icon-folder:before,
.ui-command a:hover .icon-expand:before {
color: #fff;
}
.ui-project-terminal,
.ui-project-configure {
width: 100%;
box-sizing: border-box;
height: calc(100% - 39px);
border-top: 1px dashed #ccc;
border-left: 1px solid #ccc;
}
.ui-project-terminal {
background-color: #181818;
border-left: 1px dashed #ccc;
}
.ui-control-bar .ui-button-orange {
margin: 0 10px 0 0;
}
.ui-project-configure {
overflow: auto;
text-align: center;
}
.ui-project-configure ul {
padding: 30px 0;
text-align: left;
display: inline-block;
}
.ui-sub-item > label,
.ui-sub-item ul {
vertical-align: top;
}
.ui-sub-item > label {
vertical-align: -3px;
}
.ui-sub-item ul {
padding: 0;
}
.ui-sub-item .ui-action-bar {
margin: 0;
}
.ui-sub-item span {
width: 192px;
line-height: 25px;
border-bottom: 1px dotted #ccc;
display: inline-block;
vertical-align: middle;
}
.ui-sub-item .icon-trash {
cursor: pointer;
margin-left: 6px;
vertical-align: middle;
}
.ui-project-tree a,
.ui-sub-item span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ui-sub-item .ui-item-error {
margin: 10px 0 0;
}
.ui-item-error span {
color: #f00;
border-bottom-color: #f00;
}
.ui-item-error .icon-expand {
position: absolute;
transform: rotate(180deg);
margin-top: -11px;
margin-left: -5px;
}

24
fonts/font-icon.svg Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="font-icon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe600;" glyph-name="play" d="M170.667 42.667v768l640-384zM644.667 426.667l-388.667 233.333v-466.667z" />
<glyph unicode="&#xe601;" glyph-name="stop" d="M256 810.667h512q53 0 90.5-37.5t37.5-90.5v-512q0-53-37.5-90.5t-90.5-37.5h-512q-53 0-90.5 37.5t-37.5 90.5v512q0 53 37.5 90.5t90.5 37.5zM768 725.334h-512q-17.667 0-30.167-12.5t-12.5-30.167v-512q0-17.667 12.5-30.167t30.167-12.5h512q17.667 0 30.167 12.5t12.5 30.167v512q0 17.667-12.5 30.167t-30.167 12.5z" />
<glyph unicode="&#xe602;" glyph-name="folder" d="M170.667 853.334h213.333l85.333-128h384q53 0 90.5-37.5t37.5-90.5v-469.333q0-53-37.5-90.5t-90.5-37.5h-682.667q-53 0-90.5 37.5t-37.5 90.5v597.333q0 53 37.5 90.5t90.5 37.5zM423.667 640l-82.333 128h-170.667q-17.667 0-30.167-12.5t-12.5-30.167v-597.333q0-17.667 12.5-30.167t30.167-12.5h682.667q17.667 0 30.167 12.5t12.5 30.167v469.333q0 17.667-12.5 30.167t-30.167 12.5h-429.667z" />
<glyph unicode="&#xe603;" glyph-name="maximize" d="M597.333 384.334q17.667 0 30.333-12.667l268.333-268.333v195.667q0 17.667 12.5 30.167t30.167 12.5 30.167-12.5 12.5-30.167v-298.667q0-17.667-12.667-30.333t-30.333-12.667h-298.667q-17.667 0-30.167 12.5t-12.5 30.167 12.5 30.167 30.167 12.5h195.667l-268.333 268.333q-12.333 13-12.333 30.333 0 17.667 12.5 30.333t30.167 12.667zM85.333 896h298.667q17.667 0 30.167-12.5t12.5-30.167-12.5-30.167-30.167-12.5h-195.667l268.333-268q12.667-12.667 12.667-30.333t-12.667-30.167-30.333-12.5q-17 0-30.333 12.333l-268 268.333v-195.667q0-17.667-12.5-30.167t-30.167-12.5-30.167 12.5-12.5 30.167v298.667q0 17.667 12.5 30.167t30.167 12.5z" />
<glyph unicode="&#xe604;" glyph-name="unmaximize" d="M85.333 896q18 0 30.333-12.333l268.333-268.333v195.667q0 17.667 12.5 30.167t30.167 12.5 30.167-12.5 12.5-30.167v-298.667q0-17.667-12.667-30.333t-30.333-12.667h-298.667q-17.667 0-30.167 12.5t-12.5 30.167 12.5 30.167 30.167 12.5h195.667l-268.333 268.333q-12.333 12.333-12.333 30.333 0 18.333 12.167 30.5t30.5 12.167zM597.667 384h298.667q17.667 0 30.167-12.5t12.5-30.167-12.5-30.167-30.167-12.5h-195.667l268-268.333q12.667-12.667 12.667-30.333t-12.5-30.167-30.167-12.5q-18 0-30.333 12.333l-268.333 268.333v-195.667q0-17.667-12.5-30.167t-30.167-12.5-30.167 12.5-12.5 30.167v298.667q0 17.667 12.667 30.333t30.333 12.667z" />
<glyph unicode="&#xe605;" glyph-name="plus" d="M512 853.334q17.667 0 30.167-12.5t12.5-30.167v-341.333h341.333q17.667 0 30.167-12.5t12.5-30.167-12.5-30.167-30.167-12.5h-341.333v-341.333q0-17.667-12.5-30.167t-30.167-12.5-30.167 12.5-12.5 30.167v341.333h-341.333q-17.667 0-30.167 12.5t-12.5 30.167 12.5 30.167 30.167 12.5h341.333v341.333q0 17.667 12.5 30.167t30.167 12.5z" />
<glyph unicode="&#xe606;" glyph-name="cross" d="M810.667 768q18.333 0 30.5-12.167t12.167-30.5q0-18-12.333-30.333l-268.667-268.333 268.667-268.333q12.333-12.333 12.333-30.333 0-18.333-12.167-30.5t-30.5-12.167q-18 0-30.333 12.333l-268.333 268.667-268.333-268.667q-12.333-12.333-30.333-12.333-18.333 0-30.5 12.167t-12.167 30.5q0 18 12.333 30.333l268.667 268.333-268.667 268.333q-12.333 12.333-12.333 30.333 0 18.333 12.167 30.5t30.5 12.167q18 0 30.333-12.333l268.333-268.667 268.333 268.667q12.333 12.333 30.333 12.333z" />
<glyph unicode="&#xe607;" glyph-name="import" d="M512 896q17.667 0 30.167-12.5t12.5-30.167v-494.333l140.333 140.667q12.333 12.333 30.333 12.333 18.333 0 30.5-12.167t12.167-30.5q0-18-12.333-30.333l-213.333-213.333q-12.333-12.333-30.333-12.333t-30.333 12.333l-213.333 213.333q-12.333 13-12.333 30.333 0 17.667 12.5 30.167t30.167 12.5q18 0 30.333-12.333l140.333-140.667v494.333q0 17.667 12.5 30.167t30.167 12.5zM938.667 298.667q17.667 0 30.167-12.5t12.5-30.167v-170.667q0-53.333-37-90.333-37.667-37.667-90-37.667h-683.667q-52.333 0-90.667 37.333-37.333 38.333-37.333 90.667v170.667q0 17.667 12.5 30.167t30.167 12.5 30.167-12.5 12.5-30.167v-170.667q0-17.667 12.5-30.167t30.167-12.5h683.667q17.333 0 29.5 12.5t12.167 30.167v170.667q0 17.667 12.5 30.167t30.167 12.5z" />
<glyph unicode="&#xe608;" glyph-name="export" d="M512 894q18 0 30.333-12.333l213.333-213.333q12.333-12.333 12.333-30.333 0-18.333-12.167-30.5t-30.5-12.167q-18 0-30.333 12.333l-140.333 140.667v-494.333q0-17.667-12.5-30.167t-30.167-12.5-30.167 12.5-12.5 30.167v494.333l-140.333-140.667q-13-12.333-30.333-12.333-17.667 0-30.167 12.5t-12.5 30.167q0 17.333 12.333 30.333l213.333 213.333q12.333 12.333 30.333 12.333zM938.667 296.667q17.667 0 30.167-12.5t12.5-30.167v-170.667q0-53.333-37-90.333-37.667-37.667-90-37.667h-683.667q-52.333 0-90.667 37.333-37.333 38.333-37.333 90.667v170.667q0 17.667 12.5 30.167t30.167 12.5 30.167-12.5 12.5-30.167v-170.667q0-17.667 12.5-30.167t30.167-12.5h683.667q17.333 0 29.5 12.5t12.167 30.167v170.667q0 17.667 12.5 30.167t30.167 12.5z" />
<glyph unicode="&#xe609;" glyph-name="minimize-tray" d="M854 708.667l-496-496h282v-84h-426v426h84v-282l496 496z" />
<glyph unicode="&#xe610;" glyph-name="expand" d="M708 572.667l60-60-256-256-256 256 60 60 196-196z" />
<glyph unicode="&#xe611;" glyph-name="ellipsis" d="M512 512.667q34 0 60-26t26-60-26-60-60-26-60 26-26 60 26 60 60 26zM768 512.667q34 0 60-26t26-60-26-60-60-26-60 26-26 60 26 60 60 26zM256 512.667q34 0 60-26t26-60-26-60-60-26-60 26-26 60 26 60 60 26z" />
<glyph unicode="&#xe612;" glyph-name="trash" d="M967.111 682.667h-227.556v170.667c0 31.417-25.472 56.889-56.889 56.889h-341.333c-31.417 0-56.889-25.472-56.889-56.889v-170.667h-227.556c-31.417 0-56.889-25.472-56.889-56.889v-56.889h113.778v-512c0-31.417 25.472-56.889 56.889-56.889h682.667c31.417 0 56.889 25.472 56.889 56.889v512h113.778v56.889c0 31.417-25.472 56.889-56.889 56.889zM398.222 796.444h227.556v-113.778h-227.556v113.778zM796.444 113.778h-568.889v455.111h568.889v-455.111zM369.778 227.555h56.889c15.723 0 28.444 12.736 28.444 28.444v170.667c0 15.708-12.722 28.444-28.444 28.444h-56.889c-15.723 0-28.444-12.736-28.444-28.444v-170.667c0-15.708 12.722-28.444 28.444-28.444zM597.333 227.555h56.889c15.723 0 28.444 12.736 28.444 28.444v170.667c0 15.708-12.722 28.444-28.444 28.444h-56.889c-15.723 0-28.444-12.736-28.444-28.444v-170.667c0-15.708 12.722-28.444 28.444-28.444z" />
<glyph unicode="&#xe613;" glyph-name="gear" d="M919.584 519.931l-75.388 12.555c-8.695 37.388-23.5 72.347-43.333 103.902l41.972 58.777c16 22.416 13.472 53.111-6 72.597l-12.194 12.181c-19.472 19.472-50.167 22.014-72.583 6l-58.777-41.986c-31.556 19.847-66.5 34.667-103.89 43.348l-12.583 75.374c-4.556 27.433-28.308 47.543-56.112 47.543h-17.388c-27.806 0-51.556-20.11-56.11-47.541l-12.583-75.374c-37.388-8.681-72.334-23.5-103.89-43.348l-58.777 41.986c-22.416 16.014-53.111 13.472-72.583-6l-12.194-12.181c-19.472-19.486-22-50.181-6-72.597l41.972-58.777c-19.833-31.556-34.638-66.514-43.333-103.902l-75.388-12.555c-27.42-4.585-47.531-28.322-47.531-56.126v-17.388c0-27.806 20.11-51.541 47.527-56.11l75.388-12.569c8.695-37.388 23.5-72.347 43.333-103.902l-41.972-58.777c-16-22.416-13.472-53.111 6-72.597l12.194-12.181c19.472-19.472 50.167-22.014 72.583-6l58.777 41.986c31.556-19.847 66.5-34.667 103.89-43.348l12.583-75.374c4.555-27.431 28.306-47.541 56.11-47.541h17.388c27.806 0 51.556 20.11 56.11 47.541l12.583 75.374c37.388 8.681 72.334 23.5 103.89 43.348l58.777-41.986c22.416-16.014 53.111-13.472 72.583 6l12.194 12.181c19.472 19.486 22 50.181 6 72.597l-41.972 58.777c19.833 31.556 34.638 66.514 43.333 103.902l75.388 12.569c27.417 4.569 47.527 28.306 47.527 56.11v17.388c0.005 27.804-20.105 51.541-47.522 56.124zM512 284.444c-94.251 0-170.667 76.416-170.667 170.667s76.416 170.667 170.667 170.667 170.667-76.416 170.667-170.667-76.416-170.667-170.667-170.667z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
fonts/font-icon.ttf Normal file

Binary file not shown.

BIN
fonts/font-icon.woff Normal file

Binary file not shown.

74
index.html Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<title>命令管理器</title>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
<link href="./css/base.css" rel="stylesheet" type="text/css"/>
<link href="./css/index.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="./js/app/index.js"></script>
</head>
<body id="app" class="fn-clear">
<header class="fn-clear">
<app-configure :configure.sync="configure"></app-configure>
<window-control></window-control>
</header>
<nav class="ui-project-tree fn-left">
<app-nav :configure.sync="configure" :active-index.sync="activeIndex"></app-nav>
</nav>
<main class="ui-project-main fn-right">
<div class="ui-project-stage fn-hide">
<div class="ui-control-bar fn-clear">
<div class="ui-control-operate fn-left">
<a title="删除项目" href="javascript:;"><i class="icon-trash"></i></a>
<a title="设置项目" href="javascript:;"><i class="icon-gear"></i></a>
</div>
<div class="fn-right">
<ul class="ui-command fn-clear">
<li><a href="javascript:;"><i class="icon-play"></i>命令1</a></li>
<li><a href="javascript:;"><i class="icon-stop"></i>命令2</a></li>
<li><a href="javascript:;"><i class="icon-play"></i>命令3</a></li>
<li class="ui-command-more">
<a href="javascript:;">更多&nbsp;<i class="icon-expand"></i></a>
<ul class="ui-command-popup fn-hide">
<li><a href="javascript:;"><i class="icon-play"></i>命令4</a></li>
<li><a href="javascript:;"><i class="icon-stop"></i>命令5</a></li>
<li><a href="javascript:;"><i class="icon-play"></i>命令6</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="ui-project-terminal"></div>
</div>
<div class="ui-project-setting">
<form>
<div class="ui-control-bar fn-clear">
<div class="fn-right">
<input type="submit" class="ui-button" value="确认"/>
<input type="button" class="ui-button ui-button-orange" value="取消"/>
</div>
</div>
<div class="ui-project-configure">
<ul>
<li>
<label>项目名称:</label>
<input type="text" placeholder="项目名称"/>
</li>
<li id="open-dir">
<directory label="项目路径"></directory>
</li>
<li id="add-env" class="ui-sub-item">
<label>环境变量:</label>
<dynamic-item name-label="变量名" value-label="变量值"></dynamic-item>
</li>
<li id="add-cmd" class="ui-sub-item">
<label>项目命令:</label>
<dynamic-item name-label="名称" value-label="命令"></dynamic-item>
</li>
</ul>
</div>
</form>
</div>
</main>
</body>
</html>

35
js/app/index.js Normal file
View File

@ -0,0 +1,35 @@
/**
* Created by nuintun on 2015/11/16.
*/
'use strict';
var ipc = require('ipc-renderer');
var Vue = require('./js/vue/vue');
require('./js/components/app-configure');
require('./js/components/window-control');
require('./js/components/app-nav');
require('./js/components/directory');
require('./js/components/dynamic-item');
window.addEventListener('DOMContentLoaded', function (){
var app = new Vue({
el: '#app',
data: {
activeIndex: 0,
configure: {}
}
});
ipc.on('app-configure', function (event, command, configure){
switch (command) {
case 'refresh':
app.activeIndex = 0;
app.configure = configure;
break;
}
});
ipc.send('app-configure', 'refresh');
}, false);

View File

@ -0,0 +1,35 @@
<div class="fn-left">
<a @click="appConfigure('save', configure)" title="添加项目" class="ui-project-add" href="javascript:;">
<i class="icon-plus"></i>
<i class="icon-expand"></i>
</a>
<a @click="appConfigure('import')" class="ui-import-configure" title="导入配置" href="javascript:;">
<i class="icon-import"></i>
</a>
<a @click="appConfigure('export')" class="ui-export-configure" title="导出配置" href="javascript:;">
<i class="icon-export"></i>
</a>
</div>
<div class="ui-popup fn-hide">
<div class="ui-popup-arrow">
<em></em>
<span></span>
</div>
<div class="ui-popup-content">
<form>
<ul>
<li>
<label>项目名称:</label>
<input type="text" placeholder="项目名称"/>
</li>
<li id="popup-open-dir">
<directory label="项目路径"></directory>
</li>
<li class="ui-popup-control">
<input type="submit" class="ui-button" value="确定"/>
<input type="button" class="ui-button ui-button-orange" value="取消"/>
</li>
</ul>
</form>
</div>
</div>

View File

@ -0,0 +1,26 @@
/**
* Created by nuintun on 2015/11/19.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var ipc = require('ipc-renderer');
var Vue = require('../../vue/vue');
module.exports = Vue.component('app-configure', {
template: fs.readFileSync(path.join(__dirname, 'app-configure.html')).toString(),
props: {
configure: {
type: Object,
twoWay: true,
required: true
}
},
methods: {
appConfigure: function (command, configure){
ipc.send('app-configure', command, configure);
}
}
});

View File

View File

@ -0,0 +1,3 @@
/**
* Created by nuintun on 2015/11/20.
*/

View File

@ -0,0 +1,7 @@
<ul>
<li v-for="(index, project) in configure.projects">
<a href="javascript:;" :class="{ active: activeIndex === index }" :title="project.name" @click="select(index)">
<i class="icon-folder"></i>{{ project.name }}
</a>
</li>
</ul>

View File

@ -0,0 +1,30 @@
/**
* Created by nuintun on 2015/11/20.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var Vue = require('../../vue/vue');
module.exports = Vue.component('app-nav', {
template: fs.readFileSync(path.join(__dirname, 'app-nav.html')).toString(),
props: {
activeIndex: {
type: Number,
twoWay: true,
required: true
},
configure: {
type: Object,
twoWay: true,
required: true
}
},
methods: {
select: function (index){
this.activeIndex = index;
}
}
});

View File

@ -0,0 +1,3 @@
<label>{{label}}</label>
<input type="text" :title="path" :placeholder="label" v-model="path" lazy/>&nbsp;
<a title="选择项目" href="javascript:;" class="icon-ellipsis" @click="open"></a>

View File

@ -0,0 +1,39 @@
/**
* Created by nuintun on 2015/11/17.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var ipc = require('ipc-renderer');
var Vue = require('../../vue/vue');
module.exports = Vue.component('directory', {
// camelCase in JavaScript
props: {
label: {
type: String,
required: true
},
path: {
type: String,
default: ''
}
},
template: fs.readFileSync(path.join(__dirname, 'directory.html')).toString(),
methods: {
open: function (){
ipc.send('open-directory', this.path, this._uid);
}
},
created: function (){
var context = this;
ipc.on('select-directory', function (event, paths, uid){
if (context._uid === uid) {
context.path = paths[0];
}
});
}
});

View File

@ -0,0 +1,16 @@
<ul>
<li v-for="(index, item) in items">
<span :title="item.name">{{ item.name }}</span>&nbsp;
<span :title="item.value">{{ item.value }}</span>
<i @click="remove(index)" title="删除" class="icon-trash"></i>
</li>
<li class="ui-action-bar">
<input type="text" :placeholder="nameLabel" v-model="name" lazy @focus="focus('nameError')"/>&nbsp;
<input type="text" :placeholder="valueLabel" v-model="value" lazy @focus="focus('valueError')"/>&nbsp;
<input type="button" class="ui-button" @click="add" value="添加"/>
</li>
<li v-show="nameError || valueError" class="ui-item-error">
<span :class="nameError ? '' : 'fn-invisible'"><i class="icon-expand"></i>{{ nameLabel }}{{ nameError }}</span>&nbsp;
<span :class="valueError ? '' : 'fn-invisible'"><i class="icon-expand"></i>{{ valueLabel }}{{ valueError }}</span>
</li>
</ul>

View File

@ -0,0 +1,90 @@
/**
* Created by nuintun on 2015/11/17.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var Vue = require('../../vue/vue');
module.exports = Vue.component('dynamic-item', {
// camelCase in JavaScript
props: {
nameLabel: {
type: String,
required: true,
default: ''
},
valueLabel: {
type: String,
required: true,
default: ''
},
items: {
type: Array,
default: function (){
return [];
}
}
},
template: fs.readFileSync(path.join(__dirname, 'dynamic-item.html')).toString(),
data: function (){
return {
name: '',
value: '',
_cached: {},
nameError: '',
valueError: ''
};
},
methods: {
add: function (){
// trim value
this.name = this.name.trim();
this.value = this.value.trim();
// name error
if (!this.name) {
this.nameError = '不能为空';
} else if (this.$data._cached[this.name]) {
this.nameError = ' ' + this.name + ' 已存在';
} else {
this.nameError = '';
}
// value error
if (!this.value) {
this.valueError = '不能为空';
} else {
this.valueError = '';
}
// add item
if (this.name && this.value && !this.$data._cached[this.name]) {
// cache name
this.$data._cached[this.name] = true;
// add item
this.items.push({ name: this.name, value: this.value });
// clean input
this.name = '';
this.value = '';
}
},
focus: function (key){
this[key] = '';
},
remove: function (index){
var item = this.items[index];
this.items.splice(index, 1);
delete this.$data._cached[item.name];
}
},
created: function (){
this.items.forEach(function (item){
this.$data._cached[item.name] = true;
}, this);
}
});

View File

@ -0,0 +1,3 @@
/**
* Created by nuintun on 2015/11/20.
*/

View File

@ -0,0 +1,39 @@
/**
* Created by nuintun on 2015/11/19.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var ipc = require('ipc-renderer');
var Vue = require('../../vue/vue');
module.exports = Vue.component('window-control', {
data: function (){
return {
isMaximized: false
}
},
template: fs.readFileSync(path.join(__dirname, 'window-control.html')).toString(),
methods: {
tray: function (){
ipc.send('window', 'tray');
},
close: function (){
ipc.send('window', 'close');
},
maximize: function (){
ipc.send('window', this.isMaximized ? 'unmaximize' : 'maximize');
}
},
created: function (){
var context = this;
ipc.on('is-maximized', function (event, maximized){
context.isMaximized = maximized;
});
ipc.send('window', 'is-maximized');
}
});

View File

@ -0,0 +1,11 @@
<div class="ui-window-control fn-right">
<a @click="tray" title="缩小到托盘区" href="javascript:;">
<i class="icon-minimize-tray"></i>
</a>
<a @click="maximize" :title="isMaximized?'还原':'最大化'" href="javascript:;">
<i :class="{ 'icon-maximize' : !isMaximized, 'icon-unmaximize' : isMaximized }"></i>
</a>
<a @click="close" title="关闭" href="javascript:;">
<i class="icon-cross"></i>
</a>
</div>

10538
js/vue/vue.js Normal file

File diff suppressed because it is too large Load Diff

90
main.js Normal file
View File

@ -0,0 +1,90 @@
/**
* Created by nuintun on 2015/11/5.
*/
'use strict';
// node module
var path = require('path');
// module to control application life
var app = require('app');
var Menu = require('menu');
var Tray = require('tray');
// module to create native browser window
var BrowserWindow = require('browser-window');
// custom module
var windowControl = require('./bin/window-control');
var openDirectory = require('./bin/open-directory');
var AppConfigure = require('./bin/app-configure');
// keep a global reference of the window object, if you don't, the window will
// be closed automatically when the javascript object is GCed
var mainTray = null;
var mainWindow = null;
// const var
const APPNAME = '命令管理器';
const ICON = path.join(__dirname, './app.ico');
const INDEX = 'file:///' + path.join(__dirname, 'index.html');
// quit when all windows are closed
app.on('window-all-closed', function (){
app.quit();
});
// this method will be called when atom-shell has done everything
// initialization and ready for creating browser windows
app.on('ready', function (){
// create the tray window
mainTray = new Tray(ICON);
mainTray.setToolTip(APPNAME);
mainTray.setContextMenu(Menu.buildFromTemplate([
{
label: '显示窗口',
click: function (){
mainWindow.show();
}
},
{
label: '退出程序',
click: function (){
app.quit();
}
}
]));
// create the browser window
mainWindow = new BrowserWindow({
width: 1024,
height: 768,
icon: ICON,
'min-width': 1024,
'min-height': 768,
title: APPNAME,
frame: false,
center: true,
'use-content-size': true
});
// and load the index.html of the app
mainWindow.loadURL(INDEX);
// emitted when the window is closed
mainWindow.on('closed', function (){
// dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element
mainTray.destroy();
mainTray = null;
mainWindow = null;
});
// window control
windowControl(ICON, mainWindow, mainTray);
// open directory
openDirectory(mainWindow);
// app configure
new AppConfigure(mainWindow, mainTray);
});

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "command-manager",
"version": "0.0.1",
"main": "main.js",
"scripts": {
"start": "electron ."
}
}