@capytale/meta-player 0.3.10 → 0.3.12
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/app.module.scss +1 -0
- package/src/features/activityJS/AfterResetAction.tsx +23 -0
- package/src/features/activityJS/Saver.tsx +1 -2
- package/src/features/activityJS/hooks.ts +15 -3
- package/src/features/activityJS/saverSlice.ts +19 -2
- package/src/features/navbar/Countdown.tsx +5 -22
- package/src/features/pedago/InstructionsEditor.tsx +6 -4
- package/src/features/pedago/SharedNotesEditor.tsx +6 -4
- package/src/index.css +1 -0
- package/src/index.tsx +2 -0
- package/src/utils/server-clock.ts +42 -0
package/package.json
CHANGED
package/src/app.module.scss
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FC, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
SaveCallback,
|
|
4
|
+
addAfterReset,
|
|
5
|
+
removeAfterReset,
|
|
6
|
+
} from "./saverSlice";
|
|
7
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
8
|
+
|
|
9
|
+
type AfterResetActionProps = SaveCallback;
|
|
10
|
+
|
|
11
|
+
const AfterResetAction: FC<AfterResetActionProps> = (props) => {
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
dispatch(addAfterReset(props));
|
|
15
|
+
return () => {
|
|
16
|
+
// Remove the callback when the component is unmounted
|
|
17
|
+
dispatch(removeAfterReset(props.name));
|
|
18
|
+
};
|
|
19
|
+
}, [props]);
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default AfterResetAction;
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
setSaveState,
|
|
6
6
|
} from "../activityData/activityDataSlice";
|
|
7
7
|
import { useActivityJS } from "./ActivityJSProvider";
|
|
8
|
-
import { selectBeforeReset } from "./saverSlice";
|
|
8
|
+
import { selectBeforeReset, selectAfterReset } from "./saverSlice";
|
|
9
9
|
|
|
10
10
|
type UseResetProps = {
|
|
11
11
|
onError: (errorMessage: string) => void;
|
|
@@ -14,6 +14,7 @@ type UseResetProps = {
|
|
|
14
14
|
export const useReset = (props: UseResetProps) => {
|
|
15
15
|
const dispatch = useAppDispatch();
|
|
16
16
|
const beforeReset = useAppSelector(selectBeforeReset);
|
|
17
|
+
const afterReset = useAppSelector(selectAfterReset);
|
|
17
18
|
const activityJs = useActivityJS();
|
|
18
19
|
const mode = useAppSelector(selectMode);
|
|
19
20
|
const workflow = useAppSelector(selectWorkflow);
|
|
@@ -50,8 +51,19 @@ export const useReset = (props: UseResetProps) => {
|
|
|
50
51
|
ab.assignmentNode.binaryData.value = null;
|
|
51
52
|
}
|
|
52
53
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
await ab.save();
|
|
55
|
+
|
|
56
|
+
for (const callback of Object.values(afterReset)) {
|
|
57
|
+
try {
|
|
58
|
+
const v = callback();
|
|
59
|
+
if (v instanceof Promise) {
|
|
60
|
+
await v;
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error("Error in afterReset callback", e);
|
|
64
|
+
props.onError((e as any).toString());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
55
67
|
if (reload) {
|
|
56
68
|
location.reload();
|
|
57
69
|
}
|
|
@@ -12,12 +12,14 @@ export interface SaverState {
|
|
|
12
12
|
beforeSave: { [key: string]: CallbackType };
|
|
13
13
|
afterSave: { [key: string]: CallbackType };
|
|
14
14
|
beforeReset: { [key: string]: CallbackType };
|
|
15
|
+
afterReset: { [key: string]: CallbackType };
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const initialState: SaverState = {
|
|
18
19
|
beforeSave: {},
|
|
19
20
|
afterSave: {},
|
|
20
21
|
beforeReset: {},
|
|
22
|
+
afterReset: {},
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
@@ -43,6 +45,11 @@ export const saverSlice = createAppSlice({
|
|
|
43
45
|
state.beforeReset[action.payload.name] = action.payload.callback;
|
|
44
46
|
},
|
|
45
47
|
),
|
|
48
|
+
addAfterReset: create.reducer(
|
|
49
|
+
(state, action: PayloadAction<SaveCallback>) => {
|
|
50
|
+
state.afterReset[action.payload.name] = action.payload.callback;
|
|
51
|
+
},
|
|
52
|
+
),
|
|
46
53
|
removeBeforeSave: create.reducer((state, action: PayloadAction<string>) => {
|
|
47
54
|
delete state.beforeSave[action.payload];
|
|
48
55
|
}),
|
|
@@ -54,6 +61,9 @@ export const saverSlice = createAppSlice({
|
|
|
54
61
|
delete state.beforeReset[action.payload];
|
|
55
62
|
},
|
|
56
63
|
),
|
|
64
|
+
removeAfterReset: create.reducer((state, action: PayloadAction<string>) => {
|
|
65
|
+
delete state.afterReset[action.payload];
|
|
66
|
+
}),
|
|
57
67
|
}),
|
|
58
68
|
// You can define your selectors here. These selectors receive the slice
|
|
59
69
|
// state as their first argument.
|
|
@@ -61,6 +71,7 @@ export const saverSlice = createAppSlice({
|
|
|
61
71
|
selectBeforeSave: (saver) => saver.beforeSave,
|
|
62
72
|
selectAfterSave: (saver) => saver.afterSave,
|
|
63
73
|
selectBeforeReset: (saver) => saver.beforeReset,
|
|
74
|
+
selectAfterReset: (saver) => saver.afterReset,
|
|
64
75
|
},
|
|
65
76
|
});
|
|
66
77
|
|
|
@@ -72,8 +83,14 @@ export const {
|
|
|
72
83
|
removeAfterSave,
|
|
73
84
|
addBeforeReset,
|
|
74
85
|
removeBeforeReset,
|
|
86
|
+
addAfterReset,
|
|
87
|
+
removeAfterReset,
|
|
75
88
|
} = saverSlice.actions;
|
|
76
89
|
|
|
77
90
|
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
78
|
-
export const {
|
|
79
|
-
|
|
91
|
+
export const {
|
|
92
|
+
selectBeforeSave,
|
|
93
|
+
selectAfterSave,
|
|
94
|
+
selectBeforeReset,
|
|
95
|
+
selectAfterReset,
|
|
96
|
+
} = saverSlice.selectors;
|
|
@@ -6,6 +6,7 @@ import { useAppSelector } from "../../app/hooks";
|
|
|
6
6
|
import { selectIsDirty, selectMode } from "../activityData/activityDataSlice";
|
|
7
7
|
import { Button } from "primereact/button";
|
|
8
8
|
import { useSave } from "../activityData/hooks";
|
|
9
|
+
import serverClock from "../../utils/server-clock";
|
|
9
10
|
|
|
10
11
|
// TODO use https://capytale2.ac-paris.fr/vanilla/time-s.php
|
|
11
12
|
// https://forge.apps.education.fr/capytale/activity-js/-/blob/main/src/backend/capytale/clock.ts?ref_type=heads
|
|
@@ -47,8 +48,7 @@ const Countdown: FC = () => {
|
|
|
47
48
|
}}
|
|
48
49
|
>
|
|
49
50
|
<Button
|
|
50
|
-
severity="
|
|
51
|
-
outlined
|
|
51
|
+
severity="danger"
|
|
52
52
|
onClick={() => {
|
|
53
53
|
shouldSaveAt30s.current = false;
|
|
54
54
|
toast.current?.clear();
|
|
@@ -56,7 +56,7 @@ const Countdown: FC = () => {
|
|
|
56
56
|
>
|
|
57
57
|
Ne pas sauvegarder (danger)
|
|
58
58
|
</Button>
|
|
59
|
-
<Button severity="
|
|
59
|
+
<Button severity="success" onClick={save}>
|
|
60
60
|
Sauvegarder maintenant
|
|
61
61
|
</Button>
|
|
62
62
|
</div>
|
|
@@ -116,25 +116,8 @@ const Countdown: FC = () => {
|
|
|
116
116
|
if (!deadline) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
const deadlineUTC =
|
|
120
|
-
|
|
121
|
-
deadline.getMonth(),
|
|
122
|
-
deadline.getDate(),
|
|
123
|
-
deadline.getHours(),
|
|
124
|
-
deadline.getMinutes(),
|
|
125
|
-
deadline.getSeconds(),
|
|
126
|
-
deadline.getMilliseconds(),
|
|
127
|
-
);
|
|
128
|
-
const now = new Date();
|
|
129
|
-
const nowUTC = Date.UTC(
|
|
130
|
-
now.getFullYear(),
|
|
131
|
-
now.getMonth(),
|
|
132
|
-
now.getDate(),
|
|
133
|
-
now.getHours(),
|
|
134
|
-
now.getMinutes(),
|
|
135
|
-
now.getSeconds(),
|
|
136
|
-
now.getMilliseconds(),
|
|
137
|
-
);
|
|
119
|
+
const deadlineUTC = deadline.getTime();
|
|
120
|
+
const nowUTC = serverClock.now();
|
|
138
121
|
const newDiffMs = deadlineUTC - nowUTC;
|
|
139
122
|
setDiffMs(newDiffMs);
|
|
140
123
|
if (mode !== "assignment") {
|
|
@@ -52,10 +52,12 @@ const InstructionsEditor: React.FC = forwardRef((_props, ref) => {
|
|
|
52
52
|
{instructionsType === "none" && (
|
|
53
53
|
<>
|
|
54
54
|
<p>Pas de consigne.</p>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
{mode === "create" && (
|
|
56
|
+
<Button
|
|
57
|
+
label="Créer une consigne"
|
|
58
|
+
onClick={() => dispatch(setInstructionsType("rich"))}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
59
61
|
</>
|
|
60
62
|
)}
|
|
61
63
|
{instructionsType === "rich" && (
|
|
@@ -48,10 +48,12 @@ const SharedNotesEditor: React.FC = forwardRef((_props, ref) => {
|
|
|
48
48
|
{sharedNotesType === "none" && (
|
|
49
49
|
<>
|
|
50
50
|
<p>Pas de notes partagées.</p>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
{mode === "create" && (
|
|
52
|
+
<Button
|
|
53
|
+
label="Créer des notes partagées"
|
|
54
|
+
onClick={() => dispatch(setSharedNotesType("rich"))}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
55
57
|
<div style={{ textAlign: "start", padding: "16px" }}>
|
|
56
58
|
<h3>Que sont les notes partagées ?</h3>
|
|
57
59
|
<p>
|
package/src/index.css
CHANGED
package/src/index.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import MetaPlayerOptionsSetter from "./features/activityData/MetaPlayerOptionsSe
|
|
|
6
6
|
import BeforeSaveAction from "./features/activityJS/BeforeSaveAction";
|
|
7
7
|
import BeforeResetAction from "./features/activityJS/BeforeResetAction";
|
|
8
8
|
import AfterSaveAction from "./features/activityJS/AfterSaveAction";
|
|
9
|
+
import AfterResetAction from "./features/activityJS/AfterResetAction";
|
|
9
10
|
import { useMode, useWorkflow } from "./hooks";
|
|
10
11
|
import {
|
|
11
12
|
useActivityJS,
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
BeforeSaveAction,
|
|
27
28
|
BeforeResetAction,
|
|
28
29
|
AfterSaveAction,
|
|
30
|
+
AfterResetAction,
|
|
29
31
|
useMode,
|
|
30
32
|
useWorkflow,
|
|
31
33
|
useActivityJS,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
async function getSeverTime() {
|
|
2
|
+
const res = await fetch("/vanilla/time-s.php");
|
|
3
|
+
return parseInt(await res.text()) * 1000;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
class SeverClock {
|
|
7
|
+
private offset: number;
|
|
8
|
+
private lastSync: number;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.offset = 0;
|
|
12
|
+
this.lastSync = 0;
|
|
13
|
+
this.sync();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async sync() {
|
|
17
|
+
const offsets = [];
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
const serverTime = await getSeverTime();
|
|
20
|
+
const localTime = Date.now();
|
|
21
|
+
offsets.push(serverTime - localTime);
|
|
22
|
+
}
|
|
23
|
+
this.offset = Math.max(...offsets);
|
|
24
|
+
this.lastSync = Date.now();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
now() {
|
|
28
|
+
return Date.now() + this.offset;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get lastSyncTime() {
|
|
32
|
+
return this.lastSync;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get offsetTime() {
|
|
36
|
+
return this.offset;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const serverClock = new SeverClock();
|
|
41
|
+
|
|
42
|
+
export default serverClock;
|