DEV: Open theme settings objects editor from admin customize theme page (#26006)

Why this change?

The `/admin/customize/themes/:id/schema/name` route is a work in
progress but we want to be able to start navigating to it from the
`/admin/customize/themes/:id` route.

What does this change do?

1. Move `adminCustomizeThemes.schema` to a child route of
   `adminCustomizeThemes.show`. This is because we need the model
   from the parent route and if it isn't a child route we end up
   having to load the theme model again from the server.

1. Add the `objects_schema` attribute to `ThemeSettingsSerializer`

1. Refactor `SiteSettingComponent` to be able to render a button
   so that we don't have to hardcode the button rendering into the
   `SiteSettings::String` component
This commit is contained in:
Alan Guo Xiang Tan 2024-03-06 08:24:29 +08:00 committed by GitHub
parent 81ede05005
commit 94b09f3331
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 689 additions and 648 deletions

View File

@ -25,16 +25,25 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{component {{#if this.settingEditButton}}
this.componentName <DButton
setting=this.setting @action={{this.settingEditButton.action}}
value=this.buffered.value @icon={{this.settingEditButton.icon}}
validationMessage=this.validationMessage @label={{this.settingEditButton.label}}
preview=this.preview class="setting-value-edit-button"
isSecret=this.isSecret />
allowAny=this.allowAny {{else}}
changeValueCallback=this.changeValueCallback {{component
}} this.componentName
setting=this.setting
value=this.buffered.value
validationMessage=this.validationMessage
preview=this.preview
isSecret=this.isSecret
allowAny=this.allowAny
changeValueCallback=this.changeValueCallback
}}
{{/if}}
</div> </div>
{{#if this.dirty}} {{#if this.dirty}}

View File

@ -1,11 +1,5 @@
{{#if this.setting.textarea}} {{#if this.setting.textarea}}
<Textarea @value={{this.value}} class="input-setting-textarea" /> <Textarea @value={{this.value}} class="input-setting-textarea" />
{{else if this.setting.json_schema}}
<DButton
@action={{fn (mut this.showJsonEditorModal) true}}
@icon="pencil-alt"
@label="admin.site_settings.json_schema.edit"
/>
{{else if this.isSecret}} {{else if this.isSecret}}
<Input @type="password" @value={{this.value}} class="input-setting-string" /> <Input @type="password" @value={{this.value}} class="input-setting-string" />
{{else}} {{else}}
@ -13,14 +7,4 @@
{{/if}} {{/if}}
<SettingValidationMessage @message={{this.validationMessage}} /> <SettingValidationMessage @message={{this.validationMessage}} />
<div class="desc">{{html-safe this.setting.description}}</div> <div class="desc">{{html-safe this.setting.description}}</div>
{{#if this.showJsonEditorModal}}
<Modal::JsonSchemaEditor
@updateValue={{fn (mut this.value)}}
@value={{this.value}}
@settingName={{this.setting.setting}}
@jsonSchema={{this.setting.json_schema}}
@closeModal={{fn (mut this.showJsonEditorModal) false}}
/>
{{/if}}

View File

@ -1,98 +0,0 @@
import Controller from "@ember/controller";
export default class AdminCustomizeThemesSchemaController extends Controller {
data = [
{
name: "item 1",
width: 143,
is_valid: true,
enum_prop: 11,
children: [
{
name: "child 1-1",
grandchildren: [
{
name: "grandchild 1-1-1",
},
],
},
{
name: "child 1-2",
grandchildren: [
{
name: "grandchild 1-2-1",
},
],
},
],
},
{
name: "item 2",
width: 803,
is_valid: false,
enum_prop: 22,
children: [
{
name: "child 2-1",
grandchildren: [
{
name: "grandchild 2-1-1",
},
],
},
{
name: "child 2-2",
grandchildren: [
{
name: "grandchild 2-2-1",
},
],
},
],
},
];
schema = {
name: "item",
identifier: "name",
properties: {
name: {
type: "string",
},
width: {
type: "integer",
},
is_valid: {
type: "boolean",
},
enum_prop: {
type: "enum",
choices: [11, 22],
},
children: {
type: "objects",
schema: {
name: "child",
identifier: "name",
properties: {
name: {
type: "string",
},
grandchildren: {
type: "objects",
schema: {
name: "grandchild",
identifier: "name",
properties: {
name: {
type: "string",
},
},
},
},
},
},
},
},
};
}

View File

@ -5,6 +5,7 @@ import Mixin from "@ember/object/mixin";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { isNone } from "@ember/utils"; import { isNone } from "@ember/utils";
import JsonSchemaEditorModal from "discourse/components/modal/json-schema-editor";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { fmt, propertyNotEqual } from "discourse/lib/computed"; import { fmt, propertyNotEqual } from "discourse/lib/computed";
import { splitString } from "discourse/lib/utilities"; import { splitString } from "discourse/lib/utilities";
@ -78,6 +79,7 @@ const DEFAULT_USER_PREFERENCES = [
export default Mixin.create({ export default Mixin.create({
modal: service(), modal: service(),
router: service(),
site: service(), site: service(),
attributeBindings: ["setting.setting:data-setting"], attributeBindings: ["setting.setting:data-setting"],
classNameBindings: [":row", ":setting", "overridden", "typeClass"], classNameBindings: [":row", ":setting", "overridden", "typeClass"],
@ -168,6 +170,39 @@ export default Mixin.create({
); );
}, },
@discourseComputed("setting")
settingEditButton(setting) {
if (setting.json_schema) {
return {
action: () => {
this.modal.show(JsonSchemaEditorModal, {
model: {
updateValue: (value) => {
this.buffered.set("value", value);
},
value: this.buffered.get("value"),
settingName: setting.setting,
jsonSchema: setting.json_schema,
},
});
},
label: "admin.site_settings.json_schema.edit",
icon: "pencil-alt",
};
} else if (setting.objects_schema) {
return {
action: () => {
this.router.transitionTo(
"adminCustomizeThemes.show.schema",
setting.setting
);
},
label: "admin.customize.theme.edit_objects_theme_setting",
icon: "pencil-alt",
};
}
},
@action @action
async update() { async update() {
const key = this.buffered.get("setting"); const key = this.buffered.get("setting");

View File

@ -1,8 +0,0 @@
import Route from "@ember/routing/route";
export default class AdminCustomizeThemesSchemaRoute extends Route {
setupController() {
super.setupController(...arguments);
this.controllerFor("adminCustomizeThemes").set("editingTheme", true);
}
}

View File

@ -0,0 +1,32 @@
import Route from "@ember/routing/route";
export default class AdminCustomizeThemesShowSchemaRoute extends Route {
model(params) {
const theme = this.modelFor("adminCustomizeThemesShow");
const setting = theme.settings.findBy("setting", params.setting_name);
return {
data: setting.value,
schema: setting.objects_schema,
};
}
setupController() {
super.setupController(...arguments);
this.controllerFor("adminCustomizeThemes").set("editingTheme", true);
this.controllerFor("adminCustomizeThemes.show").set(
"editingThemeSetting",
true
);
}
deactivate() {
this.controllerFor("adminCustomizeThemes").set("editingTheme", false);
this.controllerFor("adminCustomizeThemes.show").set(
"editingThemeSetting",
false
);
}
}

View File

@ -40,6 +40,7 @@ export default class AdminCustomizeThemesShowRoute extends Route {
colorSchemeId: model.get("color_scheme_id"), colorSchemeId: model.get("color_scheme_id"),
colorSchemes: parentController.get("model.extras.color_schemes"), colorSchemes: parentController.get("model.extras.color_schemes"),
editingName: false, editingName: false,
editingThemeSetting: false,
}); });
this.handleHighlight(model); this.handleHighlight(model);

View File

@ -57,9 +57,10 @@ export default function () {
"adminCustomizeThemes", "adminCustomizeThemes",
{ path: "themes", resetNamespace: true }, { path: "themes", resetNamespace: true },
function () { function () {
this.route("show", { path: "/:theme_id" }); this.route("show", { path: "/:theme_id" }, function () {
this.route("schema", { path: "schema/:setting_name" });
});
this.route("edit", { path: "/:theme_id/:target/:field_name/edit" }); this.route("edit", { path: "/:theme_id/:target/:field_name/edit" });
this.route("schema", { path: "/:theme_id/schema/:setting_name" });
} }
); );

View File

@ -1 +0,0 @@
<SchemaThemeSetting::Editor @schema={{this.schema}} @data={{this.data}} />

View File

@ -0,0 +1,4 @@
<SchemaThemeSetting::Editor
@schema={{this.model.schema}}
@data={{this.model.data}}
/>

View File

@ -4,7 +4,7 @@
@closeModal={{@closeModal}} @closeModal={{@closeModal}}
@title={{i18n @title={{i18n
"admin.site_settings.json_schema.modal_title" "admin.site_settings.json_schema.modal_title"
name=@settingName name=@model.settingName
}} }}
class="json-schema-editor-modal" class="json-schema-editor-modal"
> >

View File

@ -8,12 +8,12 @@ import { afterRender } from "discourse-common/utils/decorators";
export default class JsonSchemaEditorModal extends Component { export default class JsonSchemaEditorModal extends Component {
@tracked editor = null; @tracked editor = null;
@tracked value = this.args.value; @tracked value = this.args.model.value;
@tracked flash; @tracked flash;
@tracked flashType; @tracked flashType;
get settingName() { get settingName() {
return this.args.settingName.replace(/\_/g, " "); return this.args.model.settingName.replace(/\_/g, " ");
} }
@action @action
@ -34,7 +34,7 @@ export default class JsonSchemaEditorModal extends Component {
if (!errors.length) { if (!errors.length) {
this.value = JSON.stringify(this.editor.getValue()); this.value = JSON.stringify(this.editor.getValue());
this.args.updateValue(this.value); this.args.model.updateValue(this.value);
this.args.closeModal(); this.args.closeModal();
} else { } else {
this.flash = errors.mapBy("message").join("\n"); this.flash = errors.mapBy("message").join("\n");
@ -53,7 +53,7 @@ export default class JsonSchemaEditorModal extends Component {
JSONEditor.defaults.options.iconlib = "discourseIcons"; JSONEditor.defaults.options.iconlib = "discourseIcons";
this.editor = new JSONEditor(editor, { this.editor = new JSONEditor(editor, {
schema: this.args.jsonSchema, schema: this.args.model.jsonSchema,
disable_array_delete_all_rows: true, disable_array_delete_all_rows: true,
disable_array_delete_last_row: true, disable_array_delete_last_row: true,
disable_array_reorder: false, disable_array_reorder: false,

View File

@ -9,7 +9,8 @@ class ThemeSettingsSerializer < ApplicationSerializer
:valid_values, :valid_values,
:list_type, :list_type,
:textarea, :textarea,
:json_schema :json_schema,
:objects_schema
def setting def setting
object.name object.name
@ -65,6 +66,14 @@ class ThemeSettingsSerializer < ApplicationSerializer
object.type == ThemeSetting.types[:string] object.type == ThemeSetting.types[:string]
end end
def objects_schema
object.schema
end
def include_objects_schema?
object.type == ThemeSetting.types[:objects]
end
def json_schema def json_schema
object.json_schema object.json_schema
end end

View File

@ -5485,6 +5485,7 @@ en:
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"
theme_settings: "Theme Settings" theme_settings: "Theme Settings"
edit_objects_theme_setting: "Objects Setting Editor"
overriden_settings_explanation: "Overridden settings are marked with a dot and have a highlighted color. To reset these settings to the default value, press the reset button next to them." overriden_settings_explanation: "Overridden settings are marked with a dot and have a highlighted color. To reset these settings to the default value, press the reset button next to them."
no_settings: "This theme has no settings." no_settings: "This theme has no settings."
theme_translations: "Theme Translations" theme_translations: "Theme Translations"

View File

@ -14,4 +14,8 @@ class ThemeSettingsManager::Objects < ThemeSettingsManager
theme.reload theme.reload
record.json_value record.json_value
end end
def schema
@opts[:schema]
end
end end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
RSpec.describe ThemeSettingsSerializer do
fab!(:theme)
describe "#objects_schema" do
let(:objects_setting) do
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml")
theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
theme.settings[:objects_setting]
end
before { SiteSetting.experimental_objects_type_for_theme_settings = true }
it "should include the attribute when theme setting is typed objects" do
payload = ThemeSettingsSerializer.new(objects_setting).as_json
expect(payload[:theme_settings][:objects_schema][:name]).to eq("sections")
end
end
end

View File

@ -5,12 +5,13 @@ describe "Admin Customize Themes", type: :system do
fab!(:theme) fab!(:theme)
fab!(:admin) fab!(:admin)
let(:admin_customize_themes_page) { PageObjects::Pages::AdminCustomizeThemes.new }
before { sign_in(admin) } before { sign_in(admin) }
describe "when visiting the page to customize themes" do describe "when visiting the page to customize themes" do
fab!(:theme_2) { Fabricate(:theme) } fab!(:theme_2) { Fabricate(:theme) }
fab!(:theme_3) { Fabricate(:theme) } fab!(:theme_3) { Fabricate(:theme) }
let(:admin_customize_themes_page) { PageObjects::Pages::AdminCustomizeThemes.new }
let(:delete_themes_confirm_modal) { PageObjects::Modals::DeleteThemesConfirm.new } let(:delete_themes_confirm_modal) { PageObjects::Modals::DeleteThemesConfirm.new }
it "should allow admin to bulk delete inactive themes" do it "should allow admin to bulk delete inactive themes" do
@ -84,4 +85,32 @@ describe "Admin Customize Themes", type: :system do
expect(ace_content.text).to eq("console.log('test')") expect(ace_content.text).to eq("console.log('test')")
end end
end end
describe "when editing a theme setting of objects type" do
let(:objects_setting) do
theme.set_field(
target: :settings,
name: "yaml",
value: File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml"),
)
theme.save!
theme.settings[:objects_setting]
end
before do
SiteSetting.experimental_objects_type_for_theme_settings = true
objects_setting
end
it "should allow admin to edit the theme setting of objecst type" do
visit("/admin/customize/themes/#{theme.id}")
admin_customize_themes_page.click_edit_objects_theme_setting_button("objects_setting")
expect(page).to have_current_path(
"/admin/customize/themes/#{theme.id}/schema/objects_setting",
)
end
end
end end

View File

@ -38,6 +38,10 @@ module PageObjects
def click_delete_themes_button def click_delete_themes_button
find(".btn-delete").click find(".btn-delete").click
end end
def click_edit_objects_theme_setting_button(setting_name)
find(".theme-setting[data-setting=\"#{setting_name}\"] .setting-value-edit-button").click
end
end end
end end
end end