mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 17:02: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 {
|
||||
propertyName = null;
|
||||
nodes = [];
|
||||
@tracked nodes = [];
|
||||
data = [];
|
||||
propertyName;
|
||||
schema;
|
||||
}
|
||||
|
||||
export default class SchemaThemeSettingEditor extends Component {
|
||||
|
@ -52,11 +54,16 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
let schema = this.schema;
|
||||
let data = this.data;
|
||||
let tree = new Tree();
|
||||
tree.data = data;
|
||||
tree.schema = schema;
|
||||
|
||||
for (const point of this.history) {
|
||||
tree.propertyName = point.propertyName;
|
||||
data = data[point.node.index][point.propertyName];
|
||||
data = data[point.parentNode.index][point.propertyName];
|
||||
schema = schema.properties[point.propertyName].schema;
|
||||
|
||||
tree.propertyName = point.propertyName;
|
||||
tree.schema = point.node.schema;
|
||||
tree.data = data;
|
||||
}
|
||||
|
||||
data.forEach((object, index) => {
|
||||
|
@ -78,6 +85,8 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
for (const childObjectsProperty of childObjectsProperties) {
|
||||
const subtree = new Tree();
|
||||
subtree.propertyName = childObjectsProperty.name;
|
||||
subtree.schema = childObjectsProperty.schema;
|
||||
subtree.data = data[index][childObjectsProperty.name];
|
||||
|
||||
data[index][childObjectsProperty.name]?.forEach(
|
||||
(childObj, childIndex) => {
|
||||
|
@ -116,6 +125,10 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
const node = this.activeNode;
|
||||
const list = [];
|
||||
|
||||
if (!node) {
|
||||
return list;
|
||||
}
|
||||
|
||||
for (const [name, spec] of Object.entries(node.schema.properties)) {
|
||||
if (spec.type === "objects") {
|
||||
continue;
|
||||
|
@ -174,7 +187,8 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
onChildClick(node, tree, parentNode) {
|
||||
this.history.push({
|
||||
propertyName: tree.propertyName,
|
||||
node: parentNode,
|
||||
parentNode,
|
||||
node,
|
||||
});
|
||||
|
||||
this.backButtonText = I18n.t("admin.customize.theme.schema.back_button", {
|
||||
|
@ -187,11 +201,11 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
@action
|
||||
backButtonClick() {
|
||||
const historyPoint = this.history.pop();
|
||||
this.activeIndex = historyPoint.node.index;
|
||||
this.activeIndex = historyPoint.parentNode.index;
|
||||
|
||||
if (this.history.length > 0) {
|
||||
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 {
|
||||
this.backButtonText = null;
|
||||
|
@ -207,6 +221,26 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
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) {
|
||||
const descriptions = this.args.setting.objects_schema_property_descriptions;
|
||||
|
||||
|
@ -225,6 +259,30 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
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>
|
||||
<div class="schema-theme-setting-editor">
|
||||
<div class="schema-theme-setting-editor__navigation">
|
||||
|
@ -286,9 +344,23 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
</div>
|
||||
</li>
|
||||
{{/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}}
|
||||
</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 class="schema-theme-setting-editor__fields">
|
||||
|
@ -301,6 +373,13 @@ export default class SchemaThemeSettingEditor extends Component {
|
|||
@description={{field.description}}
|
||||
/>
|
||||
{{/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 class="schema-theme-setting-editor__footer">
|
||||
|
|
|
@ -39,11 +39,17 @@ class TreeFromDOM {
|
|||
const childrenHeaderElement = query(
|
||||
`.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 {
|
||||
active,
|
||||
children,
|
||||
childrenHeaderElement,
|
||||
addButtons,
|
||||
element: li,
|
||||
textElement: li.querySelector(
|
||||
".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(
|
||||
"Integration | Admin | Component | schema-theme-setting/editor",
|
||||
function (hooks) {
|
||||
|
@ -800,5 +810,180 @@ module(
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree-add-button {
|
||||
&.--child {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user