From 25e8b5af9fd808306acf81e2578ec975c29eccb9 Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Wed, 12 Mar 2025 16:57:31 +0300 Subject: [PATCH] FEATURE: Introduce new color palettes config area (#31742) As part of the theme/color palette overhaul project, we're introducing a new admin page for editing color palettes. The new page is located at `/admin/config/colors/:id`. It's linked from anywhere, but it will be linked in the sidebar as we progress more in the overhaul project. Related PRs: https://github.com/discourse/discourse/pull/30893 https://github.com/discourse/discourse/pull/30915 https://github.com/discourse/discourse/pull/31328. Internal topic: t/148628. --- .../admin-config-areas/color-palette.gjs | 228 ++++++++++++++++++ .../addon/components/color-palette-editor.gjs | 46 ++-- .../admin/addon/models/color-scheme-color.js | 16 +- .../admin/addon/models/color-scheme.js | 48 +++- .../admin-config-color-palettes-show.js | 8 + .../admin/addon/routes/admin-route-map.js | 4 + .../templates/config-color-palettes-show.hbs | 1 + .../components/color-palette-editor-test.gjs | 57 +++-- app/assets/stylesheets/admin/admin_base.scss | 1 + .../admin/admin_config_color_palettes.scss | 38 +++ .../admin/color_schemes_controller.rb | 4 +- .../admin/config/color_palettes_controller.rb | 6 + .../color_scheme_color_serializer.rb | 2 +- app/services/color_scheme_revisor.rb | 6 +- config/locales/client.en.yml | 14 ++ config/routes.rb | 1 + config/site_settings.yml | 1 + .../admin_color_palettes_config_area_spec.rb | 95 ++++++++ .../components/color_palette_editor.rb | 35 +++ .../page_objects/components/form_kit.rb | 4 + .../pages/admin_color_palettes_config_area.rb | 49 ++++ 21 files changed, 616 insertions(+), 48 deletions(-) create mode 100644 app/assets/javascripts/admin/addon/components/admin-config-areas/color-palette.gjs create mode 100644 app/assets/javascripts/admin/addon/routes/admin-config-color-palettes-show.js create mode 100644 app/assets/javascripts/admin/addon/templates/config-color-palettes-show.hbs create mode 100644 app/assets/stylesheets/admin/admin_config_color_palettes.scss create mode 100644 app/controllers/admin/config/color_palettes_controller.rb create mode 100644 spec/system/admin_color_palettes_config_area_spec.rb create mode 100644 spec/system/page_objects/components/color_palette_editor.rb create mode 100644 spec/system/page_objects/pages/admin_color_palettes_config_area.rb diff --git a/app/assets/javascripts/admin/addon/components/admin-config-areas/color-palette.gjs b/app/assets/javascripts/admin/addon/components/admin-config-areas/color-palette.gjs new file mode 100644 index 00000000000..764a0d18d88 --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/admin-config-areas/color-palette.gjs @@ -0,0 +1,228 @@ +import Component from "@glimmer/component"; +import { cached, tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import { LinkTo } from "@ember/routing"; +import { service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import Form from "discourse/components/form"; +import { extractError } from "discourse/lib/ajax-error"; +import { i18n } from "discourse-i18n"; +import AdminConfigAreaCard from "admin/components/admin-config-area-card"; +import ColorPaletteEditor, { + LIGHT, +} from "admin/components/color-palette-editor"; + +export default class AdminConfigAreasColorPalette extends Component { + @service toasts; + @service router; + + @tracked editingName = false; + @tracked editorMode = LIGHT; + @tracked hasUnsavedChanges = false; + + @cached + get data() { + return { + name: this.args.colorPalette.name, + user_selectable: this.args.colorPalette.user_selectable, + colors: this.args.colorPalette.colors, + editingName: this.editingName, + }; + } + + @action + toggleEditingName() { + this.editingName = !this.editingName; + } + + @action + onLightColorChange(name, value) { + const color = this.data.colors.find((c) => c.name === name); + color.hex = value; + this.hasUnsavedChanges = true; + } + + @action + onDarkColorChange(name, value) { + const color = this.data.colors.find((c) => c.name === name); + color.dark_hex = value; + this.hasUnsavedChanges = true; + } + + @action + async handleSubmit(data) { + this.args.colorPalette.name = data.name; + this.args.colorPalette.user_selectable = data.user_selectable; + + try { + await this.args.colorPalette.save(); + this.editingName = false; + this.hasUnsavedChanges = false; + this.toasts.success({ + data: { + message: i18n("saved"), + }, + }); + } catch (error) { + this.toasts.error({ + duration: 3000, + data: { + message: extractError(error), + }, + }); + } + } + + @action + onEditorTabSwitch(newMode) { + this.editorMode = newMode; + } + + @action + async duplicate() { + const copy = this.args.colorPalette.copy(); + copy.name = i18n("admin.config_areas.color_palettes.copy_of", { + name: this.args.colorPalette.name, + }); + await copy.save(); + this.router.replaceWith("adminConfig.color-palettes-show", copy); + this.toasts.success({ + data: { + message: i18n("admin.config_areas.color_palettes.copy_created", { + name: this.args.colorPalette.name, + }), + }, + }); + } + + @action + handleNameChange(value, { set }) { + set("name", value); + this.hasUnsavedChanges = true; + } + + @action + handleUserSelectableChange(value, { set }) { + set("user_selectable", value); + this.hasUnsavedChanges = true; + } + + +} diff --git a/app/assets/javascripts/admin/addon/components/color-palette-editor.gjs b/app/assets/javascripts/admin/addon/components/color-palette-editor.gjs index de59b752f05..9c73f9f9681 100644 --- a/app/assets/javascripts/admin/addon/components/color-palette-editor.gjs +++ b/app/assets/javascripts/admin/addon/components/color-palette-editor.gjs @@ -7,25 +7,19 @@ import concatClass from "discourse/helpers/concat-class"; import dIcon from "discourse/helpers/d-icon"; import { i18n } from "discourse-i18n"; -const LIGHT = "light"; -const DARK = "dark"; +export const LIGHT = "light"; +export const DARK = "dark"; class Color { @tracked lightValue; @tracked darkValue; - constructor({ name, lightValue, darkValue }) { + constructor({ name, lightValue, darkValue, description, translatedName }) { this.name = name; this.lightValue = lightValue; this.darkValue = darkValue; - } - - get displayName() { - return this.name.replaceAll("_", " "); - } - - get description() { - return i18n(`admin.customize.colors.${this.name}.description`); + this.displayName = translatedName; + this.description = description; } } @@ -68,26 +62,38 @@ const Picker = class extends Component { } get displayedColor() { + let color; if (this.args.showDark) { - return this.args.color.darkValue; + color = this.args.color.darkValue ?? this.args.color.lightValue; } else { - return this.args.color.lightValue; + color = this.args.color.lightValue ?? this.args.color.darkValue; } + return this.ensureSixDigitsHex(color); } get activeValue() { let color; if (this.args.showDark) { - color = this.args.color.darkValue; + color = this.args.color.darkValue ?? this.args.color.lightValue; } else { - color = this.args.color.lightValue; + color = this.args.color.lightValue ?? this.args.color.darkValue; } if (color) { - return `#${color}`; + return `#${this.ensureSixDigitsHex(color)}`; } } + ensureSixDigitsHex(hex) { + if (hex.length === 3) { + return hex + .split("") + .map((digit) => `${digit}${digit}`) + .join(""); + } + return hex; + } +