Skip to content

Commit

Permalink
feat(updates): add update functionality (#162)
Browse files Browse the repository at this point in the history
* refactor: validate enviroment components

* feat(updates): added update dialog

* chore(update): addressed build comments

* refactor(update): update env validation message

* fix(update): addressed comments

* revert(package): revert verion bump

* chore: bump commons version and changed verbiage

* chore: added updated package lock
  • Loading branch information
longton95 committed Jul 15, 2019
1 parent 59bb539 commit dad3a08
Show file tree
Hide file tree
Showing 8 changed files with 3,386 additions and 1,606 deletions.
109 changes: 75 additions & 34 deletions lib/index.js
Expand Up @@ -9,8 +9,10 @@ import Console from './ui/console.jsx';
import GenerateDialog from './ui/generateDialog.jsx';
import NewProjectDialog from './ui/newProjectDialog.jsx';
import LoginDialog from './ui/loginDialog.jsx';
import UpdatesDialog from './ui/updatesDialog.jsx';
import Appc from './appc';
import Project from './project';
import Update from './update';
import Utils from './utils';
import autoCompleteHelper from './providers/autoCompleteHelper';
import tiappAutoCompleteProvider from './providers/tiappAutoCompleteProvider';
Expand All @@ -19,6 +21,7 @@ import controllerAutoCompleteProvider from './providers/controllerAutoCompletePr
import styleAutoCompleteProvider from './providers/styleAutoCompleteProvider';
import definitionsProvider from './providers/definitionsProvider';
import related from './related';
import { environment } from 'titanium-editor-commons';

export default {

Expand Down Expand Up @@ -239,7 +242,7 @@ export default {
/**
* Load titanium project
*/
loadProject() {
async loadProject() {
Project.load();
if (Project.isTitaniumApp || Project.isTitaniumModule) {
this.projectDisposable.add(
Expand Down Expand Up @@ -273,6 +276,9 @@ export default {
}
this.toolbar.toggle();
}
},
'appc:update:refresh': () => {
this.checkForUpdates();
}
}),

Expand All @@ -299,14 +305,13 @@ export default {
menu = require('./menu/app');
this.projectDisposable.add(
atom.commands.add('atom-workspace', {
'appc:generate': () => {
this.openGenerateDialog();
},
'appc:generate': () => this.openGenerateDialog(),
'appc:open-related-view': () => related.openRelatedFile('xml'),
'appc:open-related-style': () => related.openRelatedFile('tss'),
'appc:open-related-controller': () => related.openRelatedFile('js'),
'appc:open-or-close-related': () => related.toggleAllRelatedFiles(),
'appc:take-screenshot': () => this.takeScreenshot(),
'appc:updates': () => this.openUpdatesDialog(),
// 'appc:closeRelated': () => related.closeRelatedFiles({forceAllClose: true})
})
);
Expand Down Expand Up @@ -349,41 +354,47 @@ export default {
}
this.toolbar.update({ disableUI: false });
this.toolbar.hud.displayDefault();
this.checkForUpdates();
};

setTimeout(() => { // FIXME: Can we solve this without timeouts?
Appc.getInfo(() => {
const sdk = Appc.selectedSdk();
if (!sdk) {
const errorNotification = atom.notifications.addError('No Titanium SDK installed. Would you like to install the latest?', {
buttons: [
{
onDidClick: () => {
errorNotification.dismiss();
Appc.installSDK({
version: 'latest',
callback: (code) => {
if (code) {
atom.notifications.addWarning('Unable to install SDK');
return;
}
atom.notifications.addSuccess('Installed SDK');
Appc.getInfo(() => {
setupTargets();
});
}
});
},
text: 'Install latest SDK'
}
],
dismissable: true,
});
const { missing } = await environment.validateEnvironment();

if (missing.length) {
let message = 'You are missing the following required components for Titanium development:';
for (let i = 0; i < missing.length; i++) {
const product = missing[i];
if (i < missing.length - 1) {
message = `${message} ${product.name},`;
} else {
setupTargets();
message = `${message} ${product.name}`;
}
}
message = `${message}. Without these components the package will be unusable.`;

const errorNotification = atom.notifications.addError(message, {
buttons: [
{
onDidClick: async () => {
errorNotification.dismiss();
const updateInfo = [];
for (const product of missing) {
updateInfo.push(await product.getInstallInfo());
}
await Update.installUpdates(updateInfo, this.toolbar);
Appc.getInfo(() => {
setupTargets();
});
},
text: 'Install'
}
],
dismissable: true,
});
}, 1000);
} else {
Appc.getInfo(() => {
setupTargets();
});
}
} else {
if (this.toolbar) {
this.toolbar.destroy();
Expand Down Expand Up @@ -748,6 +759,25 @@ export default {
newProjectDialogPanel = atom.workspace.addModalPanel({ item: this.newProjectDialog });
},

/**
* Display Updates dialog
*/
openUpdatesDialog() {
let updatesDialogPanel;
this.updatesDialog = new UpdatesDialog({
cancel: () => {
updatesDialogPanel.destroy();
},
install: async (type, args) => {
updatesDialogPanel.destroy();
await Update.installUpdates(args, this.toolbar, true);
this.toolbar.hud.displayDefault();

}
});
updatesDialogPanel = atom.workspace.addModalPanel({ item: this.updatesDialog });
},

/**
* Check user is logged in and displays prompt if required. Returns true is prompted.
*
Expand All @@ -762,5 +792,16 @@ export default {
});
return true;
}
},

async checkForUpdates() {
const details = await Update.refresh();
if (details) {
this.toolbar.hud.update({
updates: true,
updateInfo: details
});
return details;
}
}
};
1 change: 1 addition & 0 deletions lib/menu/app.js
Expand Up @@ -17,6 +17,7 @@ module.exports = {
{ label: 'Open related Controller…', command: 'appc:open-related-controller' },
{ label: 'Open related Style…', command: 'appc:open-related-style' },
{ label: 'Open related View…', command: 'appc:open-related-view' },
{ label: 'Check For Updates', command: 'appc:update:refresh' }
]
}
]
Expand Down
30 changes: 29 additions & 1 deletion lib/ui/hud.jsx
Expand Up @@ -2,6 +2,7 @@
/** @jsx etch.dom */

import etch from 'etch';
import Update from '../update';

etch.setScheduler(atom.views);

Expand All @@ -17,7 +18,9 @@ export default class Hud {
this.state = {
icon: `${__dirname}/../../images/appc_44.png`,
text: 'Ready',
spinner: false
spinner: false,
updates: false,
updateInfo: []
};
etch.initialize(this);
}
Expand All @@ -40,6 +43,11 @@ export default class Hud {
<img className="hud-icon" alt="Loading ..." src={this.state.icon} />
<p className="hud-message">{this.state.text}</p>
<div className="hud-spinner loading loading-spinner-tiny" attributes={this.state.spinner ? { style: 'display:block;' } : { style: 'display:none;' }} />
<button className="hud-updates" onClick={this.updatesButtonClicked.bind(this)} attributes={this.state.updates ? { style: 'display:block;' } : { style: 'display:none;' }}>
<div className="row icon-issue-reopened" >
{this.state.updateInfo.length} {this.state.updateInfo.length === 1 ? 'update' : 'updates'}
</div>
</button>
</div>
);
}
Expand Down Expand Up @@ -79,6 +87,18 @@ export default class Hud {
this.state.spinner = false;
}

if (opts.updates) {
this.state.updates = opts.updates;
} else {
this.state.updates = false;
}

if (opts.updateInfo) {
this.state.updateInfo = opts.updateInfo;
} else {
this.state.updateInfo = [];
}

if (opts.default) {
this.default = opts;
}
Expand All @@ -92,4 +112,12 @@ export default class Hud {
displayDefault() {
this.display(this.default);
}

async checkforUpdates() {
return Update.refresh();
}

updatesButtonClicked() {
atom.commands.dispatch(atom.views.getView(atom.workspace), 'appc:updates');
}
}
147 changes: 147 additions & 0 deletions lib/ui/updatesDialog.jsx
@@ -0,0 +1,147 @@
/** @babel */
/** @jsx etch.dom */

import etch from 'etch';
import { updates } from 'titanium-editor-commons';

/**
* Generate component dialog
*/
export default class UpdatesDialog {

/**
* Constructor
*
* @param {Object} opts arguments
* @param {Function} opts.install install callback function
* @param {Function} opts.cancel cancel callback function
*/
constructor(opts) {
this.installButtonEnabled = true;
this.opts = opts;
this.type = '';
this.focus = '';
this.state = {
checked: {},
checking: false,
updates: [],
installs: []
};
this.pullUpdate();
etch.initialize(this);
}

/**
* Clean up
*/
async destroy() {
await etch.destroy(this);
}

/**
* Current state virtual DOM element
*
* @returns {Object}
*/
render() {
const items = this.state.updates.map((item) => (
<div className="row">
<div className="col">
<input className="input-checkbox" type="checkbox" ref={item.productName} checked={item.selected} on={{ change: (event) => this.checkedInstall(event, item) }} />
{item.productName}
</div>
<div className="col">
{item.latestVersion}
</div>
<div className="col">
<a href={item.releaseNotes}>
<div className="icon-book" />
</a>
</div>
</div>
));
return (
<div className="appc-update-dialog" on={{ keyup: this.onKeyUp }}>
<div className="title">Updates Available</div>
<div className="spinner loading loading-spinner-large" attributes={this.state.checking ? { style: 'display:block;' } : { style: 'display:none;' }} />
{items}
<div className="row-buttons">
<button className="btn" attributes={{ tabindex: '10' }} on={{ click: this.cancelButtonClicked }}>Cancel</button>
<button className="btn" ref="install" disabled={!this.installButtonEnabled} attributes={{ tabindex: '11' }} on={{ click: this.installButtonClicked }}>Install</button>
</div>
</div>
);
}

/**
* Update component
*
* @param {Object} opts state
* @returns {Promise}
*/
update() {
return etch.update(this);
}

/**
* Check for updates
*/
async pullUpdate() {
this.state.checking = true;
this.state.updates = await updates.checkAllUpdates();
this.state.checking = false;
for (let index = 0; index < this.state.updates.length; index++) {
this.state.updates[index].selected = true;
}
etch.update(this);
}

checkedInstall(event, item) {
const index = this.state.updates.findIndex(({ productName }) => productName === item.productName);
this.state.updates[index].selected = !this.state.updates[index].selected;
}

/**
* Cancel button clicked
*/
cancelButtonClicked() {
this.cancel();
}

/**
* Generate button clicked
*/
installButtonClicked() {
this.submit();
}

/**
* Call cancel callback function
*/
cancel() {
this.opts.cancel && this.opts.cancel();
}

/**
* Collate arguments and call generate callback function
*/
submit() {
this.state.updates.sort((curr, prev) => curr.priority - prev.priority);
let args = this.state.updates;
this.opts.install && this.opts.install(this.type, args);

}

/**
* Key up event handler
*
* @param {Object} event key up event object
*/
onKeyUp(event) {
if (event.keyCode === 27) {
this.cancel();
} else if (event.keyCode === 13) {
this.submit();
}
}
}

0 comments on commit dad3a08

Please sign in to comment.