@capytale/meta-player 0.5.13 → 0.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint.config.js +28 -0
- package/package.json +10 -4
- package/src/MetaPlayer.tsx +16 -1
- package/src/features/activityJS/ActivityJSProvider.tsx +11 -1
- package/src/features/navbar/capytale-menu/CloneDialog.tsx +75 -0
- package/src/features/navbar/capytale-menu/index.tsx +24 -1
- package/src/features/pedago/index.tsx +5 -1
- package/src/utils/capytale.ts +11 -0
- package/tsconfig.json +9 -7
package/eslint.config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{ ignores: ['dist'] },
|
|
9
|
+
{
|
|
10
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
globals: globals.browser,
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
'react-hooks': reactHooks,
|
|
18
|
+
'react-refresh': reactRefresh,
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
...reactHooks.configs.recommended.rules,
|
|
22
|
+
'react-refresh/only-export-components': [
|
|
23
|
+
'warn',
|
|
24
|
+
{ allowConstantExport: true },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capytale/meta-player",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@capytale/activity.js": "^3.1.
|
|
19
|
+
"@capytale/activity.js": "^3.1.21",
|
|
20
20
|
"@capytale/capytale-anti-triche": "^0.2.1",
|
|
21
21
|
"@capytale/capytale-rich-text-editor": "^0.4.3",
|
|
22
22
|
"@reduxjs/toolkit": "^2.0.1",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"use-file-upload": "^1.0.11"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.19.0",
|
|
34
35
|
"@testing-library/dom": "^9.3.4",
|
|
35
36
|
"@testing-library/jest-dom": "^6.2.0",
|
|
36
37
|
"@testing-library/react": "^14.1.2",
|
|
@@ -38,13 +39,18 @@
|
|
|
38
39
|
"@types/react": "^18.3.8",
|
|
39
40
|
"@types/react-dom": "^18.3.0",
|
|
40
41
|
"@vitejs/plugin-react": "^4.3.4",
|
|
42
|
+
"eslint": "^9.19.0",
|
|
43
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
44
|
+
"eslint-plugin-react-refresh": "^0.4.18",
|
|
45
|
+
"globals": "^15.14.0",
|
|
41
46
|
"jsdom": "^23.2.0",
|
|
42
47
|
"prettier": "^3.2.1",
|
|
43
48
|
"react": "^18.3.1",
|
|
44
49
|
"react-dom": "^18.3.1",
|
|
45
50
|
"sass": "^1.75.0",
|
|
46
|
-
"typescript": "^5.
|
|
47
|
-
"
|
|
51
|
+
"typescript": "^5.7.2",
|
|
52
|
+
"typescript-eslint": "^8.22.0",
|
|
53
|
+
"vite": "^6.1.0"
|
|
48
54
|
},
|
|
49
55
|
"peerDependencies": {
|
|
50
56
|
"react": "^18.3.1",
|
package/src/MetaPlayer.tsx
CHANGED
|
@@ -74,7 +74,22 @@ const MetaPlayer: FC<MetaPlayerProps> = (props) => {
|
|
|
74
74
|
<ThemeSwitcher />
|
|
75
75
|
<ExitWarning />
|
|
76
76
|
<ErrorBoundary fallback={<div>Une erreur est survenue</div>}>
|
|
77
|
-
<ActivityJSProvider
|
|
77
|
+
<ActivityJSProvider
|
|
78
|
+
options={props.activityJSOptions}
|
|
79
|
+
loadingFallback={<div>Chargement de l'activité...</div>}
|
|
80
|
+
errorFallback={({ children }) => (
|
|
81
|
+
<div style={{ marginLeft: "1rem", marginRight: "1rem" }}>
|
|
82
|
+
<p>Erreur lors du chargement de l'activité : {children}</p>
|
|
83
|
+
<p>
|
|
84
|
+
Êtes-vous bien connecté(e) à Capytale ? Avez-vous bien le
|
|
85
|
+
droit d'accéder à cette ressource ?
|
|
86
|
+
</p>
|
|
87
|
+
<p>
|
|
88
|
+
<a href="/">Retour à l'accueil</a>
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
78
93
|
<Saver />
|
|
79
94
|
<MetaPlayerContent antiCheatOptions={antiCheatOptions}>
|
|
80
95
|
<App>{props.children}</App>
|
|
@@ -28,6 +28,9 @@ const ActivityJSContext = createContext<ActivitySessionLoaderReturnValue>({
|
|
|
28
28
|
type ActivityJSProviderProps = PropsWithChildren<{
|
|
29
29
|
options?: LoadOptions;
|
|
30
30
|
loadingFallback?: ReactNode;
|
|
31
|
+
errorFallback?:
|
|
32
|
+
| keyof JSX.IntrinsicElements
|
|
33
|
+
| React.JSXElementConstructor<{ children: ReactNode }>;
|
|
31
34
|
}>;
|
|
32
35
|
|
|
33
36
|
export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
|
|
@@ -109,7 +112,14 @@ export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
|
|
|
109
112
|
}
|
|
110
113
|
const value = useActivitySessionLoader(activityId, props.options, callback);
|
|
111
114
|
if (!loaded) {
|
|
112
|
-
|
|
115
|
+
if (value.state === "loading") {
|
|
116
|
+
return <>{props.loadingFallback}</>;
|
|
117
|
+
} else if (value.state === "error" && props.errorFallback) {
|
|
118
|
+
console.error(value.error);
|
|
119
|
+
return (
|
|
120
|
+
<>{<props.errorFallback>{String(value.error)}</props.errorFallback>}</>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
113
123
|
}
|
|
114
124
|
return (
|
|
115
125
|
<ActivityJSContext.Provider value={value}>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { FC, useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { useAppSelector } from "../../../app/hooks";
|
|
3
|
+
import { Dialog } from "primereact/dialog";
|
|
4
|
+
import { selectActivityInfo } from "../../activityData/activityDataSlice";
|
|
5
|
+
import { Button } from "primereact/button";
|
|
6
|
+
import { apiCloneActivity } from "../../../utils/capytale";
|
|
7
|
+
import { useActivityJsEssentials } from "../../activityJS/ActivityJSProvider";
|
|
8
|
+
import { InputText } from "primereact/inputtext";
|
|
9
|
+
|
|
10
|
+
const CloneDialog: FC<{ visible: boolean; onHide: () => any }> = ({
|
|
11
|
+
visible,
|
|
12
|
+
onHide,
|
|
13
|
+
}) => {
|
|
14
|
+
const { nid } = useActivityJsEssentials();
|
|
15
|
+
const activityTitle = useAppSelector(selectActivityInfo).title;
|
|
16
|
+
const [newTitle, setNewTitle] = useState<string>(activityTitle);
|
|
17
|
+
const clone = useCallback(async () => {
|
|
18
|
+
const clone = await apiCloneActivity(nid, newTitle);
|
|
19
|
+
const newNid = clone.nid;
|
|
20
|
+
const url = `/web/c-act/n/${newNid}/play/create`;
|
|
21
|
+
// redirect to the new activity in a new tab
|
|
22
|
+
window.open(url, "_blank");
|
|
23
|
+
onHide();
|
|
24
|
+
}, [nid, newTitle, onHide]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setNewTitle(activityTitle);
|
|
28
|
+
}, [activityTitle, visible]);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Dialog
|
|
32
|
+
visible={visible}
|
|
33
|
+
header={"Choisissez le titre du clone"}
|
|
34
|
+
onHide={onHide}
|
|
35
|
+
style={{ width: "750px", maxWidth: "100%" }}
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
style={{
|
|
39
|
+
display: "flex",
|
|
40
|
+
flexDirection: "column",
|
|
41
|
+
gap: "0.5rem",
|
|
42
|
+
marginBottom: "1rem",
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<label htmlFor="title-input">Titre</label>
|
|
46
|
+
<InputText
|
|
47
|
+
id="title-input"
|
|
48
|
+
value={newTitle}
|
|
49
|
+
onChange={(e) => setNewTitle(e.target.value)}
|
|
50
|
+
/>
|
|
51
|
+
<div
|
|
52
|
+
style={{
|
|
53
|
+
display: "flex",
|
|
54
|
+
justifyContent: "flex-end",
|
|
55
|
+
gap: "0.5rem",
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
<Button
|
|
59
|
+
aria-label="Annuler le clonage"
|
|
60
|
+
label="Annuler"
|
|
61
|
+
onClick={onHide}
|
|
62
|
+
severity="secondary"
|
|
63
|
+
/>
|
|
64
|
+
<Button
|
|
65
|
+
aria-label="Confirmer le clonage"
|
|
66
|
+
label="Cloner"
|
|
67
|
+
onClick={clone}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</Dialog>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default CloneDialog;
|
|
@@ -5,7 +5,7 @@ import { Toast } from "primereact/toast";
|
|
|
5
5
|
import { OverlayPanel } from "primereact/overlaypanel";
|
|
6
6
|
|
|
7
7
|
import styles from "./style.module.scss";
|
|
8
|
-
import { useMemo, useRef } from "react";
|
|
8
|
+
import { useMemo, useRef, useState } from "react";
|
|
9
9
|
import { useWindowSize } from "@uidotdev/usehooks";
|
|
10
10
|
import { XL } from "../../../utils/breakpoints";
|
|
11
11
|
import { useAppDispatch, useAppSelector } from "../../../app/hooks";
|
|
@@ -24,10 +24,12 @@ import CardSelector from "../../../utils/CardSelector";
|
|
|
24
24
|
import { useActivityJS } from "../../activityJS/ActivityJSProvider";
|
|
25
25
|
import { selectShowWorkflow } from "../../layout/layoutSlice";
|
|
26
26
|
import CountdownAndSaveButton from "./CountdownAndSaveButton";
|
|
27
|
+
import CloneDialog from "./CloneDialog";
|
|
27
28
|
|
|
28
29
|
const CapytaleMenu: React.FC = () => {
|
|
29
30
|
const dispatch = useAppDispatch();
|
|
30
31
|
const activityJS = useActivityJS();
|
|
32
|
+
const [cloneDialogVisible, setCloneDialogVisible] = useState(false);
|
|
31
33
|
const sharingInfo = useAppSelector(selectSharingInfo);
|
|
32
34
|
const windowsSize = useWindowSize();
|
|
33
35
|
const mode = useAppSelector(selectMode);
|
|
@@ -76,6 +78,27 @@ const CapytaleMenu: React.FC = () => {
|
|
|
76
78
|
/>
|
|
77
79
|
)}
|
|
78
80
|
<CountdownAndSaveButton />
|
|
81
|
+
{!isInIframe && mode === "view" && (
|
|
82
|
+
<>
|
|
83
|
+
<Button
|
|
84
|
+
aria-label="Cloner l'activité"
|
|
85
|
+
label="Cloner"
|
|
86
|
+
icon="pi pi-clone"
|
|
87
|
+
size="small"
|
|
88
|
+
outlined
|
|
89
|
+
onClick={() => setCloneDialogVisible(true)}
|
|
90
|
+
tooltip="Cloner l'activité"
|
|
91
|
+
tooltipOptions={{
|
|
92
|
+
position: "bottom",
|
|
93
|
+
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
<CloneDialog
|
|
97
|
+
visible={cloneDialogVisible}
|
|
98
|
+
onHide={() => setCloneDialogVisible(false)}
|
|
99
|
+
/>
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
79
102
|
|
|
80
103
|
{mode === "create" && sharingInfo.code && (
|
|
81
104
|
<ButtonGroup>
|
|
@@ -37,7 +37,11 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
|
37
37
|
(hasGradingOrComments || mode === "review");
|
|
38
38
|
const isHorizontal = useAppSelector(selectOrientation) === "horizontal";
|
|
39
39
|
const mayReverse = isHorizontal
|
|
40
|
-
? (tab: Array<any>) =>
|
|
40
|
+
? (tab: Array<any>) => {
|
|
41
|
+
const reversed = [...tab];
|
|
42
|
+
reversed.reverse();
|
|
43
|
+
return reversed;
|
|
44
|
+
}
|
|
41
45
|
: (tab: Array<any>) => tab;
|
|
42
46
|
|
|
43
47
|
if (!hasInstructions) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import httpClient from '@capytale/activity.js/backend/capytale/http';
|
|
2
|
+
|
|
3
|
+
// Definit le endpoint de l'API
|
|
4
|
+
const myActivitiesApiEp = '/web/c-hdls/api/my-activities';
|
|
5
|
+
|
|
6
|
+
export async function apiCloneActivity(nid: number | string, title?: string) {
|
|
7
|
+
return httpClient.postGetJsonAsync<any>(
|
|
8
|
+
myActivitiesApiEp,
|
|
9
|
+
{ action: 'clone', nid, title },
|
|
10
|
+
)
|
|
11
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2020",
|
|
4
5
|
"useDefineForClassFields": true,
|
|
5
|
-
"lib": ["
|
|
6
|
-
"allowJs": false,
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"esModuleInterop": false,
|
|
9
|
-
"allowSyntheticDefaultImports": true,
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
10
7
|
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"allowJs": false,
|
|
10
|
+
|
|
11
11
|
"moduleResolution": "bundler",
|
|
12
|
-
"
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
13
|
"isolatedModules": true,
|
|
14
|
+
"esModuleInterop": false,
|
|
15
|
+
"resolveJsonModule": true,
|
|
14
16
|
"noEmit": true,
|
|
15
17
|
"jsx": "react-jsx",
|
|
16
18
|
|