tunapanda/h5p-standalone

How to implement this library with React?

Opened this issue · 3 comments

Hello,

I'm currently trying to implement this package inside a React app, but when I run I got this error:
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Below is my code:

import React, { useEffect } from "react";
import { H5P } from "h5p-standalone";

const App = () => {
  useEffect(() => {
    const el = document.getElementById("h5p-container");
    const options = {
      h5pJsonPath: "./h5p-folder",
      frameJs: "./h5p-assets/frame.bundle.js",
      frameCss: "./h5p-assets/styles/h5p.css",
    };
    const h5p = new H5P(el, options);

    h5p.then(() => {
      console.log('Loaded')
    }).catch((e) => {
      console.log(e)
    })
  }, []);

  return (
    <div id="h5p-container"></div>
  );
};

export default App;

Is there anything that is wrong in my code?

Thank you for your time.

Hello @danhddao1997 , could you figure it out? It would be great if you provide feedback, as I'm in the same path as you.
I tried this:

import { useCallback, useEffect, useState } from 'react'
import { Loading } from '../../../Animations/Loading'
import { useSession } from 'next-auth/react'

const H5PContainer = ({ location, course, topic }) => {

  const session = useSession()

  const initH5p = useCallback(
    async (contentLocation) => {
      const { H5P: H5PStandalone } = require('h5p-standalone')
      const h5pPath = `https://cdn.thinkeyschool.com/h5p/${contentLocation}`

      const options = {
        id: 'THINKeyLesson',
        h5pJsonPath: h5pPath,
        frameJs: '/h5p/dist/frame.bundle.js',
        frameCss: '/h5p/dist/styles/h5p.css',
      }
      let element = document.getElementById('h5p_container')
      // removeAllChildNodes(element)
      const h5pObject = await new H5PStandalone(element, options)
      console.log(h5pObject, 'h5pObject')

      // fireCompleteH5PTopic(H5P)
      setIsLoaderVisible(false)
    }, [location]
  )


  const [isLoaderVisible, setIsLoaderVisible] = useState(true)

   function removeAllChildNodes(parent) {
     // console.log(parent)
     while (parent.firstChild) {
      parent.removeChild(parent.firstChild)
    }
  }

  useEffect(() => {
    initH5p(location)
  }, [location, session.data.user.id, course.slug, topic])


  return (
    <div className='w-full mx-auto relative'>
      <div className={`${!isLoaderVisible ? 'hidden' : "flex"} absolute mx-auto my-auto left-1/2 top-1/2`}>
        <Loading />
      </div>
      {/* // H5P Container  */}
      <div className="h5p-container-wrapper" id={'h5p_container'} />
    </div>
  )
}

export default H5PContainer

I have the same problem. @DiegoGonzalezCruz can you give me the folder structure? I wonder if the h5p-folder and h5p-assets is located in the correct location.

Hey! I ended up syncing my wordpress folder to a s3 bucket with a cron (configured with a cdn).
Then, I read the cdn with my react app like this:

import { useCallback, useEffect, useMemo, useState } from 'react'
import { Loading } from '../../../Animations/Loading'
import { completeH5PTopic } from '../../../../lib/sinapsis/gamification'
import { useSession } from 'next-auth/react' // ES6
import ToastGamification from '../../../Notificaciones/ToastGamification'
import { toast } from 'react-hot-toast'
import { useRef } from 'react'
const POINTS_PER_TOPIC_COMPLETED = 5

const H5PContainer = ({ location, course, topic }) => {
  // console.log(topic, 'topic')
  const H5PStandalone = useMemo(() => require('h5p-standalone').H5P, [])
  const removeAllChildNodes = useCallback((parent) => {
    while (parent.firstChild) {
      parent.removeChild(parent.firstChild)
    }
  }, [])

  const session = useSession()
  const containerRef = useRef(null)
  const fireCompleteH5PTopic = useRef(false)

  const [isLoaderVisible, setIsLoaderVisible] = useState(true)

  useEffect(() => {
    const initH5p = async (contentLocation) => {
      const h5pPath = `https://[CDNURL]/h5p/${contentLocation}`

      const options = {
        id: 'THINKeyLesson',
        h5pJsonPath: h5pPath,
        frameJs: '/h5p/dist/frame.bundle.js',
        frameCss: '/h5p/dist/styles/h5p.css',
        // fullScreen: true
      }

      removeAllChildNodes(containerRef.current)

      const h5pInstance = await new H5PStandalone(containerRef.current, options)
      // console.log(sss, 'fired H5PStandalone')

      // fireCompleteH5PTopic()
      if (!fireCompleteH5PTopic.current) {
        fireCompleteH5PTopic.current = true
        fireCompleteH5PTopicFn(H5P, session.data.user.id, course.slug, topic)
      }

      setIsLoaderVisible(false)
    }

    initH5p(location)
  }, [
    location,
    session.data.user.id,
    course,
    topic,
    H5PStandalone,
    removeAllChildNodes,
  ])

  return (
    <div className=" w-full h-full ">
      {/* <div
        className={`${!isLoaderVisible ? 'hidden' : 'flex'
          } absolute mx-auto my-auto left-1/2 top-1/2`}
      >
        <Loading />
      </div> */}
      {/* // H5P Container  */}
      {isLoaderVisible && (<Loading />)}
      <div className=" p-5 h-full flex flex-col  overflow-scroll " ref={containerRef} style={{ width: '100%' }} />
    </div>
  )
}

export default H5PContainer

async function fireCompleteH5PTopicFn(H5P, userId, courseSlug, topic) {
  H5P.externalDispatcher.on('xAPI', async (event) => {
    if (event?.data?.statement?.result?.completion) {
      const res = await completeH5PTopic(event, userId, courseSlug, topic)
      toast.dismiss()
      if (res.status === 200) {
        toast.custom(
          (t) => (
            <ToastGamification
              isVisible={t.visible}
              onDismiss={() => toast.dismiss(t.id)}
              points={POINTS_PER_TOPIC_COMPLETED}
            />
          ),
        )
      }
      return true
    }
  })
}

These are relative to my nextjs project, in the public folder.
frameJs: '/h5p/dist/frame.bundle.js',
frameCss: '/h5p/dist/styles/h5p.css',