@capytale/meta-player 0.8.2 → 0.9.0
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/package.json +3 -3
- package/src/App.tsx +17 -16
- package/src/demo.tsx +1 -1
- package/src/features/activityData/activityDataSlice.ts +3 -7
- package/src/features/activityData/metaPlayerOptions.ts +6 -6
- package/src/features/layout/layoutSlice.ts +9 -6
- package/src/features/navbar/activity-menu/index.tsx +1 -0
- package/src/features/pedago/PedagoCommands.tsx +4 -4
- package/src/features/pedago/hooks.ts +99 -0
- package/src/features/pedago/index.tsx +19 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capytale/meta-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@testing-library/user-event": "^14.5.2",
|
|
40
40
|
"@types/react": "^18.3.8",
|
|
41
41
|
"@types/react-dom": "^18.3.0",
|
|
42
|
-
"@vitejs/plugin-react": "^
|
|
42
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
43
43
|
"eslint": "^9.19.0",
|
|
44
44
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
45
45
|
"eslint-plugin-react-refresh": "^0.4.18",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"sass": "^1.75.0",
|
|
52
52
|
"typescript": "^5.7.2",
|
|
53
53
|
"typescript-eslint": "^8.22.0",
|
|
54
|
-
"vite": "^
|
|
54
|
+
"vite": "^7.3.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": "^18.3.1",
|
package/src/App.tsx
CHANGED
|
@@ -9,16 +9,18 @@ import { selectThemeIsDark } from "./features/theming/themingSlice";
|
|
|
9
9
|
import { Ripple } from "primereact/ripple";
|
|
10
10
|
import { classNames } from "primereact/utils";
|
|
11
11
|
import {
|
|
12
|
-
selectIsPedagoVisible,
|
|
13
|
-
selectOrientation,
|
|
14
12
|
selectShowSaveForStudents,
|
|
15
|
-
|
|
13
|
+
setIsPedagoVisible,
|
|
16
14
|
} from "./features/layout/layoutSlice";
|
|
17
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
FC,
|
|
17
|
+
KeyboardEvent,
|
|
18
|
+
PropsWithChildren,
|
|
19
|
+
useCallback,
|
|
20
|
+
useMemo,
|
|
21
|
+
} from "react";
|
|
18
22
|
import { Tooltip } from "primereact/tooltip";
|
|
19
23
|
import {
|
|
20
|
-
selectHasGradingOrComments,
|
|
21
|
-
selectHasInstructions,
|
|
22
24
|
selectIsDirty,
|
|
23
25
|
selectMode,
|
|
24
26
|
selectShowSaveButton,
|
|
@@ -27,20 +29,16 @@ import settings from "./settings";
|
|
|
27
29
|
import ReviewNavbar from "./features/navbar/review-navbar";
|
|
28
30
|
import { useSave } from "./features/activityData/hooks";
|
|
29
31
|
import PreviewDialog from "./features/functionalities/PreviewDialog";
|
|
32
|
+
import { usePedagoOptions } from "./features/pedago/hooks";
|
|
30
33
|
|
|
31
34
|
type AppProps = PropsWithChildren<{}>;
|
|
32
35
|
|
|
33
36
|
const App: FC<AppProps> = (props) => {
|
|
34
37
|
const isDark = useAppSelector(selectThemeIsDark) as boolean;
|
|
35
38
|
const mode = useAppSelector(selectMode);
|
|
36
|
-
const isHorizontal =
|
|
37
|
-
|
|
38
|
-
const hasInstructions = useAppSelector(selectHasInstructions);
|
|
39
|
-
const hasGradingOrComments = useAppSelector(selectHasGradingOrComments);
|
|
40
|
-
const hasPedago =
|
|
41
|
-
hasInstructions || hasGradingOrComments || mode === "review";
|
|
39
|
+
const { showPedago, hasPedago, isHorizontal } = usePedagoOptions();
|
|
40
|
+
|
|
42
41
|
const dispatch = useAppDispatch();
|
|
43
|
-
const showPedago = hasPedago && isPedagoVisible;
|
|
44
42
|
const isDirty = useAppSelector(selectIsDirty);
|
|
45
43
|
const save = useSave();
|
|
46
44
|
|
|
@@ -65,6 +63,11 @@ const App: FC<AppProps> = (props) => {
|
|
|
65
63
|
? "Ce type d'activité n'accepte pas de consigne"
|
|
66
64
|
: "Pas de consignes ni de note";
|
|
67
65
|
|
|
66
|
+
const togglePedago = useMemo(() => {
|
|
67
|
+
if (!hasPedago) return undefined;
|
|
68
|
+
return () => dispatch(setIsPedagoVisible(!showPedago));
|
|
69
|
+
}, [hasPedago, showPedago]);
|
|
70
|
+
|
|
68
71
|
return (
|
|
69
72
|
<div
|
|
70
73
|
className={classNames(
|
|
@@ -90,9 +93,7 @@ const App: FC<AppProps> = (props) => {
|
|
|
90
93
|
styles.hiddenPedagoButton,
|
|
91
94
|
hasPedago ? "p-ripple" : null,
|
|
92
95
|
)}
|
|
93
|
-
onClick={
|
|
94
|
-
hasPedago ? () => dispatch(toggleIsPedagoVisible()) : undefined
|
|
95
|
-
}
|
|
96
|
+
onClick={togglePedago}
|
|
96
97
|
data-pr-tooltip={pedagoOpenLabel}
|
|
97
98
|
aria-label={pedagoOpenLabel}
|
|
98
99
|
role={hasPedago ? "button" : "note"}
|
package/src/demo.tsx
CHANGED
|
@@ -28,9 +28,6 @@ export const activityDataSlice = createAppSlice({
|
|
|
28
28
|
initialState,
|
|
29
29
|
// The `reducers` field lets us define reducers and generate associated actions
|
|
30
30
|
reducers: (create) => ({
|
|
31
|
-
toRemove: create.reducer((state) => {
|
|
32
|
-
state.hasInstructions = false;
|
|
33
|
-
}),
|
|
34
31
|
// Use the `PayloadAction` type to declare the contents of `action.payload`
|
|
35
32
|
setActivityJSData: create.reducer(
|
|
36
33
|
(state, action: PayloadAction<ActivityJSData>) => {
|
|
@@ -60,7 +57,7 @@ export const activityDataSlice = createAppSlice({
|
|
|
60
57
|
),
|
|
61
58
|
setPlayerSettings: create.reducer(
|
|
62
59
|
(state, action: PayloadAction<MetaPlayerOptions>) => {
|
|
63
|
-
state.
|
|
60
|
+
state.showPedagoByDefault = action.payload.showPedagoByDefault;
|
|
64
61
|
state.canReset = action.payload.canReset;
|
|
65
62
|
state.pedagoLayout = action.payload.pedagoLayout;
|
|
66
63
|
state.supportsLightTheme = action.payload.supportsLightTheme;
|
|
@@ -154,7 +151,7 @@ export const activityDataSlice = createAppSlice({
|
|
|
154
151
|
selectSharedNotesType: (data) => data.sharedNotesType,
|
|
155
152
|
selectComments: (data) => data.comments,
|
|
156
153
|
selectGrading: (data) => data.grading,
|
|
157
|
-
|
|
154
|
+
selectShowPedagoByDefault: (data) => data.showPedagoByDefault,
|
|
158
155
|
selectCanReset: (data) => data.canReset,
|
|
159
156
|
selectPedagoLayout: (data) => data.pedagoLayout,
|
|
160
157
|
selectHasGradingOrComments: (data) => !!(data.grading || data.comments),
|
|
@@ -199,7 +196,6 @@ export const activityDataSlice = createAppSlice({
|
|
|
199
196
|
|
|
200
197
|
// Action creators are generated for each case reducer function.
|
|
201
198
|
export const {
|
|
202
|
-
toRemove,
|
|
203
199
|
setActivityJSData,
|
|
204
200
|
setPlayerSettings,
|
|
205
201
|
setCanSaveInstructions,
|
|
@@ -230,7 +226,7 @@ export const {
|
|
|
230
226
|
selectSharedNotesType,
|
|
231
227
|
selectComments,
|
|
232
228
|
selectGrading,
|
|
233
|
-
|
|
229
|
+
selectShowPedagoByDefault,
|
|
234
230
|
selectCanReset,
|
|
235
231
|
selectPedagoLayout,
|
|
236
232
|
selectHasGradingOrComments,
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
export type MetaPlayerOptions = {
|
|
2
|
-
|
|
2
|
+
showPedagoByDefault: boolean;
|
|
3
3
|
canReset: boolean;
|
|
4
4
|
preventEditIfHasEvaluations: boolean;
|
|
5
5
|
pedagoLayout:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
| "horizontal"
|
|
7
|
+
| "vertical"
|
|
8
|
+
| "default-horizontal"
|
|
9
|
+
| "default-vertical";
|
|
10
10
|
supportsLightTheme: boolean;
|
|
11
11
|
supportsDarkTheme: boolean;
|
|
12
12
|
hideSaveButton: boolean;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const defaultMetaPlayerOptions: MetaPlayerOptions = {
|
|
16
|
-
|
|
16
|
+
showPedagoByDefault: true,
|
|
17
17
|
canReset: true,
|
|
18
18
|
preventEditIfHasEvaluations: false,
|
|
19
19
|
pedagoLayout: "default-horizontal",
|
|
@@ -7,7 +7,8 @@ export type PedagoTab = "instructions" | "sharedNotes" | "pdf";
|
|
|
7
7
|
|
|
8
8
|
export interface LayoutState {
|
|
9
9
|
orientation: Orientation;
|
|
10
|
-
|
|
10
|
+
/** true if open, false if closed, null if default (determined by mode and content of instructions) */
|
|
11
|
+
isPedagoVisible: boolean | null;
|
|
11
12
|
isGradingVisible: boolean;
|
|
12
13
|
pedagoTab: PedagoTab;
|
|
13
14
|
showWorkflow: boolean;
|
|
@@ -16,7 +17,7 @@ export interface LayoutState {
|
|
|
16
17
|
|
|
17
18
|
export const initialState: LayoutState = {
|
|
18
19
|
orientation: "horizontal",
|
|
19
|
-
isPedagoVisible:
|
|
20
|
+
isPedagoVisible: null,
|
|
20
21
|
isGradingVisible: true,
|
|
21
22
|
pedagoTab: "instructions",
|
|
22
23
|
showWorkflow: true,
|
|
@@ -42,9 +43,11 @@ export const layoutSlice = createAppSlice({
|
|
|
42
43
|
setLayout: create.reducer((state, action: PayloadAction<Orientation>) => {
|
|
43
44
|
state.orientation = action.payload;
|
|
44
45
|
}),
|
|
45
|
-
|
|
46
|
-
state
|
|
47
|
-
|
|
46
|
+
setIsPedagoVisible: create.reducer(
|
|
47
|
+
(state, action: PayloadAction<boolean | null>) => {
|
|
48
|
+
state.isPedagoVisible = action.payload;
|
|
49
|
+
},
|
|
50
|
+
),
|
|
48
51
|
setIsGradingVisible: create.reducer(
|
|
49
52
|
(state, action: PayloadAction<boolean>) => {
|
|
50
53
|
state.isGradingVisible = action.payload;
|
|
@@ -73,7 +76,7 @@ export const layoutSlice = createAppSlice({
|
|
|
73
76
|
export const {
|
|
74
77
|
toggleLayout,
|
|
75
78
|
setLayout,
|
|
76
|
-
|
|
79
|
+
setIsPedagoVisible,
|
|
77
80
|
setIsGradingVisible,
|
|
78
81
|
toggleIsGradingVisible,
|
|
79
82
|
setPedagoTab,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
selectPedagoTab,
|
|
16
16
|
setPedagoTab,
|
|
17
17
|
toggleIsGradingVisible,
|
|
18
|
-
|
|
18
|
+
setIsPedagoVisible,
|
|
19
19
|
} from "../layout/layoutSlice";
|
|
20
20
|
import styles from "./style.module.scss";
|
|
21
21
|
import { Button } from "primereact/button";
|
|
@@ -49,7 +49,7 @@ export const CloseOnlyPedagoCommands: FC = () => {
|
|
|
49
49
|
position: "left",
|
|
50
50
|
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
51
51
|
}}
|
|
52
|
-
onClick={() => dispatch(
|
|
52
|
+
onClick={() => dispatch(setIsPedagoVisible(false))}
|
|
53
53
|
style={{ flexShrink: 0 }}
|
|
54
54
|
/>
|
|
55
55
|
</div>
|
|
@@ -96,7 +96,7 @@ const HorizontalPedagoCommands = () => {
|
|
|
96
96
|
position: "left",
|
|
97
97
|
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
98
98
|
}}
|
|
99
|
-
onClick={() => dispatch(
|
|
99
|
+
onClick={() => dispatch(setIsPedagoVisible(false))}
|
|
100
100
|
style={{ flexShrink: 0 }}
|
|
101
101
|
/>
|
|
102
102
|
</div>
|
|
@@ -250,7 +250,7 @@ const VerticalPedagoCommands = () => {
|
|
|
250
250
|
position: "left",
|
|
251
251
|
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
252
252
|
}}
|
|
253
|
-
onClick={() => dispatch(
|
|
253
|
+
onClick={() => dispatch(setIsPedagoVisible(false))}
|
|
254
254
|
style={{ flexShrink: 0 }}
|
|
255
255
|
/>
|
|
256
256
|
</div>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useAppSelector } from "../../app/hooks";
|
|
3
|
+
import {
|
|
4
|
+
selectHasGradingOrComments,
|
|
5
|
+
selectShowPedagoByDefault,
|
|
6
|
+
selectInstructions,
|
|
7
|
+
selectInstructionsType,
|
|
8
|
+
selectMode,
|
|
9
|
+
} from "../activityData/activityDataSlice";
|
|
10
|
+
import {
|
|
11
|
+
selectIsGradingVisible,
|
|
12
|
+
selectIsPedagoVisible,
|
|
13
|
+
selectOrientation,
|
|
14
|
+
} from "../layout/layoutSlice";
|
|
15
|
+
|
|
16
|
+
export const usePedagoOptions = () => {
|
|
17
|
+
const mode = useAppSelector(selectMode);
|
|
18
|
+
const isHorizontal = useAppSelector(selectOrientation) === "horizontal";
|
|
19
|
+
const isPedagoVisible = useAppSelector(selectIsPedagoVisible);
|
|
20
|
+
const isGradingVisible = useAppSelector(selectIsGradingVisible);
|
|
21
|
+
const showPedagoByDefault = useAppSelector(selectShowPedagoByDefault);
|
|
22
|
+
const hasGradingOrComments = useAppSelector(selectHasGradingOrComments);
|
|
23
|
+
const instructionsType = useAppSelector(selectInstructionsType);
|
|
24
|
+
const instructions = useAppSelector(selectInstructions);
|
|
25
|
+
const hasNonEmptyInstructions = useMemo(() => {
|
|
26
|
+
if (instructionsType === "none" || !instructions) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (instructions.format === "html") {
|
|
30
|
+
if (typeof instructions.value !== "string") {
|
|
31
|
+
console.warn("HTML instructions value is not a string");
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return (
|
|
35
|
+
instructions.value.trim() !== "" &&
|
|
36
|
+
instructions.value.trim() !== "<p></p>"
|
|
37
|
+
);
|
|
38
|
+
} else if (instructions.format === "markdown") {
|
|
39
|
+
if (typeof instructions.value !== "string") {
|
|
40
|
+
console.warn("Markdown instructions value is not a string");
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return instructions.value.trim() !== "";
|
|
44
|
+
} else if (instructions.format === "lexical") {
|
|
45
|
+
if (typeof instructions.value !== "string") {
|
|
46
|
+
console.warn("Lexical instructions value is not a string");
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const parsedInstructions = JSON.parse(instructions.value);
|
|
51
|
+
if (
|
|
52
|
+
!parsedInstructions.root ||
|
|
53
|
+
!parsedInstructions.root.children ||
|
|
54
|
+
parsedInstructions.root.children.length === 0
|
|
55
|
+
) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (parsedInstructions.root.children.length > 1) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const singleNode = parsedInstructions.root.children[0];
|
|
62
|
+
if (singleNode.type !== "paragraph") {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return singleNode.children && singleNode.children.length > 0;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.warn("Lexical instructions value is not valid JSON");
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}, [instructionsType, instructions]);
|
|
73
|
+
|
|
74
|
+
const hasPedago =
|
|
75
|
+
mode === "review"
|
|
76
|
+
? true
|
|
77
|
+
: mode === "detached"
|
|
78
|
+
? false
|
|
79
|
+
: mode === "create"
|
|
80
|
+
? true
|
|
81
|
+
: hasNonEmptyInstructions || hasGradingOrComments;
|
|
82
|
+
|
|
83
|
+
const showPedago =
|
|
84
|
+
isPedagoVisible ?? (mode === "create" ? showPedagoByDefault : hasPedago);
|
|
85
|
+
|
|
86
|
+
const showInstructions = mode === "create" ? true : hasNonEmptyInstructions;
|
|
87
|
+
const hasGrading = mode === "review" || hasGradingOrComments;
|
|
88
|
+
const showGrading = hasGrading && isGradingVisible;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
hasPedago,
|
|
92
|
+
showPedago,
|
|
93
|
+
isHorizontal,
|
|
94
|
+
hasNonEmptyInstructions,
|
|
95
|
+
showInstructions,
|
|
96
|
+
hasGrading,
|
|
97
|
+
showGrading,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { Panel } from "primereact/panel";
|
|
2
2
|
import styles from "./style.module.scss";
|
|
3
3
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
|
4
|
-
import {
|
|
5
|
-
selectIsGradingVisible,
|
|
6
|
-
selectOrientation,
|
|
7
|
-
selectPedagoTab,
|
|
8
|
-
} from "../layout/layoutSlice";
|
|
4
|
+
import { selectPedagoTab } from "../layout/layoutSlice";
|
|
9
5
|
|
|
10
6
|
import "@capytale/capytale-rich-text-editor/style.css";
|
|
11
7
|
import { Splitter, SplitterPanel } from "primereact/splitter";
|
|
@@ -14,37 +10,34 @@ import InstructionsEditor from "./InstructionsEditor";
|
|
|
14
10
|
import {
|
|
15
11
|
selectComments,
|
|
16
12
|
selectGrading,
|
|
17
|
-
selectHasGradingOrComments,
|
|
18
|
-
selectHasInstructions,
|
|
19
13
|
selectMode,
|
|
20
14
|
setComments,
|
|
21
15
|
setGrading,
|
|
22
16
|
setIsMPDirty,
|
|
23
17
|
} from "../activityData/activityDataSlice";
|
|
24
|
-
import { ChangeEventHandler, FC } from "react";
|
|
18
|
+
import { ChangeEventHandler, FC, useCallback } from "react";
|
|
25
19
|
import { DivProps } from "react-html-props";
|
|
26
20
|
import SharedNotesEditor from "./SharedNotesEditor";
|
|
27
21
|
import { CloseOnlyPedagoCommands, PedagoCommands } from "./PedagoCommands";
|
|
28
22
|
import { PdfEditor } from "./PdfEditor";
|
|
23
|
+
import { usePedagoOptions } from "./hooks";
|
|
29
24
|
|
|
30
25
|
const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
31
|
-
const mode = useAppSelector(selectMode);
|
|
32
26
|
const pedagoTab = useAppSelector(selectPedagoTab);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
: (tab: Array<any>) => tab;
|
|
27
|
+
const { showInstructions, showGrading, isHorizontal } = usePedagoOptions();
|
|
28
|
+
|
|
29
|
+
const mayReverse = useCallback(
|
|
30
|
+
isHorizontal
|
|
31
|
+
? (tab: Array<any>) => {
|
|
32
|
+
const reversed = [...tab];
|
|
33
|
+
reversed.reverse();
|
|
34
|
+
return reversed;
|
|
35
|
+
}
|
|
36
|
+
: (tab: Array<any>) => tab,
|
|
37
|
+
[isHorizontal],
|
|
38
|
+
);
|
|
46
39
|
|
|
47
|
-
if (!
|
|
40
|
+
if (!showInstructions) {
|
|
48
41
|
return (
|
|
49
42
|
<div className={styles.onlyGradingPedago}>
|
|
50
43
|
<CloseOnlyPedagoCommands />
|
|
@@ -68,16 +61,16 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
|
68
61
|
? "PDF"
|
|
69
62
|
: "Notes partagées"
|
|
70
63
|
}
|
|
71
|
-
data-grading-visible={
|
|
64
|
+
data-grading-visible={showGrading}
|
|
72
65
|
>
|
|
73
|
-
{!
|
|
66
|
+
{!showGrading && (
|
|
74
67
|
<>
|
|
75
68
|
{pedagoTab === "instructions" && <InstructionsEditor />}
|
|
76
69
|
{pedagoTab === "sharedNotes" && <SharedNotesEditor />}
|
|
77
70
|
{pedagoTab === "pdf" && <PdfEditor />}
|
|
78
71
|
</>
|
|
79
72
|
)}
|
|
80
|
-
{
|
|
73
|
+
{showGrading && (
|
|
81
74
|
<Splitter
|
|
82
75
|
layout={isHorizontal ? "horizontal" : "vertical"}
|
|
83
76
|
className={styles.pedagoSplitter}
|