From f902e0fdd7b72d2bb17b742a8c195a716da88bab Mon Sep 17 00:00:00 2001
From: Jordan Vidrine <30537603+jordanvidrine@users.noreply.github.com>
Date: Mon, 28 Oct 2024 21:25:17 -0500
Subject: [PATCH] UX: Look and feel changes (#29245)
This PR:
- Removes components from being displayed in the card
- Adds a DMenu to house previous footer actions
- Allows themes to be updated from this grid, with an animation and different border to show the update is happening
- Stops position of cards changing when default changes
- Fixes outline colour not changing when default changes
- Show a global notice on the page when previewing a theme
- Allows updating a theme from the grid, and showing an indicator of what theme needs to be updated
- Moves "Set as default" to the dropdown for the theme
- Show screenshot for theme if it is available
- Prevent page reloading when updating the theme
- Fixes theme install modal on grid page
- Temporarily remove sorting of default theme to the top
---
.../look-and-feel-themes.gjs | 69 +++++++
.../addon/components/modal/install-theme.js | 10 +-
.../addon/components/themes-grid-card.gjs | 195 +++++++++++-------
.../admin/addon/components/themes-grid.gjs | 95 ++-------
.../admin-config-look-and-feel-index.js | 10 +
.../routes/admin-config-look-and-feel.js | 4 -
.../templates/config-look-and-feel-themes.hbs | 4 +-
.../addon/templates/config-look-and-feel.hbs | 1 +
.../discourse/app/components/global-notice.js | 9 +
.../instance-initializers/live-development.js | 7 +-
.../common/components/theme-card.scss | 78 ++++---
config/locales/client.en.yml | 9 +-
12 files changed, 296 insertions(+), 195 deletions(-)
create mode 100644 app/assets/javascripts/admin/addon/components/admin-config-areas/look-and-feel-themes.gjs
create mode 100644 app/assets/javascripts/admin/addon/routes/admin-config-look-and-feel-index.js
diff --git a/app/assets/javascripts/admin/addon/components/admin-config-areas/look-and-feel-themes.gjs b/app/assets/javascripts/admin/addon/components/admin-config-areas/look-and-feel-themes.gjs
new file mode 100644
index 00000000000..12ba5fce070
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/components/admin-config-areas/look-and-feel-themes.gjs
@@ -0,0 +1,69 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import I18n from "discourse-i18n";
+import AdminPageSubheader from "admin/components/admin-page-subheader";
+import InstallThemeModal from "admin/components/modal/install-theme";
+import ThemesGrid from "admin/components/themes-grid";
+
+export default class AdminConfigAreasLookAndFeelThemes extends Component {
+ @service modal;
+ @service router;
+ @service toasts;
+
+ @action
+ installModal() {
+ this.modal.show(InstallThemeModal, {
+ model: { ...this.installThemeOptions() },
+ });
+ }
+
+ // TODO (martin) These install methods may not belong here and they
+ // are incomplete or have stubbed or omitted properties. We may want
+ // to move this to the new config route or a dedicated component
+ // that sits in the route.
+ installThemeOptions() {
+ return {
+ selectedType: "theme",
+ userId: null,
+ content: [],
+ installedThemes: this.args.themes,
+ addTheme: this.addTheme,
+ updateSelectedType: () => {},
+ };
+ }
+
+ @action
+ addTheme(theme) {
+ this.toasts.success({
+ data: {
+ message: I18n.t("admin.customize.theme.install_success", {
+ theme: theme.name,
+ }),
+ },
+ duration: 2000,
+ });
+ this.router.refresh();
+ }
+
+
+
+ <:actions as |actions|>
+
+
+
+
+
+
+
+
+}
diff --git a/app/assets/javascripts/admin/addon/components/modal/install-theme.js b/app/assets/javascripts/admin/addon/components/modal/install-theme.js
index a2107ac5a6e..047369f7a61 100644
--- a/app/assets/javascripts/admin/addon/components/modal/install-theme.js
+++ b/app/assets/javascripts/admin/addon/components/modal/install-theme.js
@@ -10,7 +10,7 @@ import { COMPONENTS, THEMES } from "admin/models/theme";
const MIN_NAME_LENGTH = 4;
-export default class InstallTheme extends Component {
+export default class InstallThemeModal extends Component {
@service store;
@tracked selection = this.args.model.selection || "popular";
@@ -184,9 +184,11 @@ export default class InstallTheme extends Component {
}
if (this.remote || this.popular || this.directRepoInstall) {
- const duplicate = this.args.model.content.find((theme) =>
- this.themeHasSameUrl(theme, this.uploadUrl)
- );
+ const duplicate =
+ this.args.model.content &&
+ this.args.model.content.find((theme) =>
+ this.themeHasSameUrl(theme, this.uploadUrl)
+ );
if (duplicate && !this.duplicateRemoteThemeWarning) {
const warning = I18n.t("admin.customize.theme.duplicate_remote_theme", {
name: duplicate.name,
diff --git a/app/assets/javascripts/admin/addon/components/themes-grid-card.gjs b/app/assets/javascripts/admin/addon/components/themes-grid-card.gjs
index 815f6155bf3..a3f8f0e8ec8 100644
--- a/app/assets/javascripts/admin/addon/components/themes-grid-card.gjs
+++ b/app/assets/javascripts/admin/addon/components/themes-grid-card.gjs
@@ -1,13 +1,16 @@
import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { array } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
-import { htmlSafe } from "@ember/template";
import DButton from "discourse/components/d-button";
-import concatClass from "discourse/helpers/concat-class";
+import DropdownMenu from "discourse/components/dropdown-menu";
+import { popupAjaxError } from "discourse/lib/ajax-error";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n";
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
+import DMenu from "float-kit/components/d-menu";
import ThemesGridPlaceholder from "./themes-grid-placeholder";
// NOTE (martin): We will need to revisit and improve this component
@@ -20,24 +23,28 @@ export default class ThemeCard extends Component {
@service siteSettings;
@service toasts;
+ @tracked isUpdating = false;
+
+ get themeCardClasses() {
+ return [
+ "theme-card",
+ this.args.theme.get("default") ? "-active" : "",
+ this.isUpdating ? "--updating" : "",
+ ].join(" ");
+ }
+
get themeRouteModels() {
return ["themes", this.args.theme.id];
}
- get childrenString() {
- return this.args.theme.childThemes.reduce((acc, theme, idx) => {
- if (idx === this.args.theme.childThemes.length - 1) {
- return acc + theme.name;
- } else {
- return acc + theme.name + ", ";
- }
- }, "");
- }
-
get themePreviewUrl() {
return `/admin/themes/${this.args.theme.id}/preview`;
}
+ get footerActionIcon() {
+ return this.args.theme.isPendingUpdates ? "sync" : "ellipsis-h";
+ }
+
// NOTE: inspired by -> https://github.com/discourse/discourse/blob/24caa36eef826bcdaed88aebfa7df154413fb349/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js#L366
//
// Will also need some cleanup when refactoring other theme code.
@@ -75,12 +82,43 @@ export default class ThemeCard extends Component {
});
}
+ @action
+ updateTheme() {
+ if (this.isUpdating) {
+ return;
+ }
+
+ this.isUpdating = true;
+ this.args.theme
+ .updateToLatest()
+ .then(() => {
+ this.toasts.success({
+ data: {
+ message: I18n.t("admin.customize.theme.update_success", {
+ theme: this.args.theme.name,
+ }),
+ },
+ duration: 2000,
+ });
+ })
+ .catch(popupAjaxError)
+ .finally(() => {
+ this.isUpdating = false;
+ });
+ }
+
<:content>
+ {{#if @theme.isPendingUpdates}}
+ {{icon "info-circle"}}
+ {{/if}}
diff --git a/app/assets/javascripts/admin/addon/components/themes-grid.gjs b/app/assets/javascripts/admin/addon/components/themes-grid.gjs
index c810318ac72..8ea1a3a7eb7 100644
--- a/app/assets/javascripts/admin/addon/components/themes-grid.gjs
+++ b/app/assets/javascripts/admin/addon/components/themes-grid.gjs
@@ -1,11 +1,5 @@
import Component from "@glimmer/component";
-import { action } from "@ember/object";
import { service } from "@ember/service";
-import DButton from "discourse/components/d-button";
-import icon from "discourse-common/helpers/d-icon";
-import i18n from "discourse-common/helpers/i18n";
-import AdminConfigAreaCard from "admin/components/admin-config-area-card";
-import InstallThemeModal from "../components/modal/install-theme";
import ThemesGridCard from "./themes-grid-card";
// NOTE (martin): Much of the JS code in this component is placeholder code. Much
@@ -16,6 +10,8 @@ export default class ThemesGrid extends Component {
@service modal;
@service router;
+ sortedThemes;
+
externalResources = [
{
key: "admin.customize.theme.beginners_guide_title",
@@ -31,9 +27,17 @@ export default class ThemesGrid extends Component {
},
];
- // Always show the default theme first in the list
- get sortedThemes() {
- return this.args.themes.sort((a, b) => {
+ constructor() {
+ super(...arguments);
+
+ // Show default theme at the top of the list on page load,
+ // but don't move it around dynamically if the admin changes the default.
+ //
+ // TODO (martin) Figure out how to make it so we can sort default to the
+ // top but also allow the list of themes to change if an additional theme is
+ // installed. Basically don't want .get("default") to affect the sort after
+ // the first time, but if the whole array changes this needs to be recalculated.
+ this.sortedThemes = this.args.themes.sort((a, b) => {
if (a.get("default")) {
return -1;
} else if (b.get("default")) {
@@ -42,78 +46,11 @@ export default class ThemesGrid extends Component {
});
}
- // TODO (martin) These install methods may not belong here and they
- // are incomplete or have stubbed or omitted properties. We may want
- // to move this to the new config route or a dedicated component
- // that sits in the route.
- installThemeOptions() {
- return {
- selectedType: "theme",
- userId: null,
- content: null,
- installedThemes: this.args.themes,
- addTheme: this.addTheme,
- updateSelectedType: () => {},
- };
- }
-
- @action
- addTheme(theme) {
- this.refresh();
- theme.setProperties({ recentlyInstalled: true });
- this.router.transitionTo("adminCustomizeThemes.show", theme.get("id"), {
- queryParams: {
- repoName: null,
- repoUrl: null,
- },
- });
- }
-
- @action
- installModal() {
- this.modal.show(InstallThemeModal, {
- model: { ...this.installThemeOptions() },
- });
- }
-
-
- {{#each this.sortedThemes as |theme|}}
-
- {{/each}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/addon/templates/config-look-and-feel.hbs b/app/assets/javascripts/admin/addon/templates/config-look-and-feel.hbs
index ad5dfcda6e4..1dacd69dcea 100644
--- a/app/assets/javascripts/admin/addon/templates/config-look-and-feel.hbs
+++ b/app/assets/javascripts/admin/addon/templates/config-look-and-feel.hbs
@@ -1,6 +1,7 @@
<:breadcrumbs>