@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
text/typescript
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";
("webwriter-gamebook-button")
export class WebWriterGamebookButton extends LitElementWw {
({ type: Number, reflect: true }) accessor dataTargetId: number;
({ type: String, attribute: true, reflect: true })
accessor identifier: string;
({ attribute: true }) accessor getNodeEditor = () => {};
//visual properties
({ type: String, reflect: true }) accessor name: string = "Button";
({ type: String, reflect: true }) accessor size: string = "small";
({ type: Boolean, reflect: true }) accessor pill: boolean = false;
({ type: Boolean, reflect: true }) accessor outline: boolean = false;
({ type: Number, reflect: true }) accessor width: number = 50;
({ type: String, reflect: true }) accessor alignment: string =
"center";
({ type: String, reflect: true }) accessor variant: string =
"default";
({ type: Number, attribute: true, reflect: true })
accessor tabIndex = -1;
("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;
}
}