diff --git a/elements/a11y-media-player/a11y-media-player.js b/elements/a11y-media-player/a11y-media-player.js index 61283cd2d1..77a43adc3b 100644 --- a/elements/a11y-media-player/a11y-media-player.js +++ b/elements/a11y-media-player/a11y-media-player.js @@ -6,6 +6,7 @@ import { LitElement, html, css } from "lit"; import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; import { FullscreenBehaviors } from "@haxtheweb/fullscreen-behaviors/fullscreen-behaviors.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "@haxtheweb/anchor-behaviors/anchor-behaviors.js"; import "@haxtheweb/responsive-utility/responsive-utility.js"; import { normalizeEventPath } from "@haxtheweb/utils/lib/events.js"; @@ -105,7 +106,7 @@ import "./lib/a11y-media-youtube.js"; * @demo ./demo/audio.html audio demo * @demo ./demo/youtube.html YouTube demo */ -class A11yMediaPlayer extends I18NMixin(FullscreenBehaviors(DDD)) { +class A11yMediaPlayer extends SchemaBehaviors(I18NMixin(FullscreenBehaviors(DDD))) { //styles function static get styles() { return [ @@ -931,7 +932,9 @@ class A11yMediaPlayer extends I18NMixin(FullscreenBehaviors(DDD)) { // render function render() { - return html`
+ return html` + +
${this.mediaCaption}
@@ -3526,6 +3529,7 @@ class A11yMediaPlayer extends I18NMixin(FullscreenBehaviors(DDD)) { if (super.firstUpdated) { super.firstUpdated(changedProperties); } + this.setAttribute("typeof", "oer:MediaObject"); this.style.setProperty( "--a11y-media-transcript-max-height", this.height ? "146px" : "unset", diff --git a/elements/ai-usage-license/ai-usage-license.js b/elements/ai-usage-license/ai-usage-license.js index cb67690879..2019874b69 100644 --- a/elements/ai-usage-license/ai-usage-license.js +++ b/elements/ai-usage-license/ai-usage-license.js @@ -5,6 +5,7 @@ import { LitElement, html, css } from "lit"; import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; /** * `ai-usage-license` @@ -13,7 +14,7 @@ import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; * @demo index.html * @element ai-usage-license */ -class AiUsageLicense extends I18NMixin(DDDSuper(LitElement)) { +class AiUsageLicense extends SchemaBehaviors(I18NMixin(DDDSuper(LitElement))) { /** * LitElement constructable styles enhancement */ @@ -68,8 +69,16 @@ class AiUsageLicense extends I18NMixin(DDDSuper(LitElement)) { ] } + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.setAttribute("typeof", "oer:SupportingMaterial"); + } + render() { return html` +
${this.licenseImage ? html` @@ -159,6 +168,16 @@ class AiUsageLicense extends I18NMixin(DDDSuper(LitElement)) { type: String, attribute: "license-tag", }, + /** + * OER Schema: URI for the AI Usage License (AIUL) reference page/code. + * Falls back to licenseLink when not explicitly set. + * NOTE: The v1.2.0 aiUsageConstraint property (domain [Task]) is not + * emitted here because this element is a SupportingMaterial badge, + * not a Task. aiUsageConstraint resolves once the vocab is integrated. + */ + uri: { + type: String, + }, } } @@ -171,6 +190,7 @@ class AiUsageLicense extends I18NMixin(DDDSuper(LitElement)) { this.licenseLink = null; this.licenseDescription = null; this.licenseTag = null; + this.uri = null; this.__aiulDataPromise = null; this._setAIULData(); } @@ -217,6 +237,9 @@ class AiUsageLicense extends I18NMixin(DDDSuper(LitElement)) { } updated(changedProperties) { + if (super.updated) { + super.updated(changedProperties); + } changedProperties.forEach((oldValue, propName) => { if (propName === "license" || propName === "modifier") { this._licenseUpdated(this.license, this.modifier); @@ -290,6 +313,13 @@ class AiUsageLicense extends I18NMixin(DDDSuper(LitElement)) { }, icon: "image:photo", }, + { + property: "uri", + title: "AIUL URI (OER Schema)", + description: + "OER Schema: URI for the AI Usage License reference page. Auto-populated from the license link when not set.", + inputMethod: "textfield", + }, ], advanced: [], }, diff --git a/elements/bibliography-builder/bibliography-builder.js b/elements/bibliography-builder/bibliography-builder.js index ca59bc499d..8209d83e22 100644 --- a/elements/bibliography-builder/bibliography-builder.js +++ b/elements/bibliography-builder/bibliography-builder.js @@ -3,6 +3,7 @@ * @license Apache-2.0, see LICENSE for full text. */ import { LitElement, html, css, render } from "lit"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; import "@haxtheweb/simple-modal/simple-modal.js"; @@ -17,7 +18,9 @@ import "./lib/bibliography-item.js" * @demo index.html * @element bibliography-builder */ -export class BibliographyBuilder extends DDDSuper(I18NMixin(LitElement)) { +export class BibliographyBuilder extends SchemaBehaviors( + DDDSuper(I18NMixin(LitElement)), +) { static get tag() { return "bibliography-builder"; @@ -26,6 +29,7 @@ export class BibliographyBuilder extends DDDSuper(I18NMixin(LitElement)) { constructor() { super(); this.title = ""; + this.forCourse = ""; this.t = this.t || {}; this.t = { ...this.t, @@ -49,7 +53,8 @@ export class BibliographyBuilder extends DDDSuper(I18NMixin(LitElement)) { ...super.properties, title: { type: String }, exportMode: { type: String, attribute: "export-mode" }, - citationArr: { type: Array, attribute: "citation-array" } + citationArr: { type: Array, attribute: "citation-array" }, + forCourse: { type: String, attribute: "for-course" } }; } @@ -86,7 +91,8 @@ export class BibliographyBuilder extends DDDSuper(I18NMixin(LitElement)) { // Lit render the HTML render() { return html` -
+
+

${this.t.title}: ${this.title}

diff --git a/elements/bibliography-builder/lib/bibliography-builder.haxProperties.json b/elements/bibliography-builder/lib/bibliography-builder.haxProperties.json index a878d3c260..bb0f66fc6b 100644 --- a/elements/bibliography-builder/lib/bibliography-builder.haxProperties.json +++ b/elements/bibliography-builder/lib/bibliography-builder.haxProperties.json @@ -25,6 +25,13 @@ "title": "Citations", "description": "Citation items in this bibliography", "inputMethod": "code-editor" + }, + { + "property": "forCourse", + "title": "Course", + "description": "Identifier or name of the Course this bibliography is for (OER Schema forCourse).", + "inputMethod": "textfield", + "icon": "link" } ] }, diff --git a/elements/bibliography-builder/lib/bibliography-item.js b/elements/bibliography-builder/lib/bibliography-item.js index 0c1895d751..edbea60c97 100644 --- a/elements/bibliography-builder/lib/bibliography-item.js +++ b/elements/bibliography-builder/lib/bibliography-item.js @@ -3,6 +3,7 @@ * @license Apache-2.0, see LICENSE for full text. */ import { LitElement, html, css, render } from "lit"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; import "@haxtheweb/simple-icon/lib/simple-icons.js"; @@ -15,7 +16,9 @@ import "@haxtheweb/hax-iconset/lib/simple-hax-iconset.js"; * @demo index.html * @element bibliography-item */ -export class BibliographyItem extends DDDSuper(I18NMixin(LitElement)) { +export class BibliographyItem extends SchemaBehaviors( + DDDSuper(I18NMixin(LitElement)), +) { static get tag() { return "bibliography-item"; @@ -89,7 +92,8 @@ export class BibliographyItem extends DDDSuper(I18NMixin(LitElement)) { // Lit render the HTML render() { return html` -
+
+
${this.exportAPA()}
${this.publisher}, ${this._formatSource().getJournalSource("APA")}. ${this.url}`; + return html`${this._formatAuthors()} ${this._formatDate().getPubYear()}. ${this.title}. ${this.publisher}, ${this._formatSource().getJournalSource("APA")}. ${this.url}`; case "book": - return html`${this._formatAuthors()} ${this._formatDate().getPubYear()}. ${this.title} ${this._formatSource().getBookSource("APA")}. ${this.publisher}. ${this.url}` + return html`${this._formatAuthors()} ${this._formatDate().getPubYear()}. ${this.title} ${this._formatSource().getBookSource("APA")}. ${this.publisher}. ${this.url}` case "web": default: - return html`${this._formatAuthors()} ${this._formatDate().getFullPubDate()}. ${this.title}. ${this.publisher}. ${this.url}` + return html`${this._formatAuthors()} ${this._formatDate().getFullPubDate()}. ${this.title}. ${this.publisher}. ${this.url}` } } diff --git a/elements/career-timeline/career-timeline.js b/elements/career-timeline/career-timeline.js index 34595f8a4e..9d2ef3b252 100644 --- a/elements/career-timeline/career-timeline.js +++ b/elements/career-timeline/career-timeline.js @@ -5,6 +5,7 @@ import { LitElement, html, css } from "lit"; import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "./lib/career-org-item.js"; /** @@ -13,7 +14,7 @@ import "./lib/career-org-item.js"; * @demo index.html * @element career-timeline */ -export class CareerTimeline extends DDDSuper(I18NMixin(LitElement)) { +export class CareerTimeline extends SchemaBehaviors(DDDSuper(I18NMixin(LitElement))) { static get tag() { return "career-timeline"; @@ -59,9 +60,17 @@ export class CareerTimeline extends DDDSuper(I18NMixin(LitElement)) { `]; } + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.setAttribute("typeof", "oer:LearningComponent"); + } + // Lit render the HTML render() { return html` +
diff --git a/elements/career-timeline/lib/career-timeline.haxProperties.json b/elements/career-timeline/lib/career-timeline.haxProperties.json index 68c5027da9..1f8659def1 100644 --- a/elements/career-timeline/lib/career-timeline.haxProperties.json +++ b/elements/career-timeline/lib/career-timeline.haxProperties.json @@ -24,7 +24,14 @@ } }, "settings": { - "configure": [] + "configure": [ + { + "property": "title", + "title": "Timeline Title", + "description": "A title for the career timeline (OER Schema: oer:name).", + "inputMethod": "textfield" + } + ] }, "saveOptions": { "wipeSlot": false, diff --git a/elements/citation-element/citation-element.js b/elements/citation-element/citation-element.js index 8a3df451b7..f069c5840c 100644 --- a/elements/citation-element/citation-element.js +++ b/elements/citation-element/citation-element.js @@ -60,9 +60,10 @@ class CitationElement extends SchemaBehaviors(DDDSuper(LitElement)) { + ${this.title} by @@ -111,6 +114,7 @@ class CitationElement extends SchemaBehaviors(DDDSuper(LitElement)) { super(); this.scope = "sibling"; this.source = ""; + this.typeof = "oer:ReferencedMaterial"; } /** * LitElement properties changed @@ -199,6 +203,13 @@ class CitationElement extends SchemaBehaviors(DDDSuper(LitElement)) { license: { type: String, }, + /** + * OER Schema class for the cited work (default oer:ReferencedMaterial). + * Authors can override via the HAX editor to use a more specific OER class. + */ + typeof: { + type: String, + }, }; } /** @@ -360,6 +371,14 @@ class CitationElement extends SchemaBehaviors(DDDSuper(LitElement)) { inputMethod: "textfield", icon: "link", }, + { + property: "typeof", + title: "OER Schema type", + description: + "OER Schema class for this citation. Defaults to oer:ReferencedMaterial; override only with a valid OER class (e.g. oer:SupportingMaterial).", + inputMethod: "textfield", + icon: "code", + }, ], advanced: [], }, diff --git a/elements/course-design/course-design.js b/elements/course-design/course-design.js index 44a557c8b6..36e182dfbf 100644 --- a/elements/course-design/course-design.js +++ b/elements/course-design/course-design.js @@ -3,6 +3,7 @@ * @license Apache-2.0, see License.md for full text. */ import { LitElement, html, css } from "lit"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; /** * `course-design` @@ -10,7 +11,7 @@ import { LitElement, html, css } from "lit"; * @demo demo/index.html * @element course-design */ -class CourseDesign extends LitElement { +class CourseDesign extends SchemaBehaviors(LitElement) { //styles function static get styles() { return [ @@ -28,12 +29,94 @@ class CourseDesign extends LitElement { // Template return function render() { - return html` `; + return html` + ${this.courseIdentifier + ? html`` + : ``} + ${this.primaryInstructor + ? html`` + : ``} + ${this.termOffered + ? html`` + : ``} + ${this.syllabus + ? html`` + : ``} + ${this.deliveryFormat + ? html`` + : ``} + `; + } + + static get haxProperties() { + return { + canScale: true, + canEditSource: true, + gizmo: { + title: "Course Design", + description: + "Container for educational materials and course design elements.", + icon: "icons:school", + color: "blue", + tags: ["Instructional", "course", "design", "container"], + handles: [], + meta: { + author: "HAXTheWeb core team", + }, + }, + settings: { + configure: [ + { + property: "courseIdentifier", + title: "Course Identifier", + description: + "OER Schema: the identifier of the course, e.g. MATH-100.", + inputMethod: "textfield", + }, + { + property: "termOffered", + title: "Term Offered", + description: + "OER Schema: the term during which the course is offered.", + inputMethod: "textfield", + }, + { + property: "deliveryFormat", + title: "Delivery Format", + description: + "OER Schema: the format used to deliver the course (e.g. in-person, online, hybrid).", + inputMethod: "textfield", + }, + ], + advanced: [ + { + property: "primaryInstructor", + title: "Primary Instructor", + description: + "OER Schema: the primary instructor for the course.", + inputMethod: "textfield", + }, + { + property: "syllabus", + title: "Syllabus URL", + description: "OER Schema: URL to the course syllabus.", + inputMethod: "textfield", + }, + ], + }, + }; } // properties available to the custom element for data binding static get properties() { - return { ...super.properties }; + return { + ...super.properties, + courseIdentifier: { type: String, attribute: "course-identifier" }, + primaryInstructor: { type: String, attribute: "primary-instructor" }, + termOffered: { type: String, attribute: "term-offered" }, + syllabus: { type: String }, + deliveryFormat: { type: String, attribute: "delivery-format" }, + }; } /** @@ -48,15 +131,28 @@ class CourseDesign extends LitElement { */ constructor() { super(); + this.courseIdentifier = ""; + this.primaryInstructor = ""; + this.termOffered = ""; + this.syllabus = ""; + this.deliveryFormat = ""; } /** * LitElement ready */ - firstUpdated(changedProperties) {} + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.setAttribute("typeof", "oer:Course"); + } /** * LitElement life cycle - property changed */ updated(changedProperties) { + if (super.updated) { + super.updated(changedProperties); + } changedProperties.forEach((oldValue, propName) => { /* notify example // notify diff --git a/elements/course-model/course-model.js b/elements/course-model/course-model.js index 5e45b3a09d..e4b13c5c8f 100644 --- a/elements/course-model/course-model.js +++ b/elements/course-model/course-model.js @@ -3,6 +3,7 @@ * @license , see License.md for full text. */ import { LitElement, html, css } from "lit"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "./lib/model-option.js"; import "./lib/model-info.js"; /** @@ -11,7 +12,7 @@ import "./lib/model-info.js"; * @demo demo/index.html * @element course-model */ -class CourseModel extends LitElement { +class CourseModel extends SchemaBehaviors(LitElement) { static get tag() { return "course-model"; } @@ -25,6 +26,7 @@ class CourseModel extends LitElement { title: { type: String }, src: { type: String }, alt: { type: String }, + poster: { type: String }, }; } @@ -142,6 +144,7 @@ class CourseModel extends LitElement { this.visible = "model"; this.title = ""; this.src = ""; + this.poster = ""; if (globalThis.HaxStore) { globalThis.ModelViewerController = new AbortController(); globalThis.addEventListener( @@ -367,12 +370,14 @@ class CourseModel extends LitElement { if (super.firstUpdated) { super.firstUpdated(changedProperties); } + this.setAttribute("typeof", "oer:MediaObject"); // this is a heavy library in memory so import if we actually are rendering import("@google/model-viewer/dist/model-viewer.js"); } render() { return html` +
-

${this.title}

+

${this.title}

diff --git a/elements/editable-table/editable-table.js b/elements/editable-table/editable-table.js index 240856016b..4d9aeb6a8e 100644 --- a/elements/editable-table/editable-table.js +++ b/elements/editable-table/editable-table.js @@ -2,8 +2,9 @@ * Copyright 2018 The Pennsylvania State University * @license Apache-2.0, see License.md for full text. */ -import { html, css } from "lit"; +import { html, css, nothing } from "lit"; import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import { editBehaviors, editableTableStyles, @@ -78,7 +79,7 @@ Custom property | Description | Default * @extends ResponsiveUtilityBehaviors * @extends editableTableStyles */ -class EditableTable extends editBehaviors(DDD) { +class EditableTable extends editBehaviors(SchemaBehaviors(DDD)) { static get styles() { return [ css` @@ -95,6 +96,7 @@ class EditableTable extends editBehaviors(DDD) { } render() { return html` + ${this.rubricMode ? this.renderRubricSchema() : nothing} element receives typeof="oer:Rubric" via + * setAttribute in firstUpdated/updated. This hidden block emits the + * rubricType property, a RubricScale with hasLevel children for each + * performance-level column, a RubricCriterion for each body row, and a + * RubricLevel (levelOrdinal/levelPoints) for each criterion/level cell. + */ + renderRubricSchema() { + const headerRow = this.thead.length ? this.thead[0] : []; + const startCol = this.rowHeader ? 1 : 0; + return html` + + `; + } + static get tag() { return "editable-table"; } @@ -196,6 +261,8 @@ class EditableTable extends editBehaviors(DDD) { super(); //this.haxUIElement = true; this.editMode = false; + this.rubricMode = false; + this.rubricType = ""; } static get properties() { return { @@ -208,8 +275,47 @@ class EditableTable extends editBehaviors(DDD) { attribute: "edit-mode", reflect: true, }, + /** + * Opt-in: interpret this table as an OER Schema Rubric so that + * Rubric/RubricScale/RubricCriterion/RubricLevel metadata is emitted. + */ + rubricMode: { + type: Boolean, + attribute: "rubric-mode", + reflect: true, + }, + /** + * Rubric style (analytic, holistic, single-point, checklist) emitted + * as oer:rubricType when rubricMode is true. + */ + rubricType: { + type: String, + attribute: "rubric-type", + }, }; } + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + if (this.rubricMode) { + this.setAttribute("typeof", "oer:Rubric"); + } + } + updated(changedProperties) { + if (super.updated) { + super.updated(changedProperties); + } + changedProperties.forEach((oldValue, propName) => { + if (propName === "rubricMode") { + if (this.rubricMode) { + this.setAttribute("typeof", "oer:Rubric"); + } else { + this.removeAttribute("typeof"); + } + } + }); + } get display() { return ( diff --git a/elements/editable-table/lib/editable-table.haxProperties.json b/elements/editable-table/lib/editable-table.haxProperties.json index d15b0ba748..c81c58a0bd 100644 --- a/elements/editable-table/lib/editable-table.haxProperties.json +++ b/elements/editable-table/lib/editable-table.haxProperties.json @@ -68,6 +68,25 @@ "title": "Responsive", "description": "Adjust table layout on small screens.", "inputMethod": "boolean" + }, + { + "property": "rubricMode", + "title": "Rubric Mode", + "description": "Interpret this table as an OER Schema Rubric so that Rubric, RubricScale, RubricCriterion, and RubricLevel metadata is emitted.", + "inputMethod": "boolean" + }, + { + "property": "rubricType", + "title": "Rubric Type", + "description": "Rubric style emitted as oer:rubricType (only meaningful when Rubric Mode is on).", + "inputMethod": "select", + "options": { + "": "(none)", + "analytic": "Analytic", + "holistic": "Holistic", + "single-point": "Single Point", + "checklist": "Checklist" + } } ], "advanced": [ diff --git a/elements/fill-in-the-blanks/lib/fill-in-the-blanks.haxProperties.json b/elements/fill-in-the-blanks/lib/fill-in-the-blanks.haxProperties.json index 7928e0a6ea..ad2ca32733 100644 --- a/elements/fill-in-the-blanks/lib/fill-in-the-blanks.haxProperties.json +++ b/elements/fill-in-the-blanks/lib/fill-in-the-blanks.haxProperties.json @@ -34,6 +34,24 @@ "title": "Statement", "description": "Statement that will be converted into the mark the words interaction.", "inputMethod": "textarea" + }, + { + "property": "gradingFormat", + "title": "Grading format", + "description": "OER Schema: grading format for this assessment (e.g. points, letter, percent, completion).", + "inputMethod": "textfield" + }, + { + "property": "hasLearningObjective", + "title": "Learning objective", + "description": "OER Schema: learning objective associated with this assessment.", + "inputMethod": "textfield" + }, + { + "property": "forCourse", + "title": "Course", + "description": "OER Schema: course this assessment is intended for.", + "inputMethod": "textfield" } ], "advanced": [], diff --git a/elements/flash-card/flash-card.js b/elements/flash-card/flash-card.js index 79e62e0f43..2b435f8d1b 100644 --- a/elements/flash-card/flash-card.js +++ b/elements/flash-card/flash-card.js @@ -1,11 +1,12 @@ import { html, css } from "lit"; import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js"; import "@haxtheweb/simple-toast/simple-toast.js"; import "./lib/flash-card-answer-box.js"; import "./lib/flash-card-prompt-img.js"; -export class FlashCard extends DDD { +export class FlashCard extends SchemaBehaviors(DDD) { static get tag() { return "flash-card"; } @@ -15,6 +16,8 @@ export class FlashCard extends DDD { super(); this.imgKeyword = ""; this.imgSource = ""; + this.typeOfAction = ""; + this.hasLearningObjective = ""; } // properties that you wish to use as data in HTML, CSS, and the updated life-cycle @@ -25,6 +28,11 @@ export class FlashCard extends DDD { imgSource: { type: String, attribute: "img-source", reflect: true }, imgKeyword: { type: String, attribute: "img-keyword" }, status: { type: String, reflect: true }, + typeOfAction: { type: String, attribute: "type-of-action" }, + hasLearningObjective: { + type: String, + attribute: "has-learning-objective", + }, }; } @@ -195,9 +203,21 @@ export class FlashCard extends DDD { ); } + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.setAttribute("typeof", "oer:Practice"); + } + // HTML - specific to Lit render() { return html` + + ${!this.imgSource && !this.imgKeyword ? `` diff --git a/elements/flash-card/lib/flash-card.haxProperties.json b/elements/flash-card/lib/flash-card.haxProperties.json index fada476c62..53f5a9fc73 100644 --- a/elements/flash-card/lib/flash-card.haxProperties.json +++ b/elements/flash-card/lib/flash-card.haxProperties.json @@ -29,6 +29,18 @@ "description": "Image file used for the card. Overrides Image Keyword.", "inputMethod": "haxupload", "noVoiceRecord": true + }, + { + "property": "typeOfAction", + "title": "Type of Action", + "description": "OER Schema: the type of action this practice involves (e.g. Reading, Writing, Making, Researching, Listening, Watching, Reflecting, Discussing, Observing, Presenting, Assess).", + "inputMethod": "textfield" + }, + { + "property": "hasLearningObjective", + "title": "Learning Objective", + "description": "OER Schema: ID or URL of the learning objective this practice supports.", + "inputMethod": "textfield" } ], "advanced": [ diff --git a/elements/grade-book/grade-book.js b/elements/grade-book/grade-book.js index b821a87aa9..a3b6f17746 100644 --- a/elements/grade-book/grade-book.js +++ b/elements/grade-book/grade-book.js @@ -4,6 +4,7 @@ */ import { html, css, render, nothing } from "lit"; import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; import { validURL, @@ -37,7 +38,7 @@ import { XLSXFileSystemBrokerSingleton } from "@haxtheweb/file-system-broker/lib * @demo demo/index.html Grade book * @element grade-book */ -class GradeBook extends UIRenderPieces(I18NMixin(SimpleColors)) { +class GradeBook extends UIRenderPieces(I18NMixin(SchemaBehaviors(SimpleColors))) { constructor() { super(); this.__disposer = []; @@ -71,6 +72,7 @@ class GradeBook extends UIRenderPieces(I18NMixin(SimpleColors)) { surname: true, email: true, }; + this.forCourse = ""; this.disabled = false; // shows progress indicator as it loads this.loading = false; @@ -578,6 +580,10 @@ class GradeBook extends UIRenderPieces(I18NMixin(SimpleColors)) { assessmentView: { type: Object, attribute: false }, activeGrading: { type: Object, attribute: false }, activeStudentSubmissions: { type: Array }, + /** + * Course reference emitted as oer:forCourse on each Assessment. + */ + forCourse: { type: String, attribute: "for-course" }, }; } async changeStudent(e) { @@ -1356,12 +1362,44 @@ class GradeBook extends UIRenderPieces(I18NMixin(SimpleColors)) { ); } } + /** + * Emit OER Schema Assessment metadata for each graded assignment. + * Each assignment in the database becomes an oer:Assessment resource + * with gradingFormat (points) and forCourse (when set) properties. + */ + renderAssessmentSchema() { + if ( + !this.database || + !this.database.assignments || + !this.database.assignments.length + ) { + return nothing; + } + return html` + + `; + } /** * LitElement render method */ render() { return html` + ${this.renderAssessmentSchema()}
@@ -2123,6 +2161,51 @@ class GradeBook extends UIRenderPieces(I18NMixin(SimpleColors)) { static get tag() { return "grade-book"; } + static get haxProperties() { + return { + canScale: false, + gizmo: { + title: "Grade Book", + description: "A headless gradebook that supports multiple backends with rubrics.", + icon: "icons:assignment", + color: "green", + tags: ["Instructional", "gradebook", "grading", "assessment", "rubric"], + handles: [], + meta: { + author: "HAXTheWeb core team", + }, + }, + settings: { + configure: [ + { + property: "source", + title: "Source", + description: "Data source type (googledocs, url, json, filesystem).", + inputMethod: "select", + options: { + googledocs: "Google Docs", + url: "URL endpoint (JSON)", + json: "JSON data blob", + filesystem: "File system", + }, + }, + { + property: "sourceData", + title: "Source Data", + description: "URL or JSON data for the gradebook.", + inputMethod: "textfield", + }, + { + property: "forCourse", + title: "Course Reference", + description: + "Course identifier emitted as oer:forCourse on each Assessment resource.", + inputMethod: "textfield", + }, + ], + }, + }; + } } globalThis.customElements.define(GradeBook.tag, GradeBook); export { GradeBook }; diff --git a/elements/license-element/license-element.js b/elements/license-element/license-element.js index 14c3c5f4e5..c7a60e4f1b 100644 --- a/elements/license-element/license-element.js +++ b/elements/license-element/license-element.js @@ -122,9 +122,12 @@ class LicenseElement extends SchemaBehaviors(DDDSuper(LitElement)) { />
+ + ${this.licenseImage ? html` { this.addEventListener("click", this.openDialog.bind(this)); }, 0); } render() { - return html` `; + return html``; } static get tag() { @@ -61,6 +62,9 @@ class LrnVocab extends SchemaBehaviors(LitElement) { type: String, reflect: true, }, + description: { + type: String, + }, }; } /** @@ -133,6 +137,10 @@ class LrnVocab extends SchemaBehaviors(LitElement) { if (super.firstUpdated) { super.firstUpdated(changedProperties); } + this.setAttribute("typeof", "oer:LearningComponent"); + if (!this.description) { + this.description = (this.textContent || "").trim(); + } globalThis.SimpleModal.requestAvailability(); } static get haxProperties() { @@ -183,6 +191,13 @@ class LrnVocab extends SchemaBehaviors(LitElement) { inputMethod: "textarea", required: true, }, + { + property: "description", + title: "Description (OER Schema)", + description: + "OER Schema: a short description of the vocabulary term. Auto-populated from the definition text if left blank.", + inputMethod: "textfield", + }, ], advanced: [], }, diff --git a/elements/lrndesign-timeline/lrndesign-timeline.js b/elements/lrndesign-timeline/lrndesign-timeline.js index 5710e97941..35d6e374fb 100644 --- a/elements/lrndesign-timeline/lrndesign-timeline.js +++ b/elements/lrndesign-timeline/lrndesign-timeline.js @@ -4,6 +4,7 @@ */ import { html, css } from "lit"; import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "@haxtheweb/responsive-utility/responsive-utility.js"; import { normalizeEventPath } from "@haxtheweb/utils/lib/events.js"; @@ -16,7 +17,7 @@ import { normalizeEventPath } from "@haxtheweb/utils/lib/events.js"; * @lit-element * @demo demo/index.html */ -class LrndesignTimeline extends SimpleColors { +class LrndesignTimeline extends SchemaBehaviors(SimpleColors) { //styles function static get styles() { return [ @@ -315,7 +316,7 @@ class LrndesignTimeline extends SimpleColors { @focus="${this._setScroll}" >
-

${event.heading}

+

${event.heading}

${!event.imagesrc || event.imagesrc === "" ? `` @@ -331,7 +332,7 @@ class LrndesignTimeline extends SimpleColors { `}
-
${event.details}
+
${event.details}
`, )} @@ -538,6 +539,7 @@ class LrndesignTimeline extends SimpleColors { */ firstUpdated(changedProperties) { if (super.firstUpdated) super.firstUpdated(changedProperties); + this.setAttribute("typeof", "oer:LearningComponent"); globalThis.dispatchEvent( new CustomEvent("responsive-element", { detail: { diff --git a/elements/map-menu/map-menu.js b/elements/map-menu/map-menu.js index 05d54bcc10..259fdf148c 100644 --- a/elements/map-menu/map-menu.js +++ b/elements/map-menu/map-menu.js @@ -6,6 +6,7 @@ import { LitElement, html, css } from "lit"; import "@haxtheweb/map-menu/lib/map-menu-builder.js"; import "@haxtheweb/map-menu/lib/map-menu-container.js"; import { normalizeEventPath } from "@haxtheweb/utils/lib/events.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; /** * `map-menu` @@ -14,7 +15,7 @@ import { normalizeEventPath } from "@haxtheweb/utils/lib/events.js"; * @demo demo/index.html * @element map-menu */ -class MapMenu extends LitElement { +class MapMenu extends SchemaBehaviors(LitElement) { /** * LitElement constructable styles enhancement */ @@ -479,7 +480,7 @@ class MapMenu extends LitElement { */ render() { return html` -
+
+ html``, + )} +
${activeItem @@ -594,6 +602,7 @@ export class MediaPlaylist extends DDDSuper(I18NMixin(LitElement)) { if (super.firstUpdated) { super.firstUpdated(changedProperties); } + this.setAttribute("typeof", "oer:LearningComponent"); this._updateMediaItems(); const playerArea = this.shadowRoot.querySelector(".player-area"); const playlist = this.shadowRoot.querySelector(".playlist"); diff --git a/elements/merit-badge/merit-badge.js b/elements/merit-badge/merit-badge.js index b9671a694c..998bacedb6 100644 --- a/elements/merit-badge/merit-badge.js +++ b/elements/merit-badge/merit-badge.js @@ -3,6 +3,7 @@ * @license Apache-2.0, see License.md for full text. */ import { LitElement, html, css } from "lit"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "./lib/badge-sticker.js"; /** @@ -11,7 +12,7 @@ import "./lib/badge-sticker.js"; * @demo demo/index.html * @element merit-badge */ -class MeritBadge extends LitElement { +class MeritBadge extends SchemaBehaviors(LitElement) { static get tag() { return "merit-badge"; } @@ -27,6 +28,7 @@ class MeritBadge extends LitElement { detailsOpened: { type: Boolean }, badgeUnlocked: { type: Boolean }, badgeColor: { type: String }, + skill: { type: String }, }; } @@ -78,10 +80,12 @@ class MeritBadge extends LitElement { constructor() { super(); this.badgeUnlocked = false; + this.skill = ""; } render() { return html` +
${this.badgeUnlocked ? html` + ${this.gradingFormat + ? html`` + : nothing} + ${this.hasLearningObjective + ? html`` + : nothing} + ${this.forCourse + ? html`` + : nothing}
diff --git a/elements/multiple-choice/lib/multiple-choice.haxProperties.json b/elements/multiple-choice/lib/multiple-choice.haxProperties.json index 30c17c0c65..d582f736ee 100644 --- a/elements/multiple-choice/lib/multiple-choice.haxProperties.json +++ b/elements/multiple-choice/lib/multiple-choice.haxProperties.json @@ -51,6 +51,24 @@ "description": "Allow only one answer.", "inputMethod": "boolean" }, + { + "property": "gradingFormat", + "title": "Grading format", + "description": "OER Schema: grading format for this assessment (e.g. points, letter, percent, completion).", + "inputMethod": "textfield" + }, + { + "property": "hasLearningObjective", + "title": "Learning objective", + "description": "OER Schema: learning objective associated with this assessment.", + "inputMethod": "textfield" + }, + { + "property": "forCourse", + "title": "Course", + "description": "OER Schema: course this assessment is intended for.", + "inputMethod": "textfield" + }, { "property": "answers", "title": "Answers", diff --git a/elements/outline-designer/outline-designer.js b/elements/outline-designer/outline-designer.js index 389716a147..48c254e288 100644 --- a/elements/outline-designer/outline-designer.js +++ b/elements/outline-designer/outline-designer.js @@ -4,6 +4,7 @@ */ import { LitElement, css, html } from "lit"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { HAXStore } from "@haxtheweb/hax-body/lib/hax-store"; import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; @@ -28,7 +29,7 @@ import { * `tools to modify and visualize JSON Outline Schema for editing` * @demo demo/index.html */ -export class OutlineDesigner extends I18NMixin(LitElement) { +export class OutlineDesigner extends SchemaBehaviors(I18NMixin(LitElement)) { static get styles() { return [ css` @@ -1106,6 +1107,7 @@ export class OutlineDesigner extends I18NMixin(LitElement) { id="list" role="tree" aria-label="Outline structure of pages and content" + typeof="oer:TableOfContents" > ${this.items.map((item, index) => this.getItemParentsCollapsed(item) === "" @@ -1326,7 +1328,10 @@ export class OutlineDesigner extends I18NMixin(LitElement) { ?data-has-children="${this.hasChildren(item.id)}" ?data-about-to-delete="${item.delete}" ?hidden="${this.hideDelete && item.delete}" + typeof="oer:TableOfContentsEntry" + property="oer:entry" > +
${this.hasChildren(item.id) ? html`${item.title} Skip to content
-
`; } + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.setAttribute("typeof", "oer:SupportingMaterial"); + } static get tag() { return "person-testimonial"; } diff --git a/elements/self-check/lib/self-check.haxProperties.json b/elements/self-check/lib/self-check.haxProperties.json index 091a94c023..9fd9280d99 100644 --- a/elements/self-check/lib/self-check.haxProperties.json +++ b/elements/self-check/lib/self-check.haxProperties.json @@ -74,6 +74,18 @@ "error": "Error", "info": "Info" } + }, + { + "property": "hasLearningObjective", + "title": "Learning Objective", + "description": "Learning objective reference emitted as oer:hasLearningObjective.", + "inputMethod": "textfield" + }, + { + "property": "assessing", + "title": "Assessing", + "description": "Activity reference emitted as oer:assessing.", + "inputMethod": "textfield" } ], "advanced": [ diff --git a/elements/self-check/self-check.js b/elements/self-check/self-check.js index f5d6ec97d4..130f518da1 100644 --- a/elements/self-check/self-check.js +++ b/elements/self-check/self-check.js @@ -42,6 +42,8 @@ class SelfCheck extends I18NMixin(lazyImageLoader(SchemaBehaviors(DDD))) { this.accentColor = "primary"; this.title = "Self-Check"; this.fullWidthImage = false; + this.hasLearningObjective = ""; + this.assessing = ""; this.t = { revealAnswer: "Reveal Answer", close: "Close", @@ -398,6 +400,15 @@ class SelfCheck extends I18NMixin(lazyImageLoader(SchemaBehaviors(DDD))) { } render() { return html` + ${this.hasLearningObjective + ? html`` + : ``} + ${this.assessing + ? html`` + : ``}
${this.image @@ -502,6 +513,12 @@ class SelfCheck extends I18NMixin(lazyImageLoader(SchemaBehaviors(DDD))) { static get tag() { return "self-check"; } + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.setAttribute("typeof", "oer:Assessment"); + } static get properties() { return { ...super.properties, @@ -559,6 +576,20 @@ class SelfCheck extends I18NMixin(lazyImageLoader(SchemaBehaviors(DDD))) { attribute: "accent-color", reflect: true, }, + /** + * Learning objective reference emitted as oer:hasLearningObjective. + */ + hasLearningObjective: { + type: String, + attribute: "has-learning-objective", + }, + /** + * Activity reference emitted as oer:assessing. + */ + assessing: { + type: String, + attribute: "assessing", + }, }; } /** diff --git a/elements/sorting-question/lib/sorting-question.haxProperties.json b/elements/sorting-question/lib/sorting-question.haxProperties.json index ee36ba3fa1..14bb3a9aa2 100644 --- a/elements/sorting-question/lib/sorting-question.haxProperties.json +++ b/elements/sorting-question/lib/sorting-question.haxProperties.json @@ -28,6 +28,24 @@ "description": "Question Header.", "inputMethod": "textfield" }, + { + "property": "gradingFormat", + "title": "Grading format", + "description": "OER Schema: grading format for this assessment (e.g. points, letter, percent, completion).", + "inputMethod": "textfield" + }, + { + "property": "hasLearningObjective", + "title": "Learning objective", + "description": "OER Schema: learning objective associated with this assessment.", + "inputMethod": "textfield" + }, + { + "property": "forCourse", + "title": "Course", + "description": "OER Schema: course this assessment is intended for.", + "inputMethod": "textfield" + }, { "property": "answers", "title": "Answers", diff --git a/elements/star-rating/star-rating.js b/elements/star-rating/star-rating.js index 2764b8cf40..028dc8f3c9 100644 --- a/elements/star-rating/star-rating.js +++ b/elements/star-rating/star-rating.js @@ -2,10 +2,11 @@ * Copyright 2021 The Pennsylvania State University * @license Apache-2.0, see License.md for full text. */ -import { html, css } from "lit"; +import { html, css, nothing } from "lit"; import "@haxtheweb/simple-icon/lib/simple-icon-button.js"; import "@haxtheweb/simple-icon/simple-icon.js"; import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; /** * `star-rating` * `Rating display widget or button to do rating` @@ -13,11 +14,70 @@ import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; * @demo demo/index.html * @element star-rating */ -class StarRating extends SimpleColors { +class StarRating extends SchemaBehaviors(SimpleColors) { static get tag() { return "star-rating"; } + static get haxProperties() { + return { + canScale: true, + gizmo: { + title: "Star Rating", + description: "Rating display widget or button to do rating.", + icon: "icons:star", + color: "yellow", + tags: [ + "Instructional", + "rating", + "stars", + "rubric", + "scale", + "feedback", + ], + handles: [], + meta: { + author: "HAXTheWeb core team", + }, + }, + settings: { + configure: [ + { + property: "score", + title: "Score", + description: "Current score value.", + inputMethod: "number", + }, + { + property: "possible", + title: "Possible", + description: "Maximum possible score.", + inputMethod: "number", + }, + { + property: "numStars", + title: "Number of Stars", + description: "Number of stars to display.", + inputMethod: "number", + }, + { + property: "interactive", + title: "Interactive", + description: "Allow user interaction to set the rating.", + inputMethod: "boolean", + }, + { + property: "rubricScaleMode", + title: "Rubric Scale Mode", + description: + "Emit OER Schema RubricScale and RubricLevel metadata (levelOrdinal/levelPoints) for each star.", + inputMethod: "boolean", + }, + ], + }, + }; + } + static get styles() { return [ super.styles, @@ -69,6 +129,14 @@ class StarRating extends SimpleColors { interactive: { type: Boolean, reflect: true }, numStars: { type: Number, attribute: "num-stars" }, _calPercent: { type: Number }, // this value we'll calculate based on changes to score and possible + /** + * Opt-in: emit OER Schema RubricScale + RubricLevel metadata. + */ + rubricScaleMode: { + type: Boolean, + attribute: "rubric-scale-mode", + reflect: true, + }, }; } @@ -82,11 +150,15 @@ class StarRating extends SimpleColors { this.dark = true; this.contrast = 0; this.accentColor = "yellow"; + this.rubricScaleMode = false; } render() { return html` -
+
${this.renderStar(this.numStars, this.interactive)}
@@ -125,7 +197,18 @@ class StarRating extends SimpleColors { contrast="${this.contrast}" class="star" data-value="${Number(count + 1)}" - >`, + typeof="${this.rubricScaleMode ? "oer:RubricLevel" : nothing}" + property="${this.rubricScaleMode ? "oer:hasLevel" : nothing}" + >${this.rubricScaleMode + ? html`` + : nothing}`, ); } else { content.push( @@ -135,7 +218,18 @@ class StarRating extends SimpleColors { ?dark="${this.dark}" contrast="${this.contrast}" class="star" - >`, + typeof="${this.rubricScaleMode ? "oer:RubricLevel" : nothing}" + property="${this.rubricScaleMode ? "oer:hasLevel" : nothing}" + >${this.rubricScaleMode + ? html`` + : nothing}`, ); } count++; @@ -154,6 +248,14 @@ class StarRating extends SimpleColors { }), ); } + /** + * Calculate the points assigned to a given rubric level (1-indexed) + * based on the total possible score spread evenly across all stars. + */ + _rubricLevelPoints(level) { + if (this.numStars === 0) return 0; + return Math.round((this.possible / this.numStars) * level); + } updated(changedProperties) { if (super.updated) { super.updated(changedProperties); diff --git a/elements/stop-note/stop-note.js b/elements/stop-note/stop-note.js index e4c53202bb..c048353757 100644 --- a/elements/stop-note/stop-note.js +++ b/elements/stop-note/stop-note.js @@ -2,6 +2,7 @@ import { html, css } from "lit"; import { remoteLinkBehavior } from "@haxtheweb/utils/lib/remoteLinkBehavior.js"; import { SimpleIconsetStore } from "@haxtheweb/simple-icon/lib/simple-iconset.js"; import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import { SchemaBehaviors } from "@haxtheweb/schema-behaviors/schema-behaviors.js"; import "@haxtheweb/simple-icon/simple-icon.js"; import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; @@ -27,7 +28,7 @@ export const StopNoteIconList = { info: "stopnoteicons:book-icon", }; -class StopNote extends I18NMixin(remoteLinkBehavior(DDD)) { +class StopNote extends SchemaBehaviors(I18NMixin(remoteLinkBehavior(DDD))) { /** * LitElement constructable styles enhancement */ @@ -208,7 +209,7 @@ class StopNote extends I18NMixin(remoteLinkBehavior(DDD)) {
-

+

${this.title}

@@ -328,6 +329,7 @@ class StopNote extends I18NMixin(remoteLinkBehavior(DDD)) { */ firstUpdated(changedProperties) { if (super.firstUpdated) super.firstUpdated(changedProperties); + this.setAttribute("typeof", "oer:LearningComponent"); this.remoteLinkTarget = this.shadowRoot.querySelector("#link"); this._syncSlottedLinkColor(); // if we have no status BUT icon was supplied; this is to support legacy implementations diff --git a/elements/tagging-question/lib/tagging-question.haxProperties.json b/elements/tagging-question/lib/tagging-question.haxProperties.json index fc3f1c0bc1..3cfd1199c8 100644 --- a/elements/tagging-question/lib/tagging-question.haxProperties.json +++ b/elements/tagging-question/lib/tagging-question.haxProperties.json @@ -29,6 +29,24 @@ "description": "Question for users to respond to.", "inputMethod": "textfield" }, + { + "property": "gradingFormat", + "title": "Grading format", + "description": "OER Schema: grading format for this assessment (e.g. points, letter, percent, completion).", + "inputMethod": "textfield" + }, + { + "property": "hasLearningObjective", + "title": "Learning objective", + "description": "OER Schema: learning objective associated with this assessment.", + "inputMethod": "textfield" + }, + { + "property": "forCourse", + "title": "Course", + "description": "OER Schema: course this assessment is intended for.", + "inputMethod": "textfield" + }, { "property": "answers", "title": "Answers", diff --git a/elements/un-sdg/un-sdg.js b/elements/un-sdg/un-sdg.js index d451eeb2a0..9f5dc8c947 100644 --- a/elements/un-sdg/un-sdg.js +++ b/elements/un-sdg/un-sdg.js @@ -1,5 +1,6 @@ import { LitElement, html, css } from 'lit'; -export class UnSdg extends LitElement { +import { SchemaBehaviors } from '@haxtheweb/schema-behaviors/schema-behaviors.js'; +export class UnSdg extends SchemaBehaviors(LitElement) { static get tag() { return 'un-sdg'; } @@ -58,11 +59,13 @@ export class UnSdg extends LitElement { // ensure we are between the 17 goals we support if (this.goal >= 1 && this.goal <= 17) { if (this.colorOnly) { - return html`
`; + return html`
`; } else { return html` + ${this.alt} + + ${this.audioDescriptionSource && this.audioDescriptionEnabled ? html`