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._renameSelectedNode(e.detail.newTitle)}"
@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)}
@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;
}
}