Skip to content

Commit

Permalink
Merge pull request #113 from NDLA-H5P/feature/h5p-widget-interface
Browse files Browse the repository at this point in the history
  • Loading branch information
boyum authored Mar 14, 2022
2 parents c957f4d + fe1a0fd commit d733a5e
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 64 deletions.
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"machineName": "H5PEditor.NDLATimeline",
"majorVersion": 0,
"minorVersion": 0,
"patchVersion": 2,
"patchVersion": 3,
"runnable": 0,
"preloadedJs": [
{
Expand Down
68 changes: 43 additions & 25 deletions src/apps/NDLATagsPicker.app.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import chroma from "chroma-js";
import * as React from "react";
import { FC, useEffect, useState } from "react";
import Select, { StylesConfig } from "react-select";
import { EditorTagType } from "../types/EditorTagType";
import Select, { MultiValue, StylesConfig } from "react-select";
import { H5P } from "../H5P/H5P.util";
import { H5PForm } from "../types/H5P/H5PForm";
import { PickerTagType } from "../types/PickerTagType";

type NDLATagsPickerAppProps = {
updateTags: (tags: Array<PickerTagType>) => void;
storeTags: (tags: Array<PickerTagType>) => void;
tags: Array<PickerTagType>;
parent: H5PForm;
fieldNameToWatch: string;
Expand All @@ -25,48 +25,65 @@ const getSemanticsParent = (h5pForm: H5PForm): H5PForm => {
};

export const NDLATagsPickerApp: FC<NDLATagsPickerAppProps> = ({
updateTags,
storeTags,
tags: initialTags,
parent,
fieldNameToWatch,
label,
}) => {
const [tags, setTags] = useState<Array<PickerTagType>>(initialTags);
const [selectedTags, setSelectedTags] = useState(initialTags);
const [availableTags, setAvailableTags] = useState(initialTags);

useEffect(() => {
const eldestParent = getSemanticsParent(parent);

const interval = window.setInterval(() => {
const watchedField = eldestParent?.params?.[
fieldNameToWatch
] as Array<EditorTagType> | null;
] as Array<PickerTagType> | null;

const noTagsYet = !watchedField;
if (noTagsYet) {
return;
}

const editorTags = watchedField.map(tag => ({
...tag,
isActive: false,
}));

const updatedTags = tags.filter(tag =>
editorTags.find(t => tag.id === t.id),
);
watchedField.forEach(tag => {
const noId = !tag.id;
if (noId) {
// eslint-disable-next-line no-param-reassign
tag.id = H5P.createUUID();
}

for (const tag of editorTags) {
if (!tags.find(t => t.id === tag.id)) {
updatedTags.push(tag);
const noColor = !tag.color?.trim();
if (noColor) {
// eslint-disable-next-line no-param-reassign
tag.color = "#000";
}
}
});

const hasChanges =
JSON.stringify([...watchedField]) !== JSON.stringify(availableTags);
if (hasChanges) {
setAvailableTags([...watchedField]);

const updatedSelectedTags = selectedTags.filter(
({ id }) => !!watchedField.find(tag => tag.id === id),
);

setTags(updatedTags);
updateTags(updatedTags);
setSelectedTags(updatedSelectedTags);
storeTags(updatedSelectedTags);
}
}, 1000);

return () => clearInterval(interval);
}, [fieldNameToWatch, parent, tags, updateTags]);
}, [availableTags, fieldNameToWatch, parent, selectedTags, storeTags]);

const onChange = (newTags: MultiValue<PickerTagType>): void => {
const updatedTags = newTags as Array<PickerTagType>;

setSelectedTags(updatedTags);
storeTags(updatedTags);
};

const colourStyles: StylesConfig<PickerTagType, true> = {
control: styles => ({ ...styles, backgroundColor: "white" }),
Expand Down Expand Up @@ -148,13 +165,14 @@ export const NDLATagsPickerApp: FC<NDLATagsPickerAppProps> = ({
</div>

<Select
options={tags
.filter(tag => tag.name?.trim().length > 0)
.map(tag => ({ ...tag, label: tag.name }))}
options={availableTags.filter(tag => tag.name?.trim().length > 0)}
closeMenuOnSelect={false}
defaultValue={tags.filter(tag => tag.isActive)}
defaultValue={selectedTags}
isMulti
styles={colourStyles}
onChange={newTags => onChange(newTags)}
getOptionLabel={tag => tag.name}
getOptionValue={tag => tag.id}
/>
</>
);
Expand Down
38 changes: 38 additions & 0 deletions src/types/H5P/H5PWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { H5P } from "../../H5P/H5P.util";
import { H5PField } from "./H5PField";
import { H5PForm } from "./H5PForm";
import { H5PSetValue } from "./H5PSetValue";

export class H5PWidget<
Field extends H5PField,
Params,
> extends H5P.EventDispatcher {
public field: Field;

protected parent: H5PForm;

protected params: Params | undefined;

protected setValue: H5PSetValue<Params>;

protected wrapper: HTMLElement;

constructor(
parent: H5PForm,
field: Field,
params: Params | undefined,
setValue: H5PSetValue<Params>,
) {
super();
this.wrapper = H5PWidget.createWrapperElement();

this.parent = parent;
this.field = field;
this.params = params;
this.setValue = setValue;
}

private static createWrapperElement(): HTMLDivElement {
return document.createElement("div");
}
}
5 changes: 5 additions & 0 deletions src/types/H5P/IH5PWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface IH5PWidget {
appendTo($container: JQuery<HTMLElement>): void;
validate(): boolean;
remove(): void;
}
1 change: 0 additions & 1 deletion src/types/PickerTagType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ export type PickerTagType = {
id: string;
name: string;
color: string;
isActive: boolean;
};
72 changes: 35 additions & 37 deletions src/widgets/NDLATagsPicker.widget.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,38 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { NDLATagsPickerApp } from "../apps/NDLATagsPicker.app";
import { H5P } from "../H5P/H5P.util";
import { H5PField, H5PFieldList } from "../types/H5P/H5PField";
import { H5PFieldList } from "../types/H5P/H5PField";
import { H5PForm } from "../types/H5P/H5PForm";
import { H5PSetValue } from "../types/H5P/H5PSetValue";
import { H5PWidget } from "../types/H5P/H5PWidget";
import { IH5PWidget } from "../types/H5P/IH5PWidget";
import { PickerTagType } from "../types/PickerTagType";

export type NDLATagsPickerParams = {
type Field = H5PFieldList & { fieldNameToWatch: string };

type Params = {
tags: Array<PickerTagType>;
};

export class NDLATagsPicker extends H5P.EventDispatcher {
public field: H5PField;

private wrapper: HTMLElement;

private semantics: H5PFieldList & { fieldNameToWatch: string };

export class NDLATagsPicker
extends H5PWidget<Field, Params>
implements IH5PWidget
{
constructor(
parent: H5PForm,
semantics: H5PFieldList & { fieldNameToWatch: string },
params: NDLATagsPickerParams | undefined,
setValue: H5PSetValue<NDLATagsPickerParams>,
field: Field,
params: Params | undefined,
setValue: H5PSetValue<Params>,
) {
super();
this.wrapper = NDLATagsPicker.createWrapperElement();
this.field = semantics;
super(parent, field, params, setValue);

console.info("Tags picker", { params, semantics });
console.info("Tags picker", { params, field });

if (!("fieldNameToWatch" in semantics)) {
if (!("fieldNameToWatch" in field)) {
throw new Error(
"Missing field `fieldNameToWatch`. It should be the name of the corresponding editor field",
);
}

ReactDOM.render(
<NDLATagsPickerApp
updateTags={tags => setValue(semantics, { tags })}
tags={params?.tags ?? []}
fieldNameToWatch={semantics.fieldNameToWatch}
parent={parent}
label={semantics.label}
/>,
this.wrapper,
);

this.semantics = semantics;
}

appendTo($container: JQuery<HTMLElement>): void {
Expand All @@ -59,8 +44,25 @@ export class NDLATagsPicker extends H5P.EventDispatcher {
return;
}

containerElement.appendChild(this.wrapper);
this.wrapper.className = `h5p-tags-picker field field-name-${this.semantics.name}`;
const { parent, field, params, setValue, wrapper } = this;

wrapper.classList.add(
"h5p-tags-picker",
"field",
`field-name-${field.name}`,
);
containerElement.appendChild(wrapper);

ReactDOM.render(
<NDLATagsPickerApp
storeTags={tags => setValue(field, { tags })}
tags={params?.tags ?? []}
fieldNameToWatch={field.fieldNameToWatch}
parent={parent}
label={field.label}
/>,
wrapper,
);
}

validate(): boolean {
Expand All @@ -69,8 +71,4 @@ export class NDLATagsPicker extends H5P.EventDispatcher {

// eslint-disable-next-line @typescript-eslint/no-empty-function
remove(): void {}

private static createWrapperElement(): HTMLDivElement {
return document.createElement("div");
}
}

0 comments on commit d733a5e

Please sign in to comment.