Ghost/ghost/admin/app/components/gh-task-button.js
Kevin Ansfield c55d1f69b4 🐛 fix password change button not showing success state (#659)
closes https://github.com/TryGhost/Ghost/issues/8358

- the change password task was not returning a truthy value on success so you always saw the "X Retry" state
- adds option to pass `idleClass` and `runningClass` to `gh-task-button` - this is so that colour classes can be set in the "base" state without overriding the success/failure colors (some colours would have preference based on the order the colours are defined in the CSS file, eg. setting `gh-btn-red` as a base CSS class would override the `gh-btn-green` that is added after a successful save)
2017-04-19 18:21:47 +02:00

147 lines
4.6 KiB
JavaScript

import Component from 'ember-component';
import observer from 'ember-metal/observer';
import computed, {reads} from 'ember-computed';
import {isBlank} from 'ember-utils';
import {invokeAction} from 'ember-invoke-action';
import {task, timeout} from 'ember-concurrency';
/**
* Task Button works exactly like Spin button, but with one major difference:
*
* Instead of passing a "submitting" parameter (which is bound to the parent object),
* you pass an ember-concurrency task. All of the "submitting" behavior is handled automatically.
*
* As another bonus, there's no need to handle canceling the promises when something
* like a controller changes. Because the only task running is handled through this
* component, all running promises will automatically be cancelled when this
* component is removed from the DOM
*/
const GhTaskButton = Component.extend({
tagName: 'button',
classNameBindings: [
'isRunning:appear-disabled',
'isIdleClass',
'isRunningClass',
'isSuccessClass',
'isFailureClass'
],
attributeBindings: ['disabled', 'type', 'tabindex'],
task: null,
disabled: false,
buttonText: 'Save',
runningText: reads('buttonText'),
idleClass: '',
runningClass: '',
successText: 'Saved',
successClass: 'gh-btn-green',
failureText: 'Retry',
failureClass: 'gh-btn-red',
// hasRun is needed so that a newly rendered button does not show the last
// state of the associated task
hasRun: false,
isRunning: reads('task.last.isRunning'),
isIdleClass: computed('isIdle', function () {
if (this.get('isIdle')) {
return this.get('idleClass');
}
}),
isRunningClass: computed('isRunning', function () {
if (this.get('isRunning')) {
return this.get('runningClass') || this.get('idleClass');
}
}),
isSuccess: computed('hasRun', 'isRunning', 'task.last.value', function () {
if (!this.get('hasRun') || this.get('isRunning')) {
return false;
}
let value = this.get('task.last.value');
return !isBlank(value) && value !== false;
}),
isSuccessClass: computed('isSuccess', function () {
if (this.get('isSuccess')) {
return this.get('successClass');
}
}),
isFailure: computed('hasRun', 'isRunning', 'isSuccess', 'task.last.error', function () {
if (!this.get('hasRun') || this.get('isRunning') || this.get('isSuccess')) {
return false;
}
return this.get('task.last.error') !== undefined;
}),
isFailureClass: computed('isFailure', function () {
if (this.get('isFailure')) {
return this.get('failureClass');
}
}),
isIdle: computed('isRunning', 'isSuccess', 'isFailure', function () {
return !this.get('isRunning') && !this.get('isSuccess') && !this.get('isFailure');
}),
click() {
// do nothing if disabled externally
if (this.get('disabled')) {
return false;
}
let task = this.get('task');
let taskName = this.get('task.name');
let lastTaskName = this.get('task.last.task.name');
// task-buttons are never disabled whilst running so that clicks when a
// taskGroup is running don't get dropped BUT that means we need to check
// here to avoid spamming actions from multiple clicks
if (this.get('isRunning') && taskName === lastTaskName) {
return;
}
invokeAction(this, 'action');
task.perform();
this.get('_restartAnimation').perform();
// prevent the click from bubbling and triggering form actions
return false;
},
setSize: observer('isRunning', function () {
if (this.get('isRunning')) {
this.set('hasRun', true);
// this.$().width(this.$().width());
// this.$().height(this.$().height());
} else {
// this.$().width('');
// this.$().height('');
}
}),
// when local validation fails there's no transition from failed->running
// so we want to restart the retry spinner animation to show something
// has happened when the button is clicked
_restartAnimation: task(function* () {
if (this.$('.retry-animated').length) {
// eslint-disable-next-line
let elem = this.$('.retry-animated')[0];
elem.classList.remove('retry-animated');
yield timeout(10);
elem.classList.add('retry-animated');
}
})
});
GhTaskButton.reopenClass({
positionalParams: ['buttonText']
});
export default GhTaskButton;