diff --git a/package-lock.json b/package-lock.json index 1aa107f..8de606f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@types/react": "^18.2.13", "@types/react-dom": "^18.2.7", "babel-plugin-syntax-dynamic-import": "^6.18.0", - "bigbluebutton-html-plugin-sdk": "0.0.53", + "bigbluebutton-html-plugin-sdk": "0.0.54", "h5p-standalone": "file:lib/h5p-standalone", "path": "^0.12.7", "react": "^18.2.0", @@ -4530,9 +4530,9 @@ } }, "node_modules/bigbluebutton-html-plugin-sdk": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.53.tgz", - "integrity": "sha512-+MXTl3KB45uPdZOhW74f0QIxcrW0sC+hSMGN+rGjDOpfhnDxKSSezTMogu/mbth23yJUJcSUlKw9BV9pTtEtvA==", + "version": "0.0.54", + "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.54.tgz", + "integrity": "sha512-4dcu3ipM4cPDCcgQc1X8X8kM5vQ6z0nh+bKvCxsrD9i5NFF+8cl/cojektJE0Em50m1j8qnQ7fHmmu+9MtGPEA==", "dependencies": { "@apollo/client": "^3.8.7", "@browser-bunyan/console-formatted-stream": "^1.8.0", @@ -16717,9 +16717,9 @@ "dev": true }, "bigbluebutton-html-plugin-sdk": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.53.tgz", - "integrity": "sha512-+MXTl3KB45uPdZOhW74f0QIxcrW0sC+hSMGN+rGjDOpfhnDxKSSezTMogu/mbth23yJUJcSUlKw9BV9pTtEtvA==", + "version": "0.0.54", + "resolved": "https://registry.npmjs.org/bigbluebutton-html-plugin-sdk/-/bigbluebutton-html-plugin-sdk-0.0.54.tgz", + "integrity": "sha512-4dcu3ipM4cPDCcgQc1X8X8kM5vQ6z0nh+bKvCxsrD9i5NFF+8cl/cojektJE0Em50m1j8qnQ7fHmmu+9MtGPEA==", "requires": { "@apollo/client": "^3.8.7", "@browser-bunyan/console-formatted-stream": "^1.8.0", diff --git a/package.json b/package.json index 9d17bdd..b4aa7bf 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@types/react": "^18.2.13", "@types/react-dom": "^18.2.7", "babel-plugin-syntax-dynamic-import": "^6.18.0", - "bigbluebutton-html-plugin-sdk": "0.0.53", + "bigbluebutton-html-plugin-sdk": "0.0.54", "h5p-standalone": "file:lib/h5p-standalone", "path": "^0.12.7", "react": "^18.2.0", diff --git a/src/commons/types.ts b/src/commons/types.ts index 9661831..b5cbeb4 100644 --- a/src/commons/types.ts +++ b/src/commons/types.ts @@ -3,3 +3,11 @@ export interface CurrentH5pStateWindow extends Window { // eslint-disable-next-line @typescript-eslint/no-explicit-any H5P: any; } + +export interface H5pAsJsonObject { + title:string ; + mainLibrary:string ; + language:string ; + preloadedDependencies:string ; + embedTypes:string ; +} diff --git a/src/components/generic-component/component.tsx b/src/components/generic-component/component.tsx index 3e63186..afd3332 100644 --- a/src/components/generic-component/component.tsx +++ b/src/components/generic-component/component.tsx @@ -8,15 +8,17 @@ import { useEffect } from 'react'; import * as React from 'react'; import { GenericContentRenderFunctionProps, + LADTestResult, TestResult, UserH5pCurrentState, UsersMoreInformationGraphqlResponse, } from './types'; import NonPresenterViewComponent from './non-presenter-view/component'; import PresenterViewComponent from './presenter-view/component'; import { USERS_MORE_INFORMATION } from './subscriptions'; +import { extractH5pContents } from '../h5p-plugin/utils'; export function GenericContentRenderFunction(props: GenericContentRenderFunctionProps) { const { - jsonContent, currentUser, + h5pContentText, currentUser, pluginUuid, } = props; @@ -32,7 +34,8 @@ export function GenericContentRenderFunction(props: GenericContentRenderFunction // TODO: Refactor the test results to be just a request done for an external server to be // validated and all - const { data: testResult, pushEntry: pushEntryTestResult } = pluginApi.useDataChannel('testResult', DataChannelTypes.LATEST_ITEM); + const { pushEntry: pushEntryTestResult } = pluginApi.useDataChannel('testResult', DataChannelTypes.LATEST_ITEM); + const { pushEntry: pushEntryLadTestResult } = pluginApi.useDataChannel('testResult', DataChannelTypes.LATEST_ITEM, 'learning-analytics-dashboard'); useEffect(() => () => { if (currentUser && currentUser.presenter) deleteUserH5pCurrentStateList([RESET_DATA_CHANNEL]); @@ -46,6 +49,8 @@ export function GenericContentRenderFunction(props: GenericContentRenderFunction (h5pState) => h5pState.payloadJson.userId === currentUser?.userId, ).map((h5pState) => ({ entryId: h5pState.entryId, payloadJson: h5pState.payloadJson }))[0]; + const { contentAsJson, h5pAsJson } = extractH5pContents(h5pContentText); + // TODO: Filter the ones where the loading is not done yet (needs refactor in html5) // if (responseUserH5pCurrentStateList.loading) return null; return ( @@ -53,18 +58,21 @@ export function GenericContentRenderFunction(props: GenericContentRenderFunction ? ( ) : ( async () => { @@ -32,7 +33,8 @@ export const renderH5p = ( { state: JSON.stringify(previousState), }], - contentAsJson: jsonContent.replace(/(\r\n|\n|\r)/gm, ''), + contentAsJson: contentAsJson.replace(/(\r\n|\n|\r)/gm, ''), + h5pAsJson: h5pAsJson.replace(/(\r\n|\n|\r)/gm, ''), customCss, h5pJsonPath: 'https://bigbluebutton.nyc3.digitaloceanspaces.com/plugins/h5p/assets', frameJs: 'https://bigbluebutton.nyc3.digitaloceanspaces.com/plugins/h5p/assets/frame.bundle.js', @@ -52,7 +54,8 @@ export const renderH5pForNonPresenter = ( containerRef, lastPayloadJson, setH5pState, - jsonContent, + contentAsJson, + h5pAsJson, setContentRendered, ) => renderH5p( containerRef, @@ -60,14 +63,16 @@ export const renderH5pForNonPresenter = ( undefined, lastPayloadJson, setH5pState, - jsonContent, + contentAsJson, + h5pAsJson, setContentRendered, ); export const renderH5pForPresenter = ( containerRef, currentH5pStateToBeApplied, - jsonContent, + contentAsJson, + h5pAsJson, setContentRendered, customCss, ) => renderH5p( @@ -76,7 +81,8 @@ export const renderH5pForPresenter = ( currentH5pStateToBeApplied, '', () => {}, - jsonContent, + contentAsJson, + h5pAsJson, setContentRendered, customCss, ); diff --git a/src/components/generic-component/non-presenter-view/component.tsx b/src/components/generic-component/non-presenter-view/component.tsx index 86f49bf..5d12e9b 100644 --- a/src/components/generic-component/non-presenter-view/component.tsx +++ b/src/components/generic-component/non-presenter-view/component.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import { renderH5pForNonPresenter } from '../h5p-renderer/utils'; import { NonPresenterViewComponentProps } from './types'; import * as Styled from '../styles'; -import { CurrentH5pStateWindow } from '../../../commons/types'; +import { CurrentH5pStateWindow, H5pAsJsonObject } from '../../../commons/types'; declare const window: CurrentH5pStateWindow; @@ -15,20 +15,29 @@ function NonPresenterViewComponent(props: NonPresenterViewComponentProps) { const [contentRendered, setContentRendered] = useState(false); const [h5pState, setH5pState] = useState({}); const { - jsonContent, currentUserName, - pushEntryTestResult, currentUserId, + contentAsJson, currentUserName, h5pAsJson, + pushEntryTestResult, pushEntryLadTestResult, currentUserId, pushH5pCurrentState, lastUpdateId, lastPayloadJson, - replaceH5pCurrentState, + replaceH5pCurrentState, pluginApi, } = props; + const h5pAsJsonObject: H5pAsJsonObject = JSON.parse(h5pAsJson); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const eventHandler = (event: any) => { - if (event.getScore && event.getMaxScore && event.getVerb && pushEntryTestResult) { + if (event.getScore && event.getMaxScore && event.getVerb + && pushEntryTestResult && pushEntryLadTestResult) { const score = event.getScore(); const maxScore = event.getMaxScore(); const verb = event.getVerb(); if (verb === 'answered') { + pluginApi.sendGenericDataForLearningAnalyticsDashboard({ + columnTitle: h5pAsJsonObject.title, + value: `${(parseFloat(score) / parseFloat(maxScore)) * 10.0} / 10`, + cardTitle: 'H5P', + }); pushEntryTestResult({ + testResultActivityTitle: h5pAsJsonObject.title, userId: currentUserId, testResultObject: score, testResultMaximumScore: maxScore, @@ -64,7 +73,8 @@ function NonPresenterViewComponent(props: NonPresenterViewComponentProps) { containerRef, lastPayloadJson, setH5pState, - jsonContent, + contentAsJson, + h5pAsJson, setContentRendered, ), 100, @@ -75,7 +85,8 @@ function NonPresenterViewComponent(props: NonPresenterViewComponentProps) { clearTimeout(timeoutReference); }; }, []); - if (pushEntryTestResult && !stopInfinitLoop.current && contentRendered) { + if (pushEntryTestResult + && pushEntryLadTestResult && !stopInfinitLoop.current && contentRendered) { stopInfinitLoop.current = true; window.H5P?.externalDispatcher?.on('xAPI', eventHandler); } diff --git a/src/components/generic-component/non-presenter-view/types.ts b/src/components/generic-component/non-presenter-view/types.ts index e97a6f6..c1e27f1 100644 --- a/src/components/generic-component/non-presenter-view/types.ts +++ b/src/components/generic-component/non-presenter-view/types.ts @@ -1,14 +1,17 @@ import { ReplaceEntryFunction } from 'bigbluebutton-html-plugin-sdk/dist/cjs/data-channel/types'; -import { PushEntryFunction } from 'bigbluebutton-html-plugin-sdk'; -import { TestResult, UserH5pCurrentState } from '../types'; +import { PluginApi, PushEntryFunction } from 'bigbluebutton-html-plugin-sdk'; +import { LADTestResult, TestResult, UserH5pCurrentState } from '../types'; export interface NonPresenterViewComponentProps { currentUserName: string; - jsonContent: string; + contentAsJson: string; + h5pAsJson: string; pushEntryTestResult: PushEntryFunction; + pushEntryLadTestResult: PushEntryFunction; currentUserId: string; pushH5pCurrentState: PushEntryFunction; lastUpdateId: string; + pluginApi: PluginApi; lastPayloadJson: UserH5pCurrentState; replaceH5pCurrentState: ReplaceEntryFunction; } diff --git a/src/components/generic-component/presenter-view/component.tsx b/src/components/generic-component/presenter-view/component.tsx index 85437dd..436a279 100644 --- a/src/components/generic-component/presenter-view/component.tsx +++ b/src/components/generic-component/presenter-view/component.tsx @@ -7,28 +7,28 @@ import { H5pPlayerManagerComponent } from './h5p-player-manager/component'; const mapObject = ( currentUserH5pStateList: DataChannelEntryResponseType[], - jsonContent: string, + contentAsJson: string, + h5pAsJson: string, ) => ( currentUserH5pStateList?.map((item) => ( )) ); function PresenterViewComponent(props: PresenterViewComponentProps) { const { - h5pLatestStateUpdate, jsonContent, testResult, + h5pLatestStateUpdate, contentAsJson, h5pAsJson, } = props; - const userIdTestResultList = testResult?.data?.map((item) => item.payloadJson.userId); - const dataToBeRendered = h5pLatestStateUpdate?.data?.filter( - (item) => !userIdTestResultList?.includes(item?.payloadJson.userId), - ); + const dataToBeRendered = h5pLatestStateUpdate?.data; return (
- {mapObject(dataToBeRendered, jsonContent)} + {mapObject(dataToBeRendered, contentAsJson, h5pAsJson)}
); diff --git a/src/components/generic-component/presenter-view/h5p-player-manager/component.tsx b/src/components/generic-component/presenter-view/h5p-player-manager/component.tsx index 98029dc..e0c7ad0 100644 --- a/src/components/generic-component/presenter-view/h5p-player-manager/component.tsx +++ b/src/components/generic-component/presenter-view/h5p-player-manager/component.tsx @@ -6,7 +6,7 @@ import H5pPresenterComponent from '../h5p-presenter-component/component'; export function H5pPlayerManagerComponent(props: H5pPlayerManagerComponentProps) { const { - jsonContent, + contentAsJson, h5pAsJson, userName, currentH5pStateToBeApplied, } = props; @@ -41,7 +41,8 @@ export function H5pPlayerManagerComponent(props: H5pPlayerManagerComponentProps) setLatestH5pStates={setLatestH5pStates} currentH5pStateToBeApplied={state.state} idOfCurrentState={state.id} - jsonContent={jsonContent} + contentAsJson={contentAsJson} + h5pAsJson={h5pAsJson} /> ))} diff --git a/src/components/generic-component/presenter-view/h5p-player-manager/types.ts b/src/components/generic-component/presenter-view/h5p-player-manager/types.ts index 0510e7e..94ef263 100644 --- a/src/components/generic-component/presenter-view/h5p-player-manager/types.ts +++ b/src/components/generic-component/presenter-view/h5p-player-manager/types.ts @@ -1,9 +1,10 @@ export interface H5pPlayerManagerComponentProps { - currentH5pStateToBeApplied: object; userName: string - jsonContent: string; + contentAsJson: string; + h5pAsJson: string; + currentH5pStateToBeApplied: object; + userId: string; } - export interface LatestH5pStateItem { id: string; state: object; diff --git a/src/components/generic-component/presenter-view/h5p-presenter-component/component.tsx b/src/components/generic-component/presenter-view/h5p-presenter-component/component.tsx index 0faaaa7..576a59d 100644 --- a/src/components/generic-component/presenter-view/h5p-presenter-component/component.tsx +++ b/src/components/generic-component/presenter-view/h5p-presenter-component/component.tsx @@ -4,7 +4,6 @@ import { renderH5pForPresenter } from '../../h5p-renderer/utils'; import * as Styled from '../../styles'; import { H5pPresenterComponentProps } from './types'; import { CurrentH5pStateWindow } from '../../../../commons/types'; -import './custom-crossword.css'; declare const window: CurrentH5pStateWindow; @@ -14,7 +13,7 @@ function H5pPresenterComponent(props: H5pPresenterComponentProps) { const [contentRendered, setContentRendered] = useState(false); const { - jsonContent, + contentAsJson, h5pAsJson, currentH5pStateToBeApplied, setLatestH5pStates, idOfCurrentState, } = props; @@ -39,7 +38,8 @@ function H5pPresenterComponent(props: H5pPresenterComponentProps) { renderH5pForPresenter( containerRef, currentH5pStateToBeApplied, - jsonContent, + contentAsJson, + h5pAsJson, setContentRendered, ['https://bigbluebutton.nyc3.digitaloceanspaces.com/plugins/h5p/assets/custom/css/custom-crossword.css'], ), diff --git a/src/components/generic-component/presenter-view/h5p-presenter-component/custom-crossword.css b/src/components/generic-component/presenter-view/h5p-presenter-component/custom-crossword.css deleted file mode 100644 index d7807e3..0000000 --- a/src/components/generic-component/presenter-view/h5p-presenter-component/custom-crossword.css +++ /dev/null @@ -1,6 +0,0 @@ -.h5p-question-buttons h5p-question-visible { - display: none; -} -.h5p-crossword-input-container { - display: none; -} \ No newline at end of file diff --git a/src/components/generic-component/presenter-view/h5p-presenter-component/types.ts b/src/components/generic-component/presenter-view/h5p-presenter-component/types.ts index c092a97..3da9bb4 100644 --- a/src/components/generic-component/presenter-view/h5p-presenter-component/types.ts +++ b/src/components/generic-component/presenter-view/h5p-presenter-component/types.ts @@ -1,7 +1,8 @@ import { LatestH5pStateItem } from '../h5p-player-manager/types'; export interface H5pPresenterComponentProps { - jsonContent: string; + contentAsJson: string; + h5pAsJson: string; indexOfCurrentStateInList: number; stateListLength: number; currentH5pStateToBeApplied: object; diff --git a/src/components/generic-component/presenter-view/sidekick-content/component.tsx b/src/components/generic-component/presenter-view/sidekick-content/component.tsx index 6904059..92423b6 100644 --- a/src/components/generic-component/presenter-view/sidekick-content/component.tsx +++ b/src/components/generic-component/presenter-view/sidekick-content/component.tsx @@ -3,6 +3,8 @@ import { BbbPluginSdk, DataChannelTypes } from 'bigbluebutton-html-plugin-sdk'; import { PresenterViewerRenderFunctionProps } from './types'; import * as Styled from './styles'; import { MoreInfoUser, TestResult } from '../../types'; +import { extractH5pContents } from '../../../h5p-plugin/utils'; +import { H5pAsJsonObject } from '../../../../commons/types'; const mapObject = (results: TestResult[], usersList: MoreInfoUser[], presenterId: string) => ( usersList?.map((item) => { @@ -30,7 +32,7 @@ const mapObject = (results: TestResult[], usersList: MoreInfoUser[], presenterId function PresenterViewerSidekickRenderResultFunction(props: PresenterViewerRenderFunctionProps) { const { - usersList, currentUserId, pluginUuid, + usersList, currentUserId, pluginUuid, h5pContentText, } = props; const pluginApi = BbbPluginSdk.getPluginApi(pluginUuid); @@ -40,6 +42,15 @@ function PresenterViewerSidekickRenderResultFunction(props: PresenterViewerRende testResultObject: data.payloadJson.testResultObject, testResultMaximumScore: data.payloadJson.testResultMaximumScore, } as TestResult)); + + let sidekickAreaTitle: string = 'No h5p content is playing'; + + if (h5pContentText) { + const { h5pAsJson } = extractH5pContents(h5pContentText); + const h5pAsObject: H5pAsJsonObject = JSON.parse(h5pAsJson); + sidekickAreaTitle = h5pAsObject.title; + } + return (
-

Results of each student

+

{sidekickAreaTitle}

{mapObject(restults, usersList, currentUserId)}
); diff --git a/src/components/generic-component/presenter-view/sidekick-content/types.ts b/src/components/generic-component/presenter-view/sidekick-content/types.ts index d87b783..d3b9c10 100644 --- a/src/components/generic-component/presenter-view/sidekick-content/types.ts +++ b/src/components/generic-component/presenter-view/sidekick-content/types.ts @@ -1,15 +1,8 @@ -import { CurrentUserData } from 'bigbluebutton-html-plugin-sdk'; import { MoreInfoUser } from '../../types'; -export interface GenericComponentRenderFunctionProps { - jsonContent: string; - currentUser: CurrentUserData; - linkThatGeneratedJsonContent: string; - pluginUuid: string; -} - export interface PresenterViewerRenderFunctionProps { pluginUuid: string; currentUserId: string; usersList: MoreInfoUser[]; + h5pContentText: string; } diff --git a/src/components/generic-component/presenter-view/types.ts b/src/components/generic-component/presenter-view/types.ts index 1ac4f19..70f704e 100644 --- a/src/components/generic-component/presenter-view/types.ts +++ b/src/components/generic-component/presenter-view/types.ts @@ -1,12 +1,12 @@ import { GraphqlResponseWrapper } from 'bigbluebutton-html-plugin-sdk'; import { DataChannelEntryResponseType } from 'bigbluebutton-html-plugin-sdk/dist/cjs/data-channel/types'; -import { MoreInfoUser, TestResult, UserH5pCurrentState } from '../types'; +import { MoreInfoUser, UserH5pCurrentState } from '../types'; export interface PresenterViewComponentProps { currentUserId: string; - testResult: GraphqlResponseWrapper[]>; usersList: MoreInfoUser[]; h5pLatestStateUpdate: GraphqlResponseWrapper< DataChannelEntryResponseType[]>; - jsonContent: string; + contentAsJson: string; + h5pAsJson: string; } diff --git a/src/components/generic-component/styles.ts b/src/components/generic-component/styles.ts index 14c2e6a..3ab7c0b 100644 --- a/src/components/generic-component/styles.ts +++ b/src/components/generic-component/styles.ts @@ -1,9 +1,5 @@ import styled from 'styled-components'; -interface LockedDivProps { - readOnly: boolean; -} - const ScrollboxVertical = styled.div` overflow-y: auto; padding: 2px 50px; @@ -45,16 +41,13 @@ const ScrollboxVertical = styled.div` &::-webkit-scrollbar-corner { background: 0 0; } `; -const LockedDiv = styled.div` +const LockedDiv = styled.div` top: 0; left: 0; right: 0; bottom: 0; position: absolute; z-index: 100; - ${({ readOnly }) => !readOnly && ` - display: none; - `} &:hover { cursor: not-allowed; } diff --git a/src/components/generic-component/types.ts b/src/components/generic-component/types.ts index 4a5af42..afb9827 100644 --- a/src/components/generic-component/types.ts +++ b/src/components/generic-component/types.ts @@ -7,17 +7,23 @@ export interface UserH5pCurrentState { } export interface GenericContentRenderFunctionProps { - jsonContent: string; + h5pContentText: string; currentUser: CurrentUserData; pluginUuid: string; } export interface TestResult { userId: string; + testResultActivityTitle: string; testResultObject: number; testResultMaximumScore: number; } +export interface LADTestResult { + learningAnalyticsDashboardColumnTitle: string; + learningAnalyticsDashboardValue: string; +} + export interface MoreInfoUser { presenter: boolean; userId: string; diff --git a/src/components/h5p-plugin/component.tsx b/src/components/h5p-plugin/component.tsx index db97678..f670ae9 100644 --- a/src/components/h5p-plugin/component.tsx +++ b/src/components/h5p-plugin/component.tsx @@ -140,6 +140,7 @@ function H5pPlugin( onClick: () => { jsonContentPushFunction({ contentJson: currentText, link: linkThatGeneratedJsonContent }); }, + style: { background: '#54a1f3', color: '#fff' }, }); pluginApi.setPresentationToolbarItems([currentObjectToSendToClient]); @@ -177,7 +178,7 @@ function H5pPlugin( root.render( @@ -198,6 +199,7 @@ function H5pPlugin( diff --git a/src/components/h5p-plugin/types.ts b/src/components/h5p-plugin/types.ts index 18fbc04..a9b3dff 100644 --- a/src/components/h5p-plugin/types.ts +++ b/src/components/h5p-plugin/types.ts @@ -3,6 +3,12 @@ interface H5pPluginProps { pluginUuid: string, } +interface H5pContents { + contentAsJson: string; + h5pAsJson: string; +} + export { H5pPluginProps, + H5pContents, }; diff --git a/src/components/h5p-plugin/utils.ts b/src/components/h5p-plugin/utils.ts index 116baeb..4d62fa1 100644 --- a/src/components/h5p-plugin/utils.ts +++ b/src/components/h5p-plugin/utils.ts @@ -1,3 +1,10 @@ +import { H5pContents } from './types'; + +interface H5pContentsObject { + contentAsJson: object; + h5pAsJson: object; +} + export const isValidJSON = (obj: string) => { try { JSON.parse(obj); @@ -6,3 +13,17 @@ export const isValidJSON = (obj: string) => { return false; } }; + +export const extractH5pContents = (obj: string): H5pContents | undefined => { + try { + const parseResult: H5pContentsObject = JSON.parse(obj); + + const h5pContents: H5pContents = { + contentAsJson: JSON.stringify(parseResult.contentAsJson), + h5pAsJson: JSON.stringify(parseResult.h5pAsJson), + }; + return h5pContents; + } catch (e) { + return undefined; + } +};