diff --git a/.github/workflows/cdelivery-s3-caller.yml b/.github/workflows/cdelivery-s3-caller.yml
index fab893fbe..dd9347f2a 100644
--- a/.github/workflows/cdelivery-s3-caller.yml
+++ b/.github/workflows/cdelivery-s3-caller.yml
@@ -9,23 +9,24 @@ on:
jobs:
graasp-deploy-s3-workflow:
name: Graasp Builder
- uses: graasp/graasp-deploy/.github/workflows/cdelivery-s3.yml@bea834ec77096ee815ec190d28d7e2cf7d94ada1
+ uses: graasp/graasp-deploy/.github/workflows/cdelivery-s3.yml@1a7c4c74273be6fd1c56eb6c5a8fb4af2d211b86
with:
build-folder: 'build'
tag: ${{ github.event.client_payload.tag }}
secrets:
+ api-host: ${{ secrets.REACT_APP_API_HOST_STAGE }}
+ authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_STAGE }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_STAGE }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGE }}
aws-region: ${{ secrets.AWS_REGION_STAGE }}
aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_STAGE }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGE }}
cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_STAGE }}
- api-host: ${{ secrets.REACT_APP_API_HOST_STAGE }}
- show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
- authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_STAGE }}
- graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_STAGE }}
- graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_STAGE }}
- graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_STAGE }}
+ domain: ${{ secrets.REACT_APP_DOMAIN_STAGE }}
ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_STAGE }}
+ graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_STAGE }}
+ graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_STAGE }}
+ graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_STAGE }}
+ h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_STAGE }}
hidden-item-tag-id: ${{ secrets.REACT_APP_HIDDEN_ITEM_TAG_ID_STAGE }}
- domain: ${{ secrets.REACT_APP_DOMAIN_STAGE }}
sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }}
+ show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
diff --git a/.github/workflows/cdeployment-s3-caller.yml b/.github/workflows/cdeployment-s3-caller.yml
index cb50a269a..114babba8 100644
--- a/.github/workflows/cdeployment-s3-caller.yml
+++ b/.github/workflows/cdeployment-s3-caller.yml
@@ -13,25 +13,26 @@ jobs:
name: Graasp Builder
# Replace 'main' with the hash of a commit, so it points to an specific version of the reusable workflow that is used
# Reference reusable workflow file. Using the commit SHA is the safest for stability and security
- uses: graasp/graasp-deploy/.github/workflows/cdeployment-s3.yml@bea834ec77096ee815ec190d28d7e2cf7d94ada1
- # Replace input build-folder if needed.
+ uses: graasp/graasp-deploy/.github/workflows/cdeployment-s3.yml@1a7c4c74273be6fd1c56eb6c5a8fb4af2d211b86
+ # Replace input build-folder if needed.
with:
build-folder: 'build'
tag: ${{ github.event.client_payload.tag }}
# Insert required secrets based on repository with the following format: ${{ secrets.SECRET_NAME }}
secrets:
+ api-host: ${{ secrets.REACT_APP_API_HOST_PROD }}
+ authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_PROD }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }}
aws-region: ${{ secrets.AWS_REGION_PROD }}
aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_PROD }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }}
cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_PROD }}
- api-host: ${{ secrets.REACT_APP_API_HOST_PROD }}
- show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
- authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_PROD }}
- graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_PROD }}
- graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_PROD }}
- graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_PROD }}
+ domain: ${{ secrets.REACT_APP_DOMAIN_PROD }}
ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_PROD }}
+ graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_PROD }}
+ graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_PROD }}
+ graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_PROD }}
+ h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_PROD }}
hidden-item-tag-id: ${{ secrets.REACT_APP_HIDDEN_ITEM_TAG_ID_PROD }}
- domain: ${{ secrets.REACT_APP_DOMAIN_PROD }}
sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }}
+ show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
diff --git a/.github/workflows/cintegration-s3-caller.yml b/.github/workflows/cintegration-s3-caller.yml
index f9a3f30d3..63c3eac31 100644
--- a/.github/workflows/cintegration-s3-caller.yml
+++ b/.github/workflows/cintegration-s3-caller.yml
@@ -16,22 +16,23 @@ jobs:
graasp-deploy-s3-workflow:
name: Graasp Builder
# Reference reusable workflow file. Using the commit SHA is the safest for stability and security
- uses: graasp/graasp-deploy/.github/workflows/cintegration-s3.yml@bea834ec77096ee815ec190d28d7e2cf7d94ada1
+ uses: graasp/graasp-deploy/.github/workflows/cintegration-s3.yml@1a7c4c74273be6fd1c56eb6c5a8fb4af2d211b86
with:
build-folder: 'build'
secrets:
+ api-host: ${{ secrets.REACT_APP_API_HOST_DEV }}
+ authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_DEV }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEV }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }}
aws-region: ${{ secrets.AWS_REGION_DEV }}
aws-s3-bucket-name: ${{ secrets.AWS_S3_BUCKET_NAME_GRAASP_COMPOSE_DEV }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }}
cloudfront-distribution-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_GRAASP_COMPOSE_DEV }}
- api-host: ${{ secrets.REACT_APP_API_HOST_DEV }}
- show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
- authentication-host: ${{ secrets.REACT_APP_AUTHENTICATION_HOST_DEV }}
- graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_DEV }}
- graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_DEV }}
- graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_DEV }}
+ domain: ${{ secrets.REACT_APP_DOMAIN_DEV }}
ga-measurement-id: ${{ secrets.REACT_APP_GA_MEASUREMENT_ID_DEV }}
+ graasp-analyzer-host: ${{ secrets.ANALYZER_CLIENT_HOST_DEV }}
+ graasp-explorer-host: ${{ secrets.EXPLORER_CLIENT_HOST_DEV }}
+ graasp-perform-host: ${{ secrets.PLAYER_CLIENT_HOST_DEV }}
+ h5p-integration-url: ${{ secrets.H5P_INTEGRATION_URL_DEV }}
hidden-item-tag-id: ${{ secrets.REACT_APP_HIDDEN_ITEM_TAG_ID_DEV }}
- domain: ${{ secrets.REACT_APP_DOMAIN_DEV }}
sentry-dsn: ${{ secrets.REACT_APP_SENTRY_DSN }}
+ show-notifications: ${{ secrets.REACT_APP_SHOW_NOTIFICATIONS }}
diff --git a/README.md b/README.md
index 63d6a886e..9e43c908d 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ REACT_APP_API_HOST=http://localhost:3000
PORT=3111
REACT_APP_SHOW_NOTIFICATIONS=true
REACT_APP_AUTHENTICATION_HOST=http://localhost:3001
+REACT_APP_H5P_INTEGRATION_URL=
```
4. Run `yarn start`. The client should be accessible at `localhost:3111`
@@ -36,6 +37,7 @@ REACT_APP_API_HOST=http://localhost:3000
PORT=3111
REACT_APP_SHOW_NOTIFICATIONS=false
REACT_APP_NODE_ENV=test
+REACT_APP_H5P_INTEGRATION_URL=
```
Run `yarn cypress`. This should run every tests headlessly.
diff --git a/package.json b/package.json
index 743b1fea8..cc64626ff 100644
--- a/package.json
+++ b/package.json
@@ -97,7 +97,7 @@
"@commitlint/config-conventional": "16.2.1",
"@cypress/code-coverage": "3.9.12",
"@cypress/instrument-cra": "1.4.0",
- "@graasp/websockets": "github:graasp/graasp-websockets.git#master",
+ "@graasp/websockets": "github:graasp/graasp-websockets.git",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
diff --git a/src/components/item/ItemContent.js b/src/components/item/ItemContent.js
index 8ae3b4fec..3f7084d19 100644
--- a/src/components/item/ItemContent.js
+++ b/src/components/item/ItemContent.js
@@ -1,9 +1,15 @@
-import React, { useContext } from 'react';
-import PropTypes from 'prop-types';
-import { Record } from 'immutable';
+import { Api, MUTATION_KEYS } from '@graasp/query-client';
+import { AppItem, DocumentItem, FileItem, H5PItem, LinkItem } from '@graasp/ui';
import { makeStyles } from '@material-ui/core';
-import { FileItem, DocumentItem, LinkItem, AppItem } from '@graasp/ui';
-import { MUTATION_KEYS, Api } from '@graasp/query-client';
+import { Record } from 'immutable';
+import PropTypes from 'prop-types';
+import React, { useContext } from 'react';
+import {
+ API_HOST,
+ CONTEXT_BUILDER,
+ H5P_INTEGRATION_URL,
+ ITEM_DEFAULT_HEIGHT,
+} from '../../config/constants';
import { hooks, useMutation } from '../../config/queryClient';
import {
buildFileItemId,
@@ -13,19 +19,14 @@ import {
ITEM_SCREEN_ERROR_ALERT_ID,
} from '../../config/selectors';
import { ITEM_KEYS, ITEM_TYPES } from '../../enums';
-import Loader from '../common/Loader';
+import { buildDocumentExtra, getDocumentExtra } from '../../utils/itemExtra';
import ErrorAlert from '../common/ErrorAlert';
-import {
- API_HOST,
- ITEM_DEFAULT_HEIGHT,
- CONTEXT_BUILDER,
-} from '../../config/constants';
+import Loader from '../common/Loader';
+import { CurrentUserContext } from '../context/CurrentUserContext';
import { LayoutContext } from '../context/LayoutContext';
+import ItemActions from '../main/ItemActions';
import Items from '../main/Items';
-import { buildDocumentExtra, getDocumentExtra } from '../../utils/itemExtra';
import NewItemButton from '../main/NewItemButton';
-import { CurrentUserContext } from '../context/CurrentUserContext';
-import ItemActions from '../main/ItemActions';
const { useChildren, useFileContent } = hooks;
@@ -173,6 +174,20 @@ const ItemContent = ({ item, enableEditing, permission }) => {
/>
>
);
+ case ITEM_TYPES.H5P: {
+ const contentId = item.get('extra')?.h5p?.contentId;
+ if (!contentId) {
+ return ;
+ }
+
+ return (
+
+ );
+ }
default:
return ;
diff --git a/src/components/main/ImportH5P.js b/src/components/main/ImportH5P.js
new file mode 100644
index 000000000..b8cc88b49
--- /dev/null
+++ b/src/components/main/ImportH5P.js
@@ -0,0 +1,99 @@
+import { routines } from '@graasp/query-client';
+import Typography from '@material-ui/core/Typography';
+import '@uppy/dashboard/dist/style.css';
+import { Dashboard } from '@uppy/react';
+import React, { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useMatch } from 'react-router';
+import notifier from '../../config/notifier';
+import { buildItemPath } from '../../config/paths';
+import { H5P_DASHBOARD_UPLOADER_ID } from '../../config/selectors';
+import { configureH5PImportUppy } from '../../utils/uppy';
+
+const ImportH5P = () => {
+ const [uppy, setUppy] = useState(null);
+ const match = useMatch(buildItemPath());
+ const itemId = match?.params?.itemId;
+ const { t } = useTranslation();
+
+ const onComplete = (result) => {
+ // update app on complete
+ // todo: improve with websockets or by receiving corresponding items
+ if (!result?.failed.length) {
+ notifier({ type: routines.importH5PRoutine.SUCCESS });
+ }
+ };
+ const onError = (error) => {
+ notifier({ type: routines.importH5PRoutine.FAILURE, payload: { error } });
+ };
+
+ const onUpload = () => {
+ notifier({ type: routines.importH5PRoutine.REQUEST });
+ };
+
+ const applyUppy = () =>
+ setUppy(
+ configureH5PImportUppy({
+ itemId,
+ onComplete,
+ onError,
+ onUpload,
+ }),
+ );
+
+ useEffect(() => {
+ applyUppy();
+
+ return () => {
+ uppy?.close();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ applyUppy();
+ // update uppy configuration each time itemId changes
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [itemId]);
+
+ if (!uppy) {
+ return null;
+ }
+
+ return (
+ <>
+ {t('Import H5P rich content')}
+
+ {t(
+ 'You can upload H5P rich content by uploading exported .h5p files (e.g. from H5P.com, external Moodle services, etc).',
+ )}
+
+
+
+ {t(
+ 'Once your file is accepted, it will take several minutes for it to be available.',
+ )}
+
+
+
+
+ >
+ );
+};
+
+export default ImportH5P;
diff --git a/src/components/main/ItemTypeTabs.js b/src/components/main/ItemTypeTabs.js
index 417763b35..967072db4 100644
--- a/src/components/main/ItemTypeTabs.js
+++ b/src/components/main/ItemTypeTabs.js
@@ -11,6 +11,7 @@ import {
CREATE_ITEM_DOCUMENT_ID,
CREATE_ITEM_APP_ID,
CREATE_ITEM_ZIP_ID,
+ CREATE_ITEM_H5P_ID,
} from '../../config/selectors';
const useStyles = makeStyles((theme) => ({
@@ -97,6 +98,13 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }) => {
icon={zipIcon}
classes={{ wrapper: classes.wrapper }}
/>
+ }
+ classes={{ wrapper: classes.wrapper }}
+ />
);
};
diff --git a/src/components/main/NewItemModal.js b/src/components/main/NewItemModal.js
index 55d14c6f9..3c14b0f5f 100644
--- a/src/components/main/NewItemModal.js
+++ b/src/components/main/NewItemModal.js
@@ -25,6 +25,7 @@ import AppForm from '../item/form/AppForm';
import ItemTypeTabs from './ItemTypeTabs';
import ImportZip from './ImportZip';
import { DOUBLE_CLICK_DELAY_MS } from '../../config/constants';
+import ImportH5P from './ImportH5P';
const useStyles = makeStyles((theme) => ({
dialogContent: {
@@ -104,6 +105,8 @@ const NewItemModal = ({ open, handleClose }) => {
return ;
case ITEM_TYPES.ZIP:
return ;
+ case ITEM_TYPES.H5P:
+ return ;
case ITEM_TYPES.APP:
return (
{
);
case ITEM_TYPES.FILE:
case ITEM_TYPES.ZIP:
+ case ITEM_TYPES.H5P:
return (