Skip to content

Commit 59314bd

Browse files
authoredFeb 1, 2020
Merge pull request #375 from angrykoala/dev
0.8.0
2 parents b118f1d + a816dec commit 59314bd

25 files changed

+3728
-3230
lines changed
 

‎.github/workflows/publish.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
on:
2+
release:
3+
types: [published]
4+
5+
name: Publish
6+
jobs:
7+
build:
8+
name: Publish Gaucho
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout code
12+
uses: actions/checkout@master
13+
- name: Build project
14+
run: npm run dist-all
15+
- name: Upload files
16+
uses: JasonEtco/upload-to-release@master
17+
with:
18+
args: dist/gaucho*
19+
env:
20+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

‎CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
0.8.0 / 2020-02-01
2+
==================
3+
4+
* Import/Export suites
5+
* Duplicate suite option
6+
* Tasks descriptions
7+
* Quit option in context menu
8+
* Modal alerts styles changed for better consistency
9+
* Styles improved
10+
* Suite name, Task name and description characters limit
11+
* Dependencies updated, electron v7
12+
113
0.7.0 / 2019-08-12
214
==================
315

‎package-lock.json

+3,262-3,091
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+18-17
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
11
{
22
"name": "gaucho",
3-
"version": "0.7.0",
3+
"version": "0.8.0",
44
"description": "Minimalistic task launcher",
55
"main": "main.js",
66
"dependencies": {
7-
"@fortawesome/fontawesome-free": "^5.11.2",
7+
"@fortawesome/fontawesome-free": "^5.12.0",
88
"ansi-to-html": "^0.6.13",
99
"electron-store": "^5.1.0",
10-
"mousetrap": "^1.6.3",
10+
"mousetrap": "^1.6.5",
1111
"run": "^1.4.0",
12-
"sweetalert2": "^9.4.3",
13-
"vue": "^2.6.10",
12+
"sweetalert2": "^9.7.1",
13+
"vue": "^2.6.11",
1414
"vue-clickaway": "^2.2.2",
1515
"vuedraggable": "^2.23.2",
1616
"vuex": "^3.1.2",
1717
"yerbamate": "^2.1.3"
1818
},
1919
"devDependencies": {
20-
"@vue/component-compiler-utils": "^3.1.0",
20+
"@vue/component-compiler-utils": "^3.1.1",
2121
"bulma": "^0.8.0",
2222
"bulma-switch": "^2.0.0",
2323
"chai": "^4.2.0",
24-
"cross-env": "^6.0.3",
25-
"electron": "~6.0.0",
26-
"electron-builder": "^21.2.0",
27-
"eslint": "^6.7.2",
28-
"eslint-plugin-vue": "^6.0.1",
29-
"husky": "^3.1.0",
30-
"mocha": "^6.2.2",
24+
"cross-env": "^7.0.0",
25+
"electron-builder": "^22.3.2",
26+
"electron": "~7.1.11",
27+
"eslint": "^6.8.0",
28+
"eslint-plugin-vue": "^6.1.2",
29+
"husky": "^4.2.1",
30+
"mocha": "^7.0.1",
3131
"parcel-bundler": "^1.12.4",
32-
"sass": "^1.23.7",
33-
"sinon": "^7.5.0",
34-
"vue-template-compiler": "^2.6.10"
32+
"sass": "^1.25.0",
33+
"sinon": "^8.1.1",
34+
"vue-template-compiler": "^2.6.11"
3535
},
3636
"scripts": {
3737
"build": "rm -rf public/ && parcel build src/app/index.html --target electron --public-url ./ --out-dir public --no-source-maps",
@@ -51,7 +51,8 @@
5151
"target": [
5252
"deb",
5353
"AppImage",
54-
"snap"
54+
"snap",
55+
"zip"
5556
],
5657
"category": "Utility"
5758
},

‎src/app/api/about_modal.js

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ module.exports = function open(store) {
99
<a href="#">https://github.com/angrykoala/gaucho</a></p>`;
1010
const modal = new AppAlert("<h4>Gaucho</h4>", {
1111
showCloseButton: false,
12-
confirmButtonColor: "#ee6e73",
1312
confirmButtonText: "Close"
1413
}).html(aboutHtml);
1514
modal.toggle();

‎src/app/api/app_alerts.js

+33-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const swal = require('sweetalert2');
44

55
let store;
66

7+
const confirmButtonColor = "#2bbbad";
8+
const dangerButtonColor = "#f14668";
9+
710
function init(newStore) {
811
store = newStore;
912
}
@@ -17,7 +20,14 @@ class AppAlert {
1720
this.alertOptions = Object.assign({
1821
title: title,
1922
customClass: getTheme(),
20-
heightAuto: false
23+
heightAuto: false,
24+
confirmButtonColor: confirmButtonColor,
25+
showClass: {
26+
popup: ''
27+
},
28+
hideClass: {
29+
popup: ''
30+
}
2131
}, options);
2232
}
2333

@@ -46,10 +56,21 @@ class DeleteConfirmationAlert extends InteractiveAlert {
4656
super("Are you sure?", Object.assign({
4757
text: text,
4858
showCancelButton: true,
49-
confirmButtonColor: "#ee6e73",
5059
confirmButtonText: 'Yes, delete it!',
5160
cancelButtonText: 'No, keep it',
52-
type: 'warning'
61+
confirmButtonColor: dangerButtonColor
62+
}, options));
63+
}
64+
}
65+
66+
class ImportTaskAlert extends DeleteConfirmationAlert {
67+
constructor(text, options = {}) {
68+
super("Importing tasks will remove all current tasks.", Object.assign({
69+
text: text,
70+
showCancelButton: true,
71+
confirmButtonText: "Yes, import tasks",
72+
cancelButtonText: "No, cancel import",
73+
confirmButtonColor: confirmButtonColor
5374
}, options));
5475
}
5576
}
@@ -58,7 +79,6 @@ class InputAlert extends InteractiveAlert {
5879
constructor(title, defaultValue = "", options = {}) {
5980
super(title, Object.assign({
6081
showCancelButton: true,
61-
confirmButtonColor: "#ee6e73",
6282
confirmButtonText: 'Rename',
6383
input: 'text',
6484
inputValue: defaultValue
@@ -71,16 +91,20 @@ class SchedulerAlert extends InputAlert {
7191
super(title, "", Object.assign({
7292
confirmButtonText: 'Schedule',
7393
input: 'number',
74-
inputPlaceholder: 'Seconds'
94+
inputPlaceholder: 'Seconds',
95+
inputAttributes: {
96+
min: 0
97+
}
7598
}, options));
7699
}
77100
}
78101

79102
module.exports = {
80-
AppAlert: AppAlert,
81-
DeleteConfirmationAlert: DeleteConfirmationAlert,
82-
InputAlert: InputAlert,
83-
SchedulerAlert: SchedulerAlert,
103+
AppAlert,
104+
DeleteConfirmationAlert,
105+
InputAlert,
106+
SchedulerAlert,
107+
ImportTaskAlert,
84108
init: init,
85109
isVisible: swal.isVisible
86110
};

‎src/app/api/context_menu.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use strict";
22
const remote = require('electron').remote;
3+
const ipcRenderer = require('electron').ipcRenderer;
4+
35
const {Menu, MenuItem} = remote;
46

57
const EventEmitter = require('events');
@@ -41,7 +43,8 @@ class DefaultContextMenu extends ContextMenu {
4143
constructor(extraOptions = []) {
4244
super(extraOptions.concat([{label: "Settings",
4345
event: "settings"}, {label: "About",
44-
event: "about"}]));
46+
event: "about"}, {label: "Quit",
47+
event: "quit"}]));
4548
}
4649

4750
toggle(extraData) {
@@ -51,6 +54,9 @@ class DefaultContextMenu extends ContextMenu {
5154
this.on("about", () => {
5255
aboutModal(store);
5356
});
57+
this.on("quit", () => {
58+
ipcRenderer.send("close-app");
59+
});
5460

5561
super.toggle(extraData);
5662
}
@@ -60,7 +66,9 @@ class TabMenu extends DefaultContextMenu {
6066
constructor() {
6167
super([{label: "Delete",
6268
event: "delete"}, {label: "Rename",
63-
event: "rename"}, {type: "separator"}]);
69+
event: "rename"}, {label: "Export Suite",
70+
event: "export-suite"}, {label: "Duplicate Suite",
71+
event: "duplicate-suite"}, {type: "separator"}]);
6472
}
6573
}
6674

