Skip to content

Commit

Permalink
feat: export options: rights, embed code and layout (#2032)
Browse files Browse the repository at this point in the history
* feat: added show rights and embed options to export

* refactor: fixed imports

* refactor: extracted export functionality to controller

* feat: added embed hider in reporter

* feat: added locales

* feat: app remembers last directory in file picker

* refactor: dependency injection for electron state

* refactor: make mui happy

* feat: added margin and centering in simple export template

* feat: add visual display options to export dialog

* feat: export dialog logic and localization

* feat: implemented display options

* feat: implement display options in SCORM

* refactor: client doesn't use paths when opening or saving

The old way of saving and loading h5ps was insecure, as it
would have been possible for a malicious H5P library to send client
requests that do something undesired on the user's disk. Now there are
only file handles that reference files the user has picked in the file
picker before.

* feat: custom CSS files can be added to exports

* fix: localized updater message box

* refactor: added comments

* fix: validation in export dialog

* chore(deps): update to react-scripts 5 and Node 17
  • Loading branch information
sr258 committed Jan 15, 2022
1 parent 99dbc46 commit 58995cc
Show file tree
Hide file tree
Showing 73 changed files with 37,983 additions and 71,279 deletions.
1 change: 1 addition & 0 deletions .dcignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.*
62,127 changes: 19,208 additions & 42,919 deletions client/package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@
"@testing-library/react": "12.1.2",
"@testing-library/user-event": "13.5.0",
"@types/jest": "27.0.2",
"@types/node": "16.11.19",
"@types/node": "17.0.8",
"@types/path-browserify": "^1.0.0",
"@types/react": "17.0.38",
"@types/react-dom": "17.0.11",
"classnames": "2.3.1",
"i18next": "21.6.6",
"i18next-http-backend": "1.3.1",
"lodash": "4.17.21",
"notistack": "2.0.3",
"path-browserify": "^1.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "11.15.3",
"react-redux": "7.2.6",
"react-redux-i18n": "1.9.3",
"react-router-dom": "5.3.0",
"react-scripts": "4.0.3",
"react-scripts": "5.0.0",
"redux": "4.1.2",
"redux-thunk": "2.4.1",
"save": "2.4.0",
Expand Down Expand Up @@ -75,6 +77,7 @@
"@types/shortid": "0.0.29",
"@types/socket.io-client": "1.4.36",
"@types/superagent": "4.1.14",
"mini-css-extract-plugin": "2.4.5",
"prettier": "2.5.1",
"redux-mock-store": "1.5.4"
},
Expand Down
72 changes: 72 additions & 0 deletions client/src/helpers/contentTypeWidths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Sets default width limits for all known content types
*/
export const getDefaultContentTypeWidth = (mainLibrary: string): number => {
const machineName = mainLibrary.substr(0, mainLibrary.indexOf(' '));
switch (machineName) {
case 'H5P.Audio':
case 'H5P.AudioRecorder':
case 'H5P.Column':
case 'H5P.Dialogcards':
case 'H5P.GreetingCard':
case 'H5P.KewarCode':
return 800;

case 'H5P.Accordion':
case 'H5P.AdvancedBlanks':
case 'H5P.AdventCalendar':
case 'H5P.Agamotto':
case 'H5P.Bingo':
case 'H5P.Blanks':
case 'H5P.Chart':
case 'H5P.Collage':
case 'H5P.Cornell':
case 'H5P.Crossword':
case 'H5P.Dictation':
case 'H5P.DocumentationTool':
case 'H5P.DragText':
case 'H5P.Essay':
case 'H5P.FacebookFeedPage':
case 'H5P.FindTheWords':
case 'H5P.GuessTheAnswer':
case 'H5P.ImagePair':
case 'H5P.InteractiveBook':
case 'H5P.MarkTheWords':
case 'H5P.MemoryGame':
case 'H5P.MultiChoice':
case 'H5P.PersonalityQuiz':
case 'H5P.PickTheSymbols':
case 'H5P.Questionnaire':
case 'H5P.QuestionSet':
case 'H5P.SingleChoiceSet':
case 'H5P.SortParagraphs':
case 'H5P.SpeakTheWords':
case 'H5P.SpeakTheWordsSet':
case 'H5P.Summary':
case 'H5P.TrueFalse':
case 'H5P.TwitterUserFeed':
return 1100;

case 'H5P.ArithmeticQuiz':
case 'H5P.ArScavenger':
case 'H5P.BranchingScenario':
case 'H5P.CoursePresentation':
case 'H5P.DragQuestion':
case 'H5P.Flashcards':
case 'H5P.ImageHotspotQuestion':
case 'H5P.ImageHotspots':
case 'H5P.ImageJuxtaposition':
case 'H5P.ImageMultipleHotspotQuestion':
case 'H5P.ImageSequencing':
case 'H5P.ImageSlider':
case 'H5P.ImpressivePresentation':
case 'H5P.InteractiveVideo':
case 'H5P.MultiMediaChoice':
case 'H5P.ThreeImage':
case 'H5P.Timeline':
return 1400;

default:
return 1100;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,28 @@ export function exportContent(
contentId: string,
includeReporter: boolean,
format: 'bundle' | 'external' | 'scorm',
options: { masteryScore?: string }
options: {
addCss: boolean;
cssFileHandleId: string;
marginX: number;
marginY: number;
masteryScore?: string;
maxWidth: number;
restrictWidthAndCenter: boolean;
showEmbed: boolean;
showRights: boolean;
}
): Promise<superagent.Response> {
return superagent.get(
`/api/v1/h5p/${contentId}/export?includeReporter=${includeReporter}&format=${format}${
options.masteryScore ? `&masteryScore=${options.masteryScore}` : ''
}`
}&showRights=${options.showRights.toString()}&showEmbed=${options.showEmbed.toString()}&marginX=${
options.marginX
}&marginY=${
options.marginY
}&restrictWidthAndCenter=${options.restrictWidthAndCenter.toString()}&maxWidth=${
options.maxWidth
}${options.addCss ? `&cssFileHandleId=${options.cssFileHandleId}` : ''}`
);
}

Expand Down Expand Up @@ -51,14 +67,16 @@ export function deleteH5P(contentId: string): Promise<superagent.Response> {

export function exportH5P(
contentId: string,
path?: string
fileHandleId?: string
): Promise<superagent.Response> {
return superagent.get(`/api/v1/lumi?contentId=${contentId}&path=${path}`);
return superagent.get(
`/api/v1/lumi?contentId=${contentId}&fileHandleId=${fileHandleId}`
);
}

export function importH5P(path: string): Promise<superagent.Response> {
export function importH5P(fileHandleId: string): Promise<superagent.Response> {
return superagent.post(`/api/v1/lumi`).send({
path
fileHandleId
});
}

Expand All @@ -71,8 +89,12 @@ export function updateH5P(
.send(content);
}

export function openFiles(): Promise<superagent.Response> {
return superagent.get('/api/v1/lumi/open_files');
export function pickH5PFiles(): Promise<superagent.Response> {
return superagent.get('/api/v1/lumi/pick_h5p_files');
}

export function pickCSSFile(): Promise<superagent.Response> {
return superagent.get('/api/v1/lumi/pick_css_file');
}

/**
Expand Down
1 change: 0 additions & 1 deletion client/src/state/Analytics/AnalyticsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export function importAnalytics(): any {
} catch (error: any) {
Sentry.captureException(error);

console.log(error);
dispatch({
payload: { message: JSON.stringify(error) },
type: ANALYTICS_IMPORT_ERROR
Expand Down
59 changes: 40 additions & 19 deletions client/src/state/H5PEditor/H5PEditorActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import { track } from '../track/actions';

import store from '../index';

import * as api from './H5PApi';
import * as api from '../../services/H5PApi';

const log = new Logger('actions:tabs');

Expand Down Expand Up @@ -132,7 +132,17 @@ export function cancelExportH5P(contentId?: string) {
export function exportH5P(
includeReporter: boolean,
format: 'bundle' | 'external' | 'scorm',
options: { masteryScore?: string }
options: {
addCss: boolean;
cssFileHandleId: string;
marginX: number;
marginY: number;
masteryScore?: string;
maxWidth: number;
restrictWidthAndCenter: boolean;
showEmbed: boolean;
showRights: boolean;
}
): any {
return async (dispatch: any) => {
try {
Expand Down Expand Up @@ -203,13 +213,15 @@ export function exportH5P(

export function openH5P(): any {
return (dispatch: any) => {
api.openFiles()
api.pickH5PFiles()
.then((response) => {
const files = response.body;

files.forEach((file: string) => {
dispatch(importH5P(file));
});
files.forEach(
(file: { fileHandleId: string; path: string }) => {
dispatch(importH5P(file.fileHandleId, file.path));
}
);
})
.catch((error) => Sentry.captureException(error));
return dispatch;
Expand All @@ -223,14 +235,14 @@ export function openTab(tab?: Partial<ITab>): any {
track(
'H5PEditor',
'openTab',
tab?.path ? 'existing h5p' : 'new h5p'
tab?.fileHandleId ? 'existing h5p' : 'new h5p'
)
);

dispatch({
payload: {
id: shortid(),
tab
tab,
id: shortid()
},
type: H5PEDITOR_OPEN_TAB
});
Expand Down Expand Up @@ -439,18 +451,18 @@ export function deleteH5P(
}

export function save(
path?: string
fileHandleId?: string
): ThunkAction<void, null, null, SaveActions> {
return async (dispatch: any) => {
try {
const data = await dispatch(updateContentOnServer());

dispatch({
payload: { id: data.contentId, path },
payload: { id: data.contentId },
type: H5PEDITOR_SAVE_REQUEST
});

const response = await api.exportH5P(data.contentId, path);
const response = await api.exportH5P(data.contentId, fileHandleId);

if (response.status !== 200) {
throw new Error(`Error while saving H5P: ${response.text}`);
Expand All @@ -463,8 +475,11 @@ export function save(
}

return dispatch({
// tslint:disable-next-line: object-shorthand-properties-first
payload: { id: data.contentId, ...response.body },
payload: {
id: data.contentId,
fileHandleId: response.body.fileHandleId,
path: response.body.path
},
type: H5PEDITOR_SAVE_SUCCESS
});
} catch (error: any) {
Expand All @@ -478,7 +493,7 @@ export function save(

return dispatch({
error,
payload: { path },
payload: { fileHandleId },
type: H5PEDITOR_SAVE_ERROR
});
}
Expand All @@ -487,14 +502,15 @@ export function save(
}

export function importH5P(
fileHandleId: string,
path: string,
history?: H.History
): ThunkAction<void, null, null, SaveActions> {
return (dispatch: any) => {
const tabId = shortid();

dispatch({
payload: { tabId, path },
payload: { tabId, fileHandleId, path },
type: H5P_IMPORT_REQUEST
});

Expand All @@ -503,7 +519,7 @@ export function importH5P(
}

return api
.importH5P(path)
.importH5P(fileHandleId)
.then(({ body }) => {
try {
dispatch(track('H5P', 'open', body.metadata.mainLibrary));
Expand All @@ -512,7 +528,12 @@ export function importH5P(
}

dispatch({
payload: { tabId, path, h5p: body },
payload: {
tabId,
fileHandleId,
path,
h5p: body
},
type: H5P_IMPORT_SUCCESS
});
})
Expand All @@ -522,7 +543,7 @@ export function importH5P(
dispatch({
error,
payload: {
path
fileHandleId
},
type: H5P_IMPORT_ERROR
});
Expand Down
12 changes: 8 additions & 4 deletions client/src/state/H5PEditor/H5PEditorReducer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Logger from '../../helpers/Logger';
import * as Sentry from '@sentry/browser';
import i18next from 'i18next';
import path from 'path';
import path from 'path-browserify';

import {
IH5PEditorState,
Expand Down Expand Up @@ -75,6 +75,7 @@ export default function tabReducer(
? {
...tab,
loadingIndicator: false,
fileHandleId: action.payload.fileHandleId,
path: action.payload.path
}
: tab
Expand Down Expand Up @@ -279,7 +280,8 @@ export default function tabReducer(
mainLibrary: '',
name: path.basename(action.payload.path),
mode: Modes.edit,
opening: true
opening: true,
fileHandleId: action.payload.fileHandleId
}
]
};
Expand All @@ -298,7 +300,8 @@ export default function tabReducer(
mainLibrary: action.payload.h5p.library,
name: action.payload.h5p.metadata.title,
mode: Modes.edit,
opening: false
opening: false,
fileHandleId: action.payload.fileHandleId
}
: tab
)
Expand All @@ -319,7 +322,8 @@ export default function tabReducer(
path: undefined,
mode: Modes.edit,
opening: false,
...action.payload.tab
...action.payload.tab,
fileHandleId: undefined
}
]
};
Expand Down

0 comments on commit 58995cc

Please sign in to comment.