FEATURE: Automatically generate optimized site metadata icons (#7372)

FEATURE: Automatically generate optimized site metadata icons (#7372)

This change automatically resizes icons for various purposes. Admins can now upload logo and logo_small, and everything else will be auto-generated. Specific icons can still be uploaded separately if required.

Core

  • Adds an SiteIconManager module which manages automatic resizing and fallback

  • Icons are looked up in the OptimizedImage table at runtime, and then cached in Redis. If the resized version is missing for some reason, then most icons will fall back to the original files. Some icons (e.g. PWA Manifest) will return nil (because an incorrectly sized icon is worse than a missing icon).

  • SiteSetting.site_large_icon_url will return the optimized version, including any fallback. SiteSetting.large_icon continues to return the upload object. This means that (almost) no changes are required in core/plugins to support this new system.

  • Icons are resized whenever a relevant site setting is changed, and during post-deploy migrations

Wizard

  • Allows requiresRefresh wizard steps to reload data via AJAX instead of a full page reload

  • Add placeholders to the icons step of the wizard, which automatically update from the “Square Logo”

  • Various copy updates to support the changes

  • Remove the “upload-time” resizing for large_icon. This is no longer required.

Site Settings UX

  • Move logo/icon settings under a new “Branding” tab

  • Various copy changes to support the changes

  • Adds placeholder support to the image-uploader component

  • Automatically reloads site settings after saving. This allows setting placeholders to change based on changes to other settings

  • Upload site settings will be assigned a placeholder if SiteIconManager responds_to? an icon of the same name

Dashboard Warnings

  • Remove PWA icon and PWA title warnings. Both are now handled automatically.

Bonus

  • Updated the sketch logos to use @awesomerobot’s new high-res designs
diff --git a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6
index 25420b5..9473cbc 100644
--- a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6
@@ -17,7 +17,9 @@ export default Ember.Controller.extend({
 
     if ((!filter || 0 === filter.length) && !this.get("onlyOverridden")) {
       this.set("visibleSiteSettings", this.get("allSiteSettings"));
-      this.transitionToRoute("adminSiteSettings");
+      if (this.get("categoryNameKey") === "all_results") {
+        this.transitionToRoute("adminSiteSettings");
+      }
       return;
     }
 
@@ -77,7 +79,7 @@ export default Ember.Controller.extend({
     } else {
       this.filterContentNow();
     }
-  }, 250).observes("filter", "onlyOverridden"),
+  }, 250).observes("filter", "onlyOverridden", "model"),
 
   actions: {
     clearFilter() {
diff --git a/app/assets/javascripts/admin/mixins/setting-component.js.es6 b/app/assets/javascripts/admin/mixins/setting-component.js.es6
index c3f4f19..9f8166f 100644
--- a/app/assets/javascripts/admin/mixins/setting-component.js.es6
+++ b/app/assets/javascripts/admin/mixins/setting-component.js.es6
@@ -113,6 +113,7 @@ export default Ember.Mixin.create({
         .then(() => {
           this.set("validationMessage", null);
           this.commitBuffer();
+          this.afterSave();
         })
         .catch(e => {
           if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
diff --git a/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6 b/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6
index c90a099..a774415 100644
--- a/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6
@@ -5,6 +5,10 @@ export default Discourse.Route.extend({
       "categoryNameKey",
       params.category_id
     );
+    this.controllerFor("adminSiteSettings").set(
+      "categoryNameKey",
+      params.category_id
+    );
     return Ember.Object.create({
       nameKey: params.category_id,
       name: I18n.t("admin.site_settings.categories." + params.category_id),
diff --git a/app/assets/javascripts/admin/routes/admin-site-settings.js.es6 b/app/assets/javascripts/admin/routes/admin-site-settings.js.es6
index 58ac724..b6c3e85 100644
--- a/app/assets/javascripts/admin/routes/admin-site-settings.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-site-settings.js.es6
@@ -15,5 +15,13 @@ export default Discourse.Route.extend({
     if (!controller.get("visibleSiteSettings")) {
       controller.set("visibleSiteSettings", siteSettings);
     }
+  },
+
+  actions: {
+    refreshAll() {
+      SiteSetting.findAll().then(settings => {
+        this.controllerFor("adminSiteSettings").set("model", settings);
+      });
+    }
   }
 });
diff --git a/app/assets/javascripts/admin/templates/components/site-settings/upload.hbs b/app/assets/javascripts/admin/templates/components/site-settings/upload.hbs
index d138921..51711ee 100644
--- a/app/assets/javascripts/admin/templates/components/site-settings/upload.hbs
+++ b/app/assets/javascripts/admin/templates/components/site-settings/upload.hbs
@@ -1,2 +1,2 @@
-{{site-settings-image-uploader imageUrl=value type="site_setting"}}
+{{site-settings-image-uploader imageUrl=value placeholderUrl=setting.placeholder type="site_setting"}}
 <div class='desc'>{{{unbound setting.description}}}</div>
diff --git a/app/assets/javascripts/admin/templates/site-settings-category.hbs b/app/assets/javascripts/admin/templates/site-settings-category.hbs
index 5f61241..928daa2 100644
--- a/app/assets/javascripts/admin/templates/site-settings-category.hbs
+++ b/app/assets/javascripts/admin/templates/site-settings-category.hbs
@@ -1,7 +1,7 @@
 {{#if filteredContent}}
   {{#d-section class="form-horizontal settings"}}
     {{#each filteredContent as |setting|}}
-      {{site-setting setting=setting}}
+      {{site-setting setting=setting afterSave=(route-action "refreshAll")}}
     {{/each}}
     {{#if category.hasMore}}
       <p class="warning">{{i18n 'admin.site_settings.more_than_30_results'}}</p>
diff --git a/app/assets/javascripts/discourse/components/image-uploader.js.es6 b/app/assets/javascripts/discourse/components/image-uploader.js.es6
index 5d468b1..234de6e 100644
--- a/app/assets/javascripts/discourse/components/image-uploader.js.es6
+++ b/app/assets/javascripts/discourse/components/image-uploader.js.es6
@@ -21,13 +21,25 @@ export default Ember.Component.extend(UploadMixin, {
     }
   },
 
-  @computed("imageUrl")
-  backgroundStyle(imageUrl) {
-    if (Ember.isEmpty(imageUrl)) {
+  @computed("imageUrl", "placeholderUrl")
+  showingPlaceholder(imageUrl, placeholderUrl) {
+    return !imageUrl && placeholderUrl;
+  },
+
+  @computed("placeholderUrl")
+  placeholderStyle(url) {
+    if (Ember.isEmpty(url)) {
       return "".htmlSafe();
     }
+    return `background-image: url(${url})`.htmlSafe();
+  },
 
-    return `background-image: url(${imageUrl})`.htmlSafe();
+  @computed("imageUrl")
+  backgroundStyle(url) {
+    if (Ember.isEmpty(url)) {
+      return "".htmlSafe();
+    }
+    return `background-image: url(${url})`.htmlSafe();
   },
 
   @computed("imageUrl")
@@ -36,11 +48,6 @@ export default Ember.Component.extend(UploadMixin, {
     return imageUrl.split("/").slice(-1)[0];
   },
 
-  @computed("backgroundStyle")
-  hasBackgroundStyle(backgroundStyle) {
-    return !Ember.isEmpty(backgroundStyle.string);
-  },
-
   validateUploadedFilesOptions() {
     return { imagesOnly: true };
   },
diff --git a/app/assets/javascripts/discourse/templates/components/image-uploader.hbs b/app/assets/javascripts/discourse/templates/components/image-uploader.hbs
index c795a85..716f4c6 100644
--- a/app/assets/javascripts/discourse/templates/components/image-uploader.hbs
+++ b/app/assets/javascripts/discourse/templates/components/image-uploader.hbs
@@ -1,11 +1,14 @@
 <div class="uploaded-image-preview input-xxlarge" style={{backgroundStyle}}>
+  {{#if showingPlaceholder}}
+    <div class="placeholder-overlay" style={{placeholderStyle}}></div>
+  {{/if}}
   <div class="image-upload-controls">
     <label class="btn btn-default pad-left no-text {{if uploading 'disabled'}}">
       {{d-icon "far-image"}}
       <input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" />
     </label>
 
-    {{#if hasBackgroundStyle}}
+    {{#if imageUrl}}
       <button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "far-trash-alt"}}</button>
     {{/if}}
 
diff --git a/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon.js.es6 b/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon.js.es6
deleted file mode 100644
index 17a605c..0000000
--- a/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon.js.es6
+++ /dev/null
@@ -1,24 +0,0 @@
-import { observes } from "ember-addons/ember-computed-decorators";
-import { createPreviewComponent } from "wizard/lib/preview";
-
-export default createPreviewComponent(325, 125, {
-  ios: null,
-  image: null,
-
-  @observes("field.value")
-  imageChanged() {
-    this.reload();
-  },
-
-  images() {
-    return {
-      ios: "/images/wizard/apple-mask.png",
-      image: this.get("field.value")
-    };
-  },
-
-  paint(ctx, colors, width, height) {
-    this.scaleImage(this.image, 10, 8, 87, 87);
-    this.scaleImage(this.ios, 0, 0, width, height);
-  }
-});
diff --git a/app/assets/javascripts/wizard/components/image-preview-large-icon.js.es6 b/app/assets/javascripts/wizard/components/image-preview-large-icon.js.es6
new file mode 100644
index 0000000..17a605c
--- /dev/null
+++ b/app/assets/javascripts/wizard/components/image-preview-large-icon.js.es6
@@ -0,0 +1,24 @@
+import { observes } from "ember-addons/ember-computed-decorators";

[... diff too long, it was truncated ...]

GitHub sha: 0e303c7f

1 Like

FIX: Do not refresh all settings on save for all settings, limit to only a few