Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(updates): add update functionality #162

Merged
merged 8 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
106 changes: 72 additions & 34 deletions lib/index.js
Original file line number Diff line number Diff line change
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 Down Expand Up @@ -239,7 +241,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 +275,9 @@ export default {
}
this.toolbar.toggle();
}
},
'appc:update:refresh': () => {
this.checkForUpdates();
}
}),

Expand All @@ -299,14 +304,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 +353,45 @@ 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 Update.validateEnvironment();

if (missing.length) {
let message = 'You are missing the following required components for:';
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 extension will be unusable.`;
longton95 marked this conversation as resolved.
Show resolved Hide resolved

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 {
setupTargets();
longton95 marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
if (this.toolbar) {
this.toolbar.destroy();
Expand Down Expand Up @@ -748,6 +756,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 +789,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
}
}