@capytale/meta-player 0.1.1 → 0.1.3
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 +1 -1
- package/src/MetaPlayer.tsx +1 -1
- package/src/features/activityJS/ActivityJSProvider.tsx +10 -2
- package/src/features/activityJS/BeforeResetAction.tsx +23 -0
- package/src/features/activityJS/hooks.ts +59 -91
- package/src/features/activityJS/internal-hooks.ts +93 -0
- package/src/features/activityJS/saverSlice.ts +23 -2
- package/src/features/navbar/CapytaleMenu.tsx +60 -54
- package/src/features/navbar/SidebarContent.tsx +37 -4
- package/src/features/navbar/style.module.scss +7 -0
- package/src/utils/ButtonDoubleIcon.tsx +35 -0
- package/src/utils/CardSelector.tsx +39 -0
- package/src/utils/style.module.scss +46 -0
package/package.json
CHANGED
package/src/MetaPlayer.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { store } from "./app/store";
|
|
|
10
10
|
import "./index.css";
|
|
11
11
|
import ThemeSwitcher from "./features/theming/ThemeSwitcher";
|
|
12
12
|
import { ActivityJSProvider } from "./features/activityJS/ActivityJSProvider";
|
|
13
|
-
import { LoadOptions } from "./features/activityJS/hooks";
|
|
13
|
+
import { LoadOptions } from "./features/activityJS/internal-hooks";
|
|
14
14
|
import { ErrorBoundary } from "./utils/ErrorBoundary";
|
|
15
15
|
import Saver from "./features/activityJS/Saver";
|
|
16
16
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ReactNode,
|
|
5
5
|
createContext,
|
|
6
6
|
useContext,
|
|
7
|
+
useEffect,
|
|
7
8
|
useState,
|
|
8
9
|
} from "react";
|
|
9
10
|
|
|
@@ -11,12 +12,14 @@ import {
|
|
|
11
12
|
ActivitySessionLoaderReturnValue,
|
|
12
13
|
LoadOptions,
|
|
13
14
|
useActivitySessionLoader,
|
|
14
|
-
} from "./hooks";
|
|
15
|
+
} from "./internal-hooks";
|
|
15
16
|
import { getIdFromUrl } from "../../utils/activity";
|
|
16
17
|
import { useAppDispatch } from "../../app/hooks";
|
|
17
18
|
import ActivitySession from "@capytale/activity.js/activity/activitySession";
|
|
18
19
|
import { setActivityJSData } from "../activityData/activityDataSlice";
|
|
19
20
|
|
|
21
|
+
import tracker from "@capytale/activity.js/backend/capytale/tracker";
|
|
22
|
+
|
|
20
23
|
const ActivityJSContext = createContext<ActivitySessionLoaderReturnValue>({
|
|
21
24
|
state: "loading",
|
|
22
25
|
activitySession: null,
|
|
@@ -32,10 +35,14 @@ export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
|
|
|
32
35
|
const activityId = getIdFromUrl();
|
|
33
36
|
const dispatch = useAppDispatch();
|
|
34
37
|
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
tracker.trackPageView();
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
35
42
|
const callback = (data: ActivitySession) => {
|
|
36
43
|
//@ts-expect-error
|
|
37
44
|
window.capy = data;
|
|
38
|
-
|
|
45
|
+
|
|
39
46
|
const ab = data.activityBunch;
|
|
40
47
|
/*
|
|
41
48
|
console.log("Test accès");
|
|
@@ -87,6 +94,7 @@ export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
|
|
|
87
94
|
workflow: ab.assignmentNode?.workflow,
|
|
88
95
|
}),
|
|
89
96
|
);
|
|
97
|
+
tracker.trackActivity(data);
|
|
90
98
|
setLoaded(true);
|
|
91
99
|
};
|
|
92
100
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FC, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
SaveCallback,
|
|
4
|
+
addBeforeReset,
|
|
5
|
+
removeBeforeReset,
|
|
6
|
+
} from "./saverSlice";
|
|
7
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
8
|
+
|
|
9
|
+
type BeforeResetActionProps = SaveCallback;
|
|
10
|
+
|
|
11
|
+
const BeforeResetAction: FC<BeforeResetActionProps> = (props) => {
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
dispatch(addBeforeReset(props));
|
|
15
|
+
return () => {
|
|
16
|
+
// Remove the callback when the component is unmounted
|
|
17
|
+
dispatch(removeBeforeReset(props.name));
|
|
18
|
+
};
|
|
19
|
+
}, [props]);
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default BeforeResetAction;
|
|
@@ -1,93 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
state: "loading" | "loaded" | "error";
|
|
13
|
-
activitySession: ActivitySession | null;
|
|
14
|
-
error?: any;
|
|
1
|
+
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
|
2
|
+
import {
|
|
3
|
+
selectMode,
|
|
4
|
+
selectWorkflow,
|
|
5
|
+
setSaveState,
|
|
6
|
+
} from "../activityData/activityDataSlice";
|
|
7
|
+
import { useActivityJS } from "./ActivityJSProvider";
|
|
8
|
+
import { selectBeforeReset } from "./saverSlice";
|
|
9
|
+
|
|
10
|
+
type UseResetProps = {
|
|
11
|
+
onError: (errorMessage: string) => void;
|
|
15
12
|
};
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return state;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Si un handler est passé en props à un composant, il peut changer. Il n'y a pas trop de raison mais
|
|
70
|
-
* rien ne l'interdit.
|
|
71
|
-
* Si un useEffect invoque ce handler, il faudrait que le handler figure dans les dépendances du useEffect.
|
|
72
|
-
* Du coup, si le handler change, le useEffect est ré-exécuté.
|
|
73
|
-
* Ce hook crée un wrapper immuable autour du handler passé en props. Ce wrapper ne change jamais et n'a donc
|
|
74
|
-
* pas besoin de figurer dans les dépendances du useEffect.
|
|
75
|
-
*
|
|
76
|
-
* @param handler
|
|
77
|
-
*/
|
|
78
|
-
export function useHandlerWrapper<H extends () => any>(
|
|
79
|
-
handler?: H,
|
|
80
|
-
): () => ReturnType<H> | void;
|
|
81
|
-
export function useHandlerWrapper<H extends (...args: any[]) => any>(
|
|
82
|
-
handler?: H,
|
|
83
|
-
): (...p: Parameters<H>) => ReturnType<H> | void;
|
|
84
|
-
export function useHandlerWrapper<H extends (...args: any[]) => any>(
|
|
85
|
-
handler?: H,
|
|
86
|
-
): (...p: Parameters<H>) => ReturnType<H> | void {
|
|
87
|
-
const handlerRef = useRef<H>();
|
|
88
|
-
handlerRef.current = handler;
|
|
89
|
-
return useRef((...p: Parameters<H>) => {
|
|
90
|
-
if (null == handlerRef.current) return;
|
|
91
|
-
return handlerRef.current(...p);
|
|
92
|
-
}).current;
|
|
93
|
-
}
|
|
14
|
+
export const useReset = (props: UseResetProps) => {
|
|
15
|
+
const dispatch = useAppDispatch();
|
|
16
|
+
const beforeReset = useAppSelector(selectBeforeReset);
|
|
17
|
+
const activityJs = useActivityJS();
|
|
18
|
+
const mode = useAppSelector(selectMode);
|
|
19
|
+
const workflow = useAppSelector(selectWorkflow);
|
|
20
|
+
if (mode !== "assignment" || workflow !== "current") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return async () => {
|
|
24
|
+
if (!activityJs.activitySession) {
|
|
25
|
+
throw new Error("No activity session to reset");
|
|
26
|
+
}
|
|
27
|
+
for (const callback of Object.values(beforeReset)) {
|
|
28
|
+
try {
|
|
29
|
+
const v = callback();
|
|
30
|
+
if (v instanceof Promise) {
|
|
31
|
+
await v;
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error("Error in beforeReset callback", e);
|
|
35
|
+
props.onError((e as any).toString());
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
dispatch(setSaveState("saving"));
|
|
41
|
+
const ab = activityJs.activitySession.activityBunch;
|
|
42
|
+
// @ts-expect-error
|
|
43
|
+
ab.assignmentNode.content.value = null;
|
|
44
|
+
// @ts-expect-error
|
|
45
|
+
ab.assignmentNode.binaryData.value = null;
|
|
46
|
+
try {
|
|
47
|
+
const saveData = await ab.save();
|
|
48
|
+
console.log("Save return data", saveData);
|
|
49
|
+
location.reload();
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error("Error in reset", e);
|
|
52
|
+
props.onError((e as any).toString());
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
dispatch(setSaveState("idle"));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error("Error in reset process", e);
|
|
58
|
+
props.onError((e as any).toString());
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect, useState, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { autoLoad } from "@capytale/activity.js";
|
|
4
|
+
|
|
5
|
+
import type ActivitySession from "@capytale/activity.js/activity/activitySession/uni";
|
|
6
|
+
|
|
7
|
+
import type { ActivityBunchOptions as LoadOptions } from "@capytale/activity.js/activity/activityBunch/uni/backend";
|
|
8
|
+
|
|
9
|
+
export type { LoadOptions };
|
|
10
|
+
|
|
11
|
+
export type ActivitySessionLoaderReturnValue = {
|
|
12
|
+
state: "loading" | "loaded" | "error";
|
|
13
|
+
activitySession: ActivitySession | null;
|
|
14
|
+
error?: any;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Un hook pour utiliser activity.js
|
|
19
|
+
* Charge une activité de façon réactive.
|
|
20
|
+
*
|
|
21
|
+
* @param id id de l'activité à charger
|
|
22
|
+
* @param loadOptions les options de chargement
|
|
23
|
+
* @param callback une callback appelée avec l'activité chargée
|
|
24
|
+
* @returns un objet contenant l'état de chargement de l'activité et l'activité dès qu'elle est chargée
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export function useActivitySessionLoader(
|
|
28
|
+
id: number,
|
|
29
|
+
loadOptions?: LoadOptions,
|
|
30
|
+
callback?: (activitySession: ActivitySession) => void,
|
|
31
|
+
): ActivitySessionLoaderReturnValue {
|
|
32
|
+
const callbackW = useHandlerWrapper(callback);
|
|
33
|
+
const [state, setState] = useState<ActivitySessionLoaderReturnValue>({
|
|
34
|
+
state: "loading",
|
|
35
|
+
activitySession: null,
|
|
36
|
+
});
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
let cancelled = false;
|
|
39
|
+
autoLoad(loadOptions)
|
|
40
|
+
.then((data) => {
|
|
41
|
+
if (cancelled) return;
|
|
42
|
+
callbackW(data);
|
|
43
|
+
setState({
|
|
44
|
+
state: "loaded",
|
|
45
|
+
activitySession: data,
|
|
46
|
+
});
|
|
47
|
+
(window as any).capy = data;
|
|
48
|
+
})
|
|
49
|
+
.catch((error) => {
|
|
50
|
+
if (cancelled) return;
|
|
51
|
+
setState({
|
|
52
|
+
state: "error",
|
|
53
|
+
activitySession: null,
|
|
54
|
+
error,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return () => {
|
|
58
|
+
cancelled = true;
|
|
59
|
+
setState({
|
|
60
|
+
state: "loading",
|
|
61
|
+
activitySession: null,
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}, [id, loadOptions?.binaryDataType, loadOptions?.readOnly]);
|
|
65
|
+
return state;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Si un handler est passé en props à un composant, il peut changer. Il n'y a pas trop de raison mais
|
|
70
|
+
* rien ne l'interdit.
|
|
71
|
+
* Si un useEffect invoque ce handler, il faudrait que le handler figure dans les dépendances du useEffect.
|
|
72
|
+
* Du coup, si le handler change, le useEffect est ré-exécuté.
|
|
73
|
+
* Ce hook crée un wrapper immuable autour du handler passé en props. Ce wrapper ne change jamais et n'a donc
|
|
74
|
+
* pas besoin de figurer dans les dépendances du useEffect.
|
|
75
|
+
*
|
|
76
|
+
* @param handler
|
|
77
|
+
*/
|
|
78
|
+
export function useHandlerWrapper<H extends () => any>(
|
|
79
|
+
handler?: H,
|
|
80
|
+
): () => ReturnType<H> | void;
|
|
81
|
+
export function useHandlerWrapper<H extends (...args: any[]) => any>(
|
|
82
|
+
handler?: H,
|
|
83
|
+
): (...p: Parameters<H>) => ReturnType<H> | void;
|
|
84
|
+
export function useHandlerWrapper<H extends (...args: any[]) => any>(
|
|
85
|
+
handler?: H,
|
|
86
|
+
): (...p: Parameters<H>) => ReturnType<H> | void {
|
|
87
|
+
const handlerRef = useRef<H>();
|
|
88
|
+
handlerRef.current = handler;
|
|
89
|
+
return useRef((...p: Parameters<H>) => {
|
|
90
|
+
if (null == handlerRef.current) return;
|
|
91
|
+
return handlerRef.current(...p);
|
|
92
|
+
}).current;
|
|
93
|
+
}
|
|
@@ -11,11 +11,13 @@ export type SaveCallback = {
|
|
|
11
11
|
export interface SaverState {
|
|
12
12
|
beforeSave: { [key: string]: CallbackType };
|
|
13
13
|
afterSave: { [key: string]: CallbackType };
|
|
14
|
+
beforeReset: { [key: string]: CallbackType };
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const initialState: SaverState = {
|
|
17
18
|
beforeSave: {},
|
|
18
19
|
afterSave: {},
|
|
20
|
+
beforeReset: {},
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
@@ -36,23 +38,42 @@ export const saverSlice = createAppSlice({
|
|
|
36
38
|
state.afterSave[action.payload.name] = action.payload.callback;
|
|
37
39
|
},
|
|
38
40
|
),
|
|
41
|
+
addBeforeReset: create.reducer(
|
|
42
|
+
(state, action: PayloadAction<SaveCallback>) => {
|
|
43
|
+
state.beforeReset[action.payload.name] = action.payload.callback;
|
|
44
|
+
},
|
|
45
|
+
),
|
|
39
46
|
removeBeforeSave: create.reducer((state, action: PayloadAction<string>) => {
|
|
40
47
|
delete state.beforeSave[action.payload];
|
|
41
48
|
}),
|
|
42
49
|
removeAfterSave: create.reducer((state, action: PayloadAction<string>) => {
|
|
43
50
|
delete state.afterSave[action.payload];
|
|
44
51
|
}),
|
|
52
|
+
removeBeforeReset: create.reducer(
|
|
53
|
+
(state, action: PayloadAction<string>) => {
|
|
54
|
+
delete state.beforeReset[action.payload];
|
|
55
|
+
},
|
|
56
|
+
),
|
|
45
57
|
}),
|
|
46
58
|
// You can define your selectors here. These selectors receive the slice
|
|
47
59
|
// state as their first argument.
|
|
48
60
|
selectors: {
|
|
49
61
|
selectBeforeSave: (saver) => saver.beforeSave,
|
|
50
62
|
selectAfterSave: (saver) => saver.afterSave,
|
|
63
|
+
selectBeforeReset: (saver) => saver.beforeReset,
|
|
51
64
|
},
|
|
52
65
|
});
|
|
53
66
|
|
|
54
67
|
// Action creators are generated for each case reducer function.
|
|
55
|
-
export const {
|
|
68
|
+
export const {
|
|
69
|
+
addBeforeSave,
|
|
70
|
+
removeBeforeSave,
|
|
71
|
+
addAfterSave,
|
|
72
|
+
removeAfterSave,
|
|
73
|
+
addBeforeReset,
|
|
74
|
+
removeBeforeReset,
|
|
75
|
+
} = saverSlice.actions;
|
|
56
76
|
|
|
57
77
|
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
58
|
-
export const { selectBeforeSave, selectAfterSave } =
|
|
78
|
+
export const { selectBeforeSave, selectAfterSave, selectBeforeReset } =
|
|
79
|
+
saverSlice.selectors;
|
|
@@ -2,6 +2,7 @@ import { Button } from "primereact/button";
|
|
|
2
2
|
import { SplitButton } from "primereact/splitbutton";
|
|
3
3
|
import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
|
|
4
4
|
import { Toast } from "primereact/toast";
|
|
5
|
+
import { OverlayPanel } from "primereact/overlaypanel";
|
|
5
6
|
|
|
6
7
|
import styles from "./style.module.scss";
|
|
7
8
|
import { useMemo, useRef } from "react";
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
22
23
|
import settings from "../../settings";
|
|
23
24
|
import { wf } from "@capytale/activity.js/activity/field/workflow";
|
|
25
|
+
import ButtonDoubleIcon from "../../utils/ButtonDoubleIcon";
|
|
26
|
+
import CardSelector from "../../utils/CardSelector";
|
|
24
27
|
|
|
25
28
|
const CapytaleMenu: React.FC = () => {
|
|
26
29
|
const dispatch = useAppDispatch();
|
|
@@ -56,6 +59,7 @@ const CapytaleMenu: React.FC = () => {
|
|
|
56
59
|
accept: () => changeWorkflow("finished"),
|
|
57
60
|
});
|
|
58
61
|
};
|
|
62
|
+
const overlayPanelWorkflow = useRef<OverlayPanel>(null);
|
|
59
63
|
return (
|
|
60
64
|
<>
|
|
61
65
|
<Toast ref={toast} position="bottom-right" />
|
|
@@ -144,60 +148,62 @@ const CapytaleMenu: React.FC = () => {
|
|
|
144
148
|
/>
|
|
145
149
|
)}
|
|
146
150
|
{mode === "review" && (
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
151
|
+
<div>
|
|
152
|
+
<ButtonDoubleIcon
|
|
153
|
+
severity="secondary"
|
|
154
|
+
size="small"
|
|
155
|
+
outlined
|
|
156
|
+
onClick={(e) => {
|
|
157
|
+
overlayPanelWorkflow.current!.toggle(e);
|
|
158
|
+
}}
|
|
159
|
+
label={
|
|
160
|
+
workflow === "current"
|
|
161
|
+
? "En cours"
|
|
162
|
+
: workflow === "finished"
|
|
163
|
+
? "Rendue"
|
|
164
|
+
: "Corrigée"
|
|
165
|
+
}
|
|
166
|
+
leftIcon={
|
|
167
|
+
"pi " +
|
|
168
|
+
(workflow === "current"
|
|
169
|
+
? "pi pi-pencil"
|
|
170
|
+
: workflow === "finished"
|
|
171
|
+
? "pi pi-envelope"
|
|
172
|
+
: "pi pi-check-square")
|
|
173
|
+
}
|
|
174
|
+
rightIcon="pi pi-angle-down"
|
|
175
|
+
/>
|
|
176
|
+
<OverlayPanel ref={overlayPanelWorkflow}>
|
|
177
|
+
<h3 className={styles.overlayPanelWorkflowTitle}>État de la copie</h3>
|
|
178
|
+
<CardSelector
|
|
179
|
+
selected={workflow}
|
|
180
|
+
onChange={(option: wf) => {
|
|
181
|
+
changeWorkflow(option);
|
|
182
|
+
|
|
183
|
+
overlayPanelWorkflow.current!.hide();
|
|
184
|
+
}}
|
|
185
|
+
options={[
|
|
186
|
+
{
|
|
187
|
+
value: "current",
|
|
188
|
+
title: "En cours",
|
|
189
|
+
description:
|
|
190
|
+
"L'élève peut modifier sa copie à tout moment.",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
value: "finished",
|
|
194
|
+
title: "Rendue",
|
|
195
|
+
description: "L'élève ne peut plus modifier sa copie.",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
value: "corrected",
|
|
199
|
+
title: "Corrigée",
|
|
200
|
+
description:
|
|
201
|
+
"L'élève ne peut plus modifier sa copie et a reçu une correction.",
|
|
202
|
+
},
|
|
203
|
+
]}
|
|
204
|
+
/>
|
|
205
|
+
</OverlayPanel>
|
|
206
|
+
</div>
|
|
201
207
|
)}
|
|
202
208
|
<ConfirmDialog />
|
|
203
209
|
</div>
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
} from "../activityData/activityDataSlice";
|
|
22
22
|
import { ActivitySettingsDisplay } from "../activitySettings";
|
|
23
23
|
import { Button } from "primereact/button";
|
|
24
|
+
import { ConfirmPopup, confirmPopup } from "primereact/confirmpopup";
|
|
24
25
|
import settings from "../../settings";
|
|
26
|
+
import { useReset } from "../activityJS/hooks";
|
|
25
27
|
|
|
26
28
|
type SidebarContentProps = {
|
|
27
29
|
showHelp?: () => void;
|
|
@@ -33,11 +35,12 @@ const SidebarContent: FC<SidebarContentProps> = (props) => {
|
|
|
33
35
|
const orientation = useAppSelector(selectOrientation);
|
|
34
36
|
const themeName = useAppSelector(selectThemeNameOrAuto);
|
|
35
37
|
const sidebarActions = useAppSelector(selectSidebarActions);
|
|
38
|
+
const reset = useReset({ onError: console.error });
|
|
36
39
|
const dispatch = useAppDispatch();
|
|
37
40
|
return (
|
|
38
41
|
<>
|
|
39
|
-
{
|
|
40
|
-
|
|
42
|
+
<div className={styles.sidebarCapytaleActions}>
|
|
43
|
+
{props.showHelp && (
|
|
41
44
|
<Button
|
|
42
45
|
severity="secondary"
|
|
43
46
|
size="small"
|
|
@@ -53,8 +56,38 @@ const SidebarContent: FC<SidebarContentProps> = (props) => {
|
|
|
53
56
|
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
54
57
|
}}
|
|
55
58
|
/>
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
)}
|
|
60
|
+
{reset && (
|
|
61
|
+
<>
|
|
62
|
+
<ConfirmPopup />
|
|
63
|
+
<Button
|
|
64
|
+
severity="secondary"
|
|
65
|
+
size="small"
|
|
66
|
+
icon={"pi pi-undo"}
|
|
67
|
+
onClick={(event) => {
|
|
68
|
+
confirmPopup({
|
|
69
|
+
target: event.currentTarget,
|
|
70
|
+
message:
|
|
71
|
+
"Toutes vos modifications seront perdues. Continuer ?",
|
|
72
|
+
icon: "pi pi-exclamation-triangle",
|
|
73
|
+
defaultFocus: "reject",
|
|
74
|
+
acceptClassName: "p-button-danger",
|
|
75
|
+
accept: reset,
|
|
76
|
+
acceptLabel: "Oui",
|
|
77
|
+
rejectLabel: "Non",
|
|
78
|
+
});
|
|
79
|
+
}}
|
|
80
|
+
outlined
|
|
81
|
+
label="Réinitialiser l'activité"
|
|
82
|
+
tooltip="Revenir à la version de l'enseignant"
|
|
83
|
+
tooltipOptions={{
|
|
84
|
+
position: "left",
|
|
85
|
+
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
58
91
|
{sidebarActions.length > 0 && (
|
|
59
92
|
<Fieldset
|
|
60
93
|
legend="Actions"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Button, ButtonProps } from "primereact/button";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
type ButtonDoubleIconProps = Exclude<
|
|
5
|
+
ButtonProps,
|
|
6
|
+
"icon" | "children" | "iconPos"
|
|
7
|
+
> & {
|
|
8
|
+
leftIcon: string;
|
|
9
|
+
rightIcon: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ButtonDoubleIcon: React.FC<ButtonDoubleIconProps> = function ({
|
|
13
|
+
label,
|
|
14
|
+
leftIcon,
|
|
15
|
+
rightIcon,
|
|
16
|
+
...props
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<Button {...props}>
|
|
20
|
+
<span
|
|
21
|
+
className={"p-button-icon p-c p-button-icon-left " + leftIcon}
|
|
22
|
+
data-pc-section="icon"
|
|
23
|
+
></span>
|
|
24
|
+
<span className="p-button-label p-c" data-pc-section="label">
|
|
25
|
+
{label}
|
|
26
|
+
</span>
|
|
27
|
+
<span
|
|
28
|
+
className={"p-button-icon p-c p-button-icon-right " + rightIcon}
|
|
29
|
+
data-pc-section="icon"
|
|
30
|
+
></span>
|
|
31
|
+
</Button>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default ButtonDoubleIcon;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RadioButton } from "primereact/radiobutton";
|
|
2
|
+
import styles from "./style.module.scss";
|
|
3
|
+
|
|
4
|
+
type CardSelectorProps<ValueType> = {
|
|
5
|
+
options: { title: string; description: string; value: ValueType }[];
|
|
6
|
+
selected?: ValueType | null;
|
|
7
|
+
onChange: (value: ValueType) => void | Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
const CardSelector = function <ValueType>(props: CardSelectorProps<ValueType>) {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<div className={styles.cardSelector}>
|
|
13
|
+
{props.options.map((option) => (
|
|
14
|
+
<div
|
|
15
|
+
className={styles.cardSelectorOption}
|
|
16
|
+
data-selected={props.selected === option.value}
|
|
17
|
+
onClick={() => {
|
|
18
|
+
props.onChange(option.value);
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<div className={styles.cardSelectorOptionRadio}>
|
|
22
|
+
<RadioButton checked={props.selected === option.value} />
|
|
23
|
+
</div>
|
|
24
|
+
<div className={styles.cardSelectorOptionContent}>
|
|
25
|
+
<div className={styles.cardSelectorOptionTitle}>
|
|
26
|
+
{option.title}
|
|
27
|
+
</div>
|
|
28
|
+
<div className={styles.cardSelectorOptionDescription}>
|
|
29
|
+
{option.description}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default CardSelector;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.cardSelector {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 1.25rem;
|
|
5
|
+
& :global(.p-card-title) {
|
|
6
|
+
font-size: 1.2rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
& :global(.p-card-content) {
|
|
10
|
+
padding: 0.5rem 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
& p {
|
|
14
|
+
margin: 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.cardSelectorOption {
|
|
19
|
+
padding: 1.25rem;
|
|
20
|
+
display: flex;
|
|
21
|
+
gap: 1.25rem;
|
|
22
|
+
background-color: var(--surface-100);
|
|
23
|
+
box-shadow: none;
|
|
24
|
+
border: 1px solid var(--surface-300);
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition: background-color 0.2s;
|
|
28
|
+
&[data-selected="true"] {
|
|
29
|
+
background-color: var(--surface-0);
|
|
30
|
+
border-color: var(--primary-color);
|
|
31
|
+
}
|
|
32
|
+
&:hover {
|
|
33
|
+
background-color: var(--surface-0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.cardSelectorOptionRadio {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.cardSelectorOptionTitle {
|
|
43
|
+
font-size: 1.2rem;
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
margin-bottom: 0.5rem;
|
|
46
|
+
}
|