mirror of
https://github.com/discourse/discourse.git
synced 2025-02-17 00:02:46 +08:00
UX: Look and feel changes (#29245)
Some checks are pending
Licenses / run (push) Waiting to run
Linting / run (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (annotations, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (backend, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (backend, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (frontend, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (frontend, themes) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, chat) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, themes) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Chrome) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Firefox ESR) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Firefox Evergreen) (push) Waiting to run
Some checks are pending
Licenses / run (push) Waiting to run
Linting / run (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (annotations, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (backend, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (backend, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (frontend, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (frontend, themes) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, chat) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, themes) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Chrome) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Firefox ESR) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Firefox Evergreen) (push) Waiting to run
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
This commit is contained in:
parent
4d7f70b923
commit
f902e0fdd7
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminPageSubheader
|
||||||
|
@titleLabel="admin.config_areas.look_and_feel.themes.title"
|
||||||
|
@descriptionLabel="admin.customize.theme.themes_intro_new"
|
||||||
|
@learnMoreUrl="https://meta.discourse.org/t/93648"
|
||||||
|
>
|
||||||
|
<:actions as |actions|>
|
||||||
|
<actions.Primary
|
||||||
|
@action={{this.installModal}}
|
||||||
|
@label="admin.customize.install"
|
||||||
|
@icon="upload"
|
||||||
|
class="admin-look-and-feel__install-theme"
|
||||||
|
/>
|
||||||
|
</:actions>
|
||||||
|
</AdminPageSubheader>
|
||||||
|
|
||||||
|
<div class="admin-detail">
|
||||||
|
<ThemesGrid @themes={{@themes}} />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import { COMPONENTS, THEMES } from "admin/models/theme";
|
||||||
|
|
||||||
const MIN_NAME_LENGTH = 4;
|
const MIN_NAME_LENGTH = 4;
|
||||||
|
|
||||||
export default class InstallTheme extends Component {
|
export default class InstallThemeModal extends Component {
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
@tracked selection = this.args.model.selection || "popular";
|
@tracked selection = this.args.model.selection || "popular";
|
||||||
|
@ -184,9 +184,11 @@ export default class InstallTheme extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.remote || this.popular || this.directRepoInstall) {
|
if (this.remote || this.popular || this.directRepoInstall) {
|
||||||
const duplicate = this.args.model.content.find((theme) =>
|
const duplicate =
|
||||||
this.themeHasSameUrl(theme, this.uploadUrl)
|
this.args.model.content &&
|
||||||
);
|
this.args.model.content.find((theme) =>
|
||||||
|
this.themeHasSameUrl(theme, this.uploadUrl)
|
||||||
|
);
|
||||||
if (duplicate && !this.duplicateRemoteThemeWarning) {
|
if (duplicate && !this.duplicateRemoteThemeWarning) {
|
||||||
const warning = I18n.t("admin.customize.theme.duplicate_remote_theme", {
|
const warning = I18n.t("admin.customize.theme.duplicate_remote_theme", {
|
||||||
name: duplicate.name,
|
name: duplicate.name,
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { array } from "@ember/helper";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import DButton from "discourse/components/d-button";
|
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 icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
|
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
|
||||||
|
import DMenu from "float-kit/components/d-menu";
|
||||||
import ThemesGridPlaceholder from "./themes-grid-placeholder";
|
import ThemesGridPlaceholder from "./themes-grid-placeholder";
|
||||||
|
|
||||||
// NOTE (martin): We will need to revisit and improve this component
|
// NOTE (martin): We will need to revisit and improve this component
|
||||||
|
@ -20,24 +23,28 @@ export default class ThemeCard extends Component {
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
@service toasts;
|
@service toasts;
|
||||||
|
|
||||||
|
@tracked isUpdating = false;
|
||||||
|
|
||||||
|
get themeCardClasses() {
|
||||||
|
return [
|
||||||
|
"theme-card",
|
||||||
|
this.args.theme.get("default") ? "-active" : "",
|
||||||
|
this.isUpdating ? "--updating" : "",
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
get themeRouteModels() {
|
get themeRouteModels() {
|
||||||
return ["themes", this.args.theme.id];
|
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() {
|
get themePreviewUrl() {
|
||||||
return `/admin/themes/${this.args.theme.id}/preview`;
|
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
|
// 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.
|
// 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AdminConfigAreaCard
|
<AdminConfigAreaCard
|
||||||
class={{concatClass "theme-card" (if @theme.default "-active")}}
|
class={{this.themeCardClasses}}
|
||||||
@translatedHeading={{@theme.name}}
|
@translatedHeading={{@theme.name}}
|
||||||
>
|
>
|
||||||
<:content>
|
<:content>
|
||||||
|
{{#if @theme.isPendingUpdates}}
|
||||||
|
<span
|
||||||
|
title={{i18n "admin.customize.theme.updates_available_tooltip"}}
|
||||||
|
class="theme-card__update-available"
|
||||||
|
>{{icon "info-circle"}}</span>
|
||||||
|
{{/if}}
|
||||||
<div class="theme-card__image-wrapper">
|
<div class="theme-card__image-wrapper">
|
||||||
{{#if @theme.screenshot_url}}
|
{{#if @theme.screenshot_url}}
|
||||||
<img
|
<img
|
||||||
|
@ -96,77 +134,78 @@ export default class ThemeCard extends Component {
|
||||||
{{#if @theme.description}}
|
{{#if @theme.description}}
|
||||||
<p class="theme-card__description">{{@theme.description}}</p>
|
<p class="theme-card__description">{{@theme.description}}</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if @theme.childThemes}}
|
|
||||||
<span class="theme-card__components">{{i18n
|
|
||||||
"admin.customize.theme.components"
|
|
||||||
}}:
|
|
||||||
{{htmlSafe this.childrenString}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-card__footer">
|
<div class="theme-card__footer">
|
||||||
<DButton
|
<DButton
|
||||||
@action={{this.setDefault}}
|
@translatedLabel={{i18n "admin.customize.theme.edit"}}
|
||||||
|
@route="adminCustomizeThemes.show"
|
||||||
|
@routeModels={{this.themeRouteModels}}
|
||||||
|
@class="btn-primary theme-card__button"
|
||||||
@preventFocus={{true}}
|
@preventFocus={{true}}
|
||||||
@icon={{if @theme.default "far-check-square" "far-square"}}
|
|
||||||
@class={{concatClass
|
|
||||||
"theme-card__button"
|
|
||||||
(if @theme.default "btn-primary" "btn-default")
|
|
||||||
}}
|
|
||||||
@translatedLabel={{i18n
|
|
||||||
(if
|
|
||||||
@theme.default
|
|
||||||
"admin.customize.theme.default_theme"
|
|
||||||
"admin.customize.theme.set_default_theme"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
@disabled={{@theme.default}}
|
|
||||||
/>
|
/>
|
||||||
{{#if @theme.isPendingUpdates}}
|
|
||||||
<DButton
|
|
||||||
@route="adminCustomizeThemes.show"
|
|
||||||
@routeModels={{this.themeRouteModels}}
|
|
||||||
@icon="sync"
|
|
||||||
@class="btn btn-flat theme-card__button"
|
|
||||||
@title="admin.customize.theme.updates_available_tooltip"
|
|
||||||
@preventFocus={{true}}
|
|
||||||
/>
|
|
||||||
{{else}}
|
|
||||||
{{#if @theme.isBroken}}
|
|
||||||
<DButton
|
|
||||||
@route="adminCustomizeThemes.show"
|
|
||||||
@routeModels={{this.themeRouteModels}}
|
|
||||||
@icon="exclamation-circle"
|
|
||||||
@class="btn btn-flat theme-card__button broken-indicator"
|
|
||||||
@title="admin.customize.theme.broken_theme_tooltip"
|
|
||||||
@preventFocus={{true}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
{{#unless @theme.enabled}}
|
|
||||||
<DButton
|
|
||||||
@route="adminCustomizeThemes.show"
|
|
||||||
@routeModels={{this.themeRouteModels}}
|
|
||||||
@icon="ban"
|
|
||||||
@class="btn btn-flat theme-card__button broken-indicator light-grey-icon"
|
|
||||||
@title="admin.customize.theme.disabled_component_tooltip"
|
|
||||||
@preventFocus={{true}}
|
|
||||||
/>
|
|
||||||
{{/unless}}
|
|
||||||
{{/if}}
|
|
||||||
<div class="theme-card__footer-actions">
|
<div class="theme-card__footer-actions">
|
||||||
<a
|
<DMenu
|
||||||
href={{this.themePreviewUrl}}
|
@identifier="theme-card__footer-menu"
|
||||||
title={{i18n "admin.customize.explain_preview"}}
|
@triggerClass="theme-card__footer-menu btn-flat"
|
||||||
rel="noopener noreferrer"
|
@modalForMobile={{true}}
|
||||||
target="_blank"
|
@icon={{this.footerActionIcon}}
|
||||||
class="btn btn-flat theme-card__button"
|
@label={{if
|
||||||
>{{icon "eye"}}</a>
|
this.isUpdating
|
||||||
<DButton
|
(i18n "admin.customize.theme.updating")
|
||||||
@route="adminCustomizeThemes.show"
|
""
|
||||||
@routeModels={{this.themeRouteModels}}
|
}}
|
||||||
@icon="cog"
|
@triggers={{array "click"}}
|
||||||
@class="btn-flat theme-card__button"
|
>
|
||||||
@preventFocus={{true}}
|
<:content>
|
||||||
/>
|
<DropdownMenu as |dropdown|>
|
||||||
|
{{#if @theme.isPendingUpdates}}
|
||||||
|
<dropdown.item>
|
||||||
|
<DButton
|
||||||
|
@action={{this.updateTheme}}
|
||||||
|
@icon="download"
|
||||||
|
@class="theme-card__button -update"
|
||||||
|
@preventFocus={{true}}
|
||||||
|
@translatedLabel={{i18n
|
||||||
|
"admin.customize.theme.update_to_latest"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</dropdown.item>
|
||||||
|
{{/if}}
|
||||||
|
{{! TODO: Jordan
|
||||||
|
solutions for broken, disabled states }}
|
||||||
|
<dropdown.item>
|
||||||
|
<DButton
|
||||||
|
@action={{this.setDefault}}
|
||||||
|
@preventFocus={{true}}
|
||||||
|
@icon={{if
|
||||||
|
@theme.default
|
||||||
|
"far-check-square"
|
||||||
|
"far-square"
|
||||||
|
}}
|
||||||
|
@class="theme-card__button"
|
||||||
|
@translatedLabel={{i18n
|
||||||
|
(if
|
||||||
|
@theme.default
|
||||||
|
"admin.customize.theme.default_theme"
|
||||||
|
"admin.customize.theme.set_default_theme"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@disabled={{@theme.default}}
|
||||||
|
/>
|
||||||
|
</dropdown.item>
|
||||||
|
<dropdown.item>
|
||||||
|
<a
|
||||||
|
href={{this.themePreviewUrl}}
|
||||||
|
title={{i18n "admin.customize.explain_preview"}}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-transparent theme-card__button"
|
||||||
|
>{{icon "eye"}} {{i18n "admin.customize.theme.preview"}}</a>
|
||||||
|
</dropdown.item>
|
||||||
|
</DropdownMenu>
|
||||||
|
</:content>
|
||||||
|
</DMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</:content>
|
</:content>
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { action } from "@ember/object";
|
|
||||||
import { service } from "@ember/service";
|
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";
|
import ThemesGridCard from "./themes-grid-card";
|
||||||
|
|
||||||
// NOTE (martin): Much of the JS code in this component is placeholder code. Much
|
// 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 modal;
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
|
sortedThemes;
|
||||||
|
|
||||||
externalResources = [
|
externalResources = [
|
||||||
{
|
{
|
||||||
key: "admin.customize.theme.beginners_guide_title",
|
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
|
constructor() {
|
||||||
get sortedThemes() {
|
super(...arguments);
|
||||||
return this.args.themes.sort((a, b) => {
|
|
||||||
|
// 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")) {
|
if (a.get("default")) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (b.get("default")) {
|
} 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() },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="themes-cards-container">
|
<div class="themes-cards-container">
|
||||||
<div class="themes-cards-container__main">
|
{{#each @themes as |theme|}}
|
||||||
{{#each this.sortedThemes as |theme|}}
|
<ThemesGridCard @theme={{theme}} @allThemes={{@themes}} />
|
||||||
<ThemesGridCard @theme={{theme}} @allThemes={{@themes}} />
|
{{/each}}
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
<div class="themes-cards-container__helper">
|
|
||||||
<AdminConfigAreaCard
|
|
||||||
class="theme-card"
|
|
||||||
@heading="admin.config_areas.look_and_feel.themes.new_theme"
|
|
||||||
>
|
|
||||||
<:content>
|
|
||||||
<p class="theme-card__description">{{i18n
|
|
||||||
"admin.customize.theme.themes_intro_new"
|
|
||||||
}}</p>
|
|
||||||
<div class="external-resources">
|
|
||||||
{{#each this.externalResources as |resource|}}
|
|
||||||
<a
|
|
||||||
href={{resource.link}}
|
|
||||||
class="external-link"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{i18n resource.key}}
|
|
||||||
{{icon "external-link-alt"}}
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
<DButton
|
|
||||||
@action={{this.installModal}}
|
|
||||||
@icon="upload"
|
|
||||||
@label="admin.customize.install"
|
|
||||||
class="btn-primary theme-card__install-button"
|
|
||||||
/>
|
|
||||||
</:content>
|
|
||||||
</AdminConfigAreaCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
|
export default class AdminConfigLookAndFeelIndexRoute extends DiscourseRoute {
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
beforeModel() {
|
||||||
|
this.router.replaceWith("adminConfig.lookAndFeel.themes");
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,6 @@ import I18n from "discourse-i18n";
|
||||||
export default class AdminConfigLookAndFeelRoute extends DiscourseRoute {
|
export default class AdminConfigLookAndFeelRoute extends DiscourseRoute {
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
beforeModel() {
|
|
||||||
this.router.replaceWith("adminConfig.lookAndFeel.themes");
|
|
||||||
}
|
|
||||||
|
|
||||||
titleToken() {
|
titleToken() {
|
||||||
return I18n.t("admin.config_areas.look_and_feel.title");
|
return I18n.t("admin.config_areas.look_and_feel.title");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,4 @@
|
||||||
@label={{i18n "admin.config_areas.look_and_feel.themes.title"}}
|
@label={{i18n "admin.config_areas.look_and_feel.themes.title"}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="admin-detail">
|
<AdminConfigAreas::LookAndFeelThemes @themes={{this.model}} />
|
||||||
<ThemesGrid @themes={{this.model}} />
|
|
||||||
</div>
|
|
|
@ -1,6 +1,7 @@
|
||||||
<AdminPageHeader
|
<AdminPageHeader
|
||||||
@titleLabel="admin.config_areas.look_and_feel.title"
|
@titleLabel="admin.config_areas.look_and_feel.title"
|
||||||
@descriptionLabel="admin.config_areas.look_and_feel.description"
|
@descriptionLabel="admin.config_areas.look_and_feel.description"
|
||||||
|
@learnMoreUrl="https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
|
|
|
@ -116,6 +116,15 @@ export default class GlobalNotice extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.router.currentRoute?.queryParams?.preview_theme_id) {
|
||||||
|
notices.push(
|
||||||
|
Notice.create({
|
||||||
|
text: I18n.t("theme_preview_notice"),
|
||||||
|
id: "theme-preview",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.siteSettings.disable_emails === "yes") {
|
if (this.siteSettings.disable_emails === "yes") {
|
||||||
notices.push(
|
notices.push(
|
||||||
Notice.create({
|
Notice.create({
|
||||||
|
|
|
@ -51,8 +51,11 @@ class LiveDevelopmentInit {
|
||||||
// Refresh if necessary
|
// Refresh if necessary
|
||||||
document.location.reload(true);
|
document.location.reload(true);
|
||||||
} else if (me === "development-mode-theme-changed") {
|
} else if (me === "development-mode-theme-changed") {
|
||||||
if (window.location.pathname.startsWith("/admin/customize/themes")) {
|
if (
|
||||||
// don't refresh users on routes which make theme changes - would be very inconvenient.
|
window.location.pathname.startsWith("/admin/customize/themes") ||
|
||||||
|
window.location.pathname.startsWith("/admin/config/look-and-feel")
|
||||||
|
) {
|
||||||
|
// Don't refresh users on routes which make theme changes - would be very inconvenient.
|
||||||
// Instead, refresh on their next route navigation.
|
// Instead, refresh on their next route navigation.
|
||||||
this.session.requiresRefresh = true;
|
this.session.requiresRefresh = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,25 +5,60 @@
|
||||||
|
|
||||||
.themes-cards-container {
|
.themes-cards-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: calc(66.66% - 0.5em) calc(33.33% - 0.5em);
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
|
||||||
@media screen and (max-width: 1300px) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(325px - 1em), 1fr));
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-card {
|
.theme-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
box-shadow: 0px 0px 0px 3px transparent;
|
||||||
|
transition: box-shadow 0.3s ease-in-out;
|
||||||
|
&.--updating {
|
||||||
|
animation: updating 3s ease-in-out 1,
|
||||||
|
updatingInfinite 3s ease-in-out 3s infinite;
|
||||||
|
@keyframes updating {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0px 0px 0px 3px transparent;
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0px 0px 0px 6px var(--success-low);
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0px 0px 0px 3px var(--success-low);
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes updatingInfinite {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0px 0px 0px 3px var(--success-low);
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0px 0px 0px 6px var(--success-low);
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0px 0px 0px 3px var(--success-low);
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.--updating .d-icon-sync {
|
||||||
|
animation: rotate 3s linear infinite;
|
||||||
|
margin-right: 0.45em;
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.admin-config-area-card__content {
|
.admin-config-area-card__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -34,16 +69,14 @@
|
||||||
@include theme-card-border(tertiary);
|
@include theme-card-border(tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-disabled {
|
&__update-available {
|
||||||
@include theme-card-border(primary);
|
position: absolute;
|
||||||
}
|
right: -9px;
|
||||||
|
top: -9px;
|
||||||
&.-broken {
|
color: var(--success);
|
||||||
@include theme-card-border(danger);
|
font-size: var(--font-up-1);
|
||||||
}
|
background: var(--secondary);
|
||||||
|
display: flex;
|
||||||
.broken-indicator {
|
|
||||||
color: var(--danger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icons {
|
&__icons {
|
||||||
|
@ -139,6 +172,5 @@
|
||||||
&__title {
|
&__title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4740,6 +4740,8 @@ en:
|
||||||
safe_mode:
|
safe_mode:
|
||||||
enabled: "Safe mode is enabled, to exit safe mode close this browser window"
|
enabled: "Safe mode is enabled, to exit safe mode close this browser window"
|
||||||
|
|
||||||
|
theme_preview_notice: "You are currently previewing a theme, close this browser tab or window to return to your normal site configuration."
|
||||||
|
|
||||||
image_removed: "(image removed)"
|
image_removed: "(image removed)"
|
||||||
|
|
||||||
pause_notifications:
|
pause_notifications:
|
||||||
|
@ -5694,8 +5696,8 @@ en:
|
||||||
move_up: "Move up"
|
move_up: "Move up"
|
||||||
move_down: "Move down"
|
move_down: "Move down"
|
||||||
look_and_feel:
|
look_and_feel:
|
||||||
title: "Look & feel"
|
title: "Look and feel"
|
||||||
description: "Themes, components, and color schemes can be used to customise and brand your Discourse site, giving it a distinctive style."
|
description: "Customize and brand your Discourse site, giving it a distinctive style."
|
||||||
themes:
|
themes:
|
||||||
title: "Themes"
|
title: "Themes"
|
||||||
themes_intro: "Install a new theme to get started, or create your own from scratch using these resources."
|
themes_intro: "Install a new theme to get started, or create your own from scratch using these resources."
|
||||||
|
@ -5929,12 +5931,14 @@ en:
|
||||||
set_default_theme: "Set default"
|
set_default_theme: "Set default"
|
||||||
default_theme: "Default theme"
|
default_theme: "Default theme"
|
||||||
set_default_success: "Default theme set to %{theme}"
|
set_default_success: "Default theme set to %{theme}"
|
||||||
|
install_success: "%{theme} installed successfully!"
|
||||||
inactive_components: "Unused components:"
|
inactive_components: "Unused components:"
|
||||||
selected:
|
selected:
|
||||||
one: "%{count} selected"
|
one: "%{count} selected"
|
||||||
other: "%{count} selected"
|
other: "%{count} selected"
|
||||||
cancel: "Cancel"
|
cancel: "Cancel"
|
||||||
broken_theme_tooltip: "This theme has errors in its CSS, HTML or YAML"
|
broken_theme_tooltip: "This theme has errors in its CSS, HTML or YAML"
|
||||||
|
broken_theme: "Disable broken theme"
|
||||||
disabled_component_tooltip: "This component has been disabled"
|
disabled_component_tooltip: "This component has been disabled"
|
||||||
default_theme_tooltip: "This theme is the site's default theme"
|
default_theme_tooltip: "This theme is the site's default theme"
|
||||||
updates_available_tooltip: "Updates are available for this theme"
|
updates_available_tooltip: "Updates are available for this theme"
|
||||||
|
@ -6001,6 +6005,7 @@ en:
|
||||||
update_to_latest: "Update to Latest"
|
update_to_latest: "Update to Latest"
|
||||||
check_for_updates: "Check for Updates"
|
check_for_updates: "Check for Updates"
|
||||||
updating: "Updating…"
|
updating: "Updating…"
|
||||||
|
update_success: "%{theme} Update complete"
|
||||||
up_to_date: "Theme is up-to-date, last checked:"
|
up_to_date: "Theme is up-to-date, last checked:"
|
||||||
has_overwritten_history: "Current theme version no longer exists because the Git history has been overwritten by a force push."
|
has_overwritten_history: "Current theme version no longer exists because the Git history has been overwritten by a force push."
|
||||||
add: "Add"
|
add: "Add"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user