mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 20:22:45 +08:00
FEATURE: Buttons to add and remove objects for schema theme settings (#26256)
Continue from https://github.com/discourse/discourse/pull/25673. This feature adds new buttons for schema theme settings that add/remove objects from lists.
This commit is contained in:
parent
c51ae33a54
commit
ec63f3e782
|
@ -33,8 +33,10 @@ class Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tree {
|
class Tree {
|
||||||
propertyName = null;
|
@tracked nodes = [];
|
||||||
nodes = [];
|
data = [];
|
||||||
|
propertyName;
|
||||||
|
schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SchemaThemeSettingEditor extends Component {
|
export default class SchemaThemeSettingEditor extends Component {
|
||||||
|
@ -52,11 +54,16 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
let schema = this.schema;
|
let schema = this.schema;
|
||||||
let data = this.data;
|
let data = this.data;
|
||||||
let tree = new Tree();
|
let tree = new Tree();
|
||||||
|
tree.data = data;
|
||||||
|
tree.schema = schema;
|
||||||
|
|
||||||
for (const point of this.history) {
|
for (const point of this.history) {
|
||||||
tree.propertyName = point.propertyName;
|
data = data[point.parentNode.index][point.propertyName];
|
||||||
data = data[point.node.index][point.propertyName];
|
|
||||||
schema = schema.properties[point.propertyName].schema;
|
schema = schema.properties[point.propertyName].schema;
|
||||||
|
|
||||||
|
tree.propertyName = point.propertyName;
|
||||||
|
tree.schema = point.node.schema;
|
||||||
|
tree.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.forEach((object, index) => {
|
data.forEach((object, index) => {
|
||||||
|
@ -78,6 +85,8 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
for (const childObjectsProperty of childObjectsProperties) {
|
for (const childObjectsProperty of childObjectsProperties) {
|
||||||
const subtree = new Tree();
|
const subtree = new Tree();
|
||||||
subtree.propertyName = childObjectsProperty.name;
|
subtree.propertyName = childObjectsProperty.name;
|
||||||
|
subtree.schema = childObjectsProperty.schema;
|
||||||
|
subtree.data = data[index][childObjectsProperty.name];
|
||||||
|
|
||||||
data[index][childObjectsProperty.name]?.forEach(
|
data[index][childObjectsProperty.name]?.forEach(
|
||||||
(childObj, childIndex) => {
|
(childObj, childIndex) => {
|
||||||
|
@ -116,6 +125,10 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
const node = this.activeNode;
|
const node = this.activeNode;
|
||||||
const list = [];
|
const list = [];
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
for (const [name, spec] of Object.entries(node.schema.properties)) {
|
for (const [name, spec] of Object.entries(node.schema.properties)) {
|
||||||
if (spec.type === "objects") {
|
if (spec.type === "objects") {
|
||||||
continue;
|
continue;
|
||||||
|
@ -174,7 +187,8 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
onChildClick(node, tree, parentNode) {
|
onChildClick(node, tree, parentNode) {
|
||||||
this.history.push({
|
this.history.push({
|
||||||
propertyName: tree.propertyName,
|
propertyName: tree.propertyName,
|
||||||
node: parentNode,
|
parentNode,
|
||||||
|
node,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.backButtonText = I18n.t("admin.customize.theme.schema.back_button", {
|
this.backButtonText = I18n.t("admin.customize.theme.schema.back_button", {
|
||||||
|
@ -187,11 +201,11 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
@action
|
@action
|
||||||
backButtonClick() {
|
backButtonClick() {
|
||||||
const historyPoint = this.history.pop();
|
const historyPoint = this.history.pop();
|
||||||
this.activeIndex = historyPoint.node.index;
|
this.activeIndex = historyPoint.parentNode.index;
|
||||||
|
|
||||||
if (this.history.length > 0) {
|
if (this.history.length > 0) {
|
||||||
this.backButtonText = I18n.t("admin.customize.theme.schema.back_button", {
|
this.backButtonText = I18n.t("admin.customize.theme.schema.back_button", {
|
||||||
name: this.history[this.history.length - 1].node.text,
|
name: this.history[this.history.length - 1].parentNode.text,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.backButtonText = null;
|
this.backButtonText = null;
|
||||||
|
@ -207,6 +221,26 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
this.activeNode.object[field.name] = newVal;
|
this.activeNode.object[field.name] = newVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
addItem(tree) {
|
||||||
|
const schema = tree.schema;
|
||||||
|
const node = this.createNodeFromSchema(schema, tree);
|
||||||
|
tree.data.push(node.object);
|
||||||
|
tree.nodes = [...tree.nodes, node];
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
removeItem() {
|
||||||
|
const data = this.activeNode.parentTree.data;
|
||||||
|
data.splice(this.activeIndex, 1);
|
||||||
|
this.tree.nodes = this.tree.nodes.filter((n, i) => i !== this.activeIndex);
|
||||||
|
if (data.length > 0) {
|
||||||
|
this.activeIndex = Math.max(this.activeIndex - 1, 0);
|
||||||
|
} else if (this.history.length > 0) {
|
||||||
|
this.backButtonClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fieldDescription(fieldName) {
|
fieldDescription(fieldName) {
|
||||||
const descriptions = this.args.setting.objects_schema_property_descriptions;
|
const descriptions = this.args.setting.objects_schema_property_descriptions;
|
||||||
|
|
||||||
|
@ -225,6 +259,30 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
return descriptions[key];
|
return descriptions[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createNodeFromSchema(schema, tree) {
|
||||||
|
const object = {};
|
||||||
|
const index = tree.nodes.length;
|
||||||
|
const defaultName = `${schema.name} ${index + 1}`;
|
||||||
|
|
||||||
|
if (schema.identifier) {
|
||||||
|
object[schema.identifier] = defaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, spec] of Object.entries(schema.properties)) {
|
||||||
|
if (spec.type === "objects") {
|
||||||
|
object[name] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Node({
|
||||||
|
schema,
|
||||||
|
object,
|
||||||
|
index,
|
||||||
|
text: defaultName,
|
||||||
|
parentTree: tree,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="schema-theme-setting-editor">
|
<div class="schema-theme-setting-editor">
|
||||||
<div class="schema-theme-setting-editor__navigation">
|
<div class="schema-theme-setting-editor__navigation">
|
||||||
|
@ -286,9 +344,23 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@action={{fn this.addItem nestedTree}}
|
||||||
|
@translatedLabel={{nestedTree.schema.name}}
|
||||||
|
@icon="plus"
|
||||||
|
class="schema-theme-setting-editor__tree-add-button --child"
|
||||||
|
data-test-parent-index={{node.index}}
|
||||||
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
<DButton
|
||||||
|
@action={{fn this.addItem this.tree}}
|
||||||
|
@translatedLabel={{this.tree.schema.name}}
|
||||||
|
@icon="plus"
|
||||||
|
class="schema-theme-setting-editor__tree-add-button --root"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="schema-theme-setting-editor__fields">
|
<div class="schema-theme-setting-editor__fields">
|
||||||
|
@ -301,6 +373,13 @@ export default class SchemaThemeSettingEditor extends Component {
|
||||||
@description={{field.description}}
|
@description={{field.description}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/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 class="schema-theme-setting-editor__footer">
|
<div class="schema-theme-setting-editor__footer">
|
||||||
|
|
|
@ -39,11 +39,17 @@ class TreeFromDOM {
|
||||||
const childrenHeaderElement = query(
|
const childrenHeaderElement = query(
|
||||||
`.schema-theme-setting-editor__tree-node.--heading[data-test-parent-index="${index}"]`
|
`.schema-theme-setting-editor__tree-node.--heading[data-test-parent-index="${index}"]`
|
||||||
);
|
);
|
||||||
|
const addButtons = [
|
||||||
|
...queryAll(
|
||||||
|
`.schema-theme-setting-editor__tree-add-button.--child[data-test-parent-index="${index}"]`
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
active,
|
active,
|
||||||
children,
|
children,
|
||||||
childrenHeaderElement,
|
childrenHeaderElement,
|
||||||
|
addButtons,
|
||||||
element: li,
|
element: li,
|
||||||
textElement: li.querySelector(
|
textElement: li.querySelector(
|
||||||
".schema-theme-setting-editor__tree-node-text"
|
".schema-theme-setting-editor__tree-node-text"
|
||||||
|
@ -72,6 +78,10 @@ class InputFieldsFromDOM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TOP_LEVEL_ADD_BTN =
|
||||||
|
".schema-theme-setting-editor__tree-add-button.--root";
|
||||||
|
const REMOVE_ITEM_BTN = ".schema-theme-setting-editor__remove-btn";
|
||||||
|
|
||||||
module(
|
module(
|
||||||
"Integration | Admin | Component | schema-theme-setting/editor",
|
"Integration | Admin | Component | schema-theme-setting/editor",
|
||||||
function (hooks) {
|
function (hooks) {
|
||||||
|
@ -800,5 +810,180 @@ module(
|
||||||
|
|
||||||
assert.dom(inputFields.fields.text.inputElement).hasValue("Talk to us");
|
assert.dom(inputFields.fields.text.inputElement).hasValue("Talk to us");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("adding an object to the root list of objects", async function (assert) {
|
||||||
|
const setting = schemaAndData(1);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dom(TOP_LEVEL_ADD_BTN).hasText("level1");
|
||||||
|
|
||||||
|
const tree = new TreeFromDOM();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 2);
|
||||||
|
|
||||||
|
await click(TOP_LEVEL_ADD_BTN);
|
||||||
|
await click(TOP_LEVEL_ADD_BTN);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 4);
|
||||||
|
assert.dom(tree.nodes[2].textElement).hasText("level1 3");
|
||||||
|
assert.dom(tree.nodes[3].textElement).hasText("level1 4");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("adding an object to a child list of objects", async function (assert) {
|
||||||
|
const setting = schemaAndData(1);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
const tree = new TreeFromDOM();
|
||||||
|
|
||||||
|
assert.dom(tree.nodes[0].addButtons[0]).hasText("level2");
|
||||||
|
assert.strictEqual(tree.nodes[0].children.length, 2);
|
||||||
|
|
||||||
|
await click(tree.nodes[0].addButtons[0]);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
await click(tree.nodes[0].addButtons[0]);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes[0].children.length, 4);
|
||||||
|
assert.dom(tree.nodes[0].children[2].textElement).hasText("level2 3");
|
||||||
|
assert.dom(tree.nodes[0].children[3].textElement).hasText("level2 4");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigating 1 level deep and adding an object to the child list of objects that's displayed as the root list", async function (assert) {
|
||||||
|
const setting = schemaAndData(1);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
const tree = new TreeFromDOM();
|
||||||
|
|
||||||
|
await click(tree.nodes[0].children[0].element);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
assert.dom(TOP_LEVEL_ADD_BTN).hasText("level2");
|
||||||
|
assert.strictEqual(tree.nodes.length, 2);
|
||||||
|
|
||||||
|
await click(TOP_LEVEL_ADD_BTN);
|
||||||
|
await click(TOP_LEVEL_ADD_BTN);
|
||||||
|
|
||||||
|
tree.refresh();
|
||||||
|
assert.strictEqual(tree.nodes.length, 4);
|
||||||
|
assert.dom(tree.nodes[2].textElement).hasText("level2 3");
|
||||||
|
assert.dom(tree.nodes[3].textElement).hasText("level2 4");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigating 1 level deep and adding an object to a grandchild list of objects", async function (assert) {
|
||||||
|
const setting = schemaAndData(1);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
const tree = new TreeFromDOM();
|
||||||
|
|
||||||
|
await click(tree.nodes[0].children[0].element);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
assert.dom(tree.nodes[0].addButtons[0]).hasText("level3");
|
||||||
|
assert.strictEqual(tree.nodes[0].children.length, 2);
|
||||||
|
|
||||||
|
await click(tree.nodes[0].addButtons[0]);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
await click(tree.nodes[0].addButtons[0]);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes[0].children.length, 4);
|
||||||
|
assert.dom(tree.nodes[0].children[2].textElement).hasText("level3 3");
|
||||||
|
assert.dom(tree.nodes[0].children[3].textElement).hasText("level3 4");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("removing an object from the root list of objects", async function (assert) {
|
||||||
|
const setting = schemaAndData(1);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
const tree = new TreeFromDOM();
|
||||||
|
const inputFields = new InputFieldsFromDOM();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 2);
|
||||||
|
assert.dom(tree.nodes[0].textElement).hasText("item 1");
|
||||||
|
assert.dom(tree.nodes[1].textElement).hasText("item 2");
|
||||||
|
assert.dom(inputFields.fields.name.inputElement).hasValue("item 1");
|
||||||
|
|
||||||
|
await click(REMOVE_ITEM_BTN);
|
||||||
|
|
||||||
|
tree.refresh();
|
||||||
|
inputFields.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 1);
|
||||||
|
assert.dom(tree.nodes[0].textElement).hasText("item 2");
|
||||||
|
assert.dom(inputFields.fields.name.inputElement).hasValue("item 2");
|
||||||
|
|
||||||
|
await click(REMOVE_ITEM_BTN);
|
||||||
|
|
||||||
|
tree.refresh();
|
||||||
|
inputFields.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 0);
|
||||||
|
assert.strictEqual(inputFields.count, 0);
|
||||||
|
assert.dom(REMOVE_ITEM_BTN).doesNotExist();
|
||||||
|
assert.dom(TOP_LEVEL_ADD_BTN).hasText("level1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigating 1 level deep and removing an object from the child list of objects", async function (assert) {
|
||||||
|
const setting = schemaAndData(1);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<AdminSchemaThemeSettingEditor @themeId="1" @setting={{setting}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
const tree = new TreeFromDOM();
|
||||||
|
|
||||||
|
await click(tree.nodes[0].children[1].element);
|
||||||
|
tree.refresh();
|
||||||
|
|
||||||
|
const inputFields = new InputFieldsFromDOM();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 2);
|
||||||
|
assert.dom(tree.nodes[0].textElement).hasText("child 1-1");
|
||||||
|
assert.dom(tree.nodes[1].textElement).hasText("child 1-2");
|
||||||
|
assert.dom(inputFields.fields.name.inputElement).hasValue("child 1-2");
|
||||||
|
|
||||||
|
await click(REMOVE_ITEM_BTN);
|
||||||
|
|
||||||
|
tree.refresh();
|
||||||
|
inputFields.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 1);
|
||||||
|
assert.dom(tree.nodes[0].textElement).hasText("child 1-1");
|
||||||
|
assert.dom(inputFields.fields.name.inputElement).hasValue("child 1-1");
|
||||||
|
|
||||||
|
// removing the last object navigates back to the previous level
|
||||||
|
await click(REMOVE_ITEM_BTN);
|
||||||
|
|
||||||
|
tree.refresh();
|
||||||
|
inputFields.refresh();
|
||||||
|
|
||||||
|
assert.strictEqual(tree.nodes.length, 2);
|
||||||
|
assert.strictEqual(tree.nodes[0].children.length, 0);
|
||||||
|
|
||||||
|
assert.dom(tree.nodes[0].textElement).hasText("item 1");
|
||||||
|
assert.dom(tree.nodes[1].textElement).hasText("item 2");
|
||||||
|
assert.dom(inputFields.fields.name.inputElement).hasValue("item 1");
|
||||||
|
assert
|
||||||
|
.dom(".schema-theme-setting-editor__tree-node--back-btn")
|
||||||
|
.doesNotExist();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -64,6 +64,12 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schema-theme-setting-editor__tree-add-button {
|
||||||
|
&.--child {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user