0
Fork 0
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:
Rishabh Garg 2020-04-06 16:17:28 +05:30 committed by GitHub
parent 36bcae2205
commit c3883d4c6f
22 changed files with 132 additions and 31 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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);
}
})
});

View file

@ -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');

View file

@ -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);
}
})
});

View file

@ -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);

View file

@ -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()
});

View file

@ -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');

View file

@ -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()
});

View file

@ -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);

View file

@ -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"

View file

@ -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"

View file

@ -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}}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -34,6 +34,7 @@
@task={{this.saveSettings}}
@successText="Saved"
@runningText="Saving"
@autoReset={{true}}
@class="gh-btn gh-btn-blue gh-btn-icon"
/>
</div>

View file

@ -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>