import { html, css, LitElement, unsafeCSS } from "lit"; import { unsafeHTML } from "lit-html/directives/unsafe-html.js"; import { LitElementWw } from "@webwriter/lit"; import { customElement, property, query, queryAll, state, queryAssignedElements, } from "lit/decorators.js"; //Shoelace Imports import "@shoelace-style/shoelace/dist/themes/light.css"; import { SlOption, SlSelect, SlButton, SlTextarea, SlDivider, SlDropdown, SlMenu, SlMenuItem, SlIconButton, SlAlert, SlIcon, } from "@shoelace-style/shoelace"; //Import Styles import styles from "./webwriter-gamebook-viewer.styles"; import alertOctagon from "@tabler/icons/outline/alert-octagon.svg"; import { WebWriterGamebookButton } from "../gamebook-components/gamebook-button/webwriter-gamebook-button"; import { WebWriterGamebookPage } from "../gamebook-components/gamebook-containers/gamebook-page/webwriter-gamebook-page"; import { WebWriterGamebookPopup } from "../gamebook-components/gamebook-containers/gamebook-popup/webwriter-gamebook-popup"; import { WebWriterGamebookBranch } from "../gamebook-components/gamebook-containers/gamebook-branch/webwriter-gamebook-branch"; import { WebWriterGamebookBranchButton } from "../gamebook-components/gamebook-branch-button/webwriter-gamebook-branch-button"; @customElement("webwriter-gamebook-viewer") export class WebWriterGamebookViewer extends LitElementWw { //registering custom elements used in the widget static get scopedElements() { return { "sl-button": SlButton, "sl-textarea": SlTextarea, "sl-divider": SlDivider, "sl-icon-button": SlIconButton, "sl-dropdown": SlDropdown, "sl-menu": SlMenu, "sl-menu-item": SlMenuItem, "sl-select": SlSelect, "sl-option": SlOption, "sl-alert": SlAlert, "sl-icon": SlIcon, //"webwriter-gamebook-button": WebWriterGamebookButton, }; } //import CSS static styles = [styles]; @state() accessor currentContainerId: Number; @property({ type: String }) accessor gamebookTitle; @property({ type: String }) accessor pageTitle; @property({ type: Number }) accessor startPage; @property({ type: Boolean }) accessor gamebookHasError; @property({ type: String }) accessor containerWithError; @queryAssignedElements({ flatten: true, selector: "webwriter-gamebook-page, webwriter-gamebook-popup, webwriter-gamebook-branch", }) accessor gamebookContainers; /* */ protected firstUpdated(_changedProperties: any): void { const slot = this.shadowRoot.querySelector("slot"); if (slot) { slot.addEventListener("slotchange", () => this._handleSlotChange()); } this.addEventListener("submit", this._handleSubmit.bind(this)); this.gamebookHasError = this._checkForErrors(); } /* */ _handleSlotChange() { this.currentContainerId = this._resetGamebookToOrigin(); this._initializeButtons(this.currentContainerId); } /* TODO: after Thesis - Handle Reset of Quiz and Tasks */ private _handleSubmit(event: Event) { event.preventDefault(); // Prevent the default form submission const currentContainer = this.gamebookContainers.find((container) => { return container.drawflowNodeId === this.currentContainerId; }); const containersSlot = currentContainer.shadowRoot.querySelector("slot"); const assignedElements = containersSlot.assignedElements(); const smartBranchButtons = assignedElements.filter((element) => { return element instanceof WebWriterGamebookBranchButton; }); smartBranchButtons.forEach((smartBranchButton) => { const branchContainer = this.gamebookContainers.find((container) => { return container.drawflowNodeId === smartBranchButton.dataTargetId; }); let submitElements = smartBranchButton.submitElements; let submitterIndex = -1; if ( (event.target as HTMLElement).parentElement.tagName.toLowerCase() == "webwriter-quiz" ) { submitterIndex = submitElements.indexOf( (event.target as HTMLElement).parentElement.id ); } // else { submitterIndex = submitElements.indexOf( (event.target as HTMLElement).id ); } if (submitterIndex !== -1) { smartBranchButton.elementSubmitted[submitterIndex] = true; } //everything that needs to be submitted was submitted && at least one rule is satisfied if ( smartBranchButton.elementSubmitted.every( (element) => element === true ) && this._getTargetFromRules(branchContainer) !== undefined ) { smartBranchButton.disabled = false; } }); this.requestUpdate(); } /* */ render() { return html`

${this.gamebookTitle}

${this.pageTitle}