‎src/app/api/tasks_handler.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ module.exports = class TasksHandler {
4444
static _parseData(data) {
4545
return data.map((suite) => {
4646
const result = new Suite(suite.title);
47-
result.tasks = suite.tasks.map((task) => new Task(task.title, task.path, task.command, task.env));
47+
result.tasks = suite.tasks.map((task) => new Task({
48+
title: task.title,
49+
description: task.description,
50+
path: task.path,
51+
command: task.command,
52+
env: task.env
53+
}));
4854
return result;
4955
});
5056
}

‎src/app/common/suite.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"use strict";
22

3+
const utils = require('./utils');
4+
const constants = require('../../common/constants');
5+
36
class Suite {
47
constructor(title, tasks) {
5-
this.title = title || "";
68
this.tasks = tasks || [];
9+
this.setTitle(title);
710
}
811

912
get length() {
@@ -15,7 +18,7 @@ class Suite {
1518
}
1619

1720
addTask(task) {
18-
const title = this.getValidName(task.title);
21+
const title = this.getValidTaskName(task.title);
1922
task.title = title;
2023
this.tasks.push(task);
2124
}
@@ -26,7 +29,7 @@ class Suite {
2629

2730
replaceTask(index, task) {
2831
if (this.tasks[index].title !== task.title) {
29-
const title = this.getValidName(task.title);
32+
const title = this.getValidTaskName(task.title);
3033
task.title = title;
3134
}
3235
this.tasks.splice(index, 1, task);
@@ -52,14 +55,19 @@ class Suite {
5255
}
5356
}
5457

58+
setTitle(title) {
59+
this.title = utils.truncate(title || "", constants.maxSuiteNameLength);
60+
}
61+
5562
getData() {
5663
return {
5764
title: this.title,
5865
tasks: this.tasks.map((task) => task.getData())
5966
};
6067
}
6168

62-
getValidName(name) {
69+
getValidTaskName(name) {
70+
name = utils.truncate(name, constants.maxTaskNameLength);
6371
let index = 2;
6472
if (!this.existTaskName(name)) return name;
6573
while (this.existTaskName(`${name} (${index})`)) {
@@ -77,6 +85,11 @@ class Suite {
7785
name = name.trim();
7886
return this.tasks.filter(task => task.title === name).length >= 2;
7987
}
88+
89+
clone() {
90+
const taskClones = this.tasks.map(t => t.clone());
91+
return new Suite(this.title, taskClones);
92+
}
8093
}
8194

8295
module.exports = Suite;

‎src/app/common/task.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ const yerbamate = require('yerbamate');
55

66
const TaskStatus = require('./task_status');
77
const {TaskTimer, InverseTaskTimer} = require('./task_timer');
8+
const utils = require('./utils');
9+
const constants = require('../../common/constants');
810

911
const outputMaxSize = 6000;
1012

1113
class Task {
12-
constructor(title, path, command, env) {
13-
this.title = title.trim() || "";
14-
this.command = command || "";
15-
this.path = path || "";
16-
this.env = env || [];
14+
constructor(options) {
15+
this.title = options.title.trim() || "";
16+
this.command = options.command || "";
17+
this.description = this._formatDescription(options.description);
18+
this.path = options.path || "";
19+
this.env = options.env || [];
1720
this.status = TaskStatus.idle;
1821

1922
this.output = null;
@@ -83,6 +86,7 @@ class Task {
8386
};
8487
if (this.env && this.env.length > 0) res.env = this.env;
8588
if (this.path !== "") res.path = this.path;
89+
if (this.description !== "") res.description = this.description;
8690
return res;
8791
}
8892

@@ -98,6 +102,15 @@ class Task {
98102
});
99103
}
100104

105+
clone() {
106+
return new Task({
107+
title: this.title,
108+
description: this.description,
109+
path: this.path,
110+
command: this.command,
111+
env: this.env});
112+
}
113+
101114
_processCommand() {
102115
return this.command.replace(/(^|\s)sudo($|\s)/g, "$1pkexec$2");
103116
}
@@ -122,6 +135,11 @@ class Task {
122135
}, {});
123136
return res;
124137
}
138+
139+
_formatDescription(originalDescription) {
140+
const description = originalDescription || "";
141+
return utils.truncate(description.trim(), constants.maxDescriptionLength).trim();
142+
}
125143
}
126144

127145
Task.TaskStatus = TaskStatus;

‎src/app/common/utils.js

+3
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ module.exports = {
2323
res += seconds;
2424

2525
return res;
26+
},
27+
truncate(str, characters) {
28+
return str.substring(0, characters);
2629
}
2730
};

‎src/app/components/navbar/navbar.vue

+20-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
<div class="navbar-item unselectable">
66
<img class="logo-icon" src="../../../../resources/logos/gaucho_logo.ico">
77
<h1 class="title is-4">Gaucho</h1>
8-
<h2 class="beta-subtitle is-4">Beta</h2>
98
</div>
109
</div>
1110
<div class="navbar-menu is-active">
@@ -32,7 +31,10 @@
3231
<script>
3332
"use strict";
3433
34+
const app = require('electron').remote;
35+
const dialog = app.dialog;
3536
const ipcRenderer = require('electron').ipcRenderer;
37+
3638
const EventHandler = require('../../event_handler');
3739
const {
3840
SchedulerAlert
@@ -68,6 +70,23 @@ module.exports = {
6870
case "stopSuite":
6971
this.$store.dispatch("stopSuite", this.$store.state.tasks.selectedSuite);
7072
break;
73+
case "importSuite":
74+
if (this.$store.getters.canAddSuite) {
75+
dialog.showOpenDialog({
76+
filters: [{
77+
name: 'json',
78+
extensions: ['json']
79+
}]
80+
}).then((dialogResult) => {
81+
if (dialogResult.filePaths && dialogResult.filePaths[0]) {
82+
const filename = dialogResult.filePaths[0];
83+
this.$store.dispatch("importSuite", filename).catch((err) => {
84+
console.warn(err);
85+
});
86+
}
87+
});
88+
}
89+
break;
7190
case "scheduleSuite":
7291
new SchedulerAlert("Schedule Task Execution").toggle().then((res) => {
7392
this.$store.dispatch("scheduleSuite", res);
@@ -114,11 +133,6 @@ module.exports = {
114133
margin-bottom: 0;
115134
}
116135
117-
.beta-subtitle {
118-
padding-top: 12px;
119-
padding-left: 4px;
120-
}
121-
122136
.navbar-logo {
123137
cursor: pointer;
124138
}

‎src/app/components/navbar/navbar_menu.vue

+22-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<dropdown-menu open-event="showNavbarMenu" class="navbar-dropdown-menu">
33
<template v-for="(item, i) in options">
44
<hr v-if="item.value==='divider'" class="dropdown-divider" :key="i">
5+
<a v-else-if="item.disabled" class="dropdown-item disabled" :key="i">
6+
{{item.name}}
7+
</a>
58
<a v-else class="dropdown-item" @click.prevent="onClick(item.value)" :key="i">
69
{{item.name}}
710
</a>
@@ -34,8 +37,17 @@ module.exports = {
3437
value: "quit"
3538
}];
3639
37-
if (!this.$store.state.editMode) {
38-
const runModeOptions = [{
40+
let extraOptions = [];
41+
42+
if (this.$store.state.editMode) {
43+
const canImportSuite = this.$store.getters.canAddSuite;
44+
extraOptions = [{
45+
name: "Import Suite",
46+
value: "importSuite",
47+
disabled: !canImportSuite
48+
}];
49+
} else {
50+
extraOptions = [{
3951
name: "Run Suite",
4052
value: "runSuite"
4153
}, {
@@ -45,12 +57,10 @@ module.exports = {
4557
name: "Schedule Suite",
4658
value: "scheduleSuite"
4759
}];
48-
return runModeOptions.concat([{
49-
value: "divider"
50-
}], defaultOptions);
51-
} else {
52-
return defaultOptions;
5360
}
61+
return extraOptions.concat([{
62+
value: "divider"
63+
}], defaultOptions);
5464
}
5565
},
5666
methods: {
@@ -60,3 +70,8 @@ module.exports = {
6070
}
6171
};
6272
</script>
73+
74+
75+
<style lang="scss">
76+
77+
</style>

‎src/app/components/navbar/suite_tabs.vue

+53-20
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
<template>
22
<div class="tabs is-fullwidth">
33
<draggable v-model="suites" tag="ul" v-bind="draggableOptions" @end="suiteDragEnd">
4-
<li v-for="(suite, index) in suites" class="tab-suite-item"
5-
@contextmenu.stop="context(index)"
6-
:style="{ width: tabsWidth }"
7-
:class="{'is-active': isSelected(index), 'inactive': !isSelected(index)}"
8-
@click="selectSuite(index)"
9-
@dragover="selectSuite(index)"
10-
:key="index"
4+
<li v-for="(suite, index) in suites" class="tab-suite-item" @contextmenu.stop="context(index)" :style="{ width: tabsWidth }"
5+
:class="{'is-active': isSelected(index), 'inactive': !isSelected(index)}" @click="selectSuite(index)" @dragover="selectSuite(index)" :key="index"
116
>
127
<a class="columns is-mobile is-centered tab-content">
138
<div class="column tab-text-container">
@@ -35,9 +30,16 @@
3530
<script>
3631
"use strict";
3732
33+
const os = require('os');
34+
const path = require('path');
35+
const app = require('electron').remote;
36+
const dialog = app.dialog;
37+
3838
const AppAlerts = require('../../api/app_alerts');
3939
const ContextMenu = require('../../api/context_menu');
4040
41+
const constants = require('../../../common/constants.js');
42+
4143
const tabMenu = new ContextMenu.TabMenu();
4244
4345
const components = {
@@ -83,6 +85,12 @@ module.exports = {
8385
tabMenu.on("rename", (i) => {
8486
this.renameSuite(i);
8587
});
88+
tabMenu.on("export-suite", (i) => {
89+
this.exportSuite(i);
90+
});
91+
tabMenu.on("duplicate-suite", (i) => {
92+
this.duplicateSuite(i);
93+
});
8694
tabMenu.toggle(index);
8795
},
8896
isSelected(i) {
@@ -105,7 +113,12 @@ module.exports = {
105113
});
106114
},
107115
renameSuite(index) {
108-
const alert = new AppAlerts.InputAlert("Rename Suite?", this.suites[index].title);
116+
const alert = new AppAlerts.InputAlert("Rename Suite?", this.suites[index].title, {
117+
inputAttributes: {
118+
maxLength: constants.maxSuiteNameLength
119+
}
120+
});
121+
109122
alert.toggle().then((res) => {
110123
if (res.length > 0) {
111124
this.$store.commit("renameSuite", {
@@ -117,48 +130,68 @@ module.exports = {
117130
// Rename is cancelled
118131
});
119132
},
133+
exportSuite(index) {
134+
const suite = this.suites[index];
135+
dialog.showSaveDialog({
136+
defaultPath: path.join(os.homedir(), `${suite.title}.json`),
137+
filters: [{
138+
extensions: ['json']
139+
}]
140+
}).then((dialogResult) => {
141+
if (dialogResult.filePath) {
142+
this.$store.dispatch("exportSuite", {
143+
filename: dialogResult.filePath,
144+
suiteIndex: index
145+
}).catch((err) => {
146+
console.warn(err);
147+
});
148+
}
149+
});
150+
},
151+
duplicateSuite(index) {
152+
this.$store.dispatch("duplicateSuite", index);
153+
},
120154
suiteDragEnd(evt) {
121155
this.selectSuite(evt.newIndex);
122156
}
123157
}
124158
};
125-
126159
</script>
127160

128161

129162
<style lang="scss" scoped>
130163
@import "../../styles/variables";
131164
132-
.tabs{
133-
.inactive{
165+
.tabs {
166+
.inactive {
134167
background-color: rgba(0, 0, 0, 0.1);
135-
&:hover{
168+
&:hover {
136169
background-color: rgba(0, 0, 0, 0.05);
137170
}
138171
}
139-
.tab-icon{
172+
.tab-icon {
140173
margin-left: 0;
141-
margin-right:0;
174+
margin-right: 0;
142175
}
143-
.columns{
176+
.columns {
144177
margin-left: 0;
145178
margin-right: 0;
146179
}
147180
148-
ul{
181+
ul {
149182
border-bottom-style: hidden;
150183
}
151184
}
152185
153-
.tab-content{
154-
.column{
186+
.tab-content {
187+
.column {
155188
padding-left: 0;
156189
padding-right: 0;
157190
}
158191
}
159-
.tab-text-container{
192+
.tab-text-container {
160193
overflow: hidden;
161-
.tab-text{
194+
.tab-text {
162195
display: block;
163196
overflow: hidden;
164197
text-overflow: ellipsis;

‎src/app/components/settings/settings_actions.vue

+11-11
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ const path = require('path');
1818
const app = require('electron').remote;
1919
const dialog = app.dialog;
2020
21-
const DeleteConfirmationAlert = require('../../api/app_alerts').DeleteConfirmationAlert;
21+
const {
22+
ImportTaskAlert,
23+
DeleteConfirmationAlert
24+
} = require('../../api/app_alerts');
2225
2326
const components = {
2427
"button-item": require('./button_item.vue')
@@ -33,13 +36,10 @@ module.exports = {
3336
name: 'json',
3437
extensions: ['json']
3538
}]
36-
}, (filenames) => {
37-
if (filenames && filenames[0]) {
38-
const filename = filenames[0];
39-
const confirmationAlert = new DeleteConfirmationAlert("Importing tasks will remove all current tasks.", {
40-
confirmButtonText: "Yes, import tasks",
41-
cancelButtonText: "No, cancel import"
42-
});
39+
}).then((dialogResult) => {
40+
if (dialogResult.filePaths && dialogResult.filePaths[0]) {
41+
const filename = dialogResult.filePaths[0];
42+
const confirmationAlert = new ImportTaskAlert();
4343
confirmationAlert.toggle().then(() => {
4444
return this.$store.dispatch("importTasks", filename).catch((err) => {
4545
console.warn(err);
@@ -54,9 +54,9 @@ module.exports = {
5454
filters: [{
5555
extensions: ['json']
5656
}]
57-
}, (filename) => {
58-
if (filename) {
59-
this.$store.dispatch("exportTasks", filename).catch((err) => {
57+
}).then((dialogResult) => {
58+
if (dialogResult.filePath) {
59+
this.$store.dispatch("exportTasks", dialogResult.filePath).catch((err) => {
6060
console.warn(err);
6161
});
6262
}

‎src/app/components/task/task_card.vue

+54-22
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,43 @@
11
<template>
22
<div class="task-card" @contextmenu.stop="context()">
33
<div class="columns is-mobile task-card-header" @click="taskSelected">
4-
<div class="column">
5-
<p>
6-
<span v-if="editMode" class="drag-handle">
7-
<span class="icon">
8-
<i class="fas fa-equals"></i>
9-
</span>
10-
</span>
11-
<span v-show="open" class="icon">
12-
<i class="fas fa-caret-down"></i>
13-
</span>
14-
<span v-show="!open" class="icon">
15-
<i class="fas fa-caret-right"></i>
16-
</span>
17-
{{task.title}}
18-
</p>
19-
</div>
20-
<div class="column">
4+
<div class="column is-half">
215
<div class="columns is-mobile">
22-
<div v-if="showTimer" class="column has-text-centered">
23-
<p>{{executionTime}}</p>
6+
<div v-if="editMode" class="column is-narrow drag-handle-container">
7+
<p>
8+
<span class="drag-handle">
9+
<span class="icon">
10+
<i class="fas fa-equals"></i>
11+
</span>
12+
</span>
13+
</p>
14+
</div>
15+
<div class="column">
16+
<b>
17+
<span v-show="open" class="icon">
18+
<i class="fas fa-caret-down"></i>
19+
</span>
20+
<span v-show="!open" class="icon">
21+
<i class="fas fa-caret-right"></i>
22+
</span>
23+
{{task.title}}
24+
</b>
25+
<p class="description" v-show="task.description">
26+
{{task.description}}
27+
</p>
28+
</div>
29+
</div>
30+
</div>
31+
<div class="column is-half">
32+
<div class="columns is-mobile task-actions">
33+
<div v-if="showTimer" class="column has-text-centered task-action-text">
34+
<p class="execution-time">{{executionTime}}</p>
2435
</div>
2536
<div class="column">
2637
<button v-if="!editMode" :class="{'is-danger':running}" class="button is-primary task-button" @click.stop="toggleRun" :disabled="!runButtonEnabled">{{runButtonText}}</button>
2738
<button v-else class="button is-primary task-button is-danger" @click.stop="deleteTask">Delete</button>
2839
</div>
29-
<div class="column">
40+
<div class="column task-action-text">
3041
<task-status :status="status"></task-status>
3142
</div>
3243
</div>
@@ -178,8 +189,15 @@ module.exports = {
178189
margin-top: 0;
179190
}
180191
181-
p {
182-
padding-top: 4px;
192+
.description {
193+
font-size: 15px;
194+
color: #9e9e9e;
195+
margin-left: 42px;
196+
padding-top: 0;
197+
}
198+
199+
.execution-time{
200+
padding-top: 4px
183201
}
184202
185203
.task-card-header {
@@ -191,6 +209,20 @@ p {
191209
border-bottom-style: solid;
192210
border-bottom-width: 1px;
193211
border-color: #e2e2e2;
212+
height: 72px;
213+
214+
.task-actions {
215+
margin-top: -0.5rem;
216+
217+
.task-action-text {
218+
padding-top: 1rem;
219+
}
220+
}
221+
}
222+
223+
.drag-handle-container{
224+
padding-right: 0;
225+
padding-top: 1.5rem;
194226
}
195227
196228
.task-output-container{

‎src/app/components/task/task_form.vue

+21-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
<div class="task-form-wrapper">
33
<div class="container is-fluid">
44
<div class="field">
5-
<label class="label">Task Name*</label>
5+
<label class="label">Name*</label>
66
<div class="control">
7-
<input v-model="title" class="input" type="text">
7+
<input v-model="title" class="input" type="text" :maxlength="constants.maxTaskNameLength">
8+
</div>
9+
</div>
10+
<div class="field">
11+
<label class="label">Description</label>
12+
<div class="control">
13+
<input v-model="description" class="input" type="text" :maxlength="constants.maxDescriptionLength">
814
</div>
915
</div>
1016
<div class="field">
@@ -33,6 +39,7 @@
3339
"use strict";
3440
3541
const Task = require('../../common/task');
42+
const constants = require('../../../common/constants');
3643
3744
const components = {
3845
"env-variables-form": require('./env_variables_form.vue')
@@ -44,9 +51,11 @@ module.exports = {
4451
data() {
4552
return {
4653
title: "",
54+
description: "",
4755
command: "",
4856
path: "",
49-
env: []
57+
env: [],
58+
constants: constants
5059
};
5160
},
5261
computed: {
@@ -65,20 +74,28 @@ module.exports = {
6574
methods: {
6675
saveTask() {
6776
if (this.canSave) {
68-
this.$emit('save', new Task(this.title, this.path, this.command, this.env));
77+
this.$emit('save', new Task({
78+
title: this.title,
79+
description: this.description,
80+
path: this.path,
81+
command: this.command,
82+
env: this.env
83+
}));
6984
this.clear();
7085
}
7186
},
7287
clear() {
7388
this.title = "";
7489
this.command = "";
90+
this.description = "";
7591
this.path = "";
7692
this.env = [];
7793
},
7894
onTaskUpdate() {
7995
if (this.task) {
8096
this.title = this.task.title;
8197
this.command = this.task.command;
98+
this.description = this.task.description;
8299
this.path = this.task.path;
83100
this.env = this.task.env;
84101
}

‎src/app/store/task_store.js

+22-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use strict";
22

33
const Suite = require('../common/suite');
4-
const Task = require('../common/task');
54
const TasksHandler = require('../api/tasks_handler');
65
const TaskImporter = require('../common/task_importer');
76

@@ -72,8 +71,7 @@ module.exports = {
7271
},
7372
renameSuite(state, data) {
7473
const suite = state.suites[data.suite];
75-
suite.title = data.title;
76-
TasksHandler.saveTasks(state.suites);
74+
suite.setTitle(data.title);
7775
},
7876
updateSuiteTasks(state, data) {
7977
const suite = state.suites[data.suite];
@@ -88,7 +86,7 @@ module.exports = {
8886
const suite = state.suites[data.suite];
8987
const task = suite.tasks[data.task]; // todo: use a store
9088
if (suite.isDuplicate(task.title)) {
91-
task.title = suite.getValidName(task.title);
89+
task.title = suite.getValidTaskName(task.title);
9290
}
9391
},
9492
_setSuites(state, suites) {
@@ -134,9 +132,27 @@ module.exports = {
134132
});
135133
});
136134
},
135+
importSuite(context, filename) {
136+
if (context.getters.canAddSuite) {
137+
return TaskImporter.import(filename).then((data) => {
138+
const loadedSuites = TasksHandler.loadTasksFromData(data);
139+
context.commit("addSuite", loadedSuites[0]);
140+
});
141+
}
142+
},
137143
exportTasks(context, filename) {
138144
return TaskImporter.export(filename, context.getters.suites, context.getters.version);
139145
},
146+
exportSuite(context, {filename, suiteIndex}) {
147+
const suite = context.getters.suites[suiteIndex];
148+
return TaskImporter.export(filename, [suite], context.getters.version);
149+
},
150+
duplicateSuite(context, index) {
151+
if (context.getters.canAddSuite) {
152+
const suite = context.getters.suites[index];
153+
context.commit("addSuite", suite.clone());
154+
}
155+
},
140156
runTask(context, index) {
141157
const task = context.getters.currentSuite.getTask(index);
142158
if (!task.isRunning() && !task.isScheduled()) {
@@ -189,11 +205,10 @@ module.exports = {
189205
context.commit("_deleteSuite", i);
190206
},
191207
duplicateTask(context, data) {
192-
const oldTask = context.getters.suites[data.suite].getTask(data.task);
193-
const newTask = new Task(oldTask.title, oldTask.path, oldTask.command, oldTask.env);
208+
const task = context.getters.suites[data.suite].getTask(data.task);
194209
context.commit("addTask", {
195210
index: data.suite,
196-
task: newTask
211+
task: task.clone()
197212
});
198213
}
199214
}

‎src/app/styles/alerts.scss

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.swal2-popup {
2+
button {
3+
&:focus {
4+
box-shadow: none;
5+
}
6+
}
7+
8+
input {
9+
&:focus{
10+
box-shadow: none;
11+
outline: none;
12+
border: 1px solid #d9d9d9;
13+
}
14+
}
15+
}

‎src/app/styles/main.scss

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@import "variables"; // import variables before bulma for proper override
33
@import '~bulma/bulma';
44
@import '~bulma-switch/dist/css/bulma-switch';
5+
@import 'alerts';
56
@import "desktopify";
67
@import "theme_styles";
78

‎src/app/styles/theme_styles.scss

+37-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@
3232
background-color: $light-form-color;
3333
border-bottom-color: #e2e2e2;
3434
}
35+
36+
.dropdown-menu {
37+
a {
38+
color: $default-color;
39+
40+
&.disabled {
41+
color: #989898;
42+
background-color: #d6d6d6;
43+
&:hover {
44+
color: #989898;
45+
background-color: #d6d6d6;
46+
}
47+
}
48+
}
49+
}
3550
}
3651

3752

@@ -42,7 +57,7 @@
4257
}
4358
.main-view, .settings-page {
4459
background-color: $dark-background-color;
45-
p, h1, h2, h4 {
60+
p, h1, h2, h4, b {
4661
color: $dark-color;
4762
}
4863
code {
@@ -59,11 +74,22 @@
5974
a, p, b, h1, h2 {
6075
color: $dark-color;
6176
}
77+
6278
.dropdown-menu {
6379
a {
6480
color: $default-color;
81+
82+
&.disabled {
83+
color: #525050;
84+
background-color: #636363;
85+
&:hover {
86+
color: #525050;
87+
background-color: #636363;
88+
}
89+
}
6590
}
6691
}
92+
6793
.navbar-item {
6894
&:hover {
6995
color: $dark-selected-color;
@@ -140,11 +166,6 @@
140166
color: $default-color;
141167
background-color: $dark-grey-background-color;
142168
}
143-
button {
144-
&:focus {
145-
box-shadow: none;
146-
}
147-
}
148169
}
149170
}
150171

@@ -156,9 +177,19 @@
156177
color: $classic-color;
157178

158179
}
180+
159181
.dropdown-menu {
160182
a {
161183
color: $default-color;
184+
185+
&.disabled {
186+
color: #989898;
187+
background-color: #d6d6d6;
188+
&:hover {
189+
color: #989898;
190+
background-color: #d6d6d6;
191+
}
192+
}
162193
}
163194
}
164195
.navbar-item {

‎src/common/constants.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use strict";
2+
3+
module.exports = {
4+
maxTaskNameLength: 26,
5+
maxDescriptionLength: 32,
6+
maxSuiteNameLength: 23
7+
};

‎tests/suite.test.js

+30-9
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ describe("Suite", () => {
2828
}
2929

3030
beforeEach(() => {
31-
taskStub = new Task("test", "", "command");
31+
taskStub = new Task({
32+
title: "test",
33+
commands: "command"});
3234
testSuite = new Suite("Test");
3335
createStub(taskStub);
3436
});
@@ -54,7 +56,10 @@ describe("Suite", () => {
5456
});
5557

5658
it("Run Tasks", () => {
57-
const taskStub2 = new Task("test2", "", "command");
59+
const taskStub2 = new Task({
60+
title: "test2",
61+
command: "command"
62+
});
5863
createStub(taskStub2);
5964
taskStub2.status = TaskStatus.running;
6065

@@ -72,7 +77,10 @@ describe("Suite", () => {
7277
});
7378

7479
it("Stop All Tasks", () => {
75-
const taskStub2 = new Task("test2", "", "command");
80+
const taskStub2 = new Task({
81+
title: "test2",
82+
command: "command"
83+
});
7684
createStub(taskStub2);
7785
taskStub2.status = TaskStatus.running;
7886

@@ -90,7 +98,10 @@ describe("Suite", () => {
9098
});
9199

92100
it("Replace Tasks", () => {
93-
const taskStub2 = new Task("secondTask", "", "command");
101+
const taskStub2 = new Task({
102+
title: "secondTask",
103+
command: "command"
104+
});
94105
createStub(taskStub2);
95106

96107
testSuite.addTask(taskStub);
@@ -121,9 +132,13 @@ describe("Suite", () => {
121132
});
122133

123134
it("Add Task With Repeated Name", () => {
124-
const taskStub2 = new Task("test", "", "command");
135+
const taskStub2 = new Task({
136+
title: "test",
137+
command: "command"});
125138
createStub(taskStub2);
126-
const taskStub3 = new Task("test", "", "command");
139+
const taskStub3 = new Task({
140+
title: "test",
141+
command: "command"});
127142
createStub(taskStub3);
128143
testSuite.addTask(taskStub);
129144
testSuite.addTask(taskStub2);
@@ -138,9 +153,13 @@ describe("Suite", () => {
138153
});
139154

140155
it("Replace Task With Repeated Name", () => {
141-
const taskStub2 = new Task("different_name", "", "command");
156+
const taskStub2 = new Task({
157+
title: "different_name",
158+
command: "command"});
142159
createStub(taskStub2);
143-
const taskStub3 = new Task("test", "", "command");
160+
const taskStub3 = new Task({
161+
title: "test",
162+
command: "command"});
144163
createStub(taskStub3);
145164
testSuite.addTask(taskStub);
146165
testSuite.addTask(taskStub2);
@@ -151,7 +170,9 @@ describe("Suite", () => {
151170
});
152171

153172
it("Replace Task With Same Name", () => {
154-
const taskStub2 = new Task("test", "", "command");
173+
const taskStub2 = new Task({
174+
title: "test",
175+
command: "command"});
155176
createStub(taskStub2);
156177

157178
testSuite.addTask(taskStub);

‎tests/task.test.js

+22-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ describe("Tasks", () => {
1212
let testTask;
1313
const taskCommand = `node ${path.join(config.testResources, config.taskFiles.helloWorld)}`;
1414
beforeEach(() => {
15-
testTask = new Task("Test", "", taskCommand);
15+
testTask = new Task({
16+
title: "Test",
17+
command: taskCommand});
1618
});
1719

1820
afterEach(() => {
@@ -49,19 +51,30 @@ describe("Tasks", () => {
4951
assert.strictEqual(JSON.stringify(taskData), JSON.stringify(expectedResult));
5052
});
5153

52-
it("Get Task Config with path", () => {
53-
const taskConfigTest = new Task("Test", "a/path", taskCommand);
54+
it("Get Task Config with path and description", () => {
55+
const taskConfigTest = new Task({
56+
title: "Test",
57+
path: "a/path",
58+
command: taskCommand,
59+
description: "a nice description"
60+
});
5461
const taskData = taskConfigTest.getData();
5562
const expectedResult = {
5663
title: "Test",
5764
command: taskCommand,
58-
path: "a/path"
65+
path: "a/path",
66+
description: "a nice description"
5967
};
6068
assert.strictEqual(JSON.stringify(taskData), JSON.stringify(expectedResult));
6169
});
6270

6371
it("Get Task Config with env", () => {
64-
const taskConfigTest = new Task("Test", "a/path", taskCommand, [["a", "b"]]);
72+
const taskConfigTest = new Task({
73+
title: "Test",
74+
path: "a/path",
75+
command: taskCommand,
76+
env: [["a", "b"]]
77+
});
6578
const taskData = taskConfigTest.getData();
6679
const expectedResult = {
6780
title: "Test",
@@ -73,7 +86,10 @@ describe("Tasks", () => {
7386
});
7487

7588
it("Invalid Task Execution", (done) => {
76-
const invalidTask = new Task("Invalid test", "", "invalidTask");
89+
const invalidTask = new Task({
90+
title: "Invalid test",
91+
command: "invalidTask"
92+
});
7793

7894
invalidTask.run(() => {
7995
assert.strictEqual(invalidTask.status, TaskStatus.error);

‎tests/task_importer.test.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ describe("Task Importer", () => {
1515
const testJsonFile = path.join(tempFolder, "exported_test.json");
1616
const testTasksFile = path.join(config.testResources, "test_tasks.json");
1717

18-
const testTask1 = new Task("task1", ".", "test command");
19-
const testTask2 = new Task("task2", "./path", "test command2");
18+
const testTask1 = new Task({
19+
title: "task1",
20+
path: ".",
21+
command: "test command"});
22+
const testTask2 = new Task({
23+
title: "task2",
24+
path: "./path",
25+
command: "test command2"});
2026
const testSuite1 = new Suite("suite1");
2127
const testSuite2 = new Suite("suite2");
2228
testSuite1.addTask(testTask1);

0 commit comments

Comments
 (0)
Please sign in to comment.