mirror of
https://github.com/discourse/discourse.git
synced 2025-02-09 12:54:56 +00:00
Why this change? Before this change, the validation error message shown to the user when saving a theme objects setting is very cryptic. This commit changes the validation error messages to be displayed on top of the editor instead. Note that I don't think this way of displaying is the ideal state we want to get to but given the time we have this will do for now.
306 lines
8.1 KiB
Plaintext
306 lines
8.1 KiB
Plaintext
import Component from "@glimmer/component";
|
|
import { tracked } from "@glimmer/tracking";
|
|
import { fn } from "@ember/helper";
|
|
import { action } from "@ember/object";
|
|
import { service } from "@ember/service";
|
|
import { gt } from "truth-helpers";
|
|
import DButton from "discourse/components/d-button";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import { cloneJSON } from "discourse-common/lib/object";
|
|
import I18n from "discourse-i18n";
|
|
import Tree from "admin/components/schema-theme-setting/editor/tree";
|
|
import FieldInput from "admin/components/schema-theme-setting/field";
|
|
|
|
export default class SchemaThemeSettingNewEditor extends Component {
|
|
@service router;
|
|
|
|
@tracked history = [];
|
|
@tracked activeIndex = 0;
|
|
@tracked activeDataPaths = [];
|
|
@tracked activeSchemaPaths = [];
|
|
@tracked saveButtonDisabled = false;
|
|
@tracked validationErrorMessage;
|
|
inputFieldObserver = new Map();
|
|
|
|
data = cloneJSON(this.args.setting.value);
|
|
schema = this.args.setting.objects_schema;
|
|
|
|
@action
|
|
onChildClick(index, propertyName, parentNodeIndex) {
|
|
this.history.pushObject({
|
|
dataPaths: [...this.activeDataPaths],
|
|
schemaPaths: [...this.activeSchemaPaths],
|
|
index: this.activeIndex,
|
|
});
|
|
|
|
this.activeIndex = index;
|
|
this.activeDataPaths.pushObjects([parentNodeIndex, propertyName]);
|
|
this.activeSchemaPaths.pushObject(propertyName);
|
|
this.inputFieldObserver.clear();
|
|
}
|
|
|
|
@action
|
|
updateIndex(index) {
|
|
this.activeIndex = index;
|
|
}
|
|
|
|
generateSchemaTitle(object, schema, index) {
|
|
return object[schema.identifier] || `${schema.name} ${index + 1}`;
|
|
}
|
|
|
|
get backButtonText() {
|
|
if (this.history.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const lastHistory = this.history[this.history.length - 1];
|
|
|
|
return I18n.t("admin.customize.theme.schema.back_button", {
|
|
name: this.generateSchemaTitle(
|
|
this.#resolveDataFromPaths(lastHistory.dataPaths)[lastHistory.index],
|
|
this.#resolveSchemaFromPaths(lastHistory.schemaPaths),
|
|
lastHistory.index
|
|
),
|
|
});
|
|
}
|
|
|
|
get activeData() {
|
|
return this.#resolveDataFromPaths(this.activeDataPaths);
|
|
}
|
|
|
|
#resolveDataFromPaths(paths) {
|
|
if (paths.length === 0) {
|
|
return this.data;
|
|
}
|
|
|
|
let data = this.data;
|
|
|
|
paths.forEach((path) => {
|
|
data = data[path];
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
get activeSchema() {
|
|
return this.#resolveSchemaFromPaths(this.activeSchemaPaths);
|
|
}
|
|
|
|
#resolveSchemaFromPaths(paths) {
|
|
if (paths.length === 0) {
|
|
return this.schema;
|
|
}
|
|
|
|
let schema = this.schema;
|
|
|
|
paths.forEach((path) => {
|
|
schema = schema.properties[path].schema;
|
|
});
|
|
|
|
return schema;
|
|
}
|
|
|
|
@action
|
|
registerInputFieldObserver(index, callback) {
|
|
this.inputFieldObserver[index] = callback;
|
|
}
|
|
|
|
@action
|
|
unregisterInputFieldObserver(index) {
|
|
delete this.inputFieldObserver[index];
|
|
}
|
|
|
|
descriptions(fieldName, key) {
|
|
// The `property_descriptions` metadata is an object with keys in the following format as an example:
|
|
//
|
|
// {
|
|
// some_property.description: <some description>,
|
|
// some_property.label: <some label>,
|
|
// some_objects_property.some_other_property.description: <some description>,
|
|
// some_objects_property.some_other_property.label: <some label>,
|
|
// }
|
|
const descriptions = this.args.setting.metadata?.property_descriptions;
|
|
|
|
if (!descriptions) {
|
|
return;
|
|
}
|
|
|
|
if (this.activeSchemaPaths.length > 0) {
|
|
key = `${this.activeSchemaPaths.join(".")}.${fieldName}.${key}`;
|
|
} else {
|
|
key = `${fieldName}.${key}`;
|
|
}
|
|
|
|
return descriptions[key];
|
|
}
|
|
|
|
fieldLabel(fieldName) {
|
|
return this.descriptions(fieldName, "label") || fieldName;
|
|
}
|
|
|
|
fieldDescription(fieldName) {
|
|
return this.descriptions(fieldName, "description");
|
|
}
|
|
|
|
get fields() {
|
|
const list = [];
|
|
|
|
if (this.activeData.length !== 0) {
|
|
for (const [name, spec] of Object.entries(this.activeSchema.properties)) {
|
|
if (spec.type === "objects") {
|
|
continue;
|
|
}
|
|
|
|
list.push({
|
|
name,
|
|
spec,
|
|
value: this.activeData[this.activeIndex][name],
|
|
description: this.fieldDescription(name),
|
|
label: this.fieldLabel(name),
|
|
});
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
@action
|
|
clickBack() {
|
|
const {
|
|
dataPaths: lastDataPaths,
|
|
schemaPaths: lastSchemaPaths,
|
|
index: lastIndex,
|
|
} = this.history.popObject();
|
|
|
|
this.activeDataPaths = lastDataPaths;
|
|
this.activeSchemaPaths = lastSchemaPaths;
|
|
this.activeIndex = lastIndex;
|
|
this.inputFieldObserver.clear();
|
|
}
|
|
|
|
@action
|
|
addChildItem(propertyName, parentNodeIndex) {
|
|
this.activeData[parentNodeIndex][propertyName].pushObject({});
|
|
|
|
this.onChildClick(
|
|
this.activeData[parentNodeIndex][propertyName].length - 1,
|
|
propertyName,
|
|
parentNodeIndex
|
|
);
|
|
}
|
|
|
|
@action
|
|
addItem() {
|
|
this.activeData.pushObject({});
|
|
this.activeIndex = this.activeData.length - 1;
|
|
}
|
|
|
|
@action
|
|
removeItem() {
|
|
this.activeData.removeAt(this.activeIndex);
|
|
|
|
if (this.activeData.length > 0) {
|
|
this.activeIndex = Math.max(this.activeIndex - 1, 0);
|
|
} else if (this.history.length > 0) {
|
|
this.clickBack();
|
|
} else {
|
|
this.activeIndex = 0;
|
|
}
|
|
}
|
|
|
|
@action
|
|
inputFieldChanged(field, newVal) {
|
|
this.activeData[this.activeIndex][field.name] = newVal;
|
|
|
|
if (field.name === this.activeSchema.identifier) {
|
|
this.inputFieldObserver[this.activeIndex]();
|
|
}
|
|
}
|
|
|
|
@action
|
|
saveChanges() {
|
|
this.saveButtonDisabled = true;
|
|
|
|
this.args.setting
|
|
.updateSetting(this.args.themeId, this.data)
|
|
.then((result) => {
|
|
this.args.setting.set("value", result[this.args.setting.setting]);
|
|
|
|
this.router.transitionTo(
|
|
"adminCustomizeThemes.show",
|
|
this.args.themeId
|
|
);
|
|
})
|
|
.catch((e) => {
|
|
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
|
this.validationErrorMessage = e.jqXHR.responseJSON.errors[0];
|
|
} else {
|
|
popupAjaxError(e);
|
|
}
|
|
})
|
|
.finally(() => (this.saveButtonDisabled = false));
|
|
}
|
|
|
|
<template>
|
|
<div class="schema-theme-setting-editor">
|
|
{{#if this.validationErrorMessage}}
|
|
<div class="schema-theme-setting-editor__errors">
|
|
<div class="alert alert-error">
|
|
{{this.validationErrorMessage}}
|
|
</div>
|
|
</div>
|
|
{{/if}}
|
|
|
|
<div class="schema-theme-setting-editor__wrapper">
|
|
<div class="schema-theme-setting-editor__navigation">
|
|
<Tree
|
|
@data={{this.activeData}}
|
|
@schema={{this.activeSchema}}
|
|
@onChildClick={{this.onChildClick}}
|
|
@clickBack={{this.clickBack}}
|
|
@backButtonText={{this.backButtonText}}
|
|
@activeIndex={{this.activeIndex}}
|
|
@updateIndex={{this.updateIndex}}
|
|
@addItem={{this.addItem}}
|
|
@addChildItem={{this.addChildItem}}
|
|
@generateSchemaTitle={{this.generateSchemaTitle}}
|
|
@registerInputFieldObserver={{this.registerInputFieldObserver}}
|
|
@unregisterInputFieldObserver={{this.unregisterInputFieldObserver}}
|
|
/>
|
|
|
|
<div class="schema-theme-setting-editor__footer">
|
|
<DButton
|
|
@disabled={{this.saveButtonDisabled}}
|
|
@action={{this.saveChanges}}
|
|
@label="save"
|
|
class="btn-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="schema-theme-setting-editor__fields">
|
|
{{#each this.fields as |field|}}
|
|
<FieldInput
|
|
@name={{field.name}}
|
|
@value={{field.value}}
|
|
@spec={{field.spec}}
|
|
@onValueChange={{fn this.inputFieldChanged field}}
|
|
@description={{field.description}}
|
|
@label={{field.label}}
|
|
@setting={{@setting}}
|
|
/>
|
|
{{/each}}
|
|
|
|
{{#if (gt this.fields.length 0)}}
|
|
<DButton
|
|
@action={{this.removeItem}}
|
|
@icon="trash-alt"
|
|
class="btn-danger schema-theme-setting-editor__remove-btn"
|
|
/>
|
|
{{/if}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
}
|