Gamebook Error
The branching rule set in node ${this.containerWithError} is incomplete.
`; } /* */ private _navigateTo(targetId: number) { let containerFound = false; // Flag to check if a container was found this.gamebookContainers.forEach((container) => { if (container.drawflowNodeId == targetId) { containerFound = true; // Set flag to true when a matching container is found if (container instanceof WebWriterGamebookPage) { this._navigateToPage(targetId); this._initializeButtons(targetId); // const pageViewer = this.shadowRoot.getElementById("pageViewer"); if (pageViewer) { pageViewer.scrollTop = 0; pageViewer.style.overflowY = "auto"; // Enable scrolling } } // else if (container instanceof WebWriterGamebookPopup) { this._showPopupContainerDialog(targetId); this._initializeButtons(targetId); const pageViewer = this.shadowRoot.getElementById("pageViewer"); if (pageViewer) { pageViewer.scrollTop = 0; pageViewer.style.overflowY = "hidden"; // Disable scrolling } } // else if (container instanceof WebWriterGamebookBranch) { const nextId = this._getTargetFromRules(container); this._navigateTo(Number(nextId)); } } }); // If no container was found, react accordingly if (!containerFound) { console.error(`No container found for targetId: ${targetId}`); } } /* */ private _navigateToPage(pageId: number) { this.gamebookContainers.forEach((container) => { if (container.drawflowNodeId == pageId) { container.show(); this.pageTitle = container.pageTitle; } else { if (container instanceof WebWriterGamebookPopup) { container.hideDialog(); } // else { container.hide(); } } }); this.currentContainerId = pageId; } /* */ private _showPopupContainerDialog(popupId: number) { this.gamebookContainers.forEach((container) => { if (container.drawflowNodeId == popupId) { (container as WebWriterGamebookPopup).showDialog(); const previousContainerId = this.currentContainerId; container.addEventListener("sl-request-close", (event) => { this._initializeButtons(previousContainerId); this.currentContainerId = previousContainerId; const pageViewer = this.shadowRoot.getElementById("pageViewer"); if (pageViewer) { pageViewer.scrollTop = 0; pageViewer.style.overflowY = "auto"; // Disable scrolling } }); } // else if (container.drawflowNodeId != popupId) { if (container instanceof WebWriterGamebookPopup) { container.hideDialog(); container.hide(); } } }); this.currentContainerId = popupId; } /* */ private _getTargetFromRules( branchContainer: WebWriterGamebookBranch ): Number { const currentContainer = this.gamebookContainers.find((container) => { return container.drawflowNodeId === this.currentContainerId; }); if (currentContainer) { const containersSlot = currentContainer.shadowRoot.querySelector("slot"); const assignedElements = containersSlot.assignedElements(); for (const rule of branchContainer.rules) { if (rule.target !== "") { //Case: Element Id const element = assignedElements.find((element) => { return element.id === rule.elementId; }); const smartBranchButton = assignedElements.find((element) => { return element instanceof WebWriterGamebookBranchButton; }); let submitElements = smartBranchButton.submitElements; if (element) { //Case: Quiz on Page if (element.tagName.toLowerCase() == "webwriter-quiz") { //this is specific to quiz: const submitterIndex = submitElements.indexOf(element.id); if (smartBranchButton.elementSubmitted[submitterIndex] == true) { const quiz = element; let relevantTasks = quiz.querySelectorAll("webwriter-task"); relevantTasks = [...relevantTasks].filter((element) => { if (element.tagName.toLowerCase() == "webwriter-task") { if (rule.quizTasks.includes(element.id)) { return element; } } }); let amountFalseTasks = 0; relevantTasks.forEach((task) => { //Task is Choice if (task.answer.tagName.toLowerCase() == "webwriter-choice") { const children = task.answer.children; //iterate through choice items const taskHasWrongChoiceItem = [...children].some( (element) => { if (element.active != element.valid) { return true; } } ); if (taskHasWrongChoiceItem) { amountFalseTasks++; } } // else if ( task.answer.tagName.toLowerCase() == "webwriter-order" ) { const children = task.answer.children; //iterate through choice items const taskHasWrongOrder = [...children].some((element) => { if (element.validOrder != element.elementIndex) { return true; } }); if (taskHasWrongOrder) { amountFalseTasks++; } } // else if ( task.answer.tagName.toLowerCase() == "webwriter-text" ) { if (task.answer.solution !== task.answer.value) { amountFalseTasks++; } } // else if ( task.answer.tagName.toLowerCase() == "webwriter-mark" ) { let userHighlightMatches = false; for (const solution_highlight of task.answer.solution) { userHighlightMatches = task.answer.value.some( (user_highlight) => { if ( solution_highlight.startOffset == user_highlight.startOffset && solution_highlight.endOffset == user_highlight.endOffset ) { return true; } } ); if (!userHighlightMatches) { amountFalseTasks++; break; } } } }); let percentageCorrect = (relevantTasks.length - amountFalseTasks) / relevantTasks.length; let match = Number(rule.match) / 100; if (rule.condition.toLowerCase() == "correct") { if (percentageCorrect >= match) { return Number(rule.target); } } //not contains else if (rule.condition.toLowerCase() == "incorrect") { if (1 - percentageCorrect >= match) { return Number(rule.target); } } } } // else if (element.tagName.toLowerCase() == "webwriter-task") { if (element.answer.tagName.toLowerCase() == "webwriter-choice") { const children = element.answer.children; //iterate through choice items const choiceIsWrong = [...children].some((element) => { if (element.active != element.valid) { return true; } }); if ( rule.condition.toLowerCase() == "correct" && !choiceIsWrong ) { return Number(rule.target); } // else if ( rule.condition.toLowerCase() == "incorrect" && choiceIsWrong ) { return Number(rule.target); } } // else if ( element.answer.tagName.toLowerCase() == "webwriter-order" ) { const children = element.answer.children; //iterate through choice items const orderIsWrong = [...children].some((element) => { if (element.validOrder != element.elementIndex) { return true; } }); if ( rule.condition.toLowerCase() == "correct" && !orderIsWrong ) { return Number(rule.target); } // else if ( rule.condition.toLowerCase() == "incorrect" && orderIsWrong ) { return Number(rule.target); } } // else if ( element.answer.tagName.toLowerCase() == "webwriter-text" ) { const textIsWrong = element.answer.solution !== element.answer.value; if (rule.condition.toLowerCase() == "correct" && !textIsWrong) { return Number(rule.target); } // else if ( rule.condition.toLowerCase() == "incorrect" && textIsWrong ) { return Number(rule.target); } } // else if ( element.answer.tagName.toLowerCase() == "webwriter-mark" ) { let userHighlightMatches = false; for (const solution_highlight of element.answer.solution) { userHighlightMatches = element.answer.value.some( (user_highlight) => { if ( solution_highlight.startOffset == user_highlight.startOffset && solution_highlight.endOffset == user_highlight.endOffset ) { return true; } } ); if ( rule.condition.toLowerCase() == "incorrect" && !userHighlightMatches ) { return Number(rule.target); } } if ( rule.condition.toLowerCase() == "correct" && userHighlightMatches ) { return Number(rule.target); } // } } } } } } //Case: No rule was satisfied return Number(branchContainer.elseRule?.target); } /* */ private _resetGamebookToOrigin() { const originPageContainer = this.gamebookContainers.find( (container) => container.getAttribute("originPage") == 1 ); this.pageTitle = originPageContainer.pageTitle; this.gamebookContainers.forEach((container) => { if (container.drawflowNodeId == originPageContainer.drawflowNodeId) { container.show(); } else { container.hide(); } }); return originPageContainer.drawflowNodeId; } /* */ private _initializeButtons(containerId: Number) { const container = this.gamebookContainers.find( (container) => container.getAttribute("drawflowNodeId") == containerId ); //initialise the elements on the origin page container.buttons.forEach((button) => { const targetId = parseInt(button.getAttribute("dataTargetId"), 10); button.addEventListener("click", () => this._navigateTo(targetId)); button.classList.remove("highlighted"); if (button instanceof WebWriterGamebookBranchButton) { //In case the button already has an existing submitElements and elementSubmitted (navigated back to a page from a popup) if ( button.submitElements.length === 0 && button.elementSubmitted.length === 0 ) { let submitElements = []; let elementSubmitted = []; const containersSlot = container.shadowRoot.querySelector("slot"); const assignedElements = containersSlot.assignedElements(); const branchContainer = this.gamebookContainers.find((container) => { return container.drawflowNodeId === targetId; }); branchContainer.rules.forEach((rule) => { const element = assignedElements.find((element) => { return element.id === rule.elementId; }); if (!submitElements.includes(element.id)) { if ( element.tagName.toLowerCase() == "webwriter-quiz" || element.tagName.toLowerCase() == "webwriter-task" ) { submitElements = [...submitElements, element.id]; elementSubmitted = [...elementSubmitted, false]; } } }); button.submitElements = submitElements; button.elementSubmitted = elementSubmitted; if (submitElements.length !== 0) { button.disabled = true; } // else if (branchContainer.elseRule === undefined) { button.disabled = true; } } } }); this.requestUpdate(); } /* */ private _checkForErrors() { for (const container of this.gamebookContainers) { if (container instanceof WebWriterGamebookBranch) { if ( container.elseRule !== undefined && container.elseRule.target == "" ) { this.containerWithError = container.pageTitle; return true; } } } return false; } }