UNPKG

@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.

325 lines (293 loc) 10 kB
import { html, css, LitElement } from "lit"; import { customElement, property, query } from "lit/decorators.js"; import { LitElementWw } from "@webwriter/lit"; import "@shoelace-style/shoelace/dist/themes/light.css"; import { SlOption, SlSelect, SlButton, SlRange, SlIconButton, SlCheckbox, SlInput, SlColorPicker, SlDivider, SlButtonGroup, SlIcon, } from "@shoelace-style/shoelace"; import alignLeft from "@tabler/icons/outline/align-left.svg"; import alignRight from "@tabler/icons/outline/align-right.svg"; import alignCenter from "@tabler/icons/outline/align-center.svg"; @customElement("webwriter-gamebook-button") export class WebWriterGamebookButton extends LitElementWw { @property({ type: Number, reflect: true }) accessor dataTargetId: number; @property({ type: String, attribute: true, reflect: true }) accessor identifier: string; @property({ attribute: true }) accessor getNodeEditor = () => {}; //visual properties @property({ type: String, reflect: true }) accessor name: string = "Button"; @property({ type: String, reflect: true }) accessor size: string = "small"; @property({ type: Boolean, reflect: true }) accessor pill: boolean = false; @property({ type: Boolean, reflect: true }) accessor outline: boolean = false; @property({ type: Number, reflect: true }) accessor width: number = 50; @property({ type: String, reflect: true }) accessor alignment: string = "center"; @property({ type: String, reflect: true }) accessor variant: string = "default"; @property({ type: Number, attribute: true, reflect: true }) accessor tabIndex = -1; @query("sl-button") accessor button; static get scopedElements() { return { "sl-button": SlButton, "sl-select": SlSelect, "sl-option": SlOption, "sl-range": SlRange, "sl-icon-button": SlIconButton, "sl-checkbox": SlCheckbox, "sl-input": SlInput, "sl-color-picker": SlColorPicker, "sl-divider": SlDivider, "sl-button-group": SlButtonGroup, "sl-icon": SlIcon, }; } static get styles() { return css` :host(.highlighted) { border: 1px dashed #38bdf8; -webkit-box-shadow: 0 4px 30px 4px #c0c0c0; box-shadow: 0 4px 30px 4px #c0c0c0; } :host(:not([contenteditable="true"]):not([contenteditable=""])) .author-only { display: none; } :host([contenteditable="true"]) .author-only, :host([contenteditable=""]) .author-only { display: flex; flex-direction: column; box-sizing: border-box; padding-left: 10px; padding-bottom: 10px; gap: 12px; } .author-only .item { display: flex; flex-direction: column; gap: 7px; padding-bottom: 10px; } .author-only p { margin: 0px; font-weight: 500; font-size: 15px; box-sizing: border-box; /* border-bottom: 1.5px solid #52525b; */ color: #52525b; } .container { display: flex; width: 100%; } .controls p { padding: 0px; margin: 0px; } .active { background-color: #e0e0e0; /* example color for active state */ } sl-button.active::part(base) { background-color: #efefef; /* example color for active state */ } `; } firstUpdated() { (this.shadowRoot.querySelector("#sizeSelect") as SlSelect).value = this.size; (this.shadowRoot.querySelector("#pillCheckbox") as SlCheckbox).checked = this.pill; (this.shadowRoot.querySelector("#outlineCheckbox") as SlCheckbox).checked = this.outline; (this.shadowRoot.querySelector("#widthRange") as SlRange).value = this.width; (this.shadowRoot.querySelector("#variantSelect") as SlSelect).value = this.variant; this.addEventListener("mouseover", () => { const parsed = this.parseConnectionIdentifier(this.identifier); const event = new CustomEvent("hoverButton", { detail: { outputNodeId: parsed.outputNodeId, inputNodeId: parsed.inputNodeId, outputClass: parsed.outputClass, inputClass: "input_1", highlightButton: false, }, bubbles: true, // Allows the event to bubble up through the DOM composed: true, // Allows the event to pass through shadow DOM boundaries }); this.dispatchEvent(event); }); this.addEventListener("mouseleave", () => { const parsed = this.parseConnectionIdentifier(this.identifier); const event = new CustomEvent("leaveButton", { detail: { outputNodeId: parsed.outputNodeId, inputNodeId: parsed.inputNodeId, outputClass: parsed.outputClass, inputClass: "input_1", highlightButton: false, }, bubbles: true, // Allows the event to bubble up through the DOM composed: true, // Allows the event to pass through shadow DOM boundaries }); this.dispatchEvent(event); }); } handleSizeChange(e) { this.size = e.target.value; } handlePillChange(e) { this.pill = e.target.checked; } handleOutlineChange(e) { this.outline = e.target.checked; } handleWidthChange(e) { this.width = e.target.value; } handleVariantChange(e) { this.variant = e.target.value; } handleAlignmentChange(alignment) { this.alignment = alignment; } render() { return html` <div class="container" style="justify-content: ${this.alignment};"> <sl-button size=${this.size} style=" width: ${this.width}%; pointer-events: ${this.isContentEditable ? "none" : "auto"} " variant=${this.variant} ?pill=${this.pill} ?outline=${this.outline} > <p>${this.name}</p> </sl-button> <div part="options" class="author-only"> <div class="item"> <p>Title</p> <sl-input size="small" .value=${this.name} @input=${(e) => (this.name = e.target.value)} ></sl-input> </div> <div class="item"> <p>Size</p> <sl-select size="small" id="sizeSelect" @sl-change=${this.handleSizeChange} > <sl-option value="small">Small</sl-option> <sl-option value="medium">Medium</sl-option> <sl-option value="large">Large</sl-option> </sl-select> </div> <div class="item"> <p>Variant</p> <sl-select size="small" id="variantSelect" @sl-change=${this.handleVariantChange} > <sl-option value="default">Default</sl-option> <sl-option value="text">Text</sl-option> <sl-option value="primary">Primary</sl-option> <sl-option value="success">Success</sl-option> <sl-option value="neutral">Neutral</sl-option> <sl-option value="warning">Warning</sl-option> <sl-option value="danger">Danger</sl-option> </sl-select> </div> <div style="display: flex; gap: 5px; align-items: center; justify-content: flex-start;" > <p style="margin-right: auto;">Pill</p> <sl-checkbox id="pillCheckbox" @sl-change=${this.handlePillChange}> </sl-checkbox> </div> <div style="display: flex; gap: 5px; align-items: center; justify-content: flex-start;" > <p style="margin-right: auto;">Outline</p> <sl-checkbox id="outlineCheckbox" @sl-change=${this.handleOutlineChange} > </sl-checkbox> </div> <div class="item"> <div style="display: flex; gap: 5px; align-items: center; justify-content: flex-start; padding: 0px; margin: 0px;" > <p style="margin-right: auto;">Width</p> <p style="font-weight: 300;">${this.width}%</p> </div> <sl-range size="small" id="widthRange" min="10" max="100" tooltip="none" value=${this.width} @input=${this.handleWidthChange} style="--thumb-size: 17px;" ></sl-range> </div> <div style="display: flex; gap: 5px; align-items: center; justify-content: flex-start; padding: 0px; margin: 0px; flex-direction: column" > <p style="margin-right: auto;">Alignment</p> <sl-button-group style="width: 100%"> <sl-button class=${this.alignment === "flex-start" ? "active" : ""} @click=${() => this.handleAlignmentChange("flex-start")} > <sl-icon src=${alignLeft}></sl-icon> </sl-button> <sl-button class=${this.alignment === "center" ? "active" : ""} @click=${() => this.handleAlignmentChange("center")} > <sl-icon src=${alignCenter}></sl-icon> </sl-button> <sl-button class=${this.alignment === "flex-end" ? "active" : ""} @click=${() => this.handleAlignmentChange("flex-end")} > <sl-icon src=${alignRight}></sl-icon> </sl-button> </sl-button-group> </div> </div> </div> `; } /* */ private parseConnectionIdentifier(identifier) { const parts = identifier.split("-"); const parsed = { outputNodeId: parseInt(parts[0]), outputClass: parts[1], inputNodeId: parseInt(parts[2]), inputClass: parts[3], }; return parsed; } }