UX: List popular themes and components in admin panel (#6997)

UX: List popular themes and components in admin panel (#6997)

Reorganizes theme create/upload flows into one install flow Adds quick list of popular themes/components with one-click installation

diff --git a/app/assets/javascripts/admin/components/install-theme-item.js.es6 b/app/assets/javascripts/admin/components/install-theme-item.js.es6
new file mode 100644
index 0000000..c1f3f57
--- /dev/null
+++ b/app/assets/javascripts/admin/components/install-theme-item.js.es6
@@ -0,0 +1,3 @@
+export default Ember.Component.extend({
+  classNames: ["install-theme-item"]
+});
diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6
index 641ec62..f38ae3a 100644
--- a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6
@@ -12,5 +12,10 @@ export default Ember.Controller.extend({
   @computed("model", "model.@each.component")
   childThemes(themes) {
     return themes.filter(t => t.get("component"));
+  },
+
+  @computed("model", "model.@each.component")
+  installedThemes(themes) {
+    return themes.map(t => t.name);
   }
 });
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6
deleted file mode 100644
index 4b25e00..0000000
--- a/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6
+++ /dev/null
@@ -1,65 +0,0 @@
-import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { default as computed } from "ember-addons/ember-computed-decorators";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import { THEMES, COMPONENTS } from "admin/models/theme";
-
-const MIN_NAME_LENGTH = 4;
-
-export default Ember.Controller.extend(ModalFunctionality, {
-  saving: false,
-  triggerError: false,
-  themesController: Ember.inject.controller("adminCustomizeThemes"),
-  types: [
-    { name: I18n.t("admin.customize.theme.theme"), value: THEMES },
-    { name: I18n.t("admin.customize.theme.component"), value: COMPONENTS }
-  ],
-
-  @computed("triggerError", "nameTooShort")
-  showError(trigger, tooShort) {
-    return trigger && tooShort;
-  },
-
-  @computed("name")
-  nameTooShort(name) {
-    return !name || name.length < MIN_NAME_LENGTH;
-  },
-
-  @computed("component")
-  placeholder(component) {
-    if (component) {
-      return I18n.t("admin.customize.theme.component_name");
-    } else {
-      return I18n.t("admin.customize.theme.theme_name");
-    }
-  },
-
-  @computed("themesController.currentTab")
-  selectedType(tab) {
-    return tab;
-  },
-
-  @computed("selectedType")
-  component(type) {
-    return type === COMPONENTS;
-  },
-
-  actions: {
-    createTheme() {
-      if (this.get("nameTooShort")) {
-        this.set("triggerError", true);
-        return;
-      }
-
-      this.set("saving", true);
-      const theme = this.store.createRecord("theme");
-      theme
-        .save({ name: this.get("name"), component: this.get("component") })
-        .then(() => {
-          this.get("themesController").send("addTheme", theme);
-          this.send("closeModal");
-        })
-        .catch(popupAjaxError)
-        .finally(() => this.set("saving", false));
-    }
-  }
-});
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6
deleted file mode 100644
index 1c7752e..0000000
--- a/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6
+++ /dev/null
@@ -1,99 +0,0 @@
-import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { ajax } from "discourse/lib/ajax";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import {
-  default as computed,
-  observes
-} from "ember-addons/ember-computed-decorators";
-
-export default Ember.Controller.extend(ModalFunctionality, {
-  local: Ember.computed.equal("selection", "local"),
-  remote: Ember.computed.equal("selection", "remote"),
-  selection: "local",
-  adminCustomizeThemes: Ember.inject.controller(),
-  loading: false,
-  keyGenUrl: "/admin/themes/generate_key_pair",
-  importUrl: "/admin/themes/import",
-  checkPrivate: Ember.computed.match("uploadUrl", /^git/),
-  localFile: null,
-  uploadUrl: null,
-  urlPlaceholder: "https://github.com/discourse/sample_theme",
-  advancedVisible: false,
-
-  @computed("loading", "remote", "uploadUrl", "local", "localFile")
-  importDisabled(isLoading, isRemote, uploadUrl, isLocal, localFile) {
-    return isLoading || (isRemote && !uploadUrl) || (isLocal && !localFile);
-  },
-
-  @observes("privateChecked")
-  privateWasChecked() {
-    this.get("privateChecked")
-      ? this.set("urlPlaceholder", "git@github.com:discourse/sample_theme.git")
-      : this.set("urlPlaceholder", "https://github.com/discourse/sample_theme");
-
-    const checked = this.get("privateChecked");
-    if (checked && !this._keyLoading) {
-      this._keyLoading = true;
-      ajax(this.get("keyGenUrl"), { method: "POST" })
-        .then(pair => {
-          this.set("privateKey", pair.private_key);
-          this.set("publicKey", pair.public_key);
-        })
-        .catch(popupAjaxError)
-        .finally(() => {
-          this._keyLoading = false;
-        });
-    }
-  },
-
-  actions: {
-    uploadLocaleFile() {
-      this.set("localFile", $("#file-input")[0].files[0]);
-    },
-
-    toggleAdvanced() {
-      this.set("advancedVisible", !this.get("advancedVisible"));
-    },
-
-    importTheme() {
-      let options = {
-        type: "POST"
-      };
-
-      if (this.get("local")) {
-        options.processData = false;
-        options.contentType = false;
-        options.data = new FormData();
-        options.data.append("theme", this.get("localFile"));
-      } else {
-        options.data = {
-          remote: this.get("uploadUrl"),
-          branch: this.get("branch")
-        };
-
-        if (this.get("privateChecked")) {
-          options.data.private_key = this.get("privateKey");
-        }
-      }
-
-      if (this.get("model.user_id")) {
-        // Used by theme-creator
-        options.data["user_id"] = this.get("model.user_id");
-      }
-
-      this.set("loading", true);
-      ajax(this.get("importUrl"), options)
-        .then(result => {
-          const theme = this.store.createRecord("theme", result.theme);
-          this.get("adminCustomizeThemes").send("addTheme", theme);
-          this.send("closeModal");
-        })
-        .then(() => {
-          this.set("privateKey", null);
-          this.set("publicKey", null);
-        })
-        .catch(popupAjaxError)
-        .finally(() => this.set("loading", false));
-    }
-  }
-});
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6
new file mode 100644
index 0000000..66c89c3
--- /dev/null
+++ b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6
@@ -0,0 +1,283 @@
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import {
+  default as computed,
+  observes
+} from "ember-addons/ember-computed-decorators";
+import { THEMES, COMPONENTS } from "admin/models/theme";
+
+const MIN_NAME_LENGTH = 4;
+
+// TODO: use a central repository for themes/components
+const POPULAR_THEMES = [
+  {
+    name: "Graceful",
+    value: "https://github.com/discourse/graceful",
+    preview: "https://theme-creator.discourse.org/theme/awesomerobot/graceful",
+    description: "A light and graceful theme for Discourse.",
+    meta_url:
+      "https://meta.discourse.org/t/a-graceful-theme-for-discourse/93040"
+  },
+  {
+    name: "Material Design Theme",
+    value: "https://github.com/discourse/material-design-stock-theme",
+    preview: "https://newmaterial.trydiscourse.com",
+    description:
+      "Inspired by Material Design, this theme comes with several color palettes (incl. a dark one).",

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

GitHub sha: cafe6374

1 Like

@pmusaraj Are we tracking this some where on dev?