@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.
198 lines (168 loc) • 6.11 kB
text/typescript
import { html, css, LitElement, PropertyValues } from "lit";
import { customElement, property, state, query } from "lit/decorators.js";
import { repeat } from "lit/directives/repeat.js";
import "@shoelace-style/shoelace/dist/themes/light.css";
import {
SlSelect,
SlOption,
SlDivider,
SlIcon,
SlInput,
} from "@shoelace-style/shoelace";
import Drawflow, { DrawflowNode } from "drawflow";
import number123 from "@tabler/icons/outline/number-123.svg";
import checkbox from "@tabler/icons/outline/checkbox.svg";
import blockquote from "@tabler/icons/outline/blockquote.svg";
import highlight from "@tabler/icons/outline/highlight.svg";
import microphone from "@tabler/icons/outline/microphone.svg";
import file from "@tabler/icons/outline/file.svg";
import squares from "@tabler/icons/outline/squares.svg";
const NO_NODE_SELECTED: DrawflowNode = {
id: -1,
name: "unselect",
inputs: {},
outputs: {},
pos_x: 0,
pos_y: 0,
class: "unselect",
data: {},
html: "",
typenode: false,
};
("webwriter-quiz-tasks-select")
export class WebWriterQuizTasksSelect extends LitElement {
({ type: Object }) accessor quiz;
({ type: Object }) accessor options = [];
({ type: String }) accessor value;
() accessor searchTerm = "";
("sl-input") accessor searchElement!: SlInput;
("sl-select") accessor selectElement!: SlSelect;
static styles = css`
.nodeSelect {
width: 100%;
}
sl-select::part(listbox) {
width: 250px;
height: 250px;
}
sl-select::part(tags) {
width: 80px;
}
sl-select::part(tag) {
max-width: 40%; /* Adjust the width to your needs */
white-space: nowrap; /* Ensure text is in one line */
overflow: hidden; /* Hide overflowing text */
text-overflow: ellipsis; /* Add "..." if text overflows */
}
sl-select.two-tags::part(tag) {
display: inline-flex; /* Keep the layout as inline flex */
max-width: 30px; /* Control the minimum width */
white-space: nowrap; /* Ensure the text stays in one line */
overflow: hidden; /* Hide overflowing content */
text-overflow: ellipsis; /* Add ellipsis for truncated content */
box-sizing: border-box; /* Include padding/border in width calculation */
}
.node-option-visible {
display: block;
}
.node-option-hidden {
display: none;
}
.icon-header {
display: flex;
align-items: center;
gap: 7px;
}
`;
/*
*/
// Move the option gathering logic to firstUpdated lifecycle method
firstUpdated() {
const containersSlot = this.quiz?.shadowRoot.querySelector("slot");
const assignedElements = containersSlot.assignedElements();
// Filter nodes with class "ww-widget" and add them to this.options
const tasks = assignedElements.filter((el) =>
el.tagName.toLowerCase().includes("webwriter-task")
);
let taskOptions = [];
tasks.forEach((task) => {
const taskContent = task.children;
const prompt = taskContent[0];
const taskElement = taskContent[1];
//(TODO: after Thesis) - Add Support for Speech Quiz Task
if (!taskElement.tagName.toLowerCase().includes("speech")) {
taskOptions = [
...taskOptions,
{ task: task, prompt: prompt, taskElement: taskElement },
];
}
});
this.options = [...this.options, ...taskOptions];
}
protected updated(_changedProperties: PropertyValues): void {
const partTags =
this.selectElement.shadowRoot?.querySelector('div[part="tags"]');
if (partTags?.childElementCount >= 2) {
this.selectElement.classList.add("two-tags");
} else {
this.selectElement.classList.remove("two-tags");
}
}
render() {
return html` <sl-select
placement="bottom"
hoist
class="nodeSelect"
placeholder="Select Tasks"
.value=${this.value}
@sl-input=${this._handleElementSelect}
multiple
max-options-visible="1"
>
<small class="icon-header" id="divider-page"
>Select task(s) from the quiz</small
>
${this.options.length === 0
? html`<small class="message">No tasks found in the quiz. </small>`
: html` ${repeat(
this.options,
(element) => element.id,
(element, index) => html`
<sl-option value=${`${element.task.id}`}>
${`${index + 1}. ${element.taskElement.tagName
.replace("WEBWRITER-", "")
.toLowerCase()
.replace(/^./, (str) => str.toUpperCase())}`}
${element.taskElement.tagName.toLowerCase().includes("order")
? html` <sl-icon slot="prefix" src=${number123}></sl-icon>`
: element.taskElement.tagName.toLowerCase().includes("choice")
? html`<sl-icon slot="prefix" src=${checkbox}></sl-icon>`
: element.taskElement.tagName.toLowerCase().includes("text")
? html`<sl-icon slot="prefix" src=${blockquote}></sl-icon>`
: element.taskElement.tagName.toLowerCase().includes("mark")
? html`<sl-icon slot="prefix" src=${highlight}></sl-icon>`
: element.taskElement.tagName.toLowerCase().includes("speech")
? html`<sl-icon slot="prefix" src=${microphone}></sl-icon>`
: null}
<p
slot="suffix"
style="color: lightgray; margin: 0px; padding: 4px;"
>
(${element.prompt.textContent.substring(0, 10) + "..."})
</p>
</sl-option>
`
)}`}
</sl-select>`;
}
private _handleElementSelect(event: Event) {
if (
event.target instanceof HTMLElement &&
event.target.tagName.toLowerCase() === "sl-select"
) {
const selectedValue = (event.target as SlSelect).value;
// If it's not an array, just assign it as-is
this.value = selectedValue;
}
}
}