UX: Switched composer draft saving to animations (#7356)

UX: Switched composer draft saving to animations (#7356)

diff --git a/app/assets/javascripts/discourse/components/invite-panel.js.es6 b/app/assets/javascripts/discourse/components/invite-panel.js.es6
index 4427410..3fca499 100644
--- a/app/assets/javascripts/discourse/components/invite-panel.js.es6
+++ b/app/assets/javascripts/discourse/components/invite-panel.js.es6
@@ -14,7 +14,6 @@ export default Ember.Component.extend({
   // page which is wrong.
   emailOrUsername: null,
   hasCustomMessage: false,
-  hasCustomMessage: false,
   customMessage: null,
   inviteIcon: "envelope",
   invitingExistingUserToTopic: false,
diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6
index 2be3462..be08943 100644
--- a/app/assets/javascripts/discourse/models/composer.js.es6
+++ b/app/assets/javascripts/discourse/models/composer.js.es6
@@ -74,6 +74,8 @@ const Composer = RestModel.extend({
   _categoryId: null,
   unlistTopic: false,
   noBump: false,
+  draftSaving: false,
+  draftSaved: false,
 
   archetypes: function() {
     return this.site.get("archetypes");
@@ -971,7 +973,8 @@ const Composer = RestModel.extend({
     }
 
     this.setProperties({
-      draftStatus: I18n.t("composer.saving_draft_tip"),
+      draftSaved: false,
+      draftSaving: true,
       draftConflictUser: null
     });
 
@@ -1004,18 +1007,21 @@ const Composer = RestModel.extend({
       .then(result => {
         if (result.conflict_user) {
           this.setProperties({
+            draftSaving: false,
             draftStatus: I18n.t("composer.edit_conflict"),
             draftConflictUser: result.conflict_user
           });
         } else {
           this.setProperties({
-            draftStatus: I18n.t("composer.saved_draft_tip"),
+            draftSaving: false,
+            draftSaved: true,
             draftConflictUser: null
           });
         }
       })
       .catch(() => {
         this.setProperties({
+          draftSaving: false,
           draftStatus: I18n.t("composer.drafts_offline"),
           draftConflictUser: null
         });
@@ -1033,6 +1039,8 @@ const Composer = RestModel.extend({
           self.set("draftStatus", null);
           self.set("draftConflictUser", null);
           self._clearingStatus = null;
+          self.set("draftSaving", false);
+          self.set("draftSaved", false);
         },
         Ember.Test ? 0 : 1000
       );
diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs
index 834f65d..a1b4850 100644
--- a/app/assets/javascripts/discourse/templates/composer.hbs
+++ b/app/assets/javascripts/discourse/templates/composer.hbs
@@ -162,6 +162,8 @@
                 {{#if model.draftConflictUser}}
                   {{avatar model.draftConflictUser imageSize="small"}}
                 {{/if}}
+                {{#if model.draftSaving}}<div class="spinner small"></div>{{/if}}
+                {{#if model.draftSaved}}{{d-icon 'check' class='save-animation'}}{{/if}}
                 {{model.draftStatus}}
               </div>
             </div>
diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss
index e09bd7b..59941c7 100644
--- a/app/assets/stylesheets/common/base/compose.scss
+++ b/app/assets/stylesheets/common/base/compose.scss
@@ -453,3 +453,52 @@ div.ac-wrap {
     }
   }
 }
+
+.save-animation {
+  transition: all 1s ease-in-out;
+  -webkit-transform: scale(0.1);
+  transform: scale(0.1);
+  -webkit-animation: transformer 1.5s forwards;
+  animation: transformer 1.5s forwards;
+}
+
+@-webkit-keyframes transformer {
+  0% {
+    -webkit-transform: scale(0.1);
+  }
+  20% {
+    -webkit-transform: scale(1.2);
+  }
+  40% {
+    -webkit-transform: scale(1);
+    -webkit-filter: opacity(1);
+  }
+  60% {
+    -webkit-transform: scale(1);
+    -webkit-filter: opacity(1);
+  }
+  100% {
+    -webkit-transform: scale(1);
+    -webkit-filter: opacity(0);
+  }
+}
+@keyframes transformer {
+  0% {
+    transform: scale(0.1);
+  }
+  20% {
+    transform: scale(1.2);
+  }
+  40% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  60% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 0;
+  }
+}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 36e1fbc..4d2f668 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1497,8 +1497,6 @@ en:
       toggle_whisper: "Toggle Whisper"
       toggle_unlisted: "Toggle Unlisted"
       posting_not_on_topic: "Which topic do you want to reply to?"
-      saving_draft_tip: "saving..."
-      saved_draft_tip: "saved"
       saved_local_draft_tip: "saved locally"
       similar_topics: "Your topic is similar to..."
       drafts_offline: "drafts offline"
diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6
index cc8b5e1..e7e801e 100644
--- a/test/javascripts/acceptance/composer-test.js.es6
+++ b/test/javascripts/acceptance/composer-test.js.es6
@@ -638,6 +638,8 @@ QUnit.test("Can switch states without abandon popup", async assert => {
     "mode should have changed"
   );
 
+  assert.ok(find(".save-animation"), "save animation should show");
+
   toggleCheckDraftPopup(false);
 });

GitHub sha: 9a428acc