@capytale/meta-player 0.1.5 → 0.2.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/.eslintrc.json +2 -1
- package/package.json +12 -6
- package/src/MetaPlayer.tsx +94 -4
- package/src/features/activityData/IsAntiCheatExitDetectionDisabledSetter.tsx +19 -0
- package/src/features/activityData/activityDataSlice.ts +29 -0
- package/src/features/activityJS/ActivityJSProvider.tsx +6 -0
- package/src/features/activityJS/Saver.tsx +7 -8
- package/src/features/navbar/ActivityMenu.tsx +17 -12
- package/src/features/navbar/CapytaleMenu.tsx +19 -16
- package/src/index.css +10 -0
package/.eslintrc.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capytale/meta-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -15,13 +15,12 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@capytale/activity.js": "^3.1.0",
|
|
18
|
+
"@capytale/capytale-anti-triche": "^0.2.1",
|
|
18
19
|
"@capytale/capytale-rich-text-editor": "^0.4.2",
|
|
19
20
|
"@reduxjs/toolkit": "^2.0.1",
|
|
20
21
|
"@uidotdev/usehooks": "^2.4.1",
|
|
21
22
|
"primeicons": "^7.0.0",
|
|
22
23
|
"primereact": "^10.8.3",
|
|
23
|
-
"react": "^18.2.0",
|
|
24
|
-
"react-dom": "^18.2.0",
|
|
25
24
|
"react-html-props": "^2.0.9",
|
|
26
25
|
"react-redux": "^9.1.0",
|
|
27
26
|
"screenfull": "^6.0.2"
|
|
@@ -31,18 +30,25 @@
|
|
|
31
30
|
"@testing-library/jest-dom": "^6.2.0",
|
|
32
31
|
"@testing-library/react": "^14.1.2",
|
|
33
32
|
"@testing-library/user-event": "^14.5.2",
|
|
34
|
-
"@types/react": "^18.
|
|
35
|
-
"@types/react-dom": "^18.
|
|
36
|
-
"@vitejs/plugin-react": "^4.
|
|
33
|
+
"@types/react": "^18.3.8",
|
|
34
|
+
"@types/react-dom": "^18.3.0",
|
|
35
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
37
36
|
"eslint": "^8.56.0",
|
|
38
37
|
"eslint-config-prettier": "^9.1.0",
|
|
39
38
|
"eslint-config-react-app": "^7.0.1",
|
|
40
39
|
"eslint-plugin-prettier": "^5.1.3",
|
|
40
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
41
41
|
"jsdom": "^23.2.0",
|
|
42
42
|
"prettier": "^3.2.1",
|
|
43
|
+
"react": "^18.3.1",
|
|
44
|
+
"react-dom": "^18.3.1",
|
|
43
45
|
"sass": "^1.75.0",
|
|
44
46
|
"typescript": "^5.3.3",
|
|
45
47
|
"vite": "^5.4.6",
|
|
46
48
|
"vitest": "^1.2.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"react": "^18.3.1",
|
|
52
|
+
"react-dom": "^18.3.1"
|
|
47
53
|
}
|
|
48
54
|
}
|
package/src/MetaPlayer.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { PropsWithChildren } from "react";
|
|
1
|
+
import React, { FC, PropsWithChildren } from "react";
|
|
2
2
|
|
|
3
3
|
import { Provider } from "react-redux";
|
|
4
4
|
|
|
@@ -9,19 +9,47 @@ import App from "./App";
|
|
|
9
9
|
import { store } from "./app/store";
|
|
10
10
|
import "./index.css";
|
|
11
11
|
import ThemeSwitcher from "./features/theming/ThemeSwitcher";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ActivityJSProvider,
|
|
14
|
+
useActivityJS,
|
|
15
|
+
} from "./features/activityJS/ActivityJSProvider";
|
|
13
16
|
import { LoadOptions } from "./features/activityJS/internal-hooks";
|
|
14
17
|
import { ErrorBoundary } from "./utils/ErrorBoundary";
|
|
15
18
|
import Saver from "./features/activityJS/Saver";
|
|
19
|
+
import {
|
|
20
|
+
CapytaleAntiCheat,
|
|
21
|
+
ContentHidingMethod,
|
|
22
|
+
ExitDetectionMethod,
|
|
23
|
+
} from "@capytale/capytale-anti-triche";
|
|
24
|
+
import { useAppSelector } from "./app/hooks";
|
|
25
|
+
import {
|
|
26
|
+
selectActivityInfo,
|
|
27
|
+
selectAntiCheat,
|
|
28
|
+
selectHasAntiCheat,
|
|
29
|
+
selectIsAntiCheatExitDetectionDisabled,
|
|
30
|
+
selectIsDirty,
|
|
31
|
+
selectReturnUrl,
|
|
32
|
+
} from "./features/activityData/activityDataSlice";
|
|
33
|
+
|
|
34
|
+
type AntiCheatOptions = {
|
|
35
|
+
preserveDom: boolean;
|
|
36
|
+
hasIframes: boolean;
|
|
37
|
+
};
|
|
16
38
|
|
|
17
39
|
type MetaPlayerProps = PropsWithChildren<{
|
|
18
40
|
activityJSOptions?: LoadOptions;
|
|
41
|
+
antiCheatOptions?: Partial<AntiCheatOptions>;
|
|
19
42
|
}>;
|
|
20
43
|
|
|
21
|
-
const MetaPlayer:
|
|
44
|
+
const MetaPlayer: FC<MetaPlayerProps> = (props) => {
|
|
22
45
|
const primeSettings: Partial<APIOptions> = {
|
|
23
46
|
ripple: true,
|
|
24
47
|
};
|
|
48
|
+
const antiCheatOptions: AntiCheatOptions = {
|
|
49
|
+
preserveDom: true,
|
|
50
|
+
hasIframes: false,
|
|
51
|
+
...props.antiCheatOptions,
|
|
52
|
+
};
|
|
25
53
|
return (
|
|
26
54
|
<PrimeReactProvider value={primeSettings}>
|
|
27
55
|
<Provider store={store}>
|
|
@@ -29,7 +57,9 @@ const MetaPlayer: React.FC<MetaPlayerProps> = (props) => {
|
|
|
29
57
|
<ErrorBoundary fallback={<div>Une erreur est survenue</div>}>
|
|
30
58
|
<ActivityJSProvider options={props.activityJSOptions}>
|
|
31
59
|
<Saver />
|
|
32
|
-
<
|
|
60
|
+
<MetaPlayerContent antiCheatOptions={antiCheatOptions}>
|
|
61
|
+
<App>{props.children}</App>
|
|
62
|
+
</MetaPlayerContent>
|
|
33
63
|
</ActivityJSProvider>
|
|
34
64
|
</ErrorBoundary>
|
|
35
65
|
</Provider>
|
|
@@ -37,4 +67,64 @@ const MetaPlayer: React.FC<MetaPlayerProps> = (props) => {
|
|
|
37
67
|
);
|
|
38
68
|
};
|
|
39
69
|
|
|
70
|
+
type MetaPlayerContentProps = {
|
|
71
|
+
antiCheatOptions: AntiCheatOptions;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const MetaPlayerContent: React.FC<PropsWithChildren<MetaPlayerContentProps>> = (
|
|
75
|
+
props,
|
|
76
|
+
) => {
|
|
77
|
+
const isDirty = useAppSelector(selectIsDirty);
|
|
78
|
+
const antiCheat = useAppSelector(selectAntiCheat);
|
|
79
|
+
const hasAntiCheat = useAppSelector(selectHasAntiCheat);
|
|
80
|
+
const activityInfo = useAppSelector(selectActivityInfo);
|
|
81
|
+
const returnUrl = useAppSelector(selectReturnUrl);
|
|
82
|
+
const isAntiCheatExitDetectionDisabled = useAppSelector(
|
|
83
|
+
selectIsAntiCheatExitDetectionDisabled,
|
|
84
|
+
);
|
|
85
|
+
const studentName = activityInfo.studentInfo
|
|
86
|
+
? `${activityInfo.studentInfo.firstName} ${activityInfo.studentInfo.lastName}`
|
|
87
|
+
: "";
|
|
88
|
+
const activityJS = useActivityJS();
|
|
89
|
+
|
|
90
|
+
const touch = () => {
|
|
91
|
+
if (
|
|
92
|
+
activityJS.activitySession == null ||
|
|
93
|
+
activityJS.activitySession.activityBunch.assignmentNode == null
|
|
94
|
+
) {
|
|
95
|
+
console.error("ActivityJS data not loaded or touch not available");
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
} else {
|
|
98
|
+
return activityJS.activitySession.activityBunch.assignmentNode.touch();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<CapytaleAntiCheat
|
|
104
|
+
enabled={hasAntiCheat}
|
|
105
|
+
hashedPassword={antiCheat?.passwordHash || null}
|
|
106
|
+
startLocked={antiCheat?.startLocked || null}
|
|
107
|
+
activityTitle={activityInfo.title}
|
|
108
|
+
studentName={studentName}
|
|
109
|
+
isDirty={isDirty}
|
|
110
|
+
returnUrl={returnUrl}
|
|
111
|
+
dbTouchFunction={touch}
|
|
112
|
+
disableExitDetection={isAntiCheatExitDetectionDisabled}
|
|
113
|
+
contentClassName="anti-cheat-content"
|
|
114
|
+
exitDetectionMethod={
|
|
115
|
+
props.antiCheatOptions.hasIframes
|
|
116
|
+
? ExitDetectionMethod.VISIBILITY_CHANGE
|
|
117
|
+
: ExitDetectionMethod.BLUR
|
|
118
|
+
}
|
|
119
|
+
contentHidingMethod={
|
|
120
|
+
props.antiCheatOptions.preserveDom
|
|
121
|
+
? ContentHidingMethod.DISPLAY_NONE
|
|
122
|
+
: ContentHidingMethod.REMOVE_FROM_DOM
|
|
123
|
+
}
|
|
124
|
+
>
|
|
125
|
+
{props.children}
|
|
126
|
+
</CapytaleAntiCheat>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
40
130
|
export default MetaPlayer;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FC, useEffect } from "react";
|
|
2
|
+
import { setIsAntiCheatExitDetectionDisabled } from "./activityDataSlice";
|
|
3
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
4
|
+
|
|
5
|
+
type IsAntiCheatExitDetectionDisabledSetterProps = {
|
|
6
|
+
isDisabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const IsAntiCheatExitDetectionDisabledSetter: FC<
|
|
10
|
+
IsAntiCheatExitDetectionDisabledSetterProps
|
|
11
|
+
> = (props) => {
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
dispatch(setIsAntiCheatExitDetectionDisabled(props.isDisabled));
|
|
15
|
+
}, [props]);
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default IsAntiCheatExitDetectionDisabledSetter;
|
|
@@ -45,6 +45,10 @@ interface ActivityJSData {
|
|
|
45
45
|
comments: string | null;
|
|
46
46
|
grading: string | null;
|
|
47
47
|
workflow: wf | null | undefined;
|
|
48
|
+
antiCheat?: null | {
|
|
49
|
+
passwordHash?: string | null;
|
|
50
|
+
startLocked: boolean;
|
|
51
|
+
};
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
type SaveState = "idle" | "should-save" | "saving";
|
|
@@ -55,6 +59,7 @@ interface UIState {
|
|
|
55
59
|
saveState: SaveState;
|
|
56
60
|
isPlayerDirty: boolean;
|
|
57
61
|
isMPDirty: boolean;
|
|
62
|
+
isAntiCheatExitDetectionDisabled: boolean;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
export interface ActivityDataState
|
|
@@ -93,6 +98,8 @@ const initialState: ActivityDataState = {
|
|
|
93
98
|
saveState: "idle",
|
|
94
99
|
isPlayerDirty: false,
|
|
95
100
|
isMPDirty: false,
|
|
101
|
+
|
|
102
|
+
isAntiCheatExitDetectionDisabled: false,
|
|
96
103
|
};
|
|
97
104
|
|
|
98
105
|
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
@@ -125,6 +132,7 @@ export const activityDataSlice = createAppSlice({
|
|
|
125
132
|
state.nid = action.payload.nid;
|
|
126
133
|
state.activityNid = action.payload.activityNid;
|
|
127
134
|
state.workflow = action.payload.workflow;
|
|
135
|
+
state.antiCheat = action.payload.antiCheat;
|
|
128
136
|
},
|
|
129
137
|
),
|
|
130
138
|
setPlayerSettings: create.reducer(
|
|
@@ -179,6 +187,11 @@ export const activityDataSlice = createAppSlice({
|
|
|
179
187
|
setIsMPDirty: create.reducer((state, action: PayloadAction<boolean>) => {
|
|
180
188
|
state.isMPDirty = action.payload;
|
|
181
189
|
}),
|
|
190
|
+
setIsAntiCheatExitDetectionDisabled: create.reducer(
|
|
191
|
+
(state, action: PayloadAction<boolean>) => {
|
|
192
|
+
state.isAntiCheatExitDetectionDisabled = action.payload;
|
|
193
|
+
},
|
|
194
|
+
),
|
|
182
195
|
}),
|
|
183
196
|
// You can define your selectors here. These selectors receive the slice
|
|
184
197
|
// state as their first argument.
|
|
@@ -220,6 +233,17 @@ export const activityDataSlice = createAppSlice({
|
|
|
220
233
|
data.mode === "create" ||
|
|
221
234
|
data.mode === "review" ||
|
|
222
235
|
(data.mode === "assignment" && data.workflow === "current"),
|
|
236
|
+
|
|
237
|
+
selectAntiCheat: (data) => data.antiCheat,
|
|
238
|
+
selectHasAntiCheat: (data) =>
|
|
239
|
+
!!(
|
|
240
|
+
data.antiCheat &&
|
|
241
|
+
data.antiCheat.passwordHash &&
|
|
242
|
+
data.mode === "assignment" &&
|
|
243
|
+
data.workflow === "current"
|
|
244
|
+
),
|
|
245
|
+
selectIsAntiCheatExitDetectionDisabled: (data) =>
|
|
246
|
+
data.isAntiCheatExitDetectionDisabled,
|
|
223
247
|
},
|
|
224
248
|
});
|
|
225
249
|
|
|
@@ -238,6 +262,7 @@ export const {
|
|
|
238
262
|
setWorkflow,
|
|
239
263
|
setIsPlayerDirty,
|
|
240
264
|
setIsMPDirty,
|
|
265
|
+
setIsAntiCheatExitDetectionDisabled,
|
|
241
266
|
} = activityDataSlice.actions;
|
|
242
267
|
|
|
243
268
|
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
@@ -265,4 +290,8 @@ export const {
|
|
|
265
290
|
selectCanChoosePedagoLayout,
|
|
266
291
|
selectCanChooseTheme,
|
|
267
292
|
selectShowSaveButton,
|
|
293
|
+
|
|
294
|
+
selectAntiCheat,
|
|
295
|
+
selectHasAntiCheat,
|
|
296
|
+
selectIsAntiCheatExitDetectionDisabled,
|
|
268
297
|
} = activityDataSlice.selectors;
|
|
@@ -92,6 +92,12 @@ export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
|
|
|
92
92
|
comments: ab.assignmentNode?.comments.value || null,
|
|
93
93
|
grading: ab.assignmentNode?.grading.value || null,
|
|
94
94
|
workflow: ab.assignmentNode?.workflow,
|
|
95
|
+
antiCheat: ab.hasAntiCheat
|
|
96
|
+
? {
|
|
97
|
+
passwordHash: ab.antiCheatPasswd,
|
|
98
|
+
startLocked: !ab.assignmentNode?.isNew,
|
|
99
|
+
}
|
|
100
|
+
: null,
|
|
95
101
|
}),
|
|
96
102
|
);
|
|
97
103
|
tracker.trackActivity(data);
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
selectGrading,
|
|
7
7
|
selectInstructions,
|
|
8
8
|
selectSaveState,
|
|
9
|
-
selectWorkflow,
|
|
10
9
|
setIsMPDirty,
|
|
11
10
|
setIsPlayerDirty,
|
|
12
11
|
setSaveState,
|
|
12
|
+
setWorkflow,
|
|
13
13
|
} from "../activityData/activityDataSlice";
|
|
14
14
|
import { useActivityJS } from "./ActivityJSProvider";
|
|
15
15
|
import { Toast } from "primereact/toast";
|
|
@@ -24,7 +24,6 @@ const Saver: FC<{}> = () => {
|
|
|
24
24
|
const grading = useAppSelector(selectGrading);
|
|
25
25
|
const beforeSave = useAppSelector(selectBeforeSave);
|
|
26
26
|
const afterSave = useAppSelector(selectAfterSave);
|
|
27
|
-
const workflow = useAppSelector(selectWorkflow);
|
|
28
27
|
|
|
29
28
|
const toast = useRef<Toast>(null);
|
|
30
29
|
|
|
@@ -85,16 +84,16 @@ const Saver: FC<{}> = () => {
|
|
|
85
84
|
? answerSheetContent
|
|
86
85
|
: JSON.stringify(answerSheetContent);
|
|
87
86
|
}
|
|
88
|
-
if (
|
|
89
|
-
activityJs.activitySession.mode === "assignment" ||
|
|
90
|
-
activityJs.activitySession.mode === "review"
|
|
91
|
-
) {
|
|
92
|
-
ab.assignmentNode!.workflow = workflow!;
|
|
93
|
-
}
|
|
94
87
|
try {
|
|
95
88
|
const saveData = await ab.save();
|
|
96
89
|
console.log("Save return data", saveData);
|
|
97
90
|
dispatch(setSaveState("idle"));
|
|
91
|
+
if (
|
|
92
|
+
activityJs.activitySession.mode === "assignment" ||
|
|
93
|
+
activityJs.activitySession.mode === "review"
|
|
94
|
+
) {
|
|
95
|
+
dispatch(setWorkflow(ab.assignmentNode!.workflow));
|
|
96
|
+
}
|
|
98
97
|
dispatch(setIsPlayerDirty(false));
|
|
99
98
|
dispatch(setIsMPDirty(false));
|
|
100
99
|
toast.current!.show({
|
|
@@ -10,6 +10,8 @@ import useFullscreen from "../../utils/useFullscreen";
|
|
|
10
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";
|
|
13
15
|
|
|
14
16
|
const ActivityMenu: React.FC = memo(() => {
|
|
15
17
|
const [settingsSidebarVisible, setSettingsSidebarVisible] = useState(false);
|
|
@@ -20,6 +22,7 @@ const ActivityMenu: React.FC = memo(() => {
|
|
|
20
22
|
const isInIframe = useMemo(() => capytaleUI.isInCapytaletIframe(), []);
|
|
21
23
|
const [helpDialogVisible, setHelpDialogVisible] = useState(false);
|
|
22
24
|
const activityJS = useActivityJS();
|
|
25
|
+
const hasAntiCheat = useAppSelector(selectHasAntiCheat);
|
|
23
26
|
return (
|
|
24
27
|
<div className={styles.activityMenu}>
|
|
25
28
|
<ActivityQuickActions />
|
|
@@ -35,18 +38,20 @@ const ActivityMenu: React.FC = memo(() => {
|
|
|
35
38
|
}}
|
|
36
39
|
onClick={() => setSettingsSidebarVisible(true)}
|
|
37
40
|
/>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
{!hasAntiCheat && (
|
|
42
|
+
<Button
|
|
43
|
+
severity="secondary"
|
|
44
|
+
size="small"
|
|
45
|
+
icon={isFullscreen ? "pi pi-expand" : "pi pi-expand"}
|
|
46
|
+
text
|
|
47
|
+
tooltip="Plein écran"
|
|
48
|
+
tooltipOptions={{
|
|
49
|
+
position: "bottom",
|
|
50
|
+
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
51
|
+
}}
|
|
52
|
+
onClick={() => setWantFullscreen((prev) => !prev)}
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
50
55
|
{isInIframe && (
|
|
51
56
|
<Button
|
|
52
57
|
severity="secondary"
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
selectShowSaveButton,
|
|
18
18
|
selectWorkflow,
|
|
19
19
|
setSaveState,
|
|
20
|
-
setWorkflow,
|
|
21
20
|
} from "../activityData/activityDataSlice";
|
|
22
21
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
23
22
|
import settings from "../../settings";
|
|
@@ -25,9 +24,11 @@ import { wf } from "@capytale/activity.js/activity/field/workflow";
|
|
|
25
24
|
import capytaleUI from "@capytale/activity.js/backend/capytale/ui";
|
|
26
25
|
import ButtonDoubleIcon from "../../utils/ButtonDoubleIcon";
|
|
27
26
|
import CardSelector from "../../utils/CardSelector";
|
|
27
|
+
import { useActivityJS } from "../activityJS/ActivityJSProvider";
|
|
28
28
|
|
|
29
29
|
const CapytaleMenu: React.FC = () => {
|
|
30
30
|
const dispatch = useAppDispatch();
|
|
31
|
+
const activityJS = useActivityJS();
|
|
31
32
|
const sharingInfo = useAppSelector(selectSharingInfo);
|
|
32
33
|
const saveState = useAppSelector(selectSaveState);
|
|
33
34
|
const windowsSize = useWindowSize();
|
|
@@ -46,7 +47,7 @@ const CapytaleMenu: React.FC = () => {
|
|
|
46
47
|
const isInIframe = useMemo(() => capytaleUI.isInCapytaletIframe(), []);
|
|
47
48
|
const toast = useRef<Toast>(null);
|
|
48
49
|
const changeWorkflow = (value: wf) => {
|
|
49
|
-
|
|
50
|
+
activityJS.activitySession!.activityBunch.assignmentNode!.workflow = value;
|
|
50
51
|
dispatch(setSaveState("should-save"));
|
|
51
52
|
};
|
|
52
53
|
const confirmFinishAssignment = () => {
|
|
@@ -80,25 +81,27 @@ const CapytaleMenu: React.FC = () => {
|
|
|
80
81
|
}}
|
|
81
82
|
/>
|
|
82
83
|
)}
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
{
|
|
85
|
+
showSaveButton && (
|
|
86
|
+
<Button
|
|
87
|
+
label={isQuiteSmall ? undefined : "Enregistrer"}
|
|
88
|
+
disabled={!isDirty}
|
|
89
|
+
severity={"warning"}
|
|
90
|
+
size="small"
|
|
91
|
+
icon="pi pi-save"
|
|
92
|
+
loading={saveState !== "idle"}
|
|
93
|
+
onClick={() => {
|
|
94
|
+
dispatch(setSaveState("should-save"));
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
) /**
|
|
91
98
|
tooltip={isDirty ? "Enregistrer l'activité" : "Rien à enregistrer"}
|
|
92
99
|
tooltipOptions={{
|
|
93
100
|
position: "bottom",
|
|
94
101
|
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
95
102
|
showOnDisabled: true,
|
|
96
|
-
}}
|
|
97
|
-
|
|
98
|
-
dispatch(setSaveState("should-save"));
|
|
99
|
-
}}
|
|
100
|
-
/>
|
|
101
|
-
)}
|
|
103
|
+
}} */
|
|
104
|
+
}
|
|
102
105
|
|
|
103
106
|
{mode === "create" && (
|
|
104
107
|
<SplitButton
|