@capytale/meta-player 0.5.3 → 0.5.5

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.
Files changed (32) hide show
  1. package/package.json +2 -2
  2. package/src/App.tsx +2 -4
  3. package/src/demo.tsx +2 -2
  4. package/src/features/activityData/activityDataSlice.ts +11 -96
  5. package/src/features/activityData/activityJsData.ts +81 -0
  6. package/src/features/activityData/hooks.ts +41 -7
  7. package/src/features/activityData/metaPlayerOptions.ts +6 -4
  8. package/src/features/activityData/uiState.ts +21 -0
  9. package/src/features/activityJS/ActivityJSProvider.tsx +1 -0
  10. package/src/features/navbar/{ActivityInfo.tsx → activity-info/index.tsx} +3 -3
  11. package/src/features/navbar/activity-info/style.module.scss +38 -0
  12. package/src/features/navbar/{ActivityQuickActions.tsx → activity-menu/ActivityQuickActions.tsx} +3 -3
  13. package/src/features/navbar/{ActivityMenu.tsx → activity-menu/index.tsx} +9 -9
  14. package/src/features/navbar/activity-menu/style.module.scss +7 -0
  15. package/src/features/navbar/{Countdown.tsx → capytale-menu/Countdown.tsx} +5 -5
  16. package/src/features/navbar/capytale-menu/CountdownAndSaveButton.tsx +115 -0
  17. package/src/features/navbar/{CapytaleMenu.tsx → capytale-menu/index.tsx} +12 -51
  18. package/src/features/navbar/capytale-menu/style.module.scss +9 -0
  19. package/src/features/navbar/index.tsx +3 -3
  20. package/src/features/navbar/{GradingNav.tsx → review-navbar/GradingNav.tsx} +6 -4
  21. package/src/features/navbar/review-navbar/style.module.scss +22 -0
  22. package/src/features/navbar/{ActivitySidebarActions.tsx → sidebars/ActivitySidebarActions.tsx} +3 -3
  23. package/src/features/navbar/{AttachedFilesSidebarContent.tsx → sidebars/AttachedFilesSidebarContent.tsx} +5 -5
  24. package/src/features/navbar/{SettingsSidebarContent.tsx → sidebars/SettingsSidebarContent.tsx} +8 -8
  25. package/src/features/navbar/sidebars/style.module.scss +15 -0
  26. package/src/features/navbar/student-utils.ts +1 -1
  27. package/src/features/navbar/style.module.scss +0 -96
  28. package/src/features/pedago/PedagoCommands.tsx +2 -0
  29. package/src/features/pedago/index.tsx +14 -0
  30. package/src/index.tsx +10 -4
  31. /package/src/features/navbar/{ReviewNavbar.tsx → review-navbar/index.tsx} +0 -0
  32. /package/src/features/navbar/{AttachedFilesSidebarContent.module.scss → sidebars/AttachedFilesSidebarContent.module.scss} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capytale/meta-player",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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.10",
19
+ "@capytale/activity.js": "^3.1.14",
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",
package/src/App.tsx CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  selectMode,
23
23
  } from "./features/activityData/activityDataSlice";
24
24
  import settings from "./settings";
25
- import ReviewNavbar from "./features/navbar/ReviewNavbar";
25
+ import ReviewNavbar from "./features/navbar/review-navbar";
26
26
  import { useSave } from "./features/activityData/hooks";
27
27
 
28
28
  type AppProps = PropsWithChildren<{}>;
