DEV: Experimenting with using built in core color palettes for the color picking component (#71)
This PR is the beginning of converting the color picker to use built in core color palettes. The `color_schemes` defined in `about.json` are created in core when the theme is imported, and we then show all user-selectable color palettes in this sidebar footer menu. An important caveat here is that all of the Horizon themes must be changed to `user_selectable` otherwise they will not show up in the color palette selector in the sidebar. When choosing a color palette that also has a corresponding dark color palette, *both* light mode and dark mode are correctly saved with the color palette(s) chosen, using the color palette cookie we already have in core. Anon users can also set a palette, which will be saved in a cookie. --------- Co-authored-by: Sérgio Saquetim <saquetim@discourse.org> Co-authored-by: Martin Brennan <martin@discourse.org> Co-authored-by: Osama Sayegh <asooomaasoooma90@gmail.com>
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
import { apiInitializer } from "discourse/lib/api";
|
||||
import CustomColorHtmlClass from "../components/custom-color-html-class";
|
||||
import CustomUserPalette from "../components/custom-user-palette";
|
||||
import ExperimentalScreen from "../components/experimental-screen";
|
||||
import UserColorPaletteSelector from "../components/user-color-palette-selector";
|
||||
|
||||
export default apiInitializer("1.8.0", (api) => {
|
||||
api.renderInOutlet("above-main-container", ExperimentalScreen);
|
||||
api.renderInOutlet("above-main-container", CustomColorHtmlClass);
|
||||
api.renderInOutlet("sidebar-footer-actions", CustomUserPalette);
|
||||
api.renderInOutlet("sidebar-footer-actions", UserColorPaletteSelector);
|
||||
|
||||
api.registerValueTransformer("site-setting-enable-welcome-banner", () => {
|
||||
return settings.enable_welcome_banner;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { concat } from "@ember/helper";
|
||||
import { service } from "@ember/service";
|
||||
import htmlClass from "discourse/helpers/html-class";
|
||||
|
||||
export default class CustomColorHtmlClass extends Component {
|
||||
@service customColor;
|
||||
|
||||
<template>
|
||||
{{htmlClass (concat "custom-color-" this.customColor.color)}}
|
||||
</template>
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import DMenu from "float-kit/components/d-menu";
|
||||
import SitePaletteMenuItem from "./site-palette-menu-item";
|
||||
|
||||
const PALETTES = [
|
||||
{
|
||||
label: "Marigold",
|
||||
name: "marigold",
|
||||
color: "#d3881f",
|
||||
},
|
||||
{
|
||||
label: "Violet",
|
||||
name: "violet",
|
||||
color: "#9b15de",
|
||||
},
|
||||
{
|
||||
label: "Lily",
|
||||
name: "lily",
|
||||
color: "#CC336F",
|
||||
},
|
||||
{
|
||||
label: "Clover",
|
||||
name: "clover",
|
||||
color: "#45a06e",
|
||||
},
|
||||
{
|
||||
label: "Royal",
|
||||
name: "royal",
|
||||
color: "#4169e1",
|
||||
},
|
||||
{
|
||||
label: "Horizon",
|
||||
name: "horizon",
|
||||
color: "#595bca",
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_PALETTE_NAME = "horizon";
|
||||
|
||||
<template>
|
||||
<DMenu
|
||||
@identifier="user-color-palette"
|
||||
@placementStrategy="fixed"
|
||||
class="btn-flat user-color-palette sidebar-footer-actions-button"
|
||||
@inline={{true}}
|
||||
>
|
||||
<:trigger>
|
||||
{{icon "paintbrush"}}
|
||||
</:trigger>
|
||||
<:content>
|
||||
<div class="color-palette-menu">
|
||||
<div class="color-palette-menu__content">
|
||||
{{#each PALETTES as |colorPalette|}}
|
||||
<SitePaletteMenuItem @colorPalette={{colorPalette}} />
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</:content>
|
||||
</DMenu>
|
||||
</template>
|
||||
+14
-11
@@ -1,40 +1,43 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { fn } 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";
|
||||
|
||||
export default class SitePaletteMenuItem extends Component {
|
||||
@service customColor;
|
||||
export default class UserColorPaletteMenuItem extends Component {
|
||||
@service site;
|
||||
@service session;
|
||||
|
||||
get siteStyle() {
|
||||
return `--icon-color: ${this.args.colorPalette.color}`;
|
||||
return `--icon-color: ${this.args.colorPalette.accent}`;
|
||||
}
|
||||
|
||||
get activeClass() {
|
||||
if (this.customColor.color === this.args.colorPalette.name) {
|
||||
if (this.args.selectedColorPaletteId === this.args.colorPalette.id) {
|
||||
return "active";
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
handleInput(colorPalette) {
|
||||
this.customColor.setColor(colorPalette.name);
|
||||
paletteSelected() {
|
||||
this.args.paletteSelected(this.args.colorPalette);
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="color-palette-menu__item" data-color={{@colorPalette.name}}>
|
||||
<div
|
||||
class="user-color-palette-menu__item"
|
||||
data-color-palette={{@colorPalette.name}}
|
||||
>
|
||||
<DButton
|
||||
class={{concatClass
|
||||
"btn-flat color-palette-menu__item-choice"
|
||||
"btn-flat user-color-palette-menu__item-choice"
|
||||
this.activeClass
|
||||
}}
|
||||
style={{htmlSafe this.siteStyle}}
|
||||
@icon="circle"
|
||||
@translatedLabel={{@colorPalette.label}}
|
||||
@action={{fn this.handleInput @colorPalette}}
|
||||
@translatedLabel={{@colorPalette.name}}
|
||||
@action={{this.paletteSelected}}
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,179 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { reload } from "discourse/helpers/page-reloader";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import {
|
||||
listColorSchemes,
|
||||
updateColorSchemeCookie,
|
||||
} from "discourse/lib/color-scheme-picker";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import DMenu from "float-kit/components/d-menu";
|
||||
import UserColorPaletteMenuItem from "./user-color-palette-menu-item";
|
||||
|
||||
const HORIZON_PALETTES = [
|
||||
"Horizon",
|
||||
"Marigold",
|
||||
"Violet",
|
||||
"Lily",
|
||||
"Clover",
|
||||
"Royal",
|
||||
];
|
||||
|
||||
export default class UserColorPaletteSelector extends Component {
|
||||
@service currentUser;
|
||||
@service keyValueStore;
|
||||
@service site;
|
||||
@service session;
|
||||
@service interfaceColor;
|
||||
@tracked anonColorPaletteId = this.#loadAnonColorPalette();
|
||||
@tracked userColorPaletteId = this.session.userColorSchemeId;
|
||||
|
||||
get userColorPalettes() {
|
||||
const availablePalettes = listColorSchemes(this.site)
|
||||
.map((userPalette) => {
|
||||
return {
|
||||
...userPalette,
|
||||
accent: `#${
|
||||
userPalette.colors.find((color) => color.name === "tertiary").hex
|
||||
}`,
|
||||
};
|
||||
})
|
||||
.filter((userPalette) => {
|
||||
return HORIZON_PALETTES.some((palette) => {
|
||||
return userPalette.name.toLowerCase().includes(palette.toLowerCase());
|
||||
});
|
||||
})
|
||||
.sort();
|
||||
|
||||
// Match the light scheme with the corresponding dark id based in the name
|
||||
return (
|
||||
availablePalettes
|
||||
.map((palette) => {
|
||||
if (palette.is_dark) {
|
||||
return palette;
|
||||
}
|
||||
|
||||
const normalizedLightName = palette.name.toLowerCase();
|
||||
|
||||
const correspondingDarkModeId = availablePalettes.find(
|
||||
(item) =>
|
||||
item.is_dark &&
|
||||
normalizedLightName ===
|
||||
item.name.toLowerCase().replace(/\s+dark$/, "")
|
||||
)?.id;
|
||||
|
||||
return {
|
||||
...palette,
|
||||
correspondingDarkModeId,
|
||||
};
|
||||
})
|
||||
// Only want to show palettes that have corresponding light/dark modes
|
||||
.filter((palette) => !palette.is_dark)
|
||||
);
|
||||
}
|
||||
|
||||
get selectedColorPaletteId() {
|
||||
if (this.currentUser) {
|
||||
return this.userColorPaletteId;
|
||||
}
|
||||
|
||||
return this.anonColorPaletteId;
|
||||
}
|
||||
|
||||
@action
|
||||
onRegisterMenu(api) {
|
||||
this.dMenu = api;
|
||||
}
|
||||
|
||||
@action
|
||||
paletteSelected(selectedPalette) {
|
||||
if (selectedPalette.id === this.selectedColorPaletteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#updatePreference(selectedPalette);
|
||||
this.#changeSiteColorPalette(
|
||||
selectedPalette.id,
|
||||
selectedPalette.correspondingDarkModeId
|
||||
);
|
||||
this.dMenu.close();
|
||||
}
|
||||
|
||||
#updatePreference(selectedPalette) {
|
||||
updateColorSchemeCookie(selectedPalette.id);
|
||||
updateColorSchemeCookie(selectedPalette.correspondingDarkModeId, {
|
||||
dark: true,
|
||||
});
|
||||
|
||||
if (!this.currentUser) {
|
||||
this.anonColorPaletteId = selectedPalette.id;
|
||||
} else {
|
||||
this.userColorPaletteId = selectedPalette.id;
|
||||
}
|
||||
}
|
||||
|
||||
#loadAnonColorPalette() {
|
||||
const storedAnonPaletteId = cookie("color_scheme_id");
|
||||
if (storedAnonPaletteId) {
|
||||
return parseInt(storedAnonPaletteId, 10);
|
||||
}
|
||||
}
|
||||
|
||||
async #changeSiteColorPalette(lightPaletteId, darkPaletteId) {
|
||||
const lightTag = document.querySelector("link.light-scheme");
|
||||
const darkTag = document.querySelector("link.dark-scheme");
|
||||
|
||||
if (!darkTag) {
|
||||
reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(osama) once we have built-in light/dark modes for each palette, we
|
||||
// can stop making the 2nd ajax call for the dark palette and replace it
|
||||
// with a `include_dark_scheme` param on the ajax call for the light
|
||||
// palette which will include the href for the dark palette in the response
|
||||
const lightPaletteInfo = await ajax(
|
||||
`/color-scheme-stylesheet/${lightPaletteId}.json`
|
||||
);
|
||||
const darkPaletteInfo = await ajax(
|
||||
`/color-scheme-stylesheet/${darkPaletteId}.json`
|
||||
);
|
||||
|
||||
lightTag.href = lightPaletteInfo.new_href;
|
||||
darkTag.href = darkPaletteInfo.new_href;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#unless (isEmpty this.userColorPalettes)}}
|
||||
<DMenu
|
||||
@identifier="user-color-palette-selector"
|
||||
@placementStrategy="fixed"
|
||||
@onRegisterApi={{this.onRegisterMenu}}
|
||||
class="btn-flat user-color-palette-selector sidebar-footer-actions-button"
|
||||
data-selected-color-palette-id={{this.selectedColorPaletteId}}
|
||||
@inline={{true}}
|
||||
>
|
||||
<:trigger>
|
||||
{{icon "paintbrush"}}
|
||||
</:trigger>
|
||||
<:content>
|
||||
<div class="user-color-palette-menu">
|
||||
<div class="user-color-palette-menu__content">
|
||||
{{#each this.userColorPalettes as |colorPalette|}}
|
||||
<UserColorPaletteMenuItem
|
||||
@selectedColorPaletteId={{this.selectedColorPaletteId}}
|
||||
@colorPalette={{colorPalette}}
|
||||
@paletteSelected={{this.paletteSelected}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</:content>
|
||||
</DMenu>
|
||||
{{/unless}}
|
||||
</template>
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import Service, { service } from "@ember/service";
|
||||
import { DEFAULT_PALETTE_NAME } from "../components/custom-user-palette";
|
||||
|
||||
const CUSTOM_COLOR_KEY = "d-custom-color-preference";
|
||||
|
||||
export default class CustomColor extends Service {
|
||||
@service keyValueStore;
|
||||
@tracked
|
||||
color = this.keyValueStore.getItem(CUSTOM_COLOR_KEY) || DEFAULT_PALETTE_NAME;
|
||||
|
||||
@action
|
||||
setColor(color) {
|
||||
this.color = color;
|
||||
this.keyValueStore.setItem(CUSTOM_COLOR_KEY, color);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user