UX: Tweaks to the theme/component pages when using admin sidebar (#30953)

There are a number of minor changes in this commit :

1. Combine the "Themes" and "Components" links in the admin sidebar into
a single tab labelled "Themes and components"
2. The combined tab links to the `/admin/config/customize/themes` page
(titled as "Themes and components")
3. Add a new "Components" tab to the "Themes and components" page.
There's already an existing "Themes" tab
4. Add a "back to" link at the top of individual theme/component page to
navigate back to the respective tab in the "Themes and components" page
5. Remove the themes/components list/sidebar that currently serves for
navigating between themes/components
6. Remove the header in the theme/component page

Changes 4–6 apply only if the admin sidebar is enabled; they have no
effect otherwise.

Internal topic: t/146006.
This commit is contained in:
Osama Sayegh
2025-03-13 15:34:17 +03:00
committed by GitHub
parent 7b5213cc8d
commit f87e5aab0b
33 changed files with 507 additions and 230 deletions

View File

@ -0,0 +1,76 @@
import Component from "@glimmer/component";
import DButton from "discourse/components/d-button";
import icon from "discourse/helpers/d-icon";
import { i18n } from "discourse-i18n";
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
export default class InstallThemeCard extends Component {
externalResources = [
{
key: "admin.customize.theme.beginners_guide_title",
link: "https://meta.discourse.org/t/91966",
},
{
key: "admin.customize.theme.developers_guide_title",
link: "https://meta.discourse.org/t/93648",
},
{
key: "admin.customize.theme.browse_themes",
link: "https://meta.discourse.org/c/theme",
},
];
get heading() {
if (this.args.component) {
return i18n(
"admin.config_areas.themes_and_components.components.new_component"
);
} else {
return i18n("admin.config_areas.themes_and_components.themes.new_theme");
}
}
get intro() {
if (this.args.component) {
return i18n(
"admin.config_areas.themes_and_components.components.components_intro"
);
} else {
return i18n(
"admin.config_areas.themes_and_components.themes.themes_intro"
);
}
}
<template>
<AdminConfigAreaCard
class="theme-install-card"
@translatedHeading={{this.heading}}
>
<:content>
<p>{{this.intro}}</p>
<div class="theme-install-card__external-links">
{{#each this.externalResources as |resource|}}
<a
href={{resource.link}}
class="external-link"
rel="noopener noreferrer"
target="_blank"
>
{{i18n resource.key}}
{{icon "up-right-from-square"}}
</a>
{{/each}}
</div>
<DButton
class="btn-primary theme-install-card__install-button"
@translatedLabel={{i18n
"admin.config_areas.themes_and_components.install"
}}
@icon="upload"
@action={{@openModal}}
/>
</:content>
</AdminConfigAreaCard>
</template>
}

View File

@ -0,0 +1,63 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { i18n } from "discourse-i18n";
import InstallThemeCard from "admin/components/admin-config-area-cards/install-theme-card";
import InstallComponentModal from "admin/components/modal/install-theme";
import ThemesGrid from "admin/components/themes-grid";
import { COMPONENTS } from "admin/models/theme";
export default class AdminConfigAreasComponents extends Component {
@service modal;
@service router;
@service toasts;
@action
installModal() {
this.modal.show(InstallComponentModal, {
model: { ...this.installOptions() },
});
}
// 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.
installOptions() {
return {
selectedType: COMPONENTS,
userId: null,
content: [],
installedThemes: this.args.components,
addTheme: this.addComponent,
updateSelectedType: () => {},
showComponentsOnly: true,
};
}
@action
addComponent(component) {
this.toasts.success({
data: {
message: i18n("admin.customize.theme.install_success", {
theme: component.name,
}),
},
duration: 2000,
});
this.router.refresh();
}
<template>
<div class="admin-detail">
<ThemesGrid @themes={{@components}}>
<:specialCard>
<InstallThemeCard
@component={{true}}
@openModal={{this.installModal}}
/>
</:specialCard>
</ThemesGrid>
</div>
</template>
}

View File

@ -1,12 +1,13 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import DPageSubheader from "discourse/components/d-page-subheader";
import { i18n } from "discourse-i18n";
import InstallThemeCard from "admin/components/admin-config-area-cards/install-theme-card";
import InstallThemeModal from "admin/components/modal/install-theme";
import ThemesGrid from "admin/components/themes-grid";
import { THEMES } from "admin/models/theme";
export default class AdminConfigAreasLookAndFeelThemes extends Component {
export default class AdminConfigAreasThemes extends Component {
@service modal;
@service router;
@service toasts;
@ -24,12 +25,13 @@ export default class AdminConfigAreasLookAndFeelThemes extends Component {
// that sits in the route.
installThemeOptions() {
return {
selectedType: "theme",
selectedType: THEMES,
userId: null,
content: [],
installedThemes: this.args.themes,
addTheme: this.addTheme,
updateSelectedType: () => {},
showThemesOnly: true,
};
}
@ -47,23 +49,12 @@ export default class AdminConfigAreasLookAndFeelThemes extends Component {
}
<template>
<DPageSubheader
@titleLabel={{i18n "admin.config_areas.look_and_feel.themes.title"}}
@descriptionLabel={{i18n "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>
</DPageSubheader>
<div class="admin-detail">
<ThemesGrid @themes={{@themes}} />
<ThemesGrid @themes={{@themes}}>
<:specialCard>
<InstallThemeCard @openModal={{this.installModal}} />
</:specialCard>
</ThemesGrid>
</div>
</template>
}

View File

@ -111,7 +111,8 @@ export default class InstallThemeModal extends Component {
}
get themes() {
return POPULAR_THEMES.map((popularTheme) => {
const list = [];
POPULAR_THEMES.forEach((popularTheme) => {
if (
this.args.model.installedThemes.some((installedTheme) =>
this.themeHasSameUrl(installedTheme, popularTheme.value)
@ -119,8 +120,20 @@ export default class InstallThemeModal extends Component {
) {
popularTheme.installed = true;
}
return popularTheme;
if (this.args.model.showComponentsOnly) {
if (popularTheme.component) {
list.push(popularTheme);
}
} else if (this.args.model.showThemesOnly) {
if (!popularTheme.component) {
list.push(popularTheme);
}
} else {
list.push(popularTheme);
}
});
return list;
}
get installingMessage() {

View File

@ -12,21 +12,6 @@ export default class ThemesGrid extends Component {
sortedThemes;
externalResources = [
{
key: "admin.customize.theme.beginners_guide_title",
link: "https://meta.discourse.org/t/91966",
},
{
key: "admin.customize.theme.developers_guide_title",
link: "https://meta.discourse.org/t/93648",
},
{
key: "admin.customize.theme.browse_themes",
link: "https://meta.discourse.org/c/theme",
},
];
constructor() {
super(...arguments);
@ -51,6 +36,9 @@ export default class ThemesGrid extends Component {
{{#each @themes as |theme|}}
<ThemesGridCard @theme={{theme}} @allThemes={{@themes}} />
{{/each}}
{{#if (has-block "specialCard")}}
{{yield to="specialCard"}}
{{/if}}
</div>
</template>
}

View File

@ -0,0 +1,13 @@
import DiscourseRoute from "discourse/routes/discourse";
import { i18n } from "discourse-i18n";
export default class AdminConfigThemesAndComponentsComponentsRoute extends DiscourseRoute {
async model() {
const components = await this.store.findAll("theme");
return components.reject((t) => !t.component);
}
titleToken() {
return i18n("admin.config_areas.themes_and_components.components.title");
}
}

View File

@ -0,0 +1,10 @@
import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
export default class AdminConfigThemesAndComponentsIndexRoute extends DiscourseRoute {
@service router;
beforeModel() {
this.router.replaceWith("adminConfig.customize.themes");
}
}

View File

@ -1,13 +1,13 @@
import DiscourseRoute from "discourse/routes/discourse";
import { i18n } from "discourse-i18n";
export default class AdminConfigLookAndFeelThemesRoute extends DiscourseRoute {
export default class AdminConfigThemesAndComponentsThemesRoute extends DiscourseRoute {
async model() {
const themes = await this.store.findAll("theme");
return themes.reject((t) => t.component);
}
titleToken() {
return i18n("admin.config_areas.look_and_feel.themes.title");
return i18n("admin.config_areas.themes_and_components.themes.title");
}
}

View File

@ -2,10 +2,10 @@ import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
import { i18n } from "discourse-i18n";
export default class AdminConfigLookAndFeelRoute extends DiscourseRoute {
export default class AdminConfigThemesAndComponentsRoute extends DiscourseRoute {
@service router;
titleToken() {
return i18n("admin.config_areas.look_and_feel.title");
return i18n("admin.config_areas.themes_and_components.title");
}
}

View File

@ -1,10 +0,0 @@
import { service } from "@ember/service";
import AdminConfigWithSettingsRoute from "./admin-config-with-settings-route";
export default class AdminConfigLookAndFeelIndexRoute extends AdminConfigWithSettingsRoute {
@service router;
beforeModel() {
this.router.replaceWith("adminConfig.lookAndFeel.themes");
}
}

View File

@ -276,8 +276,9 @@ export default function () {
path: "/",
});
});
this.route("lookAndFeel", { path: "/look-and-feel" }, function () {
this.route("customize", function () {
this.route("themes");
this.route("components");
});
this.route(
"adminPermalinks",

View File

@ -0,0 +1,6 @@
<DBreadcrumbsItem
@path="/admin/config/customize/components"
@label={{i18n "admin.config_areas.themes_and_components.components.title"}}
/>
<AdminConfigAreas::Components @components={{this.model}} />

View File

@ -0,0 +1,6 @@
<DBreadcrumbsItem
@path="/admin/config/customize/themes"
@label={{i18n "admin.config_areas.themes_and_components.themes.title"}}
/>
<AdminConfigAreas::Themes @themes={{this.model}} />

View File

@ -0,0 +1,28 @@
<DPageHeader
@titleLabel={{i18n "admin.config_areas.themes_and_components.title"}}
@descriptionLabel={{i18n
"admin.config_areas.themes_and_components.description"
}}
@learnMoreUrl="https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966"
>
<:breadcrumbs>
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
</:breadcrumbs>
<:tabs>
<NavItem
@route="adminConfig.customize.themes"
@label="admin.config_areas.themes_and_components.themes.title"
/>
<NavItem
@route="adminConfig.customize.components"
@label="admin.config_areas.themes_and_components.components.title"
/>
</:tabs>
</DPageHeader>
<div class="admin-container admin-config-page__main-area">
<PluginOutlet @name="admin-config-customize">
{{outlet}}
</PluginOutlet>
</div>

View File

@ -1,6 +0,0 @@
<DBreadcrumbsItem
@path="/admin/config/look-and-feel/themes"
@label={{i18n "admin.config_areas.look_and_feel.themes.title"}}
/>
<AdminConfigAreas::LookAndFeelThemes @themes={{this.model}} />

View File

@ -1,24 +0,0 @@
<DPageHeader
@titleLabel={{i18n "admin.config_areas.look_and_feel.title"}}
@descriptionLabel={{i18n "admin.config_areas.look_and_feel.description"}}
@learnMoreUrl="https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966"
>
<:breadcrumbs>
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
<DBreadcrumbsItem
@path="/admin/config/look-and-feel"
@label={{i18n "admin.config_areas.look_and_feel.title"}}
/>
</:breadcrumbs>
<:tabs>
<NavItem
@route="adminConfig.lookAndFeel.themes"
@label="admin.config_areas.look_and_feel.themes.title"
/>
</:tabs>
</DPageHeader>
<div class="admin-container admin-config-page__main-area">
{{outlet}}
</div>

View File

@ -1,6 +1,24 @@
{{#if this.editingThemeSetting}}
{{outlet}}
{{else}}
<div class="back-to-themes-and-components">
<LinkTo
@route={{if
this.model.component
"adminConfig.customize.components"
"adminConfig.customize.themes"
}}
>
{{dIcon "angle-left"}}
{{i18n
(if
this.model.component
"admin.config_areas.themes_and_components.components.back"
"admin.config_areas.themes_and_components.themes.back"
)
}}
</LinkTo>
</div>
<div class="show-current-style">
<span>
<PluginOutlet

View File

@ -1,43 +1,3 @@
{{#if (eq this.currentTab "themes")}}
<DPageHeader
@titleLabel={{i18n "admin.config.themes.title"}}
@descriptionLabel={{i18n "admin.config.themes.header_description"}}
@learnMoreUrl="https://meta.discourse.org/t/91966"
@hideTabs={{true}}
>
<:breadcrumbs>
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
<DBreadcrumbsItem
@path="/admin/customize/themes"
@label={{i18n "admin.config.themes.title"}}
/>
</:breadcrumbs>
</DPageHeader>
{{else}}
<DPageHeader
@titleLabel={{i18n "admin.config.components.title"}}
@descriptionLabel={{i18n "admin.config.components.header_description"}}
@hideTabs={{true}}
>
<:breadcrumbs>
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
<DBreadcrumbsItem
@path="/admin/customize/components"
@label={{i18n "admin.config.components.title"}}
/>
</:breadcrumbs>
</DPageHeader>
{{/if}}
<PluginOutlet @name="admin-customize-themes">
{{#unless this.editingTheme}}
<ThemesList
@themes={{this.fullThemes}}
@components={{this.childThemes}}
@currentTab={{this.currentTab}}
@installModal={{route-action "installModal"}}
/>
{{/unless}}
{{outlet}}
</PluginOutlet>

View File

@ -240,22 +240,14 @@ export const ADMIN_NAV_MAP = [
settings_area: "navigation",
},
{
name: "admin_themes",
route: "adminCustomizeThemes",
routeModels: ["themes"],
model: "themes",
label: "admin.config.themes.title",
description: "admin.config.themes.header_description",
name: "admin_themes_and_components",
route: "adminConfig.customize.themes",
currentWhen:
"adminConfig.customize.themes adminConfig.customize.components",
label: "admin.appearance.sidebar_link.themes_and_components.title",
icon: "paintbrush",
},
{
name: "admin_components",
route: "adminCustomizeThemes",
routeModels: ["components"],
label: "admin.config.components.title",
description: "admin.config.components.header_description",
icon: "puzzle-piece",
keywords: "admin.config.components.keywords",
keywords:
"admin.appearance.sidebar_link.themes_and_components.keywords",
},
{
name: "admin_customize_site_texts",

View File

@ -93,6 +93,9 @@ class SidebarAdminSectionLink extends BaseCustomSidebarSectionLink {
return this.router.currentRoute.name;
}
}
if (this.adminSidebarNavLink.currentWhen) {
return this.adminSidebarNavLink.currentWhen;
}
}
get keywords() {
@ -352,11 +355,11 @@ export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
store.findAll("theme").then((themes) => {
this.adminSidebarStateManager.setLinkKeywords(
"admin_themes",
"admin_themes_and_components",
themes.content.rejectBy("component").mapBy("name")
);
this.adminSidebarStateManager.setLinkKeywords(
"admin_components",
"admin_themes_and_components",
themes.content.filterBy("component").mapBy("name")
);
});

View File

@ -175,9 +175,9 @@ acceptance("Theme", function (needs) {
});
test("can force install themes", async function (assert) {
await visit("/admin/customize/themes");
await visit("/admin/config/customize/themes");
await click(".themes-list .create-actions button");
await click(".theme-install-card .btn-primary");
await click(".install-theme-items #remote");
await fillIn(
".install-theme-content .repo input",
@ -200,9 +200,9 @@ acceptance("Theme", function (needs) {
});
test("can continue installation", async function (assert) {
await visit("/admin/customize/themes");
await visit("/admin/config/customize/themes");
await click(".themes-list-container__item .info");
await click(".theme-card .btn-primary");
assert
.dom(".control-unit .status-message")
.includesText(

View File

@ -853,6 +853,18 @@
}
}
.theme-install-card {
&__external-links {
display: flex;
margin-bottom: 1em;
flex-direction: column;
}
&__install-button {
width: 100%;
}
}
.add-upload-modal {
input[type="file"] {
display: block;
@ -1191,7 +1203,7 @@
.desktop-view .admin-customize {
.show-current-style {
padding-left: 2%;
padding-left: 0;
width: 68%;
.title {
@ -1204,6 +1216,10 @@
.themes-list {
width: 28%;
}
.back-to-themes-and-components {
margin-bottom: 1em;
}
}
.mobile-view .admin-customize {

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class Admin::Config::CustomizeController < Admin::AdminController
def themes
end
def components
end
end

View File

@ -1,6 +0,0 @@
# frozen_string_literal: true
class Admin::Config::LookAndFeelController < Admin::AdminController
def themes
end
end

View File

@ -5958,6 +5958,72 @@ en:
failed: "Failed"
home:
title: "Home"
account:
title: "Account"
sidebar_link:
backups: "Backups"
whats_new:
title: "What's new?"
keywords: "changelog|feature|release"
community:
title: "Community"
sidebar_link:
about_your_site: "About your site"
badges: "Badges"
login_and_authentication: "Login & authentication"
notifications: "Notifications"
permalinks: "Permalinks"
trust_levels: "Trust levels"
group_permissions: "Group permissions"
users: "Users"
groups: "Groups"
user_fields: "User fields"
watched_words: "Watched words"
legal: "Legal"
moderation_flags:
title: "Moderation"
keywords: "flag|review"
appearance:
title: "Appearance"
sidebar_link:
font_style: "Font style"
site_logo: "Site logo"
color_schemes: "Color palettes"
emoji: "Emoji"
navigation: "Navigation"
themes_and_components:
title: "Themes and components"
keywords: "extension"
site_texts: "Site texts"
email_settings:
title: "Email Settings"
sidebar_link:
appearance: "Appearance"
server_setup:
title: "Server setup & logs"
keywords: "email|smtp|mailgun|sendgrid|sent|skipped|bounced|received|rejected|email logs|preview summary"
security:
title: "Security"
sidebar_link:
security: "Security settings"
spam: "Spam settings"
staff_action_logs:
title: "Logs & screening"
keywords: "error logs|staff action|screened emails|screened ips|screened urls|search logs"
section_landing_pages:
account:
title: "Account"
backups:
title: "Backups"
description: "Take a backup of your site's data"
whats_new:
title: "What's new?"
description: "Discover new releases and improvements to Discourse"
config_areas:
about:
@ -6054,16 +6120,21 @@ en:
delete: "Delete"
more_options:
title: "More options"
look_and_feel:
themes_and_components:
title: "Themes and components"
description: "Personalize your Discourse site with themes and components to match your needs."
breadcrumb_title: "Customize"
install: "Install"
themes:
title: "Themes"
themes_intro: "Install a new theme to get started, or create your own from scratch using these resources."
themes_intro_img_alt: "New theme placeholder"
set_default_theme: "Set default"
default_theme: "Default theme"
themes_description: "Themes are expansive customizations that change multiple elements of the style of your forum design, and often also include additional front-end features."
new_theme: "New theme"
user_selectable: "User selectable"
back: "Back to themes"
components:
title: "Components"
components_intro: "Install a new component to get started, or create your own from scratch using these resources."
new_component: "New component"
back: "Back to components"
user_fields:
field: "Field"
type: "Type"
@ -6240,6 +6311,7 @@ en:
component_name: "Component name"
themes_intro: "Select an existing theme or install a new one to get started"
themes_intro_new: "Install a new theme to get started, or create your own from scratch using these resources."
components_intro_new: "Install a new component to get started, or create your own from scratch using these resources."
themes_intro_img_alt: "New theme placeholder"
beginners_guide_title: "Beginners guide to using Discourse Themes"
developers_guide_title: "Developers guide to Discourse Themes"

View File

@ -426,11 +426,14 @@ Discourse::Application.routes.draw do
collection { put "/" => "about#update" }
end
resources :look_and_feel,
path: "look-and-feel",
resources :customize,
path: "customize",
constraints: AdminConstraint.new,
only: %i[index] do
collection { get "/themes" => "look_and_feel#themes" }
collection do
get "/themes" => "customize#themes"
get "/components" => "customize#components"
end
end
end

View File

@ -10,6 +10,7 @@ module SvgSprite
align-left
anchor
angle-down
angle-left
angle-right
angle-up
angles-down

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
describe "Admin Customize Config Area Page", type: :system do
fab!(:admin)
let(:config_area) { PageObjects::Pages::AdminCustomizeConfigArea.new }
let(:install_modal) { PageObjects::Modals::InstallTheme.new }
before { sign_in(admin) }
context "when in the themes tab" do
it "has a special card for installing new themes" do
config_area.visit
expect(config_area.install_card).to have_text(
I18n.t("admin_js.admin.config_areas.themes_and_components.themes.new_theme"),
)
config_area.install_card.find(".btn-primary").click
expect(install_modal).to be_open
expect(install_modal.popular_options.first).to have_text("Air")
end
end
context "when in the components tab" do
it "has a special card for installing new components" do
config_area.visit_components
expect(config_area.install_card).to have_text(
I18n.t("admin_js.admin.config_areas.themes_and_components.components.new_component"),
)
config_area.install_card.find(".btn-primary").click
expect(install_modal).to be_open
expect(install_modal.popular_options.first).to have_text("Brand Header")
end
end
end

View File

@ -9,76 +9,6 @@ describe "Admin Customize Themes", type: :system do
before { sign_in(admin) }
describe "when visiting the page to customize themes" do
fab!(:theme_2) { Fabricate(:theme, name: "Cool theme 2") }
fab!(:theme_3) { Fabricate(:theme, name: "Cool theme 3") }
let(:delete_themes_confirm_modal) { PageObjects::Modals::DeleteThemesConfirm.new }
it "should allow admin to bulk delete inactive themes" do
visit("/admin/customize/themes")
expect(admin_customize_themes_page).to have_inactive_themes
admin_customize_themes_page.click_select_inactive_mode
expect(admin_customize_themes_page).to have_inactive_themes_selected(count: 0)
admin_customize_themes_page.toggle_all_inactive
expect(admin_customize_themes_page).to have_inactive_themes_selected(count: 3)
admin_customize_themes_page.cancel_select_inactive_mode
expect(admin_customize_themes_page).to have_select_inactive_mode_button
admin_customize_themes_page.click_select_inactive_mode
expect(admin_customize_themes_page).to have_disabled_delete_theme_button
admin_customize_themes_page.toggle_all_inactive
admin_customize_themes_page.click_delete_themes_button
expect(delete_themes_confirm_modal).to have_theme(theme.name)
expect(delete_themes_confirm_modal).to have_theme(theme_2.name)
expect(delete_themes_confirm_modal).to have_theme(theme_3.name)
delete_themes_confirm_modal.confirm
expect(admin_customize_themes_page).to have_no_inactive_themes
end
it "selects the themes tab by default" do
visit("/admin/customize/themes")
expect(find(".themes-list-header")).to have_css(".themes-tab.active")
end
it "selects the component tab when visiting the theme-components route" do
visit("/admin/customize/components")
expect(find(".themes-list-header")).to have_css(".components-tab.active")
end
it "switching between themes and components tabs keeps the search visible only if both tabs have at least 10 items" do
(1..6).each { |number| Fabricate(:theme, component: false, name: "Cool theme #{number}") }
(1..5).each { |number| Fabricate(:theme, component: true, name: "Cool component #{number}") }
visit("/admin/customize/themes")
expect(admin_customize_themes_page).to have_themes(count: 11)
admin_customize_themes_page.search("5")
expect(admin_customize_themes_page).to have_themes(count: 1)
admin_customize_themes_page.switch_to_components
expect(admin_customize_themes_page).to have_no_search
expect(admin_customize_themes_page).to have_themes(count: 5)
(6..11).each { |number| Fabricate(:theme, component: true, name: "Cool component #{number}") }
visit("/admin/customize/components")
expect(admin_customize_themes_page).to have_themes(count: 11)
admin_customize_themes_page.search("5")
expect(admin_customize_themes_page).to have_themes(count: 1)
admin_customize_themes_page.switch_to_themes
expect(admin_customize_themes_page).to have_themes(count: 1)
end
end
describe "when visiting the page to customize a single theme" do
it "should allow admin to update the color scheme of the theme" do
visit("/admin/customize/themes/#{theme.id}")
@ -213,4 +143,20 @@ describe "Admin Customize Themes", type: :system do
expect(theme_translations_picker.component.text).to eq("English (US)")
end
end
context "when visting a theme's page" do
it "has a link to the themes page" do
visit("/admin/customize/themes/#{theme.id}")
expect(admin_customize_themes_page).to have_back_button_to_themes_page
end
end
context "when visting a component's page" do
fab!(:component) { Fabricate(:theme, component: true, name: "Cool component 493") }
it "has a link to the components page" do
visit("/admin/customize/themes/#{component.id}")
expect(admin_customize_themes_page).to have_back_button_to_components_page
end
end
end

View File

@ -293,12 +293,26 @@ describe "Admin | Sidebar Navigation", type: :system do
filter.filter("air")
links = page.all(".sidebar-section-link-content-text")
expect(links.count).to eq(1)
expect(links.map(&:text)).to eq(["Themes"])
expect(links.map(&:text)).to eq(["Themes and components"])
filter.filter("kanban")
links = page.all(".sidebar-section-link-content-text")
expect(links.count).to eq(1)
expect(links.map(&:text)).to eq(["Components"])
expect(links.map(&:text)).to eq(["Themes and components"])
end
it "highlights the 'Themes and components' link when the themes page is visited" do
visit("/admin/config/customize/themes")
expect(page).to have_css(
'.sidebar-section-link-wrapper[data-list-item-name="admin_themes_and_components"] a.active',
)
end
it "highlights the 'Themes and components' link when the components page is visited" do
visit("/admin/config/customize/components")
expect(page).to have_css(
'.sidebar-section-link-wrapper[data-list-item-name="admin_themes_and_components"] a.active',
)
end
it "does not show the button to customize sidebar sections, that is only supported in the main panel" do

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module PageObjects
module Modals
class InstallTheme < PageObjects::Modals::Base
def modal
find(".admin-install-theme-modal")
end
def popular_options
all(".popular-theme-item")
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module PageObjects
module Pages
class AdminCustomizeConfigArea < PageObjects::Pages::Base
def visit
page.visit("/admin/config/customize")
end
def visit_components
page.visit("/admin/config/customize/components")
end
def install_card
find(".theme-install-card")
end
end
end
end

View File

@ -27,6 +27,28 @@ module PageObjects
has_css?("#{setting_selector(setting_name)} .desc", exact_text: description)
end
def has_no_themes_list?
has_no_css?(".themes-list-header")
end
def has_back_button_to_themes_page?
has_css?(
'.back-to-themes-and-components a[href="/admin/config/customize/themes"]',
text: I18n.t("admin_js.admin.config_areas.themes_and_components.themes.back"),
)
end
def has_back_button_to_components_page?
has_css?(
'.back-to-themes-and-components a[href="/admin/config/customize/components"]',
text: I18n.t("admin_js.admin.config_areas.themes_and_components.components.back"),
)
end
def has_no_page_header?
has_no_css?(".d-page-header")
end
def reset_overridden_setting(setting_name)
setting_section = find("section.theme.settings .setting[data-setting=\"#{setting_name}\"]")
setting_section.click_button(I18n.t("admin_js.admin.settings.reset"))