@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capytale/meta-player",
3
- "version": "0.8.2",
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": "^4.3.4",
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": "^6.1.0"
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
- toggleIsPedagoVisible,
13
+ setIsPedagoVisible,
16
14
  } from "./features/layout/layoutSlice";
17
- import { FC, KeyboardEvent, PropsWithChildren, useCallback } from "react";
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 = useAppSelector(selectOrientation) === "horizontal";
37
- const isPedagoVisible = useAppSelector(selectIsPedagoVisible) as boolean;
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
@@ -70,7 +70,7 @@ if (container) {
70
70
  <MetaPlayer>
71
71
  <MetaPlayerOptionsSetter
72
72
  options={{
73
- hasInstructions: true,
73
+ showPedagoByDefault: true,
74
74
  pedagoLayout: "default-horizontal",
75
75
  supportsLightTheme: true,
76
76
  supportsDarkTheme: true,
@@ -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.hasInstructions = action.payload.hasInstructions;
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
- selectHasInstructions: (data) => data.hasInstructions,
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
- selectHasInstructions,
229
+ selectShowPedagoByDefault,
234
230
  selectCanReset,
235
231
  selectPedagoLayout,
236
232
  selectHasGradingOrComments,
@@ -1,19 +1,19 @@
1
1
  export type MetaPlayerOptions = {
2
- hasInstructions: boolean;
2
+ showPedagoByDefault: boolean;
3
3
  canReset: boolean;
4
4
  preventEditIfHasEvaluations: boolean;
5
5
  pedagoLayout:
6
- | "horizontal"
7
- | "vertical"
8
- | "default-horizontal"
9
- | "default-vertical";
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
- hasInstructions: true,
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
- isPedagoVisible: boolean;
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: true,
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
- toggleIsPedagoVisible: create.reducer((state) => {
46
- state.isPedagoVisible = !state.isPedagoVisible;
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
- toggleIsPedagoVisible,
79
+ setIsPedagoVisible,
77
80
  setIsGradingVisible,
78
81
  toggleIsGradingVisible,
79
82
  setPedagoTab,
@@ -111,6 +111,7 @@ const ActivityMenu: React.FC = () => {
111
111
  visible={settingsSidebarVisible}
112
112
  position="right"
113
113
  onHide={() => setSettingsSidebarVisible(false)}
114
+ style={{ width: "24rem", maxWidth: "100%" }}
114
115
  >
115
116
  <SettingsSidebarContent
116
117
  showHelp={
@@ -15,7 +15,7 @@ import {
15
15
  selectPedagoTab,
16
16
  setPedagoTab,
17
17
  toggleIsGradingVisible,
18
- toggleIsPedagoVisible,
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(toggleIsPedagoVisible())}
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(toggleIsPedagoVisible())}
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(toggleIsPedagoVisible())}
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 hasInstructions = useAppSelector(selectHasInstructions);
34
- const hasGradingOrComments = useAppSelector(selectHasGradingOrComments);
35
- const isGradingVisible =
36
- useAppSelector(selectIsGradingVisible) &&
37
- (hasGradingOrComments || mode === "review");
38
- const isHorizontal = useAppSelector(selectOrientation) === "horizontal";
39
- const mayReverse = isHorizontal
40
- ? (tab: Array<any>) => {
41
- const reversed = [...tab];
42
- reversed.reverse();
43
- return reversed;
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 (!hasInstructions) {
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={isGradingVisible}
64
+ data-grading-visible={showGrading}
72
65
  >
73
- {!isGradingVisible && (
66
+ {!showGrading && (
74
67
  <>
75
68
  {pedagoTab === "instructions" && <InstructionsEditor />}
76
69
  {pedagoTab === "sharedNotes" && <SharedNotesEditor />}
77
70
  {pedagoTab === "pdf" && <PdfEditor />}
78
71
  </>
79
72
  )}
80
- {isGradingVisible && (
73
+ {showGrading && (
81
74
  <Splitter
82
75
  layout={isHorizontal ? "horizontal" : "vertical"}
83
76
  className={styles.pedagoSplitter}