UNPKG

@webwriter/timeline

Version:

Create/learn with a digital timeline and test your knowledge.

474 lines (436 loc) 15.2 kB
import { html, PropertyValues, css } from "lit"; import { LitElementWw } from "@webwriter/lit"; import { customElement, property, query } from "lit/decorators.js"; import "@shoelace-style/shoelace/dist/themes/light.css"; import { SlButton } from "@shoelace-style/shoelace"; import { TlEventData, TlEventHelper} from "../tl-event-data"; import { QuizElementDate } from "./q-element-date" import { QuizTitles } from "./q-titles"; import { QuizDateField } from "./q-date-field"; import { QuizElementTitle } from "./q-element-title"; import { HelpOverlay, HelpPopup } from "@webwriter/wui/dist/helpSystem/helpSystem.js"; @customElement("main-quiz") export class MainQuiz extends LitElementWw { @property({ type: Number, attribute: true, reflect: true }) accessor tabIndex = -1; @property({ type: Array }) accessor event_startDate: TlEventData["startDate"]; @property({ type: Array }) accessor event_endDate: TlEventData["endDate"]; @property({ type: Array, attribute: true, reflect: true }) accessor appendedEvents: Array<{ date: String; title: string }> = []; @property({ type: Number, attribute: true, reflect: true }) accessor matchCount = 0; @property({ type: Number, attribute: true, reflect: true }) accessor score; @property({ type: Number, attribute: true, reflect: true }) accessor selectedOption; @property({ type: Object, attribute: true, reflect: false }) accessor drag; @property({ type: Object, attribute: true, reflect: false }) accessor source; @property({ type: Object, attribute: true, reflect: false }) accessor target; @property({ type: Boolean, attribute: true, reflect: false }) accessor activateCheck; @query("#date-container") accessor date_container: QuizDateField; @query("#title-container") accessor title_container: QuizTitles; @query("#score-feedback") accessor score_feedback: HTMLParagraphElement; static get styles() { return css` /* :host(:not([contenteditable="true"]):not([contenteditable=""])) .author-only { display: none; } */ :host { display: block; height: 100%; overflow: hidden; } .border { display: flex; flex-direction: column; height: 100%; max-height: 700px; width: 100%; box-sizing: border-box; } .quiz-static-top { padding-left: 20px; padding-right: 20px; background-color: white; padding-bottom: 25px; height: 15%; max-height: 15%; position: sticky; top: 0; display: flex; flex-direction: column; overflow-y: hidden; box-sizing: border-box; z-index: 2; min-height: 210px; } .quiz-container { font-weight: 500; padding-left: 20px; padding-right: 20px; flex-grow: 1; overflow-y: auto; z-index: 1; max-height: 50%; position: relative; display: flex; flex-direction: column; min-height: 200px; } .quiz-header { align-items: center; display: grid; width: 100%; padding-bottom: 15px; justify-content: space-between; } .quiz-description { padding-top: 20px; grid-column: 1; grid-row: 1; margin-top: 0px; font-size: 18px; font-weight: 500; color: #484848; } .button-container { grid-column: 2; grid-row: 1; display: grid; } #reset-button { grid-column: 1; grid-row: 1; padding-right: 15px; width: max-content; } #check-match-button { grid-column: 2; grid-row: 1; width: max-content; } #title-container { height: 100% display_ flex; flex-direction: column; overflow-y: scroll; } #score-feedback{ font-size: 18px; font-weight: 500; color: rgb(72, 72, 72); } `; } static get scopedElements() { return { "sl-button": SlButton, "quiz-title": QuizTitles, "quiz-date-field": QuizDateField, "quiz-element-date": QuizElementDate, "quiz-element-title": QuizElementTitle, "webwriter-helpoverlay": HelpOverlay, "webwriter-helppopup": HelpPopup, }; } protected firstUpdated(_changedProperties: PropertyValues): void {} connectedCallback() { super.connectedCallback(); this.addEventListener("title-dropped-in-section", (e) => this.titleDropped(e) ); this.addEventListener("title-dropped-in-titles", (e) => this.titleDropped(e) ); this.addEventListener("drag-start-title", (e) => { this.titleDragStart(e) }); } // start dragging title, if match has already been tried, remove match feedback attributes titleDragStart(e){ this.drag = (e as CustomEvent).detail.title; this.source = (e as CustomEvent).detail.parent; if(this.score !== undefined){ this.score = undefined; this.matchCount = 0; const date_elements = Array.from(this.date_container.children); date_elements.forEach((element) => { if (element.shadowRoot) { const dropSection = element.shadowRoot.querySelector(".drop-section"); if (dropSection) { dropSection.removeAttribute("quiz-result"); } const titleElement = element.querySelector("quiz-element-title"); if (titleElement) { if (titleElement.shadowRoot) { const titleDiv = titleElement.shadowRoot.querySelector("div"); if (titleDiv) { titleDiv.removeAttribute("mismatch"); titleDiv.removeAttribute("match"); } } } } }); } } // if title dropped, look for target and source to see where its dropped to, emit css changes, enable quiz checking titleDropped(e) { this.target = (e as CustomEvent).detail.target; if ( (this.target as HTMLElement).tagName.toLowerCase() === "quiz-element-date" && this.target.childElementCount < 1 ) { this.target.appendChild(this.drag); this.activateCheck = true; } else if ( (this.target as HTMLElement).tagName.toLowerCase() === "quiz-title" ) { this.target.appendChild(this.drag); } if ( (this.source as HTMLElement).tagName.toLocaleLowerCase() === "quiz-element-date" ) { const date_parent = this.source.shadowRoot.querySelector("#date-drop-section"); date_parent.querySelector(".drop-section").removeAttribute("dropped"); } this.drag.shadowRoot .querySelector(".title-border") .classList.remove("dragging"); // activate submit button const date_elements = Array.from(this.date_container.children); let isActivated = false; for (const element of date_elements) { const date_el_appended_titles = ( element.shadowRoot.querySelector("slot") as HTMLSlotElement ).assignedElements({ flatten: true }); if (date_el_appended_titles.length > 0) { isActivated = true; break; } } this.activateCheck = isActivated; this.requestUpdate(); } render() { return html` <!-- help overlay not working --> <!-- <webwriter-helpoverlay> <webwriter-helppopup slot="popupContainer" target="reset-button" > <div slot="content"> <h4>Quiz Reset</h4> <p>Reset the quiz answers.</p> </div> </webwriter-helppopup> <webwriter-helppopup slot="popupContainer" target="check-match-button" > <div slot="content"> <h4>Quiz Submit</h4> <p>Click me to submit the created timeline and get the selected feedback.</p> </div> </webwriter-helppopup> <webwriter-helppopup slot="popupContainer" target="title-container" > <div slot="content"> <h4>Quiz Titles</h4> <p>Guess which title fits to which timeline event and drag the title to slot. I am scrollable.</p> </div> </webwriter-helppopup> <webwriter-helppopup slot="popupContainer" target="date-container" > <div slot="content"> <h4>Quiz Dates</h4> <p>Guess which title fits to which timeline event and drop the title to here. I am scrollable.</p> </div> </webwriter-helppopup> </webwriter-helpoverlay> --> <div class="border" id="parent"> <div class="quiz-static-top"> <div class="quiz-header"> <p class="quiz-description"> Drag the correct title to the drop section </p> <div class="button-container"> <sl-button id="reset-button" variant="neutral" outline @click="${this.resetQuiz}" >Reset </sl-button> <sl-button id="check-match-button" variant="primary" outline ?disabled="${!this.activateCheck}" @click="${() => { this.checkMatchAndCalculate(); }}" >Submit </sl-button> </div> </div> <quiz-title id="title-container"></quiz-title> ${(this.selectedOption === 1 || this.selectedOption === 3) && this.score !== undefined ? html`<p id="score-feedback">Your Score: ${this.score + " %"}</p>` : ""} </div> <div class="quiz-container"> <quiz-date-field id="date-container"></quiz-date-field> </div> </div> `; } // get selected feedback option findSelection(selected) { this.selectedOption = selected; } // append new events to quiz, save appended events to array startQuiz(events) { const existingEvents = this.appendedEvents; const formatDate = (startDate, endDate) => { return endDate ? `${startDate} - ${endDate}` : startDate; }; const eventsToAppend = events.filter((event) => { const title = event.getAttribute("event_title"); const startdate = event.getAttribute("event_startdate"); const enddate = event.getAttribute("event_enddate"); const date = formatDate(startdate, enddate); const isNewEvent = !existingEvents.some( (existingEvent) => existingEvent.title === title || existingEvent.date === date ); return isNewEvent; }); eventsToAppend.forEach((event) => { const title = event.getAttribute("event_title"); const startDate = event.getAttribute("event_startdate"); const endDate = event.getAttribute("event_enddate"); const start_parsedArray = JSON.parse(startDate); const display_startdate = TlEventHelper.displayDate(start_parsedArray); var date = display_startdate; if (endDate) { const end_parsedArray = JSON.parse(endDate); const display_enddate = TlEventHelper.displayDate(end_parsedArray); date = formatDate(display_startdate, display_enddate); } this.initializeDate(date); this.initializeTitle(title); this.appendedEvents.push({ date, title }); }); } // reset quiz to restart resetQuiz() { const quizElementsDate = this.date_container.querySelectorAll("quiz-element-date"); const quizElementsTitle = this.date_container.querySelectorAll("quiz-element-date"); this.date_container.innerHTML = ""; this.title_container.innerHTML = ""; this.appendedEvents.forEach((event) => { this.initializeTitle(event.title); this.initializeDate(event.date); }); this.matchCount = 0; this.score = undefined; this.drag = ""; this.source = ""; this.target = ""; } // set up date container + timeline + drop sections initializeDate(date) { const quizElemenDate = document.createElement( "quiz-element-date" ) as QuizElementDate; quizElemenDate.date = date; const date_attacher = this.date_container.shadowRoot.querySelector("#date"); quizElemenDate.setAttribute("slot", "quiz-element-date"); this.date_container.appendChild(quizElemenDate); } // set up titles initializeTitle(title) { const quizElemenTitle = document.createElement( "quiz-element-title" ) as QuizElementTitle; quizElemenTitle.title = title; this.title_container.appendChild(quizElemenTitle); this.title_container.randomiseTitleOrder(); } // check for a title-date match andgive feedback checkMatchAndCalculate() { this.matchCount = 0; this.score = 0; if (this.selectedOption === undefined || this.selectedOption === 0) { this.dispatchEvent( new CustomEvent("show-quiz-feedback-error", { bubbles: true, composed: true, }) ); return; } const date_elements = Array.from(this.date_container.children); date_elements.forEach((element) => { const date_el_appended_title = ( element.shadowRoot.querySelector("slot") as HTMLSlotElement ).assignedElements({ flatten: true }); if (date_el_appended_title.length === 1) { const title = (date_el_appended_title[0] as QuizElementTitle).title; const date = element.shadowRoot.querySelector(".event-date").textContent; const matchFound = this.appendedEvents.find( (event) => `${event.title}` === title && event.date === date ); if (matchFound && this.selectedOption === 1) { element.shadowRoot .querySelector(".drop-section") .setAttribute("quiz-result", "match"); date_el_appended_title[0].shadowRoot .querySelector(".title-border") .setAttribute("match", "true"); this.matchCount++; } if (matchFound && this.selectedOption === 2) { element.shadowRoot .querySelector(".drop-section") .setAttribute("quiz-result", "match"); date_el_appended_title[0].shadowRoot .querySelector(".title-border") .setAttribute("match", "true"); } if (matchFound && this.selectedOption === 3) { this.matchCount++; } if (matchFound && this.selectedOption === 4) { this.matchCount = 0; } if ( matchFound === undefined && (this.selectedOption === 1 || this.selectedOption === 2) ) { element.shadowRoot .querySelector(".drop-section") .setAttribute("quiz-result", "mismatch"); date_el_appended_title[0].shadowRoot .querySelector(".title-border") .setAttribute("mismatch", "true"); } } }); if (this.selectedOption === 1 || this.selectedOption === 3) { const achievedPoints = this.matchCount; const possiblePoints = this.appendedEvents.length; this.score = parseFloat( ((achievedPoints / possiblePoints) * 100).toFixed(2) ); } } }