@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capytale/meta-player",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -1,5 +1,6 @@
1
1
  .app {
2
2
  height: 100%;
3
+ height: 100dvh;
3
4
  display: grid;
4
5
  grid-template-rows: auto 1fr;
5
6
  }
@@ -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;
@@ -95,8 +95,7 @@ const Saver: FC<{}> = () => {
95
95
  }
96
96
  }
97
97
  try {
98
- const saveData = await ab.save();
99
- console.log("Save return data", saveData);
98
+ await ab.save();
100
99
  dispatch(setSaveState("idle"));
101
100
  if (
102
101
  activityJs.activitySession.mode === "assignment" ||
@@ -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
- const saveData = await ab.save();
54
- console.log("Save return data", saveData);
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 { selectBeforeSave, selectAfterSave, selectBeforeReset } =
79
- saverSlice.selectors;
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="secondary"
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="warning" onClick={save}>
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 = Date.UTC(
120
- deadline.getFullYear(),
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
- <Button
56
- label="Créer une consigne"
57
- onClick={() => dispatch(setInstructionsType("rich"))}
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
- <Button
52
- label="Créer des notes partagées"
53
- onClick={() => dispatch(setSharedNotesType("rich"))}
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
@@ -94,6 +94,7 @@ body,
94
94
 
95
95
  .anti-cheat-content {
96
96
  height: 100vh;
97
+ height: 100dvh;
97
98
  width: 100vw;
98
99
  overflow: hidden;
99
100
  }
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;