mirror of
https://github.com/discourse/discourse.git
synced 2024-12-02 11:33:39 +08:00
3ad2fd032b
* UX: More additions * UX: more * DEV: Add admin/config/themes route * UX: Use admin config card * syntax merge fixes * cleanup * cleanup * checkbox * more * error * save on click * more * fix setter * DEV: Implement vanilla checkbox * cleanup * UX: save themes as default * DEV: Add component list to card * DEV: Add placeholder for no screenshots * DEV: Fix default theme reactivity Also add content/optionalAction yields to config area card and put the theme user selectable checkbox there, along with adding styles. * DEV: Change to generic "look and feel" config area * DEV: Auto redirect to themes on base look and feel route * UX: Remove computed from sorted themes * linting * UX: Turn update icon into button that routes to settings * DEV: remove unused function * UX: center icons with title * DEV: Lint * UX: Hook up theme preview button * DEV: Minor fixes --------- Co-authored-by: Martin Brennan <martin@discourse.org> Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
217 lines
6.7 KiB
Plaintext
217 lines
6.7 KiB
Plaintext
import Component from "@glimmer/component";
|
|
import { Input } from "@ember/component";
|
|
import { action, computed } 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 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 ThemesGridPlaceholder from "./themes-grid-placeholder";
|
|
|
|
// NOTE (martin): We will need to revisit and improve this component
|
|
// over time.
|
|
//
|
|
// Much of the existing theme logic in /admin/customize/themes has old patterns
|
|
// and technical debt, so anything copied from there to here is subject
|
|
// to change as we improve this incrementally.
|
|
export default class ThemeCard extends Component {
|
|
@service siteSettings;
|
|
@service toasts;
|
|
|
|
// NOTE: These 3 shouldn't need @computed, if we convert
|
|
// theme to a pure JS class with @tracked properties we
|
|
// won't need to do this.
|
|
@computed("args.theme.default")
|
|
get setDefaultButtonIcon() {
|
|
return this.args.theme.default ? "far-check-square" : "far-square";
|
|
}
|
|
|
|
@computed("args.theme.default")
|
|
get setDefaultButtonTitle() {
|
|
return this.args.theme.default
|
|
? "admin.customize.theme.default_theme"
|
|
: "admin.customize.theme.set_default_theme";
|
|
}
|
|
|
|
@computed("args.theme.default")
|
|
get setDefaultButtonClasses() {
|
|
return this.args.theme.default
|
|
? "btn-primary theme-card__button"
|
|
: "btn-default theme-card__button";
|
|
}
|
|
|
|
@computed(
|
|
"args.theme.default",
|
|
"args.theme.isBroken",
|
|
"args.theme.enabled",
|
|
"args.theme.isPendingUpdates"
|
|
)
|
|
get themeCardClasses() {
|
|
return this.args.theme.isBroken
|
|
? "--broken"
|
|
: !this.args.theme.enabled
|
|
? "--disabled"
|
|
: this.args.theme.isPendingUpdates
|
|
? "--updates"
|
|
: this.args.theme.default
|
|
? "--active"
|
|
: "";
|
|
}
|
|
|
|
get imageAlt() {
|
|
return this.args.theme.name;
|
|
}
|
|
|
|
get hasScreenshot() {
|
|
return this.args.theme.screenshot ? true : false;
|
|
}
|
|
|
|
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`;
|
|
}
|
|
|
|
// 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.
|
|
@action
|
|
async setDefault() {
|
|
this.args.theme.set("default", true);
|
|
this.args.theme.saveChanges("default").then(() => {
|
|
this.args.allThemes.forEach((theme) => {
|
|
if (theme.id !== this.args.theme.id) {
|
|
theme.set("default", !this.args.theme.get("default"));
|
|
}
|
|
});
|
|
this.toasts.success({
|
|
data: {
|
|
message: I18n.t("admin.customize.theme.set_default_success", {
|
|
theme: this.args.theme.name,
|
|
}),
|
|
},
|
|
duration: 2000,
|
|
});
|
|
});
|
|
}
|
|
|
|
@action
|
|
async handleSubmit(event) {
|
|
this.args.theme.set("user_selectable", event.target.checked);
|
|
this.args.theme.saveChanges("user_selectable");
|
|
}
|
|
|
|
<template>
|
|
<AdminConfigAreaCard
|
|
class={{concatClass "theme-card" this.themeCardClasses}}
|
|
>
|
|
<:header>
|
|
{{@theme.name}}
|
|
<span class="theme-card__icons">
|
|
{{#if @theme.isPendingUpdates}}
|
|
<DButton
|
|
@route="adminCustomizeThemes.show"
|
|
@routeModels={{this.themeRouteModels}}
|
|
@icon="sync"
|
|
@class="btn-flat theme-card__button"
|
|
@preventFocus={{true}}
|
|
/>
|
|
{{else}}
|
|
{{#if @theme.isBroken}}
|
|
{{icon
|
|
"exclamation-circle"
|
|
class="broken-indicator"
|
|
title="admin.customize.theme.broken_theme_tooltip"
|
|
}}
|
|
{{/if}}
|
|
{{#unless @theme.enabled}}
|
|
{{icon
|
|
"ban"
|
|
class="light-grey-icon"
|
|
title="admin.customize.theme.disabled_component_tooltip"
|
|
}}
|
|
{{/unless}}
|
|
{{/if}}
|
|
</span>
|
|
</:header>
|
|
<:headerAction>
|
|
<Input
|
|
@type="checkbox"
|
|
@checked={{@theme.user_selectable}}
|
|
id="user-select-theme-{{@theme.id}}"
|
|
onclick={{this.handleSubmit}}
|
|
/>
|
|
<label
|
|
class="theme-card__checkbox-label"
|
|
for="user-select-theme-{{@theme.id}}"
|
|
>
|
|
{{i18n "admin.config_areas.look_and_feel.themes.user_selectable"}}
|
|
</label>
|
|
</:headerAction>
|
|
<:content>
|
|
<div class="theme-card__image-wrapper">
|
|
{{#if this.hasScreenshot}}
|
|
<img
|
|
class="theme-card__image"
|
|
src={{htmlSafe @theme.screenshot}}
|
|
alt={{this.imageAlt}}
|
|
/>
|
|
{{else}}
|
|
<ThemesGridPlaceholder @theme={{@theme}} />
|
|
{{/if}}
|
|
</div>
|
|
<div class="theme-card__content">
|
|
<p class="theme-card__description">{{@theme.description}}</p>
|
|
{{#if @theme.childThemes}}
|
|
<span class="theme-card__components">{{i18n
|
|
"admin.customize.theme.components"
|
|
}}:
|
|
{{htmlSafe this.childrenString}}</span>
|
|
{{/if}}
|
|
</div>
|
|
<div class="theme-card__footer">
|
|
<DButton
|
|
@action={{this.setDefault}}
|
|
@preventFocus={{true}}
|
|
@icon={{this.setDefaultButtonIcon}}
|
|
@class={{this.setDefaultButtonClasses}}
|
|
@translatedLabel={{i18n this.setDefaultButtonTitle}}
|
|
@disabled={{@theme.default}}
|
|
/>
|
|
<div class="theme-card-footer__actions">
|
|
<a
|
|
href={{this.themePreviewUrl}}
|
|
title={{i18n "admin.customize.explain_preview"}}
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
class="btn btn-flat theme-card__button"
|
|
>{{icon "eye"}}</a>
|
|
<DButton
|
|
@route="adminCustomizeThemes.show"
|
|
@routeModels={{this.themeRouteModels}}
|
|
@icon="cog"
|
|
@class="btn-flat theme-card__button"
|
|
@preventFocus={{true}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</:content>
|
|
</AdminConfigAreaCard>
|
|
</template>
|
|
}
|