Skip to content

Commit

Permalink
feat(all): improved handling of native H5P app bar (#1743)
Browse files Browse the repository at this point in the history
* refactor(h5p-webcomponents): typed core H5P objects

* feat(h5p-webcomponent): access to copyright notice

* feat(h5p-react): completed copyright notice & documentation

* feat(h5p-rest-example): completed copyright notice example

* feat(h5p-server): added bottom info bar options to player

* test(h5p-server): corrected tests
  • Loading branch information
sr258 authored Sep 10, 2021
1 parent 22f8343 commit 45c3de7
Show file tree
Hide file tree
Showing 11 changed files with 477 additions and 81 deletions.
90 changes: 73 additions & 17 deletions docs/packages/h5p-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ using
[@lumieducation/h5p-server](https://www.npmjs.com/package/@lumieducation/h5p-server))**
that provides endpoints that do these things:

- get the required data about content (one for playing, one for editing)
- save content created in the editor
- serve all AJAX endpoints required by the H5P core
- get the required data about content (one for playing, one for editing)
- save content created in the editor
- serve all AJAX endpoints required by the H5P core

It is recommended to checkout the rest example that uses
@lumieducation/h5p-server to see how the component can be used in an
Expand Down Expand Up @@ -54,19 +54,19 @@ $ npm install @lumieducation/h5p-react
Then, import the component in your JavaScript / TypeScript code:

```js
import { H5PPlayerUI, H5PEditorUI } from '@lumieducation/h5p-react';
import { H5PPlayerUI, H5PEditorUI } from '@lumieducation/h5p-react';
```

Then, you can insert the components into your JSX:

```jsx
<H5PPlayerUI
id='player'
<H5PPlayerUI
id='player'
contentId='XXXX'
loadContentCallback = { async (contentId) => { /** retrieve content model from server and return it as Promise **/ } }
/>
<H5PEditorUI
id='editor'
<H5PEditorUI
id='editor'
contentId='XXXX'
loadContentCallback = { async (contentId) => { /** retrieve content model from server and return it as Promise **/} }
saveContentCallback = { async (contentId, requestBody) => { /** save content on server **/ } }/>
Expand All @@ -80,8 +80,8 @@ The components automatically (re-)load data from the server by calling
The content is automatically loaded from the server after **both** of these
conditions have been fulfilled (in any order):

1) `loadContentCallback` is set or changed
2) `contentId` is set
1. `loadContentCallback` is set or changed
2. `contentId` is set

You can change the value of `contentId` and it will discard the old content and
display the new one. You can also safely remove the component from the DOM.
Expand Down Expand Up @@ -137,7 +137,7 @@ using a renderer that simply returns the player model if you call
`H5PPlayer.render(...)`:

```ts
h5pPlayerOnServer.setRenderer(model => model);
h5pPlayerOnServer.setRenderer((model) => model);
const playerModel = await h5pPlayerOnServer.render(contentId, user);
// send playerModel to client and return it in loadContentCallback
```
Expand All @@ -163,16 +163,16 @@ H5PEditor.render(...) and H5PEditor.getContent(...). The render must be set to
simply return the editor model like this:

```js
h5pEditorOnServer.setRenderer(model => model);
h5pEditorOnServer.setRenderer((model) => model);
```

Notes:

- `contentId` can be `undefined` if the editor is used to create new content
- The library, metadata and params property of the returned object must
only be defined if `contentId` is defined.
- The callback should throw an error with a message in the message property if
something goes wrong.
- `contentId` can be `undefined` if the editor is used to create new content
- The library, metadata and params property of the returned object must
only be defined if `contentId` is defined.
- The callback should throw an error with a message in the message property if
something goes wrong.

#### saveContentCallback

Expand Down Expand Up @@ -241,3 +241,59 @@ detailed message can be found in the `message` paramter.

Note: You can also simply catch errors by wrapping the `save()` method in a `try
{...} catch {...}` block instead of subscribing to this event.

## Executing underlying H5P functionality

The H5PPlayerComponent offers properties and methods that can be used to do
things with the underlying "core" H5P data structures and objects:

### h5pInstance

This property is the object found in H5P.instances for the contentId of the
object. Contains things like the parameters of the content, its metadata and
structures created by the content type's JavaScript.

**The object is only available after the `initialized` event was fired.
Important: This object is only partially typed and there are more properties
and methods on it!**

### h5pObject

H5P has a global "H5P" namespace that is often used like this:

```ts
H5P.init(); // initialize H5P
H5P.externalDispatcher.on('xAPI', myCallback);
const dialog = new H5P.Dialog(...);
```

The problem you'll face when you try to use this namespace is that you typically
want to operate on the object inside the H5P iframe if a content type requires
iframe embedding. The h5pObject property solves this problem: It contains the
correct global H5P namespace regardless of whether there's an iframe or not.

You can use it like this:

```ts
const H5Pns = myPlayerComponent.h5pObject;
H5Pns.externalDispatcher.on('xAPI', myCallback);
const dialog = new H5Pns.Dialog(...);
```

**The property is only available after the `initialized` event was fired.
Important: This object is only partially typed and there are more properties and
methods on it!**

### getCopyrightHtml

You can get the copyright notice of the content by calling this method. Returns
undefined if there is not copyright information. Returns HTML code that you must
display somewhere.

### hasCopyrightInformation

Returns true if there is copyright information to be displayed.

### showCopyright

Shows the copyright information in a window overlaying the H5P content.
56 changes: 56 additions & 0 deletions docs/packages/h5p-webcomponents.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,62 @@ detailed message can be found in `event.detail.message`.
Note: You can also simply catch errors by wrapping the `save()` method in a `try
{...} catch {...}` block instead of subscribing to this event.

## Executing underlying H5P functionality

The H5PPlayerComponent offers properties and methods that can be used to do
things with the underlying "core" H5P data structures and objects:

### h5pInstance

This property is the object found in H5P.instances for the contentId of the
object. Contains things like the parameters of the content, its metadata and
structures created by the content type's JavaScript.

**The object is only available after the `initialized` event was fired.
Important: This object is only partially typed and there are more properties
and methods on it!**

### h5pObject

H5P has a global "H5P" namespace that is often used like this:

```ts
H5P.init(); // initialize H5P
H5P.externalDispatcher.on('xAPI', myCallback);
const dialog = new H5P.Dialog(...);
```

The problem you'll face when you try to use this namespace is that you typically
want to operate on the object inside the H5P iframe if a content type requires
iframe embedding. The h5pObject property solves this problem: It contains the
correct global H5P namespace regardless of whether there's an iframe or not.

You can use it like this:

```ts
const H5Pns = myPlayerComponent.h5pObject;
H5Pns.externalDispatcher.on('xAPI', myCallback);
const dialog = new H5Pns.Dialog(...);
```

**The property is only available after the `initialized` event was fired.
Important: This object is only partially typed and there are more properties and
methods on it!**

### getCopyrightHtml

You can get the copyright notice of the content by calling this method. Returns
undefined if there is not copyright information. Returns HTML code that you must
display somewhere.

### hasCopyrightInformation

Returns true if there is copyright information to be displayed.

### showCopyright

Shows the copyright information in a window overlaying the H5P content.

## Support

This work obtained financial support for development from the German
Expand Down
9 changes: 8 additions & 1 deletion packages/h5p-examples/src/expressRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ export default function (
try {
const h5pPage = await h5pPlayer.render(
req.params.contentId,
req.user
req.user,
{
showCopyButton: true,
showDownloadButton: true,
showFrame: true,
showH5PIcon: true,
showLicenseButton: true
}
);
res.send(h5pPage);
res.status(200).end();
Expand Down
63 changes: 56 additions & 7 deletions packages/h5p-react/src/H5PPlayerUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
defineElements,
H5PPlayerComponent,
IxAPIEvent,
IContext
IContext,
IH5PInstance,
IH5P
} from '@lumieducation/h5p-webcomponents';
import type { IPlayerModel } from '@lumieducation/h5p-server';

Expand Down Expand Up @@ -57,28 +59,75 @@ export default class H5PPlayerUI extends React.Component<{
this.unregisterEvents();
}

/**
* The internal H5P instance object of the H5P content.
*
* Only available after the `initialized` event was fired. Important: This
* object is only partially typed and there are more properties and methods
* on it!
*/
public get h5pInstance(): IH5PInstance | undefined {
return this.h5pPlayer.current?.h5pInstance;
}

/**
* The global H5P object / namespace (normally accessible through "H5P..."
* or "window.H5P") of the content type. Depending on the embed type this
* can be an object from the internal iframe, so you can use it to break the
* barrier of the iframe and execute JavaScript inside the iframe.
*
* Only available after the `initialized` event was fired. Important: This
* object is only partially typed and there are more properties and methods
* on it!
*/
public get h5pObject(): IH5P | undefined {
return this.h5pPlayer.current?.h5pObject;
}

/**
* Returns the copyright notice in HTML that you can insert somewhere to
* display it. Undefined if there is no copyright information.
*/
public getCopyrightHtml(): string {
return this.h5pPlayer.current?.getCopyrightHtml() ?? '';
}

public getSnapshotBeforeUpdate(): void {
// Should the old editor instance be destroyed, we unregister from it...
this.unregisterEvents();
return null;
}

/**
* @returns true if there is copyright information to be displayed.
*/
public hasCopyrightInformation(): boolean {
return this.h5pPlayer.current?.hasCopyrightInformation();
}

public render(): React.ReactNode {
return (
<h5p-player
ref={this.h5pPlayer}
content-id={this.props.contentId}
></h5p-player>
/>
);
}

/**
* Displays the copyright notice in the regular H5P way.
*/
public showCopyright(): void {
this.h5pPlayer.current?.showCopyright();
}

private loadContentCallbackWrapper = (
contentId: string
): Promise<IPlayerModel> => {
return this.props.loadContentCallback(contentId);
};
): Promise<IPlayerModel> => this.props.loadContentCallback(contentId);

private onInitialized = (event: CustomEvent<{ contentId: string }>) => {
private onInitialized = (
event: CustomEvent<{ contentId: string }>
): void => {
if (this.props.onInitialized) {
this.props.onInitialized(event.detail.contentId);
}
Expand All @@ -90,7 +139,7 @@ export default class H5PPlayerUI extends React.Component<{
event: IxAPIEvent;
statement: any;
}>
) => {
): void => {
if (this.props.onxAPIStatement) {
this.props.onxAPIStatement(
event.detail.statement,
Expand Down
Loading

0 comments on commit 45c3de7

Please sign in to comment.