@andreasdymek/branching-scenario
Version:
Create adaptive learning experiences by authoring a gamebook, where you present the student with choices on how to continue. The content in the gamebook can be based on a all other WebWriter content types.
575 lines (453 loc) • 15.1 kB
text/typescript
import { html, css, LitElement, unsafeCSS, PropertyValues } from "lit";
import { LitElementWw } from "@webwriter/lit";
import {
customElement,
property,
query,
queryAssignedElements,
} from "lit/decorators.js";
//Drawflow Imports
import { DrawflowNode } from "drawflow";
import { WebWriterGamebookButton } from "../components/gamebook/gamebook-components/gamebook-button/webwriter-gamebook-button";
import { WebWriterGamebookPage } from "../components/gamebook/gamebook-components/gamebook-containers/gamebook-page/webwriter-gamebook-page";
import { WebWriterGamebookPopup } from "../components/gamebook/gamebook-components/gamebook-containers/gamebook-popup/webwriter-gamebook-popup";
import { WebWriterGamebookBranch } from "../components/gamebook/gamebook-components/gamebook-containers/gamebook-branch/webwriter-gamebook-branch";
import { WebWriterGamebookBranchButton } from "../components/gamebook/gamebook-components/gamebook-branch-button/webwriter-gamebook-branch-button";
import { provide, consume, createContext } from "@lit/context";
import {
editorState,
GamebookEditorState,
} from "./gamebook-editor-state-context";
("gamebook-container-manager")
export class GamebookContainerManager extends LitElementWw {
({
flatten: true,
selector:
"webwriter-gamebook-page, webwriter-gamebook-popup, webwriter-gamebook-branch",
})
accessor gamebookContainers;
("slot") accessor slot;
({ context: editorState, subscribe: true })
({ type: Object, attribute: true, reflect: false })
public accessor editorStore = new GamebookEditorState("Default");
static get scopedElements() {
return {
// "webwriter-gamebook-page": WebWriterGamebookPage,
// "webwriter-gamebook-popup": WebWriterGamebookPopup,
// "webwriter-gamebook-branch": WebWriterGamebookBranch,
};
}
/*
*/
constructor() {
super();
}
/*
*/
protected firstUpdated(_changedProperties: any): void {
const event = new CustomEvent("managerInitialized", {
bubbles: true,
composed: true,
});
this.dispatchEvent(event);
if (this.editorStore.selectedContainer !== undefined) {
//Extraced the drawflowNodeId from the serialized container
const value = this.editorStore.selectedContainer.attributes.find(
(attr) => attr.name === "drawflownodeid"
).value;
if (value) {
const event = new CustomEvent("containerSelectFirstUpdate", {
detail: { id: value },
bubbles: true,
composed: true,
});
this.dispatchEvent(event);
} else {
const event = new CustomEvent("containerError", {
detail: { id: value },
bubbles: true,
composed: true,
});
this.dispatchEvent(event);
}
}
}
/*
*/
render() {
return html` <slot></slot> `;
}
/*
*/
public _deleteGamebookContainersById(drawflowNodeId: Number) {
this.gamebookContainers.forEach((container) => {
if (container.drawflowNodeId == drawflowNodeId) {
container.remove();
}
});
}
/*
*/
public _notifyContainerGotDeleted(drawflowNodeId: Number) {
const event = new CustomEvent("containerDeleted", {
detail: { id: drawflowNodeId },
bubbles: true,
composed: true,
});
this.dispatchEvent(event);
}
/*
*/
public _deleteAllGamebookContainers() {
this.gamebookContainers.forEach((container) => {
container.remove();
});
//("delete successfull", this.gamebookContainers);
}
/*
*/
public _getContainerByDrawflowNodeId(id: number) {
const container = this.gamebookContainers.find(
(container) => container.drawflowNodeId === id
);
if (!container) {
console.error(`No container found with drawflowNodeId: ${id}`);
}
return container;
}
/*
*/
public _renameContainer(id: string, title: string) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == id
);
(container as WebWriterGamebookPage).pageTitle = title;
}
/*
*/
public _showGamebookContainerById(nodeId: number) {
let isContainerShown = false; // Flag to track if any container is shown
this.gamebookContainers.forEach((container) => {
if (container.drawflowNodeId == nodeId) {
container.show();
isContainerShown = true; // Set flag to true when a container is shown
} else {
container.hide();
}
});
if (!isContainerShown) {
console.error(`No container found with drawflowNodeId: ${nodeId}`);
}
}
/*
*/
public _hideAllGamebookContainers() {
this.gamebookContainers.forEach((container) => {
container.hide();
});
}
/*
*/
public addConnectionButtonToContainer(
outputNode: DrawflowNode,
inputNode: DrawflowNode,
output_class: string,
input_class: string
) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == outputNode.id
);
const connButton = document.createElement(
"webwriter-gamebook-button"
) as WebWriterGamebookButton;
connButton.setAttribute("name", inputNode.data.title);
connButton.setAttribute("dataTargetId", inputNode.id.toString());
// Ensure uniqueness by adding a unique identifier
connButton.setAttribute(
"identifier",
`${outputNode.id}-${output_class}-${inputNode.id}-${input_class}`
);
container.appendChild(connButton);
}
/*
*/
public addSmartBranchButtonToContainer(
outputNode: DrawflowNode,
inputNode: DrawflowNode,
output_class: string,
input_class: string
) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == outputNode.id
);
const branchButton = document.createElement(
"webwriter-gamebook-branch-button"
) as WebWriterGamebookBranchButton;
branchButton.setAttribute("name", inputNode.data.title);
branchButton.setAttribute("dataTargetId", inputNode.id.toString());
// Ensure uniqueness by adding a unique identifier
branchButton.setAttribute(
"identifier",
`${outputNode.id}-${output_class}-${inputNode.id}-${input_class}`
);
container.appendChild(branchButton);
container.branchesOff = inputNode.id;
const branchContainer = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == inputNode.id
);
branchContainer.incomingContainerId = outputNode.id;
}
/*
*/
public removeButtonFromContainer(
outputId: string,
inputId: string,
outputClass: string,
inputClass: string
) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == outputId
);
const button = Array.from(
container.buttons as NodeListOf<HTMLElement>
).find((button) => {
return (
(button as any).identifier ===
`${outputId}-${outputClass}-${inputId}-${inputClass}`
);
});
if (button) {
button.setAttribute("identifier", "x");
button.remove();
if (button instanceof WebWriterGamebookBranchButton) {
container.branchesOff = -1;
const branchContainer = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == inputId
);
branchContainer.incomingContainerId = -1;
branchContainer.clearRules();
}
}
}
/*
*/
public updateButtonIdsAfterOutputRemove(
containerId: string,
removed_output_class: string
) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == containerId
);
// Extract the number from the output_class parameter
const removedOutputClassNumber = parseInt(
removed_output_class.split("_")[1],
10
);
// Iterate over each linkButton to update its identifier
if (container.buttons) {
container.buttons.forEach((button) => {
const [output_id, output_class, input_id] =
button.identifier.split("-");
const buttonOutputClassNumber = parseInt(
output_class.split("_")[1],
10
);
// Check if the linkButton should be updated
if (buttonOutputClassNumber > removedOutputClassNumber) {
// Generate the new identifier with incremented output_class
const newIdentifier = `${output_id}-output_${
buttonOutputClassNumber - 1
}-${input_id}-input_1`;
// Update the identifier
button.setAttribute("identifier", newIdentifier);
}
});
}
}
/*
*/
public highlightButtonInContainer(containerId, identifier) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == containerId
);
if (
container instanceof WebWriterGamebookPage ||
container instanceof WebWriterGamebookPopup
) {
const connButton = container.buttons.find(
(button) => button.identifier === identifier
);
if (connButton) {
if (!connButton.classList.contains("ww-selected")) {
connButton.classList.add("highlighted");
}
}
}
}
/*
*/
public unhighlightButtonInContainer(containerId, identifier) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == containerId
);
if (
container instanceof WebWriterGamebookPage ||
container instanceof WebWriterGamebookPopup
) {
const connButton = container.buttons.find(
(button) => button.identifier === identifier
);
if (connButton) {
connButton.classList.remove("highlighted");
}
}
}
/*
*/
public selectButtonInContainer(containerId, identifier) {
const container = this.gamebookContainers.find(
(container) => container.getAttribute("drawflowNodeId") == containerId
);
const connButton = container.buttons.find(
(button) => button.identifier === identifier
);
if (connButton) {
connButton.focus();
}
}
/*
*/
public createContainerFromNode(node) {
switch (node.class) {
case "page":
case "origin":
return this._createContainerElement(
node,
"webwriter-gamebook-page",
node.class == "origin" ? "1" : "0"
);
break;
case "popup":
return this._createContainerElement(node, "webwriter-gamebook-popup");
break;
case "branch":
return this._createContainerElement(node, "webwriter-gamebook-branch");
break;
}
}
/*
*/
private _createContainerElement(
node: DrawflowNode,
tagName: string,
originPage?: string
) {
const container = document.createElement(tagName) as HTMLElement;
container.setAttribute("drawflowNodeId", node.id.toString());
container.setAttribute("pageTitle", node.data.title);
if (originPage) {
container.setAttribute("originPage", originPage);
}
(container as any).hide();
return container;
}
/*
*/
public importContainers(template: Array<Object>) {
let containers = template.map((info) =>
this.createContainerFromImport(info)
);
return containers;
}
/*
*/
private createContainerFromImport(info) {
let element = document.createElement(info.tagName);
info.attributes.forEach((attr) => {
element.setAttribute(attr.name, attr.value);
});
element.innerHTML = info.innerHTML;
return element;
}
/*
*/
public searchContainers(value: string): Number[] {
let matchContainerIds: Number[] = [];
const searchValue = value.toLowerCase();
this.gamebookContainers.forEach((container) => {
const slotElements = container.querySelectorAll("*"); // Get all child elements in the container
slotElements.forEach((element) => {
// Check if the tagName or textContent includes the search value
if (
element.tagName.toLowerCase().includes(searchValue) ||
(element.textContent &&
element.textContent.toLowerCase().includes(searchValue))
) {
matchContainerIds = [...matchContainerIds, container.drawflowNodeId];
}
});
});
return matchContainerIds;
}
/*
*/
public updateBranchContainerRuleTarget(output_id, output_class, input_id) {
const branchContainer = this._getContainerByDrawflowNodeId(output_id);
branchContainer._updateRuleTarget(output_class, input_id);
}
/*
*/
public removeBranchContainerRuleElements(output_id, element_id, isQuiz) {
const branchContainer = this._getContainerByDrawflowNodeId(output_id);
const removeConnectionsFromOutputs = (
branchContainer as WebWriterGamebookBranch
).removeElementOfRules(element_id, isQuiz);
return removeConnectionsFromOutputs;
}
/*
*/
public copyAndPasteContainerContents(pastedNode) {
const pastedContainer = this.createContainerFromNode(pastedNode);
const copiedContainer = this._getContainerByDrawflowNodeId(
this.editorStore.copiedNode.id
);
// Iterate through each element in copiedContainer's slotContent
if (copiedContainer.slotContent) {
copiedContainer.slotContent.forEach((element) => {
// Skip elements with specific tag names
if (
element.tagName.toLowerCase() === "webwriter-gamebook-button" ||
element.tagName.toLowerCase() === "webwriter-gamebook-branch-button"
) {
return; // Skip this element
}
// Create a new element of the same type
const newElement = document.createElement(element.tagName);
// Manually copy desired attributes, skipping 'id'
[...element.attributes].forEach((attr) => {
if (attr.name !== "id" && attr.name !== "contenteditable") {
// Skip the 'id' attribute
newElement.setAttribute(attr.name, attr.value);
}
});
// Copy inner content (text or children) if necessary
newElement.innerHTML = element.innerHTML; // or use another approach based on your needs
// Append the new element to pastedContainer's slot
pastedContainer.appendChild(newElement);
});
}
return pastedContainer;
}
/*
*/
public changeOrigin(newId) {
const originPageContainer = this.gamebookContainers.find(
(container) => container.getAttribute("originPage") === "1"
);
if (originPageContainer) {
(originPageContainer as WebWriterGamebookPage).originPage = 0;
}
const newOriginPageContainer = this._getContainerByDrawflowNodeId(newId);
(newOriginPageContainer as WebWriterGamebookPage).originPage = 1;
}
}