@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.
319 lines (284 loc) • 12.2 kB
text/typescript
import { html, css, LitElement, PropertyValues } from "lit";
import { provide, consume, createContext } from "@lit/context";
import { LitElementWw } from "@webwriter/lit";
import { customElement, property, query } from "lit/decorators.js";
import { NodeDetailsView } from "../components/node-detail-view/node-detail-view";
import { WebWriterGamebookViewer } from "../components/gamebook/gamebook-viewer/webwriter-gamebook-viewer";
import { GamebookContainerManager } from "../utils/gamebook-container-manager";
import { NodeEditor } from "../components/node-editor/node-editor";
import { SplitView } from "../components/split-view/split-view";
// Shoelace Imports
import "@shoelace-style/shoelace/dist/themes/light.css";
import { SlDialog } from "@shoelace-style/shoelace";
import styles from "./webwriter-branching-scenario.styles";
import {
editorState,
GamebookEditorState,
} from "../utils/gamebook-editor-state-context";
import { GamebookEditorController } from "../utils/gamebook-editor-controller";
import { WebWriterGamebookOptions } from "../components/options-panel/webwriter-gamebook-options";
export class WebWriterBranchingScenario extends LitElementWw {
accessor gamebookContainerManager;
public accessor nodeEditor;
accessor gamebookOptions;
accessor splitPanel;
accessor tabIndex = -1;
//registering custom elements used in the widget
static get scopedElements() {
return {
"webwriter-gamebook-viewer": WebWriterGamebookViewer,
"gamebook-container-manager": GamebookContainerManager,
"node-detail-view": NodeDetailsView,
"node-editor": NodeEditor,
"webwriter-gamebook-options": WebWriterGamebookOptions,
"split-view": SplitView,
"sl-dialog": SlDialog,
};
}
//import CSS
static styles = [styles];
private controller = new GamebookEditorController(this);
public accessor editorState = new GamebookEditorState("Untitled Gamebook");
// public static get shadowRootOptions(): ShadowRootInit {
// return {
// ...LitElement.shadowRootOptions,
// delegatesFocus: true,
// };
// }
/*
*/
constructor() {
super();
//Initial setting necessary to notify children of change
this.reflectStoreChangesinDOM();
}
/*
*/
protected firstUpdated(_changedProperties: any): void {
this.controller.initReferences(
this.nodeEditor,
this.gamebookContainerManager
);
// Register an observer to react to store changes
this.editorState.addObserver(() => {
this.reflectStoreChangesinDOM();
this.requestUpdate(); // Ensure Lit re-renders
});
// this.addEventListener("click", function () {
// this.focus();
// });
}
/*
*/
reflectStoreChangesinDOM() {
this.editorState = new GamebookEditorState(
this.editorState.title,
this.editorState.observer,
this.editorState.selectedNode,
this.editorState.editorZoom,
this.editorState.editorPosition,
this.editorState.dividerPosition,
this.editorState.editorIsCollapsed,
this.editorState.editorContent,
this.editorState.copiedNode,
this.editorState.selectedContainer,
this.editorState.branchIncomingContainer,
this.editorState.searchTerm,
this.editorState.searchResults
);
}
/*
*/
connectedCallback() {
super.connectedCallback();
this.addEventListener("keydown", this._handleKeydown);
}
/*
*/
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener("keydown", this._handleKeydown);
}
/*
*/
private _handleKeydown = (event: KeyboardEvent) => {
// Check if CMD (Mac) or CTRL (Windows/Linux) and F key is pressed
if ((event.metaKey || event.ctrlKey) && event.key === "f") {
event.preventDefault(); // Prevent the default browser find functionality
this.gamebookOptions.searchInput.focus(); // Focus the sl-input element
}
//
else if ((event.metaKey || event.ctrlKey) && event.key === "c") {
event.preventDefault(); // Prevent the default browser find functionality
this.editorState.setCopiedNode(this.editorState.selectedNode);
}
//
else if ((event.metaKey || event.ctrlKey) && event.key === "v") {
event.preventDefault(); // Prevent the default browser find functionality
this.controller._pasteNode();
}
};
/*
*/
render() {
return html`
<!-- <button =${() => this.exportContainersAsString()}></button> -->
${this.isContentEditable
? html`
<split-view>
<node-editor
slot="start"
=${() => this.controller._markUsedOutputs()}
=${(e: CustomEvent) =>
this.controller._selectContainer(e.detail.nodeId)}
=${(e: CustomEvent) =>
this.controller._unselectContainer()}
=${(e: CustomEvent) =>
this.controller._createContainerForNode(e.detail.node)}
=${(e: CustomEvent) =>
this.controller._copyAndPasteContainer(e.detail.node)}
=${(e: CustomEvent) =>
this.controller._removeContainer(e.detail.id)}
=${(e: CustomEvent) =>
this.controller._addSmartBranchButton(e)}
=${(e: CustomEvent) =>
this.controller._addConnectionButton(e)}
=${(e: CustomEvent) =>
this.controller._branchNodeConnected(e)}
=${(e: CustomEvent) =>
this.controller._outputBranchNodeConnectionRemove(e)}
=${(e: CustomEvent) =>
this.controller._removeButton(e)}
=${(e: CustomEvent) =>
this.controller._editorCleared()}
=${(e: CustomEvent) =>
this.controller._selectConnectionButton(e)}
=${(e: CustomEvent) =>
this.controller._unselectConnectionButton(e)}
=${(e: CustomEvent) =>
this.controller._importTemplateContainers(e)}
>
</node-editor>
<node-detail-view
slot="end"
="${(e: CustomEvent) =>
this.controller._renameSelectedNode(e.detail.newTitle)}"
=${(e: CustomEvent) => this.controller._addOutput(e)}
=${(e: CustomEvent) =>
this.controller._deleteOutput(e)}
=${(e: CustomEvent) =>
this.controller._createConnection(e)}
=${(e: CustomEvent) =>
this.controller._deleteConnection(e)}
=${(e: CustomEvent) =>
this.controller._highlightConnection(e)}
=${(e: CustomEvent) =>
this.controller._unhighlightConnection(e)}
=${(e: CustomEvent) =>
this.controller._highlightOutput(e)}
=${(e: CustomEvent) =>
this.controller._unhighlightOutput(e)}
=${(e: CustomEvent) =>
this.controller._highlightNode(e)}
=${(e: CustomEvent) =>
this.controller._unhighlightNode(e)}
=${() => this.controller._markUsedOutputs()}
>
<gamebook-container-manager
=${() =>
this.controller._markUsedOutputs()}
=${(e: CustomEvent) =>
this.controller._removeNode(e.detail.id)}
=${(e: CustomEvent) =>
this.controller._selectContainer(e.detail.id)}
=${(e: CustomEvent) =>
this.controller._unselectContainer()}
=${(e: CustomEvent) =>
this.controller._removeConnection(e)}
=${(e: CustomEvent) =>
this.controller._deleteBranchRuleElementAndConnection(e)}
=${(e: CustomEvent) =>
this.controller._addOutput(e)}
=${(e: CustomEvent) =>
this.controller._deleteOutput(e)}
=${(e: CustomEvent) =>
this.controller._createConnection(e)}
=${(e: CustomEvent) =>
this.controller._deleteConnection(e)}
=${() => this.controller._markUsedOutputs()}
=${(e: CustomEvent) =>
this.controller._changeOrigin(e)}
=${() => this.controller._pasteNode()}
=${() =>
this.controller._deleteSelectedNode()}
=${() => this.controller.nodeSearch()}
=${(e: CustomEvent) =>
this.controller.moveTo(e.detail.node)}
=${(e: CustomEvent) =>
this.controller._highlightConnection(e)}
=${(e: CustomEvent) =>
this.controller._unhighlightConnection(e)}
>
<slot></slot>
</gamebook-container-manager>
</node-detail-view>
</split-view>
<!-- Options Menu -->
<webwriter-gamebook-options
part="options"
=${(e: CustomEvent) =>
this.controller._changeOrigin(e)}
=${() => this.controller._pasteNode()}
=${() => this.controller._deleteSelectedNode()}
=${() => this.controller.nodeSearch()}
=${(e: CustomEvent) =>
this.controller.moveTo(e.detail.node)}
></webwriter-gamebook-options>
`
: html` <webwriter-gamebook-viewer
gamebookTitle=${this.editorState.title != ""
? this.editorState.title
: "Untitled Gamebook"}
><slot></slot
></webwriter-gamebook-viewer>`}
`;
}
/*
*/
private exportContainersAsString() {
console.log(JSON.stringify(this.nodeEditor.editor.drawflow));
console.log(
JSON.stringify(
this.gamebookContainerManager.gamebookContainers,
this.domElementReplacer
)
);
}
/*
*/
private domElementReplacer(key, value) {
if (value instanceof HTMLElement) {
return {
tagName: value.tagName,
attributes: [...value.attributes].map((attr) => ({
name: attr.name,
value: attr.value,
})),
innerHTML: value.innerHTML,
};
}
return value;
}
}