FEATURE: Added settings/translations support to theme editor UI (#7026)

FEATURE: Added settings/translations support to theme editor UI (#7026)

  • These advanced fields are hidden behind an ‘advanced’ button, so will not affect normal use
  • The editor has been refactored into a component, and styling cleaned up so menu items do not overlap on small screens
  • Styling has been added to indicate which fields are in use for a theme
  • Icons have been added to identify which fields have errors
diff --git a/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6
new file mode 100644
index 0000000..af661e4
--- /dev/null
+++ b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6
@@ -0,0 +1,96 @@
+import { default as computed } from "ember-addons/ember-computed-decorators";
+
+export default Ember.Component.extend({
+  @computed("theme.targets", "onlyOverridden", "showAdvanced")
+  visibleTargets(targets, onlyOverridden, showAdvanced) {
+    return targets.filter(target => {
+      if (target.advanced && !showAdvanced) {
+        return false;
+      }
+      if (!onlyOverridden) {
+        return true;
+      }
+      return target.edited;
+    });
+  },
+
+  @computed("currentTargetName", "onlyOverridden", "theme.fields")
+  visibleFields(targetName, onlyOverridden, fields) {
+    fields = fields[targetName];
+    if (onlyOverridden) {
+      fields = fields.filter(field => field.edited);
+    }
+    return fields;
+  },
+
+  @computed("currentTargetName", "fieldName")
+  activeSectionMode(targetName, fieldName) {
+    if (["settings", "translations"].includes(targetName)) return "yaml";
+    return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
+  },
+
+  @computed("fieldName", "currentTargetName", "theme")
+  activeSection: {
+    get(fieldName, target, model) {
+      return model.getField(target, fieldName);
+    },
+    set(value, fieldName, target, model) {
+      model.setField(target, fieldName, value);
+      return value;
+    }
+  },
+
+  @computed("fieldName", "currentTargetName")
+  editorId(fieldName, currentTarget) {
+    return fieldName + "|" + currentTarget;
+  },
+
+  @computed("maximized")
+  maximizeIcon(maximized) {
+    return maximized ? "discourse-compress" : "discourse-expand";
+  },
+
+  @computed("currentTargetName", "theme.targets")
+  showAddField(currentTargetName, targets) {
+    return targets.find(t => t.name === currentTargetName).customNames;
+  },
+
+  @computed("currentTargetName", "fieldName", "theme.theme_fields.@each.error")
+  error(target, fieldName) {
+    return this.get("theme").getError(target, fieldName);
+  },
+
+  actions: {
+    toggleShowAdvanced() {
+      this.toggleProperty("showAdvanced");
+    },
+
+    toggleAddField() {
+      this.toggleProperty("addingField");
+    },
+
+    cancelAddField() {
+      this.set("addingField", false);
+    },
+
+    addField(name) {
+      if (!name) return;
+      name = name.replace(/\W/g, "");
+      this.get("theme").setField(this.get("currentTargetName"), name, "");
+      this.set("newFieldName", "");
+      this.set("addingField", false);
+      this.fieldAdded(this.get("currentTargetName"), name);
+    },
+
+    toggleMaximize: function() {
+      this.toggleProperty("maximized");
+      Ember.run.next(() => {
+        this.appEvents.trigger("ace:resize");
+      });
+    },
+
+    onlyOverriddenChanged(value) {
+      this.onlyOverriddenChanged(value);
+    }
+  }
+});
diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6
index 807c21e..9c33c27 100644
--- a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6
@@ -1,163 +1,28 @@
 import { url } from "discourse/lib/computed";
-import {
-  default as computed,
-  observes
-} from "ember-addons/ember-computed-decorators";
+import { default as computed } from "ember-addons/ember-computed-decorators";
 
 export default Ember.Controller.extend({
   section: null,
   currentTarget: 0,
   maximized: false,
   previewUrl: url("model.id", "/admin/themes/%@/preview"),
-
+  showAdvanced: false,
   editRouteName: "adminCustomizeThemes.edit",
-
-  targets: [
-    { id: 0, name: "common" },
-    { id: 1, name: "desktop" },
-    { id: 2, name: "mobile" },
-    { id: 3, name: "settings" },
-    { id: 4, name: "translations" }
-  ],
-
-  fieldsForTarget: function(target) {
-    const common = [
-      "scss",
-      "head_tag",
-      "header",
-      "after_header",
-      "body_tag",
-      "footer"
-    ];
-    switch (target) {
-      case "common":
-        return [...common, "embedded_scss"];
-      case "desktop":
-        return common;
-      case "mobile":
-        return common;
-      case "settings":
-        return ["yaml"];
-    }
-  },
-
-  @computed("onlyOverridden")
-  showCommon() {
-    return this.shouldShow("common");
-  },
-
-  @computed("onlyOverridden")
-  showDesktop() {
-    return this.shouldShow("desktop");
-  },
-
-  @computed("onlyOverridden")
-  showMobile() {
-    return this.shouldShow("mobile");
-  },
-
-  @observes("onlyOverridden")
-  onlyOverriddenChanged() {
-    if (this.get("onlyOverridden")) {
-      if (
-        !this.get("model").hasEdited(
-          this.get("currentTargetName"),
-          this.get("fieldName")
-        )
-      ) {
-        let target =
-          (this.get("showCommon") && "common") ||
-          (this.get("showDesktop") && "desktop") ||
-          (this.get("showMobile") && "mobile");
-
-        let fields = this.get("model.theme_fields");
-        let field = fields && fields.find(f => f.target === target);
-        this.replaceRoute(
-          this.get("editRouteName"),
-          this.get("model.id"),
-          target,
-          field && field.name
-        );
-      }
-    }
-  },
-
-  shouldShow(target) {
-    if (!this.get("onlyOverridden")) {
-      return true;
-    }
-    return this.get("model").hasEdited(target);
-  },
+  showRouteName: "adminCustomizeThemes.show",
 
   setTargetName: function(name) {
-    const target = this.get("targets").find(t => t.name === name);
+    const target = this.get("model.targets").find(t => t.name === name);
     this.set("currentTarget", target && target.id);
   },
 
   @computed("currentTarget")
   currentTargetName(id) {
-    const target = this.get("targets").find(t => t.id === parseInt(id, 10));
+    const target = this.get("model.targets").find(
+      t => t.id === parseInt(id, 10)
+    );
     return target && target.name;
   },
 
-  @computed("fieldName")
-  activeSectionMode(fieldName) {
-    if (fieldName === "yaml") return "yaml";
-    return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
-  },
-
-  @computed("currentTargetName", "fieldName", "saving")
-  error(target, fieldName) {
-    return this.get("model").getError(target, fieldName);
-  },
-
-  @computed("fieldName", "currentTargetName")
-  editorId(fieldName, currentTarget) {
-    return fieldName + "|" + currentTarget;
-  },
-
-  @computed("fieldName", "currentTargetName", "model")
-  activeSection: {
-    get(fieldName, target, model) {
-      return model.getField(target, fieldName);
-    },
-    set(value, fieldName, target, model) {
-      model.setField(target, fieldName, value);
-      return value;
-    }
-  },
-
-  @computed("currentTargetName", "onlyOverridden")
-  fields(target, onlyOverridden) {
-    let fields = this.fieldsForTarget(target);
-
-    if (onlyOverridden) {
-      const model = this.get("model");
-      const targetName = this.get("currentTargetName");
-      fields = fields.filter(name => model.hasEdited(targetName, name));
-    }
-
-    return fields.map(name => {
-      let hash = {
-        key: `admin.customize.theme.${name}.text`,
-        name: name
-      };
-
-      if (name.indexOf("_tag") > 0) {
-        hash.icon = "file-text-o";
-      }
-
-      hash.title = I18n.t(`admin.customize.theme.${name}.title`);
-
-      return hash;
-    });
-  },
-
-  @computed("maximized")
-  maximizeIcon(maximized) {
-    return maximized ? "discourse-compress" : "discourse-expand";
-  },
-
   @computed("model.isSaving")
   saveButtonText(isSaving) {
     return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
@@ -178,11 +43,36 @@ export default Ember.Controller.extend({
         });
     },
 
-    toggleMaximize: function() {

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

GitHub sha: 05ee1d1a

you can use fmt() for this.

import { fmt } from "discourse/lib/computed";

editorId: fmt("fieldName", "currentTargetName", "%@|%@")

You should group when possible with setProperties

Ember.run.next(() => this.appEvents.trigger("ace:resize"));

filter(f => f.target === target && (!name || name === f.name))

DEV: Code style improvements following review