@@ -45,9 +45,7 @@ const App: FC<AppProps> = (props) => {
45
45
  (e: KeyboardEvent<HTMLDivElement>) => {
46
46
  if ((e.ctrlKey || e.metaKey) && e.key === "s") {
47
47
  e.preventDefault();
48
- if (isDirty) {
49
- save();
50
- }
48
+ save(); // Checks if can save inside of save()
51
49
  }
52
50
  },
53
51
  [isDirty, save],
package/src/demo.tsx CHANGED
@@ -2,8 +2,8 @@ import React, { FC } from "react";
2
2
  import { createRoot } from "react-dom/client";
3
3
 
4
4
  import { MetaPlayer } from ".";
5
- import { ActivityQuickActionsSetter } from "./features/navbar/ActivityQuickActions";
6
- import { ActivitySidebarActionsSetter } from "./features/navbar/ActivitySidebarActions";
5
+ import { ActivityQuickActionsSetter } from "./features/navbar/activity-menu/ActivityQuickActions";
6
+ import { ActivitySidebarActionsSetter } from "./features/navbar/sidebars/ActivitySidebarActions";
7
7
  // import { useActivityJS } from "./features/activityJS/ActivityJSProvider";
8
8
  import BeforeSaveAction from "./features/activityJS/BeforeSaveAction";
9
9
  import MetaPlayerOptionsSetter from "./features/activityData/MetaPlayerOptionsSetter";
@@ -1,113 +1,22 @@
1
1
  import type { PayloadAction } from "@reduxjs/toolkit";
2
2
  import { createAppSlice } from "../../app/createAppSlice";
3
- import { ActivityMode } from "@capytale/activity.js/activity/activitySession";
4
- import { TimeRange } from "@capytale/activity.js/common/field";
5
3
  import { InitialEditorStateType } from "@capytale/capytale-rich-text-editor";
6
4
  import {
7
5
  MetaPlayerOptions,
8
6
  defaultMetaPlayerOptions,
9
7
  } from "./metaPlayerOptions";
10
8
  import { wf } from "@capytale/activity.js/activity/field/workflow";
9
+ import { ActivityJSData, defaultActivityJSData, EditorType } from "./activityJsData";
10
+ import { defaultUIState, SaveState, UIState } from "./uiState";
11
11
 
12
- export type StudentInfo = {
13
- firstName: string;
14
- lastName: string;
15
- class: string;
16
- };
17
-
18
- export type Instructions = {
19
- value: InitialEditorStateType | null;
20
- htmlValue: string | null;
21
- format: "html" | "markdown" | "lexical";
22
- };
23
-
24
- export type Icon = {
25
- path: string;
26
- style?: any;
27
- };
28
-
29
- export type EditorType = "rich" | "none";
30
12
 
31
- interface ActivityJSData {
32
- title: string;
33
- mode: ActivityMode;
34
- returnUrl: string;
35
- helpUrl: string;
36
- code: string | null;
37
- nid: number;
38
- activityNid: number;
39
- accessTrMode: string;
40
- accessTimerange: TimeRange | null;
41
- studentInfo: StudentInfo | null;
42
- instructions: Instructions | null;
43
- instructionsType: EditorType;
44
- pdfInstructions: Blob | null;
45
- sharedNotesContent: InitialEditorStateType | null;
46
- sharedNotesType: EditorType;
47
- codeLink: string | null;
48
- icon: Icon | null;
49
- friendlyType: string;
50
- comments: string | null;
51
- grading: string | null;
52
- workflow: wf | null | undefined;
53
- antiCheat?: null | {
54
- passwordHash?: string | null;
55
- startLocked: boolean;
56
- };
57
- }
58
13
 
59
- type SaveState = "idle" | "should-save" | "saving";
60
-
61
- interface UIState {
62
- canSaveInstructions: boolean;
63
- canSaveSharedNotes: boolean;
64
- saveState: SaveState;
65
- isPlayerDirty: boolean;
66
- isMPDirty: boolean;
67
- isAntiCheatExitDetectionDisabled: boolean;
68
- }
69
-
70
- export interface ActivityDataState
71
- extends ActivityJSData,
72
- MetaPlayerOptions,
73
- UIState {}
14
+ export type ActivityDataState = ActivityJSData & MetaPlayerOptions & UIState;
74
15
 
75
16
  const initialState: ActivityDataState = {
76
- title: "",
77
- mode: "view",
78
- returnUrl: "",
79
- helpUrl: "",
80
- code: null,
81
- nid: 0,
82
- activityNid: 0,
83
- accessTrMode: "",
84
- accessTimerange: null,
85
- studentInfo: {
86
- firstName: "",
87
- lastName: "",
88
- class: "",
89
- },
90
- instructions: null,
91
- instructionsType: "rich",
92
- pdfInstructions: null,
93
- sharedNotesContent: null,
94
- sharedNotesType: "none",
95
- codeLink: null,
96
- icon: null,
97
- friendlyType: "",
98
- comments: null,
99
- grading: null,
100
- workflow: null,
101
-
17
+ ...defaultActivityJSData,
102
18
  ...defaultMetaPlayerOptions,
103
-
104
- canSaveInstructions: true,
105
- canSaveSharedNotes: true,
106
- saveState: "idle",
107
- isPlayerDirty: false,
108
- isMPDirty: false,
109
-
110
- isAntiCheatExitDetectionDisabled: false,
19
+ ...defaultUIState,
111
20
  };
112
21
 
113
22
  // If you are not using async thunks you can use the standalone `createSlice`.
@@ -144,6 +53,7 @@ export const activityDataSlice = createAppSlice({
144
53
  state.activityNid = action.payload.activityNid;
145
54
  state.workflow = action.payload.workflow;
146
55
  state.antiCheat = action.payload.antiCheat;
56
+ state.hasEvaluations = action.payload.hasEvaluations;
147
57
  },
148
58
  ),
149
59
  setPlayerSettings: create.reducer(
@@ -153,6 +63,7 @@ export const activityDataSlice = createAppSlice({
153
63
  state.pedagoLayout = action.payload.pedagoLayout;
154
64
  state.supportsLightTheme = action.payload.supportsLightTheme;
155
65
  state.supportsDarkTheme = action.payload.supportsDarkTheme;
66
+ state.preventEditIfHasEvaluations = action.payload.preventEditIfHasEvaluations;
156
67
  },
157
68
  ),
158
69
  setCanSaveInstructions: create.reducer(
@@ -243,6 +154,7 @@ export const activityDataSlice = createAppSlice({
243
154
  selectCanReset: (data) => data.canReset,
244
155
  selectPedagoLayout: (data) => data.pedagoLayout,
245
156
  selectHasGradingOrComments: (data) => !!(data.grading || data.comments),
157
+ selectPreventEditIfHasEvaluations: (data) => data.preventEditIfHasEvaluations,
246
158
  selectSaveState: (data) => data.saveState,
247
159
  selectReturnUrl: (data) => data.returnUrl,
248
160
  selectNid: (data) => data.nid,
@@ -275,6 +187,7 @@ export const activityDataSlice = createAppSlice({
275
187
  ),
276
188
  selectIsAntiCheatExitDetectionDisabled: (data) =>
277
189
  data.isAntiCheatExitDetectionDisabled,
190
+ selectHasEvaluations: (data) => data.hasEvaluations,
278
191
  },
279
192
  });
280
193
 
@@ -315,6 +228,7 @@ export const {
315
228
  selectCanReset,
316
229
  selectPedagoLayout,
317
230
  selectHasGradingOrComments,
231
+ selectPreventEditIfHasEvaluations,
318
232
  selectSaveState,
319
233
  selectReturnUrl,
320
234
  selectNid,
@@ -332,4 +246,5 @@ export const {
332
246
  selectAntiCheat,
333
247
  selectHasAntiCheat,
334
248
  selectIsAntiCheatExitDetectionDisabled,
249
+ selectHasEvaluations,
335
250
  } = activityDataSlice.selectors;
@@ -0,0 +1,81 @@
1
+ import { ActivityMode } from "@capytale/activity.js/activity/activitySession";
2
+ import { TimeRange } from "@capytale/activity.js/common/field";
3
+ import { InitialEditorStateType } from "@capytale/capytale-rich-text-editor";
4
+ import { wf } from "@capytale/activity.js/activity/field/workflow";
5
+
6
+ export type StudentInfo = {
7
+ firstName: string;
8
+ lastName: string;
9
+ class: string;
10
+ };
11
+
12
+ export type Instructions = {
13
+ value: InitialEditorStateType | null;
14
+ htmlValue: string | null;
15
+ format: "html" | "markdown" | "lexical";
16
+ };
17
+
18
+ export type Icon = {
19
+ path: string;
20
+ style?: any;
21
+ };
22
+
23
+ export type EditorType = "rich" | "none";
24
+
25
+ export type ActivityJSData = {
26
+ title: string;
27
+ mode: ActivityMode;
28
+ returnUrl: string;
29
+ helpUrl: string;
30
+ code: string | null;
31
+ nid: number;
32
+ activityNid: number;
33
+ accessTrMode: string;
34
+ accessTimerange: TimeRange | null;
35
+ studentInfo: StudentInfo | null;
36
+ instructions: Instructions | null;
37
+ instructionsType: EditorType;
38
+ pdfInstructions: Blob | null;
39
+ sharedNotesContent: InitialEditorStateType | null;
40
+ sharedNotesType: EditorType;
41
+ codeLink: string | null;
42
+ icon: Icon | null;
43
+ friendlyType: string;
44
+ comments: string | null;
45
+ grading: string | null;
46
+ workflow: wf | null | undefined;
47
+ antiCheat?: null | {
48
+ passwordHash?: string | null;
49
+ startLocked: boolean;
50
+ };
51
+ hasEvaluations: boolean | null;
52
+ }
53
+
54
+ export const defaultActivityJSData: ActivityJSData = {
55
+ title: "",
56
+ mode: "view",
57
+ returnUrl: "",
58
+ helpUrl: "",
59
+ code: null,
60
+ nid: 0,
61
+ activityNid: 0,
62
+ accessTrMode: "",
63
+ accessTimerange: null,
64
+ studentInfo: {
65
+ firstName: "",
66
+ lastName: "",
67
+ class: "",
68
+ },
69
+ instructions: null,
70
+ instructionsType: "rich",
71
+ pdfInstructions: null,
72
+ sharedNotesContent: null,
73
+ sharedNotesType: "none",
74
+ codeLink: null,
75
+ icon: null,
76
+ friendlyType: "",
77
+ comments: null,
78
+ grading: null,
79
+ workflow: null,
80
+ hasEvaluations: null,
81
+ };
@@ -1,14 +1,48 @@
1
- import { useAppDispatch } from "../../app/hooks";
2
- import { setIsPlayerDirty, setSaveState } from "./activityDataSlice";
1
+ import { useCallback } from "react";
2
+ import { useAppDispatch, useAppSelector } from "../../app/hooks";
3
+ import { selectShowSaveForStudents } from "../layout/layoutSlice";
4
+ import { selectHasEvaluations, selectIsDirty, selectMode, selectPreventEditIfHasEvaluations, selectShowSaveButton, setIsPlayerDirty, setSaveState } from "./activityDataSlice";
3
5
 
4
- const useNotifyIsDirty = () => {
6
+ export const useNotifyIsDirty = () => {
5
7
  const dispatch = useAppDispatch();
6
8
  return (isDirty: boolean = true) => dispatch(setIsPlayerDirty(isDirty));
7
9
  };
8
10
 
9
- const useSave = () => {
11
+ export const useCanSave = () => {
12
+ const isDirty = useAppSelector(selectIsDirty);
13
+ const preventEditIfHasEvaluations = useAppSelector(
14
+ selectPreventEditIfHasEvaluations,
15
+ );
16
+ const hasEvaluations = useAppSelector(selectHasEvaluations);
17
+
18
+ const mode = useAppSelector(selectMode);
19
+ const showSaveButton = useAppSelector(selectShowSaveButton);
20
+ const showSaveForStudents = useAppSelector(selectShowSaveForStudents);
21
+ const hasSaveButton =
22
+ showSaveButton && !(mode === "assignment" && !showSaveForStudents);
23
+
24
+ if (!hasSaveButton) {
25
+ return false;
26
+ }
27
+
28
+ if (mode === "create" && hasEvaluations && preventEditIfHasEvaluations) {
29
+ return false;
30
+ }
31
+
32
+ if (!isDirty) {
33
+ return false;
34
+ }
35
+
36
+ return true;
37
+ }
38
+
39
+ export const useSave = () => {
10
40
  const dispatch = useAppDispatch();
11
- return () => dispatch(setSaveState("should-save"));
41
+ const canSave = useCanSave();
42
+ const setShouldSave = useCallback(() => {
43
+ if (canSave) {
44
+ dispatch(setSaveState("should-save"))
45
+ }
46
+ }, [dispatch, canSave]);
47
+ return setShouldSave;
12
48
  };
13
-
14
- export { useNotifyIsDirty, useSave };
@@ -1,11 +1,12 @@
1
1
  export type MetaPlayerOptions = {
2
2
  hasInstructions: boolean;
3
3
  canReset: boolean;
4
+ preventEditIfHasEvaluations: boolean;
4
5
  pedagoLayout:
5
- | "horizontal"
6
- | "vertical"
7
- | "default-horizontal"
8
- | "default-vertical";
6
+ | "horizontal"
7
+ | "vertical"
8
+ | "default-horizontal"
9
+ | "default-vertical";
9
10
  supportsLightTheme: boolean;
10
11
  supportsDarkTheme: boolean;
11
12
  };
@@ -13,6 +14,7 @@ export type MetaPlayerOptions = {
13
14
  export const defaultMetaPlayerOptions: MetaPlayerOptions = {
14
15
  hasInstructions: true,
15
16
  canReset: true,
17
+ preventEditIfHasEvaluations: false,
16
18
  pedagoLayout: "default-horizontal",
17
19
  supportsLightTheme: true,
18
20
  supportsDarkTheme: false,
@@ -0,0 +1,21 @@
1
+ export type SaveState = "idle" | "should-save" | "saving";
2
+
3
+ export type UIState = {
4
+ canSaveInstructions: boolean;
5
+ canSaveSharedNotes: boolean;
6
+ saveState: SaveState;
7
+ isPlayerDirty: boolean;
8
+ isMPDirty: boolean;
9
+
10
+ isAntiCheatExitDetectionDisabled: boolean;
11
+ }
12
+
13
+ export const defaultUIState: UIState = {
14
+ canSaveInstructions: true,
15
+ canSaveSharedNotes: true,
16
+ saveState: "idle",
17
+ isPlayerDirty: false,
18
+ isMPDirty: false,
19
+
20
+ isAntiCheatExitDetectionDisabled: false,
21
+ };
@@ -97,6 +97,7 @@ export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
97
97
  startLocked: !ab.assignmentNode?.isNew,
98
98
  }
99
99
  : null,
100
+ hasEvaluations: ab.activityNode.has_evaluation.value,
100
101
  }),
101
102
  );
102
103
  tracker.trackActivity(data);
@@ -1,10 +1,10 @@
1
- import { useAppSelector } from "../../app/hooks";
1
+ import { useAppSelector } from "../../../app/hooks";
2
2
  import {
3
3
  selectActivityInfo,
4
4
  selectIcon,
5
5
  selectMode,
6
- } from "../activityData/activityDataSlice";
7
- import { studentNameFromInfo } from "./student-utils";
6
+ } from "../../activityData/activityDataSlice";
7
+ import { studentNameFromInfo } from "../student-utils";
8
8
  import styles from "./style.module.scss";
9
9
 
10
10
  const ActivityInfo: React.FC = () => {
@@ -0,0 +1,38 @@
1
+ .activityInfo {
2
+ flex-shrink: 1;
3
+ flex-grow: 1;
4
+ display: flex;
5
+ justify-content: center;
6
+ align-items: center;
7
+ overflow: hidden;
8
+ gap: 12px;
9
+ }
10
+
11
+ .activityInfoText {
12
+ display: flex;
13
+ flex-direction: column;
14
+ justify-content: center;
15
+ align-items: center;
16
+ overflow: hidden;
17
+ & > * {
18
+ overflow: hidden;
19
+ text-overflow: ellipsis;
20
+ white-space: nowrap;
21
+ max-width: 100%;
22
+ }
23
+ }
24
+
25
+ .activityInfoTitle {
26
+ font-size: 20px;
27
+ font-weight: 500;
28
+ }
29
+
30
+ .activityLogo {
31
+ width: 35px;
32
+ height: 37px;
33
+ border-radius: 6px;
34
+ flex-shrink: 0;
35
+ @media only screen and (max-width: 992px) {
36
+ display: none;
37
+ }
38
+ }
@@ -1,13 +1,13 @@
1
1
  import { FC, useEffect } from "react";
2
- import { useAppDispatch, useAppSelector } from "../../app/hooks";
2
+ import { useAppDispatch, useAppSelector } from "../../../app/hooks";
3
3
  import {
4
4
  QuickAction,
5
5
  selectQuickActions,
6
6
  setQuickActions,
7
7
  usePerformQuickAction,
8
- } from "./navbarSlice";
8
+ } from "../navbarSlice";
9
9
  import { Button } from "primereact/button";
10
- import settings from "../../settings";
10
+ import settings from "../../../settings";
11
11
 
12
12
  const ActivityQuickActions: FC<{}> = () => {
13
13
  const quickActions = useAppSelector(selectQuickActions);
@@ -3,22 +3,22 @@ import styles from "./style.module.scss";
3
3
 
4
4
  import { Button } from "primereact/button";
5
5
  import { Sidebar } from "primereact/sidebar";
6
- import SettingsSidebarContent from "./SettingsSidebarContent";
7
- import settings from "../../settings";
6
+ import SettingsSidebarContent from "../sidebars/SettingsSidebarContent";
7
+ import settings from "../../../settings";
8
8
  import ActivityQuickActions from "./ActivityQuickActions";
9
- import useFullscreen from "../../utils/useFullscreen";
10
- import { useActivityJS } from "../activityJS/ActivityJSProvider";
9
+ import useFullscreen from "../../../utils/useFullscreen";
10
+ import { useActivityJS } from "../../activityJS/ActivityJSProvider";
11
11
  import { Dialog } from "primereact/dialog";
12
12
  import capytaleUI from "@capytale/activity.js/backend/capytale/ui";
13
- import { useAppSelector } from "../../app/hooks";
14
- import { selectHasAntiCheat } from "../activityData/activityDataSlice";
15
- import AttachedFilesSidebarContent from "./AttachedFilesSidebarContent";
13
+ import { useAppSelector } from "../../../app/hooks";
14
+ import { selectHasAntiCheat } from "../../activityData/activityDataSlice";
15
+ import AttachedFilesSidebarContent from "../sidebars/AttachedFilesSidebarContent";
16
16
  import { Badge } from "primereact/badge";
17
- import { useAttachedFiles } from "../functionalities/hooks";
17
+ import { useAttachedFiles } from "../../functionalities/hooks";
18
18
  import {
19
19
  selectAttachedFilesEnabled,
20
20
  selectAttachedFilesOptions,
21
- } from "../functionalities/functionalitiesSlice";
21
+ } from "../../functionalities/functionalitiesSlice";
22
22
 
23
23
  const ActivityMenu: React.FC = memo(() => {
24
24
  const [settingsSidebarVisible, setSettingsSidebarVisible] = useState(false);
@@ -0,0 +1,7 @@
1
+ .activityMenu {
2
+ padding-right: 16px;
3
+ display: flex;
4
+ gap: 8px;
5
+ justify-content: center;
6
+ align-items: center;
7
+ }
@@ -1,12 +1,12 @@
1
1
  import { FC, ReactNode, useCallback, useMemo, useRef, useState } from "react";
2
2
  import { useInterval } from "primereact/hooks";
3
3
  import { Toast } from "primereact/toast";
4
- import { useActivityJS } from "../activityJS/ActivityJSProvider";
5
- import { useAppSelector } from "../../app/hooks";
6
- import { selectIsDirty, selectMode } from "../activityData/activityDataSlice";
4
+ import { useActivityJS } from "../../activityJS/ActivityJSProvider";
5
+ import { useAppSelector } from "../../../app/hooks";
6
+ import { selectIsDirty, selectMode } from "../../activityData/activityDataSlice";
7
7
  import { Button } from "primereact/button";
8
- import { useSave } from "../activityData/hooks";
9
- import serverClock from "../../utils/server-clock";
8
+ import { useSave } from "../../activityData/hooks";
9
+ import serverClock from "../../../utils/server-clock";
10
10
 
11
11
  // TODO use https://capytale2.ac-paris.fr/vanilla/time-s.php
12
12
  // https://forge.apps.education.fr/capytale/activity-js/-/blob/main/src/backend/capytale/clock.ts?ref_type=heads
@@ -0,0 +1,115 @@
1
+ import { FC, useMemo, useState } from "react";
2
+ import Countdown from "./Countdown";
3
+ import { Button } from "primereact/button";
4
+ import { Dialog } from "primereact/dialog";
5
+ import { ML } from "../../../utils/breakpoints";
6
+ import { useAppDispatch, useAppSelector } from "../../../app/hooks";
7
+ import {
8
+ selectHasEvaluations,
9
+ selectIsDirty,
10
+ selectMode,
11
+ selectPreventEditIfHasEvaluations,
12
+ selectSaveState,
13
+ selectShowSaveButton,
14
+ setSaveState,
15
+ } from "../../activityData/activityDataSlice";
16
+ import { useWindowSize } from "@uidotdev/usehooks";
17
+ import { selectShowSaveForStudents } from "../../layout/layoutSlice";
18
+ import settings from "../../../settings";
19
+
20
+ const CountdownAndSaveButton: FC = () => {
21
+ const dispatch = useAppDispatch();
22
+ const saveState = useAppSelector(selectSaveState);
23
+ const windowsSize = useWindowSize();
24
+ const isQuiteSmall = useMemo(
25
+ () => windowsSize.width && windowsSize.width < ML,
26
+ [windowsSize.width],
27
+ );
28
+ const isDirty = useAppSelector(selectIsDirty);
29
+ const preventEditIfHasEvaluations = useAppSelector(
30
+ selectPreventEditIfHasEvaluations,
31
+ );
32
+ const hasEvaluations = useAppSelector(selectHasEvaluations);
33
+
34
+ const mode = useAppSelector(selectMode);
35
+ const showSaveButton = useAppSelector(selectShowSaveButton);
36
+ const showSaveForStudents = useAppSelector(selectShowSaveForStudents);
37
+ const hasSaveButton =
38
+ showSaveButton && !(mode === "assignment" && !showSaveForStudents);
39
+
40
+ const [editForbiddenDialogVisible, setEditForbiddenDialogVisible] =
41
+ useState(false);
42
+
43
+ if (!hasSaveButton) {
44
+ return null;
45
+ }
46
+
47
+ if (mode === "create" && hasEvaluations && preventEditIfHasEvaluations) {
48
+ return (
49
+ <>
50
+ <Button
51
+ label={isQuiteSmall ? undefined : "Modification interdite"}
52
+ aria-label="Modification interdite"
53
+ severity="danger"
54
+ size="small"
55
+ icon="pi pi-save"
56
+ loading={saveState !== "idle"}
57
+ onClick={() => {
58
+ setEditForbiddenDialogVisible(true);
59
+ }}
60
+ tooltip={"Évaluations déjà attribuées"}
61
+ tooltipOptions={{
62
+ position: "bottom",
63
+ showDelay: settings.TOOLTIP_SHOW_DELAY,
64
+ showOnDisabled: true,
65
+ }}
66
+ />
67
+ <Dialog
68
+ header="Modification interdite"
69
+ visible={editForbiddenDialogVisible}
70
+ onHide={() => {
71
+ if (!editForbiddenDialogVisible) return;
72
+ setEditForbiddenDialogVisible(false);
73
+ }}
74
+ style={{ maxWidth: "35rem" }}
75
+ >
76
+ <p className="m-0">
77
+ Vous avez déjà donné des évaluations à vos élèves. Ce type
78
+ d'activité ne supporte pas la modification dans ce cas car cela
79
+ rendrait les résultats des élèves incohérents.
80
+ </p>
81
+ <p className="m-0 mt-1">
82
+ Si vous souhaitez modifier cette activité, vous pouvez la clôner et
83
+ modifier la copie, que vous pouvez ensuite proposer à vos élèves.
84
+ </p>
85
+ </Dialog>
86
+ </>
87
+ );
88
+ }
89
+
90
+ return (
91
+ <>
92
+ <Countdown />
93
+ <Button
94
+ label={isQuiteSmall ? undefined : "Enregistrer"}
95
+ aria-label="Enregistrer"
96
+ disabled={!isDirty}
97
+ severity={"warning"}
98
+ size="small"
99
+ icon="pi pi-save"
100
+ loading={saveState !== "idle"}
101
+ onClick={() => {
102
+ dispatch(setSaveState("should-save"));
103
+ }} /**
104
+ tooltip={isDirty ? "Enregistrer l'activité" : "Rien à enregistrer"}
105
+ tooltipOptions={{
106
+ position: "bottom",
107
+ showDelay: settings.TOOLTIP_SHOW_DELAY,
108
+ showOnDisabled: true,
109
+ }} */
110
+ />
111
+ </>
112
+ );
113
+ };
114
+
115
+ export default CountdownAndSaveButton;
@@ -7,52 +7,36 @@ import { OverlayPanel } from "primereact/overlaypanel";
7
7
  import styles from "./style.module.scss";
8
8
  import { useMemo, useRef } from "react";
9
9
  import { useWindowSize } from "@uidotdev/usehooks";
10
- import { ML, XL } from "../../utils/breakpoints";
11
- import { useAppDispatch, useAppSelector } from "../../app/hooks";
10
+ import { XL } from "../../../utils/breakpoints";
11
+ import { useAppDispatch, useAppSelector } from "../../../app/hooks";
12
12
  import {
13
- selectIsDirty,
14
13
  selectMode,
15
- selectSaveState,
16
14
  selectSharingInfo,
17
- selectShowSaveButton,
18
15
  selectWorkflow,
19
16
  setSaveState,
20
- } from "../activityData/activityDataSlice";
21
- import { copyToClipboard } from "../../utils/clipboard";
22
- import settings from "../../settings";
17
+ } from "../../activityData/activityDataSlice";
18
+ import { copyToClipboard } from "../../../utils/clipboard";
19
+ import settings from "../../../settings";
23
20
  import { wf } from "@capytale/activity.js/activity/field/workflow";
24
21
  import capytaleUI from "@capytale/activity.js/backend/capytale/ui";
25
- import ButtonDoubleIcon from "../../utils/ButtonDoubleIcon";
26
- import CardSelector from "../../utils/CardSelector";
27
- import { useActivityJS } from "../activityJS/ActivityJSProvider";
28
- import {
29
- selectShowSaveForStudents,
30
- selectShowWorkflow,
31
- } from "../layout/layoutSlice";
32
- import Countdown from "./Countdown";
22
+ import ButtonDoubleIcon from "../../../utils/ButtonDoubleIcon";
23
+ import CardSelector from "../../../utils/CardSelector";
24
+ import { useActivityJS } from "../../activityJS/ActivityJSProvider";
25
+ import { selectShowWorkflow } from "../../layout/layoutSlice";
26
+ import CountdownAndSaveButton from "./CountdownAndSaveButton";
33
27
 
34
28
  const CapytaleMenu: React.FC = () => {
35
29
  const dispatch = useAppDispatch();
36
30
  const activityJS = useActivityJS();
37
31
  const sharingInfo = useAppSelector(selectSharingInfo);
38
- const saveState = useAppSelector(selectSaveState);
39
32
  const windowsSize = useWindowSize();
40
33
  const mode = useAppSelector(selectMode);
41
34
  const workflow = useAppSelector(selectWorkflow);
42
- const isDirty = useAppSelector(selectIsDirty);
43
- const showSaveButton = useAppSelector(selectShowSaveButton);
44
- const showSaveForStudents = useAppSelector(selectShowSaveForStudents);
45
- const hasSaveButton =
46
- showSaveButton && !(mode === "assignment" && !showSaveForStudents);
47
35
  const showWorkflow = useAppSelector(selectShowWorkflow);
48
36
  const isLarge = useMemo(
49
37
  () => windowsSize.width && windowsSize.width >= XL,
50
38
  [windowsSize.width],
51
39
  );
52
- const isQuiteSmall = useMemo(
53
- () => windowsSize.width && windowsSize.width < ML,
54
- [windowsSize.width],
55
- );
56
40
  const isInIframe = useMemo(() => capytaleUI.isInCapytaletIframe(), []);
57
41
  const toast = useRef<Toast>(null);
58
42
  const changeWorkflow = (value: wf) => {
@@ -91,31 +75,7 @@ const CapytaleMenu: React.FC = () => {
91
75
  }}
92
76
  />
93
77
  )}
94
- {
95
- hasSaveButton && (
96
- <>
97
- <Countdown />
98
- <Button
99
- label={isQuiteSmall ? undefined : "Enregistrer"}
100
- aria-label="Enregistrer"
101
- disabled={!isDirty}
102
- severity={"warning"}
103
- size="small"
104
- icon="pi pi-save"
105
- loading={saveState !== "idle"}
106
- onClick={() => {
107
- dispatch(setSaveState("should-save"));
108
- }}
109
- />
110
- </>
111
- ) /**
112
- tooltip={isDirty ? "Enregistrer l'activité" : "Rien à enregistrer"}
113
- tooltipOptions={{
114
- position: "bottom",
115
- showDelay: settings.TOOLTIP_SHOW_DELAY,
116
- showOnDisabled: true,
117
- }} */
118
- }
78
+ <CountdownAndSaveButton />
119
79
 
120
80
  {mode === "create" && sharingInfo.code && (
121
81
  <ButtonGroup>
@@ -172,6 +132,7 @@ const CapytaleMenu: React.FC = () => {
172
132
  {mode === "assignment" && workflow === "current" && (
173
133
  <Button
174
134
  aria-label="Rendre la copie"
135
+ size="small"
175
136
  outlined
176
137
  label="Rendre"
177
138
  icon="pi pi-envelope"
@@ -0,0 +1,9 @@
1
+ .capytaleMenu {
2
+ align-items: center;
3
+ display: flex;
4
+ gap: 8px;
5
+ }
6
+
7
+ .overlayPanelWorkflowTitle {
8
+ margin-top: 0;
9
+ }
@@ -1,8 +1,8 @@
1
1
  import { MD } from "../../utils/breakpoints";
2
2
  import { useWindowSize } from "@uidotdev/usehooks";
3
- import ActivityInfo from "./ActivityInfo";
4
- import ActivityMenu from "./ActivityMenu";
5
- import CapytaleMenu from "./CapytaleMenu";
3
+ import ActivityInfo from "./activity-info";
4
+ import ActivityMenu from "./activity-menu";
5
+ import CapytaleMenu from "./capytale-menu";
6
6
  import styles from "./style.module.scss";
7
7
  import { useMemo } from "react";
8
8
  import { useAppSelector } from "../../app/hooks";
@@ -2,19 +2,21 @@ import { useState, useEffect, useMemo } from "react";
2
2
  import evalApi from "@capytale/activity.js/backend/capytale/evaluation";
3
3
  import { Dropdown } from "primereact/dropdown";
4
4
 
5
- import { useAppSelector } from "../../app/hooks";
5
+ import { useAppSelector } from "../../../app/hooks";
6
6
  import {
7
+ selectActivityInfo,
7
8
  selectActivityNid,
8
9
  selectMode,
9
10
  selectNid,
10
- } from "../activityData/activityDataSlice";
11
- import { studentNameFromInfo } from "./student-utils";
11
+ } from "../../activityData/activityDataSlice";
12
+ import { studentNameFromInfo } from "../student-utils";
12
13
 
13
14
  import { SaInfo } from "@capytale/activity.js/activity/evaluation/evaluation";
14
15
  import { SelectItem, SelectItemOptionsType } from "primereact/selectitem";
15
16
  import { Button } from "primereact/button";
16
17
 
17
18
  const GradingNav: React.FC = () => {
19
+ const activityInfo = useAppSelector(selectActivityInfo);
18
20
  const [studentList, setStudentList] = useState<SaInfo[]>([]);
19
21
  const nid = useAppSelector(selectNid) as number;
20
22
  const mode = useAppSelector(selectMode);
@@ -71,7 +73,7 @@ const GradingNav: React.FC = () => {
71
73
  }, [studentList]);
72
74
 
73
75
  if (studentList.length === 0) {
74
- return <Dropdown loading placeholder="Chargement..." />;
76
+ return <span>{studentNameFromInfo(activityInfo.studentInfo)}</span>;
75
77
  }
76
78
 
77
79
  const firstNid = studentList[0].nid as number;
@@ -0,0 +1,22 @@
1
+ .reviewNavbar {
2
+ color-scheme: dark;
3
+ background-color: var(--surface-500);
4
+ --review-navbar-text-color: rgba(255, 255, 255, 0.95);
5
+ color: var(--review-navbar-text-color);
6
+ padding: 4px 16px;
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: space-between;
10
+ & :global(.p-button-secondary) {
11
+ border-color: var(--review-navbar-text-color);
12
+ color: var(--review-navbar-text-color);
13
+ }
14
+ & :global(.p-dropdown) {
15
+ border-color: var(--review-navbar-text-color);
16
+ border-left-width: 0;
17
+ border-right-width: 0;
18
+ }
19
+ :global(.dark-theme) & {
20
+ background-color: var(--surface-100);
21
+ }
22
+ }
@@ -1,13 +1,13 @@
1
1
  import { FC, useEffect } from "react";
2
- import { useAppDispatch, useAppSelector } from "../../app/hooks";
2
+ import { useAppDispatch, useAppSelector } from "../../../app/hooks";
3
3
  import {
4
4
  QuickAction,
5
5
  selectSidebarActions,
6
6
  setSidebarActions,
7
7
  usePerformQuickAction,
8
- } from "./navbarSlice";
8
+ } from "../navbarSlice";
9
9
  import { Button } from "primereact/button";
10
- import settings from "../../settings";
10
+ import settings from "../../../settings";
11
11
 
12
12
  const ActivitySidebarActions: FC<{}> = () => {
13
13
  const sidebarActions = useAppSelector(selectSidebarActions);
@@ -1,16 +1,16 @@
1
1
  import { FC, useRef } from "react";
2
- import { useAttachedFiles } from "../functionalities/hooks";
3
- import { useAppSelector } from "../../app/hooks";
2
+ import { useAttachedFiles } from "../../functionalities/hooks";
3
+ import { useAppSelector } from "../../../app/hooks";
4
4
  import {
5
5
  AttachedFileData,
6
6
  selectAttachedFilesOptions,
7
- } from "../functionalities/functionalitiesSlice";
7
+ } from "../../functionalities/functionalitiesSlice";
8
8
  import { Button } from "primereact/button";
9
9
  import { Toast } from "primereact/toast";
10
10
 
11
11
  import styles from "./AttachedFilesSidebarContent.module.scss";
12
- import { copyToClipboard } from "../../utils/clipboard";
13
- import { downloadFile } from "../../utils/download";
12
+ import { copyToClipboard } from "../../../utils/clipboard";
13
+ import { downloadFile } from "../../../utils/download";
14
14
  import { useFileUpload } from "use-file-upload";
15
15
 
16
16
  const AttachedFilesSidebarContent: FC = () => {
@@ -2,8 +2,8 @@ import { FC } from "react";
2
2
 
3
3
  import { Fieldset } from "primereact/fieldset";
4
4
 
5
- import { selectOrientation, setLayout } from "../layout/layoutSlice";
6
- import { useAppDispatch, useAppSelector } from "../../app/hooks";
5
+ import { selectOrientation, setLayout } from "../../layout/layoutSlice";
6
+ import { useAppDispatch, useAppSelector } from "../../../app/hooks";
7
7
  import { RadioButton } from "primereact/radiobutton";
8
8
 
9
9
  import styles from "./style.module.scss";
@@ -11,19 +11,19 @@ import {
11
11
  selectThemeNameOrAuto,
12
12
  setAutoSwitch,
13
13
  switchToTheme,
14
- } from "../theming/themingSlice";
15
- import { selectSidebarActions } from "./navbarSlice";
14
+ } from "../../theming/themingSlice";
15
+ import { selectSidebarActions } from "../navbarSlice";
16
16
  import ActivitySidebarActions from "./ActivitySidebarActions";
17
17
  import { classNames } from "primereact/utils";
18
18
  import {
19
19
  selectCanChoosePedagoLayout,
20
20
  selectCanChooseTheme,
21
- } from "../activityData/activityDataSlice";
22
- import { ActivitySettingsDisplay } from "../activitySettings";
21
+ } from "../../activityData/activityDataSlice";
22
+ import { ActivitySettingsDisplay } from "../../activitySettings";
23
23
  import { Button } from "primereact/button";
24
24
  import { ConfirmPopup, confirmPopup } from "primereact/confirmpopup";
25
- import settings from "../../settings";
26
- import { useReset } from "../activityJS/hooks";
25
+ import settings from "../../../settings";
26
+ import { useReset } from "../../activityJS/hooks";
27
27
 
28
28
  type SettingsSidebarContentProps = {
29
29
  showHelp?: () => void;
@@ -0,0 +1,15 @@
1
+ .sidebarCapytaleActions {
2
+ margin-bottom: 8px;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 8px;
6
+ & > button {
7
+ width: 100%;
8
+ }
9
+ }
10
+
11
+ .sidebarFieldsetButtons :global(.p-fieldset-content) {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 6px;
15
+ }
@@ -1,4 +1,4 @@
1
- import { StudentInfo } from "../activityData/activityDataSlice";
1
+ import { StudentInfo } from "../activityData/activityJsData";
2
2
 
3
3
  export function studentNameFromInfo(studentInfo: StudentInfo | null) {
4
4
  if (!studentInfo || (!studentInfo.firstName && !studentInfo.lastName)) {
@@ -31,29 +31,6 @@
31
31
  }
32
32
  }
33
33
 
34
- .reviewNavbar {
35
- color-scheme: dark;
36
- background-color: var(--surface-500);
37
- --review-navbar-text-color: rgba(255, 255, 255, 0.95);
38
- color: var(--review-navbar-text-color);
39
- padding: 4px 16px;
40
- display: flex;
41
- align-items: center;
42
- justify-content: space-between;
43
- & :global(.p-button-secondary) {
44
- border-color: var(--review-navbar-text-color);
45
- color: var(--review-navbar-text-color);
46
- }
47
- & :global(.p-dropdown) {
48
- border-color: var(--review-navbar-text-color);
49
- border-left-width: 0;
50
- border-right-width: 0;
51
- }
52
- :global(.dark-theme) & {
53
- background-color: var(--surface-100);
54
- }
55
- }
56
-
57
34
  .navbarContainer {
58
35
  height: 60px;
59
36
  flex-grow: 1;
@@ -86,76 +63,3 @@
86
63
  }
87
64
  }
88
65
  }
89
-
90
- .capytaleMenu {
91
- align-items: center;
92
- display: flex;
93
- gap: 8px;
94
- }
95
-
96
- .activityInfo {
97
- flex-shrink: 1;
98
- flex-grow: 1;
99
- display: flex;
100
- justify-content: center;
101
- align-items: center;
102
- overflow: hidden;
103
- gap: 12px;
104
- }
105
-
106
- .activityInfoText {
107
- display: flex;
108
- flex-direction: column;
109
- justify-content: center;
110
- align-items: center;
111
- overflow: hidden;
112
- & > * {
113
- overflow: hidden;
114
- text-overflow: ellipsis;
115
- white-space: nowrap;
116
- max-width: 100%;
117
- }
118
- }
119
-
120
- .activityInfoTitle {
121
- font-size: 20px;
122
- font-weight: 500;
123
- }
124
-
125
- .activityLogo {
126
- width: 35px;
127
- height: 37px;
128
- border-radius: 6px;
129
- flex-shrink: 0;
130
- @media only screen and (max-width: 992px) {
131
- display: none;
132
- }
133
- }
134
-
135
- .activityMenu {
136
- padding-right: 16px;
137
- display: flex;
138
- gap: 8px;
139
- justify-content: center;
140
- align-items: center;
141
- }
142
-
143
- .sidebarFieldsetButtons :global(.p-fieldset-content) {
144
- display: flex;
145
- flex-direction: column;
146
- gap: 6px;
147
- }
148
-
149
- .sidebarCapytaleActions {
150
- margin-bottom: 8px;
151
- display: flex;
152
- flex-direction: column;
153
- gap: 8px;
154
- & > button {
155
- width: 100%;
156
- }
157
- }
158
-
159
- .overlayPanelWorkflowTitle {
160
- margin-top: 0;
161
- }
@@ -215,6 +215,8 @@ const HorizontalDocumentSelector = () => {
215
215
  {items.map((item) => (
216
216
  <div
217
217
  className={styles.pedagoHorizontalTab}
218
+ role="button"
219
+ aria-label={`Basculer vers l'onglet ${item.name}`}
218
220
  onClick={() => dispatch(setPedagoTab(item.value))}
219
221
  data-selected={item.value === pedagoTab}
220
222
  key={item.value}
@@ -57,6 +57,13 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
57
57
  <PedagoCommands />
58
58
  <div
59
59
  className={styles.pedagoContent}
60
+ aria-label={
61
+ pedagoTab === "instructions"
62
+ ? "Consignes"
63
+ : pedagoTab === "pdf"
64
+ ? "PDF"
65
+ : "Notes partagées"
66
+ }
60
67
  data-grading-visible={isGradingVisible}
61
68
  >
62
69
  {!isGradingVisible && (
@@ -90,6 +97,13 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
90
97
  >
91
98
  <Panel
92
99
  className={styles.fullSizePanel}
100
+ aria-label={
101
+ pedagoTab === "instructions"
102
+ ? "Consignes"
103
+ : pedagoTab === "pdf"
104
+ ? "PDF"
105
+ : "Notes partagées"
106
+ }
93
107
  header={
94
108
  pedagoTab === "instructions"
95
109
  ? "Consignes"
package/src/index.tsx CHANGED
@@ -13,17 +13,21 @@ import {
13
13
  useActivityJS,
14
14
  useActivityJsEssentials,
15
15
  } from "./features/activityJS/ActivityJSProvider";
16
- import { useNotifyIsDirty, useSave } from "./features/activityData/hooks";
16
+ import { useNotifyIsDirty, useCanSave, useSave } from "./features/activityData/hooks";
17
17
  import { useOrientation } from "./features/layout/hooks";
18
- import { ActivitySidebarActionsSetter } from "./features/navbar/ActivitySidebarActions";
19
- import { ActivityQuickActionsSetter } from "./features/navbar/ActivityQuickActions";
18
+ import { ActivitySidebarActionsSetter } from "./features/navbar/sidebars/ActivitySidebarActions";
19
+ import { ActivityQuickActionsSetter } from "./features/navbar/activity-menu/ActivityQuickActions";
20
20
  import ActivitySettingsSetter from "./features/activitySettings/ActivitySettingsSetter";
21
21
  import IsDirtySetter from "./features/activityData/IsDirtySetter";
22
22
  import AttachedFilesFunctionality from "./features/functionalities/AttachedFilesFunctionality";
23
23
  import { useRefreshAttachedFiles } from "./features/functionalities/hooks";
24
+ import { useActivitySettings } from "./features/activitySettings/hooks";
24
25
  import { Toast } from "./external/prime";
25
26
  import type { ToastMessage } from "./external/prime";
26
- import type { AttachedFileData, UploadedFileInfo } from "./features/functionalities/functionalitiesSlice";
27
+ import type {
28
+ AttachedFileData,
29
+ UploadedFileInfo,
30
+ } from "./features/functionalities/functionalitiesSlice";
27
31
 
28
32
  export {
29
33
  MetaPlayer,
@@ -37,9 +41,11 @@ export {
37
41
  useActivityJS,
38
42
  useActivityJsEssentials,
39
43
  useNotifyIsDirty,
44
+ useCanSave,
40
45
  useSave,
41
46
  useOrientation,
42
47
  useThemeType,
48
+ useActivitySettings,
43
49
  ActivitySidebarActionsSetter,
44
50
  ActivityQuickActionsSetter,
45
51
  ActivitySettingsSetter,