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"; @customElement("webwriter-branching-scenario") export class WebWriterBranchingScenario extends LitElementWw { @query("gamebook-container-manager") accessor gamebookContainerManager; @query("node-editor") public accessor nodeEditor; @query("webwriter-gamebook-options") accessor gamebookOptions; @query("sl-split-panel") accessor splitPanel; @property({ type: Number, attribute: true, reflect: true }) 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); @provide({ context: editorState }) @property({ type: Object, attribute: true, reflect: true, converter: { fromAttribute: (value: string) => GamebookEditorState.fromString(value), // Deserialize toAttribute: (value: GamebookEditorState) => value.toString(), // Serialize }, }) 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` ${this.isContentEditable ? html` this.controller._markUsedOutputs()} @nodeSelected=${(e: CustomEvent) => this.controller._selectContainer(e.detail.nodeId)} @nodeUnselected=${(e: CustomEvent) => this.controller._unselectContainer()} @nodeCreated=${(e: CustomEvent) => this.controller._createContainerForNode(e.detail.node)} @nodePasted=${(e: CustomEvent) => this.controller._copyAndPasteContainer(e.detail.node)} @nodeRemoved=${(e: CustomEvent) => this.controller._removeContainer(e.detail.id)} @nodeConnectedToBranchNode=${(e: CustomEvent) => this.controller._addSmartBranchButton(e)} @nodesConnected=${(e: CustomEvent) => this.controller._addConnectionButton(e)} @branchNodeConnected=${(e: CustomEvent) => this.controller._branchNodeConnected(e)} @branchNodeConnectionRemoved=${(e: CustomEvent) => this.controller._outputBranchNodeConnectionRemove(e)} @connectionRemoved=${(e: CustomEvent) => this.controller._removeButton(e)} @editorCleared=${(e: CustomEvent) => this.controller._editorCleared()} @connectionSelected=${(e: CustomEvent) => this.controller._selectConnectionButton(e)} @connectionUnselected=${(e: CustomEvent) => this.controller._unselectConnectionButton(e)} @nodeGroupImported=${(e: CustomEvent) => this.controller._importTemplateContainers(e)} > this.controller._addOutput(e)} @deleteOutput=${(e: CustomEvent) => this.controller._deleteOutput(e)} @createConnection=${(e: CustomEvent) => this.controller._createConnection(e)} @deleteConnection=${(e: CustomEvent) => this.controller._deleteConnection(e)} @highlightConnection=${(e: CustomEvent) => this.controller._highlightConnection(e)} @unhighlightConnection=${(e: CustomEvent) => this.controller._unhighlightConnection(e)} @highlightOutput=${(e: CustomEvent) => this.controller._highlightOutput(e)} @unhighlightOutput=${(e: CustomEvent) => this.controller._unhighlightOutput(e)} @highlightNode=${(e: CustomEvent) => this.controller._highlightNode(e)} @unhighlightNode=${(e: CustomEvent) => this.controller._unhighlightNode(e)} @markOutputs=${() => this.controller._markUsedOutputs()} > this.controller._markUsedOutputs()} @containerDeleted=${(e: CustomEvent) => this.controller._removeNode(e.detail.id)} @containerSelectFirstUpdate=${(e: CustomEvent) => this.controller._selectContainer(e.detail.id)} @containerError=${(e: CustomEvent) => this.controller._unselectContainer()} @buttonDeleted=${(e: CustomEvent) => this.controller._removeConnection(e)} @quizElementDeleted=${(e: CustomEvent) => this.controller._deleteBranchRuleElementAndConnection(e)} @addOutput=${(e: CustomEvent) => this.controller._addOutput(e)} @deleteOutput=${(e: CustomEvent) => this.controller._deleteOutput(e)} @createConnection=${(e: CustomEvent) => this.controller._createConnection(e)} @deleteConnection=${(e: CustomEvent) => this.controller._deleteConnection(e)} @markOutputs=${() => this.controller._markUsedOutputs()} @makeSelectedNodeOrigin=${(e: CustomEvent) => this.controller._changeOrigin(e)} @pasteNode=${() => this.controller._pasteNode()} @deleteSelectedNode=${() => this.controller._deleteSelectedNode()} @nodeSearch=${() => this.controller.nodeSearch()} @moveTo=${(e: CustomEvent) => this.controller.moveTo(e.detail.node)} @hoverButton=${(e: CustomEvent) => this.controller._highlightConnection(e)} @leaveButton=${(e: CustomEvent) => this.controller._unhighlightConnection(e)} > this.controller._changeOrigin(e)} @pasteNode=${() => this.controller._pasteNode()} @deleteSelectedNode=${() => this.controller._deleteSelectedNode()} @nodeSearch=${() => this.controller.nodeSearch()} @moveTo=${(e: CustomEvent) => this.controller.moveTo(e.detail.node)} > ` : html` `} `; } /* */ 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; } }