import { html, css, LitElement, unsafeCSS, PropertyValues } from "lit"; import { LitElementWw } from "@webwriter/lit"; import { customElement, property, query, state, queryAssignedElements, } from "lit/decorators.js"; import { WebWriterGamebookButton } from "../../gamebook-button/webwriter-gamebook-button"; //Shoelace import { SlButton } from "@shoelace-style/shoelace"; @customElement("webwriter-gamebook-branch") export class WebWriterGamebookBranch extends LitElementWw { //associated node id @property({ type: Number, attribute: true, reflect: true }) accessor drawflowNodeId; @property({ type: Number, attribute: true, reflect: true }) accessor incomingContainerId = -1; // Array of custom objects (rules) @property({ type: Array, attribute: true, reflect: true }) accessor rules: Rule[] = []; // Array of custom objects (rules) @property({ type: Object, attribute: true, reflect: true }) accessor elseRule: Rule; @property({ type: String, attribute: true, reflect: true }) accessor pageTitle; //import CSS static get styles() { return css``; } //registering custom elements used in the widget static get scopedElements() { return { "sl-button": SlButton, //"webwriter-gamebook-button": WebWriterGamebookButton, }; } /* */ constructor() { super(); } /* */ render() { return html``; } /* */ public hide() { this.style.display = "none"; } /* */ public show() { this.style.display = "block"; } /* Clears all rules from the rules array */ public clearRules() { this.rules.forEach((rule) => { this.deleteRule((rule as Rule).output_id); }); this.rules = []; } /* Adds a new rule to the rules array */ private addRule(newRule: Rule) { // Add the new rule to the rules array this.rules = [...this.rules, newRule]; } /* */ public addEmptyRule(node) { // Step 3: Get the last created output's output_class const outputKeys = Object.keys(node.outputs); const lastOutputClass = outputKeys[outputKeys.length - 1]; const emptyRule: Rule = { output_id: lastOutputClass, elementId: "", quizTasks: "", condition: "", match: "", target: "", isConditionEnabled: false, isMatchEnabled: false, isTargetEnabled: false, }; this.addRule(emptyRule); this.dispatchEvent( new CustomEvent("markOutputs", { bubbles: true, composed: true, }) ); } /* Deletes a rule from the rules array by its ID */ public deleteRule(output_id: string) { this.dispatchEvent( new CustomEvent("deleteOutput", { detail: { nodeId: this.drawflowNodeId, outputClass: output_id, }, bubbles: true, composed: true, }) ); // Filter out the rule with the specified id this.rules = this.rules.filter((rule) => rule.output_id !== output_id); this.rules = [...this.rules]; this.updateAllRulesOutputIds(output_id); //Step 5: If its the last rule, delete it const noOfExistingRules = this.rules.length; if (noOfExistingRules == 0 && this.elseRule !== undefined) { this.removeElseRule(); } this.dispatchEvent( new CustomEvent("markOutputs", { bubbles: true, composed: true, }) ); } /* */ public updateAllRulesOutputIds(deleted_output_id: string) { // Extract the number from the output_class parameter const removedOutputClassNumber = parseInt( deleted_output_id.split("_")[1], 10 ); // this.rules.forEach((rule, index) => { const outputIdNumber = parseInt(rule.output_id.split("_")[1], 10); // Check if the linkButton should be updated if (outputIdNumber > removedOutputClassNumber) { // Generate the new identifier with incremented output_class this.rules[index].output_id = `output_${outputIdNumber - 1}`; this.rules = [...this.rules]; } }); // Update this.elseRule const elseRuleOutputIdNumber = parseInt( this.elseRule?.output_id.split("_")[1], 10 ); if (elseRuleOutputIdNumber > removedOutputClassNumber) { this.elseRule = { ...this.elseRule, output_id: `output_${elseRuleOutputIdNumber - 1}`, }; } } /* */ public updateRuleOutputId(index, new_output_id) { this.rules[index] = { ...this.rules[index], output_id: new_output_id }; // Update target to input_id this.rules = [...this.rules]; } /* */ public updateRules(rules) { this.rules = [...rules]; } /* */ public addEmptyElseRule(node) { // Step 4: Extract the last created output's output_class const outputKeys = Object.keys(node.outputs); const lastOutputClass = outputKeys[outputKeys.length - 1]; // Get the last key (latest output) // Step 5: Create an empty rule with the last output's output_class as output_id const elseRule: Rule = { output_id: lastOutputClass, // Use the last output's output_class as output_id elementId: "", // Empty element quizTasks: "", condition: "", // Empty condition match: "", // Empty match target: "", // Empty target isConditionEnabled: false, isMatchEnabled: false, isTargetEnabled: false, }; this.elseRule = { ...elseRule }; this.dispatchEvent( new CustomEvent("markOutputs", { bubbles: true, composed: true, }) ); } /* */ public removeElseRule() { this.dispatchEvent( new CustomEvent("deleteOutput", { detail: { nodeId: this.drawflowNodeId, outputClass: this.elseRule.output_id, }, bubbles: true, composed: true, }) ); this.elseRule = undefined; } /* */ public _moveElseRuleToLastOutput(node) { const { outputs } = node; const highestOutputIdIndex = this.rules.reduce( (maxIndex, rule, currentIndex) => { const maxNumber = parseInt( this.rules[maxIndex].output_id.split("_")[1], 10 ); const currentNumber = parseInt(rule.output_id.split("_")[1], 10); return currentNumber > maxNumber ? currentIndex : maxIndex; }, 0 ); // Swap the output_id between the highest rule and the elseRule. const elseRuleOutputId = this.elseRule.output_id; const newRuleOutputId = this.rules[highestOutputIdIndex].output_id; this.rules[highestOutputIdIndex].output_id = elseRuleOutputId; this.rules = [...this.rules]; this.elseRule.output_id = newRuleOutputId; // Update the connections to reflect the changes. outputs[elseRuleOutputId].connections.forEach((connection) => { // Create a new connection for the swapped rule. this.dispatchEvent( new CustomEvent("createConnection", { detail: { outputNodeId: node.id, inputNodeId: connection.node, outputClass: newRuleOutputId, inputClass: "input_1", }, bubbles: true, composed: true, }) ); // Remove the old connection from the elseRule. this.dispatchEvent( new CustomEvent("deleteConnection", { detail: { outputNodeId: node.id, inputNodeId: connection.node, outputClass: elseRuleOutputId, inputClass: "input_1", }, bubbles: true, composed: true, }) ); }); this.elseRule = { ...this.elseRule, output_id: newRuleOutputId, }; this.dispatchEvent( new CustomEvent("markOutputs", { bubbles: true, composed: true, }) ); } /* */ public _updateRuleElement( index: number, value: string, container: HTMLElement ) { this.rules[index].elementId = value; if (value == "") { this.rules[index].isConditionEnabled = false; this._updateRuleTasks(index, "", container); this._updateRuleCondition(index, "", container); this._updateRuleMatch(index, ""); this._updateRuleTarget(this.rules[index].output_id, ""); } // else if ( container?.querySelector(`#${value}`)?.tagName?.toLowerCase() == "webwriter-quiz" ) { this.rules[index].isConditionEnabled = false; } // else { this.rules[index].isConditionEnabled = true; } this.rules = [...this.rules]; } /* */ public removeElementOfRules(element_id: string, isQuiz: boolean): string[][] { const resetRule = (rule) => ({ ...rule, elementId: "", quizTasks: "", condition: "", match: "", target: "", isConditionEnabled: false, isMatchEnabled: false, isTargetEnabled: false, }); let removeConnectionsFromOutputs = []; for (let rule of this.rules) { if (rule.elementId === element_id) { if (rule.target !== "") { removeConnectionsFromOutputs.push([rule.output_id, rule.target]); } rule = resetRule(rule); this.rules = this.rules.filter( (rule) => rule.output_id !== rule.output_id ); this.addRule(rule); } // else if (!isQuiz && rule.quizTasks.includes(element_id)) { const updatedQuizTaskSelection = rule.quizTasks.replace(element_id, ""); if (rule.target !== "") { removeConnectionsFromOutputs.push([rule.output_id, rule.target]); } if (!/\S/.test(updatedQuizTaskSelection)) { rule = { ...rule, quizTasks: "", condition: "", match: "", target: "", isConditionEnabled: false, isMatchEnabled: false, isTargetEnabled: false, }; } else { rule = { ...rule, quizTasks: updatedQuizTaskSelection, condition: "", match: "", target: "", isConditionEnabled: true, isMatchEnabled: false, isTargetEnabled: false, }; } this.rules = this.rules.filter( (rule) => rule.output_id !== rule.output_id ); this.addRule(rule); } } //reference update to trigger re-render this.rules = [...this.rules]; this.requestUpdate(); return removeConnectionsFromOutputs; } /* */ public _updateRuleTasks(index: number, value: string, container) { this.rules[index].isConditionEnabled = value !== ""; if (value === "") { this._updateRuleCondition(index, "", container); this._updateRuleMatch(index, ""); this._updateRuleTarget(this.rules[index].output_id, ""); } this.rules[index].quizTasks = value.replace(/,/g, " "); this.rules = [...this.rules]; } /* */ public _updateRuleTarget(output_class, input_id) { // Helper function to find and update the rule in an array of rules this.rules.forEach((rule, index) => { if (rule.output_id === output_class) { if (input_id === "") { this.dispatchEvent( new CustomEvent("deleteConnection", { detail: { outputNodeId: this.drawflowNodeId, inputNodeId: rule.target, outputClass: rule.output_id, inputClass: "input_1", }, bubbles: true, composed: true, }) ); } this.rules[index] = { ...rule, target: input_id }; // Update target to input_id this.rules = [...this.rules]; } }); // If this.elseRule is an object, check and update it directly if (this.elseRule && this.elseRule.output_id === output_class) { if (input_id == "") { this.dispatchEvent( new CustomEvent("deleteConnection", { detail: { outputNodeId: this.drawflowNodeId, inputNodeId: this.elseRule.target, outputClass: this.elseRule.output_id, inputClass: "input_1", }, bubbles: true, composed: true, }) ); } this.elseRule = { ...this.elseRule, target: input_id, }; } } /* */ public _updateRuleCondition( index: number, value: string, container: HTMLElement ) { this.rules[index].condition = value; if (value == "") { this._updateRuleMatch(index, ""); this._updateRuleTarget(this.rules[index].output_id, ""); this.rules[index].isMatchEnabled = false; this.rules[index].isTargetEnabled = false; } // else if ( container .querySelector(`#${this.rules[index].elementId}`) ?.tagName?.toLowerCase() == "webwriter-quiz" ) { this.rules[index].isMatchEnabled = true; } // else { this.rules[index].isTargetEnabled = true; this.dispatchEvent( new CustomEvent("markOutputs", { bubbles: true, composed: true, }) ); } this.rules = [...this.rules]; } /* */ public _updateRuleMatch(index: number, value: string) { this.rules[index].match = value; this.rules[index].isTargetEnabled = value !== ""; if (value === "") { this._updateRuleTarget(this.rules[index].output_id, ""); } this.rules = [...this.rules]; this.dispatchEvent( new CustomEvent("markOutputs", { bubbles: true, composed: true, }) ); } }