mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
💄Updated save buttons to reset state (#1522)
* Updated save buttons to reset state no issue Currently the save buttons across Admin don't auto-reset to idle state after success/failure on run which can give false impression once user changes any value. This PR auto-resets the button to idle state after a fixed timeout if no subsequent action is performed as a short term UX improvement. * Fixed success check for auto reset * Updated timeout value * Added explicit save button reset for pages * Updated save buttons to reset via shortcut Auto-reset for save buttons wasn't working if not done through manual click on task button previously, this handles by splitting the original save task in controller to handle shortcut saves. * Updated reset check for only successful tasks * Added save reset to code-injection and design settings Co-authored-by: Peter Zimon <peter.zimon@gmail.com>
This commit is contained in:
parent
36bcae2205
commit
c3883d4c6f
22 changed files with 132 additions and 31 deletions
|
@ -34,6 +34,7 @@ const GhTaskButton = Component.extend({
|
|||
idleClass: '',
|
||||
runningClass: '',
|
||||
showSuccess: true, // set to false if you want the spinner to show until a transition occurs
|
||||
autoReset: false, // set to false if you want don't want task button to reset after timeout
|
||||
successText: 'Saved',
|
||||
successClass: 'gh-btn-green',
|
||||
failureText: 'Retry',
|
||||
|
@ -118,7 +119,6 @@ const GhTaskButton = Component.extend({
|
|||
return false;
|
||||
}
|
||||
|
||||
let task = this.task;
|
||||
let taskName = this.get('task.name');
|
||||
let lastTaskName = this.get('task.last.task.name');
|
||||
|
||||
|
@ -128,9 +128,8 @@ const GhTaskButton = Component.extend({
|
|||
if (this.isRunning && taskName === lastTaskName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.action();
|
||||
task.perform(this.taskArgs);
|
||||
this._handleMainTask.perform();
|
||||
|
||||
this._restartAnimation.perform();
|
||||
|
||||
|
@ -156,7 +155,24 @@ const GhTaskButton = Component.extend({
|
|||
yield timeout(10);
|
||||
elem.classList.add('retry-animated');
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
_handleMainTask: task(function* () {
|
||||
this._resetButtonState.cancelAll();
|
||||
yield this.task.perform(this.taskArgs);
|
||||
const isTaskSuccess = this.get('task.last.isSuccessful') && this.get('task.last.value');
|
||||
if (this.autoReset && this.showSuccess && isTaskSuccess) {
|
||||
this._resetButtonState.perform();
|
||||
}
|
||||
}),
|
||||
|
||||
_resetButtonState: task(function* () {
|
||||
yield timeout(2500);
|
||||
if (!this.get('task.last.isRunning')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('task.last', null);
|
||||
}
|
||||
}).restartable()
|
||||
});
|
||||
|
||||
export default GhTaskButton;
|
||||
|
|
|
@ -6,7 +6,7 @@ import {alias} from '@ember/object/computed';
|
|||
import {computed, defineProperty} from '@ember/object';
|
||||
import {inject as controller} from '@ember/controller';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
const SCRATCH_PROPS = ['name', 'email', 'note'];
|
||||
|
||||
|
@ -89,7 +89,7 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveMember: task(function* () {
|
||||
let {member, scratchMember} = this;
|
||||
|
||||
// if Cmd+S is pressed before the field loses focus make sure we're
|
||||
|
@ -111,6 +111,15 @@ export default Controller.extend({
|
|||
}
|
||||
}).drop(),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveMember.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveMember.last.isSuccessful') && this.get('saveMember.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveMember.last', null);
|
||||
}
|
||||
}).drop(),
|
||||
|
||||
fetchMember: task(function* (memberId) {
|
||||
this.set('isLoading', true);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable ghost/ember/alias-model-in-controller */
|
||||
import Controller from '@ember/controller';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
notifications: service(),
|
||||
|
@ -53,7 +53,7 @@ export default Controller.extend({
|
|||
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveTask: task(function* () {
|
||||
let notifications = this.notifications;
|
||||
|
||||
try {
|
||||
|
@ -62,5 +62,14 @@ export default Controller.extend({
|
|||
notifications.showAPIError(error, {key: 'code-injection.save'});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveTask.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveTask.last.isSuccessful') && this.get('saveTask.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveTask.last', null);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import {isEmpty} from '@ember/utils';
|
|||
import {isThemeValidationError} from 'ghost-admin/services/ajax';
|
||||
import {notEmpty} from '@ember/object/computed';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
config: service(),
|
||||
|
@ -200,7 +200,7 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveTask: task(function* () {
|
||||
let navItems = this.get('settings.navigation');
|
||||
let secondaryNavItems = this.get('settings.secondaryNavigation');
|
||||
|
||||
|
@ -235,6 +235,15 @@ export default Controller.extend({
|
|||
}
|
||||
}),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveTask.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveTask.last.isSuccessful') && this.get('saveTask.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveTask.last', null);
|
||||
}
|
||||
}),
|
||||
|
||||
addNewNavItem(item) {
|
||||
let navItems = item.isSecondary ? this.get('settings.secondaryNavigation') : this.get('settings.navigation');
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {computed} from '@ember/object';
|
|||
import {htmlSafe} from '@ember/string';
|
||||
import {run} from '@ember/runloop';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
const ICON_EXTENSIONS = ['ico', 'png'];
|
||||
|
||||
|
@ -321,7 +321,7 @@ export default Controller.extend({
|
|||
});
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveSettings: task(function* () {
|
||||
let notifications = this.notifications;
|
||||
let config = this.config;
|
||||
|
||||
|
@ -339,5 +339,14 @@ export default Controller.extend({
|
|||
}
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveSettings.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveSettings.last.isSuccessful') && this.get('saveSettings.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveSettings.last', null);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -134,10 +134,19 @@ export default Controller.extend({
|
|||
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveIntegration: task(function* () {
|
||||
return yield this.integration.save();
|
||||
}),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveIntegration.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveIntegration.last.isSuccessful') && this.get('saveIntegration.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveIntegration.last', null);
|
||||
}
|
||||
}),
|
||||
|
||||
copyContentKey: task(function* () {
|
||||
copyTextToClipboard(this.integration.contentKey.secret);
|
||||
yield timeout(3000);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import Controller from '@ember/controller';
|
||||
import {alias} from '@ember/object/computed';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
notifications: service(),
|
||||
|
@ -61,7 +61,7 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveTask: task(function* () {
|
||||
let amp = this.ampSettings;
|
||||
let settings = this.settings;
|
||||
|
||||
|
@ -73,5 +73,15 @@ export default Controller.extend({
|
|||
this.notifications.showAPIError(error);
|
||||
throw error;
|
||||
}
|
||||
}).drop(),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveTask.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveTask.last.isSuccessful') && this.get('saveTask.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveTask.last', null);
|
||||
}
|
||||
}).drop()
|
||||
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import boundOneWay from 'ghost-admin/utils/bound-one-way';
|
|||
import {empty} from '@ember/object/computed';
|
||||
import {isInvalidError} from 'ember-ajax/errors';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
ghostPaths: service(),
|
||||
|
@ -94,7 +94,7 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveTask: task(function* () {
|
||||
let slack = this.slackSettings;
|
||||
let settings = this.settings;
|
||||
let slackArray = this.slackArray;
|
||||
|
@ -113,6 +113,15 @@ export default Controller.extend({
|
|||
}
|
||||
}).drop(),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveTask.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveTask.last.isSuccessful') && this.get('saveTask.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveTask.last', null);
|
||||
}
|
||||
}).drop(),
|
||||
|
||||
sendTestNotification: task(function* () {
|
||||
let notifications = this.notifications;
|
||||
let slackApi = this.get('ghostPaths.url').api('slack', 'test');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import Controller from '@ember/controller';
|
||||
import {alias} from '@ember/object/computed';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
notifications: service(),
|
||||
|
@ -68,7 +68,7 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveTask: task(function* () {
|
||||
let unsplash = this.unsplashSettings;
|
||||
let settings = this.settings;
|
||||
|
||||
|
@ -83,5 +83,14 @@ export default Controller.extend({
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
}).drop(),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveTask.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveTask.last.isSuccessful') && this.get('saveTask.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveTask.last', null);
|
||||
}
|
||||
}).drop()
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import {alias} from '@ember/object/computed';
|
|||
import {computed, defineProperty} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {slugify} from '@tryghost/string';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
const SCRATCH_PROPS = ['name', 'slug', 'description', 'metaTitle', 'metaDescription'];
|
||||
|
||||
|
@ -74,7 +74,7 @@ export default Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
save: task(function* () {
|
||||
saveTask: task(function* () {
|
||||
let {tag, scratchTag} = this;
|
||||
|
||||
// if Cmd+S is pressed before the field loses focus make sure we're
|
||||
|
@ -96,6 +96,15 @@ export default Controller.extend({
|
|||
}
|
||||
}),
|
||||
|
||||
save: task(function* () {
|
||||
yield this.saveTask.perform();
|
||||
yield timeout(2500);
|
||||
if (this.get('saveTask.last.isSuccessful') && this.get('saveTask.last.value')) {
|
||||
// Reset last task to bring button back to idle state
|
||||
yield this.set('saveTask.last', null);
|
||||
}
|
||||
}).drop(),
|
||||
|
||||
fetchTag: task(function* (slug) {
|
||||
this.set('isLoading', true);
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<GhTaskButton
|
||||
@buttonText="Save"
|
||||
@successText="Saved"
|
||||
@autoReset={{true}}
|
||||
@task={{this.saveTask}}
|
||||
@taskArgs={{this.model}}
|
||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
@buttonText="Save"
|
||||
@runningText="Saving..."
|
||||
@successText="Saved"
|
||||
@autoReset={{true}}
|
||||
@task={{this.saveTask}}
|
||||
@taskArgs={{this.label}}
|
||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
<GhTaskButton @class="gh-btn gh-btn-blue gh-btn-icon" @type="button" @task={{this.save}} @data-test-button="save" />
|
||||
<GhTaskButton @class="gh-btn gh-btn-blue gh-btn-icon" @type="button" @task={{this.saveMember}} @autoReset={{true}} @data-test-button="save" />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
|||
{{else}}
|
||||
{{this.member.geolocation.country}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{else}}
|
||||
Unknown location
|
||||
{{/if}}
|
||||
– Created on {{this.subscribedAt}}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Code injection
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
<GhTaskButton @task={{this.saveTask}} @autoReset={{true}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Design
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
<GhTaskButton @task={{this.saveTask}} @autoReset={{true}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
General settings
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @buttonText="Save settings" @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button="true" />
|
||||
<GhTaskButton @buttonText="Save settings" @task={{this.saveSettings}} @autoReset={{true}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button="true" />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{{this.integration.name}}
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-button="save" />
|
||||
<GhTaskButton @task={{this.saveIntegration}} @class="gh-btn gh-btn-blue gh-btn-icon" @autoReset={{true}} data-test-button="save" />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
AMP
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
<GhTaskButton @task={{this.saveTask}} @class="gh-btn gh-btn-blue gh-btn-icon" @autoReset={{true}} data-test-save-button={{true}} />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
Slack
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
<GhTaskButton @task={{this.saveTask}} @autoReset={{true}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
Unsplash
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
<GhTaskButton @task={{this.saveTask}} @autoReset={{true}} @class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button={{true}} />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
@task={{this.saveSettings}}
|
||||
@successText="Saved"
|
||||
@runningText="Saving"
|
||||
@autoReset={{true}}
|
||||
@class="gh-btn gh-btn-blue gh-btn-icon"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{{if this.tag.isNew "New tag" this.tag.name}}
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @task={{this.save}} @type="button" @class="gh-btn gh-btn-blue gh-btn-icon" @data-test-button="save" />
|
||||
<GhTaskButton @task={{this.saveTask}} @type="button" @class="gh-btn gh-btn-blue gh-btn-icon" @autoReset={{true}} @data-test-button="save" />
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue