@capytale/meta-player 0.4.2 → 0.5.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/package.json +1 -1
- package/src/MetaPlayer.tsx +0 -12
- package/src/features/activityData/activityDataSlice.ts +3 -0
- package/src/features/activityData/metaPlayerOptions.ts +2 -0
- package/src/features/activityJS/hooks.ts +5 -0
- package/src/features/functionalities/AttachedFilesFunctionality.ts +28 -0
- package/src/features/functionalities/functionalitiesSlice.ts +3 -1
- package/src/features/functionalities/hooks.ts +3 -1
- package/src/features/navbar/AttachedFilesSidebarContent.module.scss +26 -0
- package/src/features/navbar/AttachedFilesSidebarContent.tsx +95 -30
- package/src/features/pedago/PedagoCommands.tsx +22 -0
- package/src/features/pedago/index.tsx +6 -3
- package/src/features/pedago/style.module.scss +23 -0
- package/src/index.tsx +2 -0
- package/src/utils/download.ts +8 -0
package/package.json
CHANGED
package/src/MetaPlayer.tsx
CHANGED
|
@@ -34,16 +34,11 @@ import {
|
|
|
34
34
|
import { initialState as layoutInitialState } from "./features/layout/layoutSlice";
|
|
35
35
|
import ExitWarning from "./features/activityData/ExitWarning";
|
|
36
36
|
import type { AntiCheatOptions, UIOptions } from "./types";
|
|
37
|
-
import {
|
|
38
|
-
AttachedFilesOptions,
|
|
39
|
-
defaultAttachedFilesOptions,
|
|
40
|
-
} from "./features/functionalities/functionalitiesSlice";
|
|
41
37
|
|
|
42
38
|
type MetaPlayerProps = PropsWithChildren<{
|
|
43
39
|
activityJSOptions?: LoadOptions;
|
|
44
40
|
antiCheatOptions?: Partial<AntiCheatOptions>;
|
|
45
41
|
uiOptions?: Partial<UIOptions>;
|
|
46
|
-
attachedFilesOptions?: AttachedFilesOptions;
|
|
47
42
|
}>;
|
|
48
43
|
|
|
49
44
|
const MetaPlayer: FC<MetaPlayerProps> = (props) => {
|
|
@@ -61,10 +56,6 @@ const MetaPlayer: FC<MetaPlayerProps> = (props) => {
|
|
|
61
56
|
noSaveForStudents: false,
|
|
62
57
|
...props.uiOptions,
|
|
63
58
|
};
|
|
64
|
-
const attachedFilesOptions: AttachedFilesOptions = {
|
|
65
|
-
...defaultAttachedFilesOptions,
|
|
66
|
-
...props.attachedFilesOptions,
|
|
67
|
-
};
|
|
68
59
|
const store = useMemo(
|
|
69
60
|
() =>
|
|
70
61
|
makeStore({
|
|
@@ -74,9 +65,6 @@ const MetaPlayer: FC<MetaPlayerProps> = (props) => {
|
|
|
74
65
|
showWorkflow: !uiOptions.noWorkflow,
|
|
75
66
|
showSaveForStudents: !uiOptions.noSaveForStudents,
|
|
76
67
|
},
|
|
77
|
-
functionalities: {
|
|
78
|
-
attachedFilesOptions,
|
|
79
|
-
},
|
|
80
68
|
}),
|
|
81
69
|
[],
|
|
82
70
|
);
|
|
@@ -149,6 +149,7 @@ export const activityDataSlice = createAppSlice({
|
|
|
149
149
|
setPlayerSettings: create.reducer(
|
|
150
150
|
(state, action: PayloadAction<MetaPlayerOptions>) => {
|
|
151
151
|
state.hasInstructions = action.payload.hasInstructions;
|
|
152
|
+
state.canReset = action.payload.canReset;
|
|
152
153
|
state.pedagoLayout = action.payload.pedagoLayout;
|
|
153
154
|
state.supportsLightTheme = action.payload.supportsLightTheme;
|
|
154
155
|
state.supportsDarkTheme = action.payload.supportsDarkTheme;
|
|
@@ -239,6 +240,7 @@ export const activityDataSlice = createAppSlice({
|
|
|
239
240
|
selectComments: (data) => data.comments,
|
|
240
241
|
selectGrading: (data) => data.grading,
|
|
241
242
|
selectHasInstructions: (data) => data.hasInstructions,
|
|
243
|
+
selectCanReset: (data) => data.canReset,
|
|
242
244
|
selectPedagoLayout: (data) => data.pedagoLayout,
|
|
243
245
|
selectHasGradingOrComments: (data) => !!(data.grading || data.comments),
|
|
244
246
|
selectSaveState: (data) => data.saveState,
|
|
@@ -310,6 +312,7 @@ export const {
|
|
|
310
312
|
selectComments,
|
|
311
313
|
selectGrading,
|
|
312
314
|
selectHasInstructions,
|
|
315
|
+
selectCanReset,
|
|
313
316
|
selectPedagoLayout,
|
|
314
317
|
selectHasGradingOrComments,
|
|
315
318
|
selectSaveState,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type MetaPlayerOptions = {
|
|
2
2
|
hasInstructions: boolean;
|
|
3
|
+
canReset: boolean;
|
|
3
4
|
pedagoLayout:
|
|
4
5
|
| "horizontal"
|
|
5
6
|
| "vertical"
|
|
@@ -11,6 +12,7 @@ export type MetaPlayerOptions = {
|
|
|
11
12
|
|
|
12
13
|
export const defaultMetaPlayerOptions: MetaPlayerOptions = {
|
|
13
14
|
hasInstructions: true,
|
|
15
|
+
canReset: true,
|
|
14
16
|
pedagoLayout: "default-horizontal",
|
|
15
17
|
supportsLightTheme: true,
|
|
16
18
|
supportsDarkTheme: false,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
|
2
2
|
import {
|
|
3
|
+
selectCanReset,
|
|
3
4
|
selectMode,
|
|
4
5
|
selectWorkflow,
|
|
5
6
|
setSaveState,
|
|
@@ -13,6 +14,7 @@ type UseResetProps = {
|
|
|
13
14
|
|
|
14
15
|
export const useReset = (props: UseResetProps) => {
|
|
15
16
|
const dispatch = useAppDispatch();
|
|
17
|
+
const canReset = useAppSelector(selectCanReset);
|
|
16
18
|
const beforeReset = useAppSelector(selectBeforeReset);
|
|
17
19
|
const afterReset = useAppSelector(selectAfterReset);
|
|
18
20
|
const activityJs = useActivityJS();
|
|
@@ -21,6 +23,9 @@ export const useReset = (props: UseResetProps) => {
|
|
|
21
23
|
if (mode !== "assignment" || workflow !== "current") {
|
|
22
24
|
return null;
|
|
23
25
|
}
|
|
26
|
+
if (!canReset) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
24
29
|
return async (reload: boolean = true) => {
|
|
25
30
|
if (!activityJs.activitySession) {
|
|
26
31
|
throw new Error("No activity session to reset");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FC, useEffect } from "react";
|
|
2
|
+
import { AttachedFilesOptions, setAttachedFilesOptions } from "./functionalitiesSlice";
|
|
3
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
4
|
+
|
|
5
|
+
type AttachedFilesFunctionalityProps = {
|
|
6
|
+
options: Omit<AttachedFilesOptions, "enabled">;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const AttachedFilesFunctionality: FC<AttachedFilesFunctionalityProps> = ({
|
|
10
|
+
options,
|
|
11
|
+
}) => {
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
dispatch(setAttachedFilesOptions({
|
|
15
|
+
...options,
|
|
16
|
+
enabled: true,
|
|
17
|
+
}));
|
|
18
|
+
return () => {
|
|
19
|
+
dispatch(setAttachedFilesOptions({
|
|
20
|
+
...options,
|
|
21
|
+
enabled: false,
|
|
22
|
+
}));
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default AttachedFilesFunctionality;
|
|
@@ -4,7 +4,9 @@ import { createAppSlice } from "../../app/createAppSlice";
|
|
|
4
4
|
export type AttachedFileData = {
|
|
5
5
|
name: string;
|
|
6
6
|
isTemporary: boolean;
|
|
7
|
-
|
|
7
|
+
canDownload: boolean;
|
|
8
|
+
canCopyLink: boolean;
|
|
9
|
+
interactionMode: "download" | "preview" | "custom" | "none";
|
|
8
10
|
urlOrId: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.attachedFiles {
|
|
2
|
+
margin: 4px 0;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.fileRow {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.fileInteraction {
|
|
11
|
+
flex-shrink: 1;
|
|
12
|
+
flex-grow: 1;
|
|
13
|
+
padding-left: 0.2rem;
|
|
14
|
+
padding-right: 0.2rem;
|
|
15
|
+
& :global(.p-button-label) {
|
|
16
|
+
text-align: left;
|
|
17
|
+
text-overflow: ellipsis;
|
|
18
|
+
overflow-x: hidden;
|
|
19
|
+
font-size: 0.8rem;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.fileSmallInteraction {
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
flex-grow: 0;
|
|
26
|
+
}
|
|
@@ -1,44 +1,109 @@
|
|
|
1
|
-
import { FC } from "react";
|
|
1
|
+
import { FC, useRef } from "react";
|
|
2
2
|
import { useAttachedFiles } from "../functionalities/hooks";
|
|
3
3
|
import { useAppSelector } from "../../app/hooks";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AttachedFileData,
|
|
6
|
+
selectAttachedFilesOptions,
|
|
7
|
+
} from "../functionalities/functionalitiesSlice";
|
|
8
|
+
import { Button } from "primereact/button";
|
|
9
|
+
import { Toast } from "primereact/toast";
|
|
10
|
+
|
|
11
|
+
import styles from "./AttachedFilesSidebarContent.module.scss";
|
|
12
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
13
|
+
import { downloadFile } from "../../utils/download";
|
|
5
14
|
|
|
6
15
|
const AttachedFilesSidebarContent: FC = () => {
|
|
7
16
|
const filesData = useAttachedFiles();
|
|
8
|
-
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
9
17
|
|
|
10
18
|
return (
|
|
11
|
-
<div>
|
|
19
|
+
<div className={styles.attachedFiles}>
|
|
12
20
|
{filesData.map((file) => (
|
|
13
|
-
<
|
|
14
|
-
<a
|
|
15
|
-
href={file.urlOrId}
|
|
16
|
-
target="_blank"
|
|
17
|
-
rel="noreferrer"
|
|
18
|
-
download={file.name}
|
|
19
|
-
style={{
|
|
20
|
-
color: file.isTemporary ? "rgb(200, 0, 0)" : "rgb(0, 50, 200)",
|
|
21
|
-
textDecoration:
|
|
22
|
-
file.interactionMode === "download" ? "underline" : "none",
|
|
23
|
-
}}
|
|
24
|
-
onClick={(e) => {
|
|
25
|
-
if (file.interactionMode !== "download") {
|
|
26
|
-
e.preventDefault();
|
|
27
|
-
if (
|
|
28
|
-
file.interactionMode === "custom" &&
|
|
29
|
-
attachedFilesOptions.middleware?.handleFileClick
|
|
30
|
-
) {
|
|
31
|
-
attachedFilesOptions.middleware.handleFileClick(file);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}}
|
|
35
|
-
>
|
|
36
|
-
{file.name}
|
|
37
|
-
</a>
|
|
38
|
-
</p>
|
|
21
|
+
<AttachedFileLinks key={file.name} fileData={file} />
|
|
39
22
|
))}
|
|
40
23
|
</div>
|
|
41
24
|
);
|
|
42
25
|
};
|
|
43
26
|
|
|
27
|
+
const AttachedFileLinks: FC<{ fileData: AttachedFileData }> = ({
|
|
28
|
+
fileData,
|
|
29
|
+
}) => {
|
|
30
|
+
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
31
|
+
const toast = useRef<Toast>(null);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={styles.fileRow}>
|
|
35
|
+
<Toast ref={toast} position="bottom-right" />
|
|
36
|
+
<Button
|
|
37
|
+
label={fileData.name}
|
|
38
|
+
severity={fileData.isTemporary ? "warning" : "secondary"}
|
|
39
|
+
disabled={fileData.interactionMode === "none"}
|
|
40
|
+
onClick={() => {
|
|
41
|
+
if (fileData.interactionMode === "custom") {
|
|
42
|
+
attachedFilesOptions.middleware?.handleFileClick?.(fileData);
|
|
43
|
+
} else if (fileData.interactionMode === "download") {
|
|
44
|
+
window.open(fileData.urlOrId, "_blank")?.focus();
|
|
45
|
+
} else if (fileData.interactionMode === "preview") {
|
|
46
|
+
toast.current?.show({
|
|
47
|
+
severity: "info",
|
|
48
|
+
summary: "Aperçu non disponible",
|
|
49
|
+
detail: fileData.name,
|
|
50
|
+
life: 3000,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
className={styles.fileInteraction}
|
|
55
|
+
text
|
|
56
|
+
/>
|
|
57
|
+
{fileData.canDownload && (
|
|
58
|
+
<Button
|
|
59
|
+
icon="pi pi-download"
|
|
60
|
+
severity="secondary"
|
|
61
|
+
onClick={() => {
|
|
62
|
+
downloadFile(fileData.urlOrId, fileData.name);
|
|
63
|
+
toast.current?.show({
|
|
64
|
+
severity: "success",
|
|
65
|
+
summary: "Téléchargement lancé",
|
|
66
|
+
detail: fileData.name,
|
|
67
|
+
life: 3000,
|
|
68
|
+
});
|
|
69
|
+
}}
|
|
70
|
+
className={styles.fileSmallInteraction}
|
|
71
|
+
text
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
{fileData.canCopyLink && (
|
|
75
|
+
<Button
|
|
76
|
+
icon="pi pi-link"
|
|
77
|
+
severity="secondary"
|
|
78
|
+
onClick={() => {
|
|
79
|
+
// copy link to clipboard
|
|
80
|
+
copyToClipboard(fileData.urlOrId, (success, error) => {
|
|
81
|
+
if (success) {
|
|
82
|
+
toast.current?.show({
|
|
83
|
+
severity: "success",
|
|
84
|
+
summary: "Lien copié",
|
|
85
|
+
detail: fileData.name,
|
|
86
|
+
life: 3000,
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
toast.current?.show({
|
|
90
|
+
severity: "error",
|
|
91
|
+
summary: "Erreur lors de la copie du lien",
|
|
92
|
+
detail: error,
|
|
93
|
+
life: 5000,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}}
|
|
98
|
+
style={{
|
|
99
|
+
flexGrow: 0,
|
|
100
|
+
flexShrink: 0,
|
|
101
|
+
}}
|
|
102
|
+
text
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
44
109
|
export default AttachedFilesSidebarContent;
|
|
@@ -34,6 +34,28 @@ export const PedagoCommands = () => {
|
|
|
34
34
|
);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
export const CloseOnlyPedagoCommands: FC = () => {
|
|
38
|
+
const dispatch = useAppDispatch();
|
|
39
|
+
return (
|
|
40
|
+
<div className={styles.closeOnlyPedagoCommands}>
|
|
41
|
+
<Button
|
|
42
|
+
severity="secondary"
|
|
43
|
+
icon="pi pi-times"
|
|
44
|
+
rounded
|
|
45
|
+
text
|
|
46
|
+
aria-label="Masquer l'évaluation"
|
|
47
|
+
tooltip="Masquer l'évaluation"
|
|
48
|
+
tooltipOptions={{
|
|
49
|
+
position: "left",
|
|
50
|
+
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
51
|
+
}}
|
|
52
|
+
onClick={() => dispatch(toggleIsPedagoVisible())}
|
|
53
|
+
style={{ flexShrink: 0 }}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
37
59
|
type DocumentSelectorHzItem = {
|
|
38
60
|
name: string;
|
|
39
61
|
value: PedagoTab;
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import { ChangeEventHandler, FC } from "react";
|
|
25
25
|
import { DivProps } from "react-html-props";
|
|
26
26
|
import SharedNotesEditor from "./SharedNotesEditor";
|
|
27
|
-
import { PedagoCommands } from "./PedagoCommands";
|
|
27
|
+
import { CloseOnlyPedagoCommands, PedagoCommands } from "./PedagoCommands";
|
|
28
28
|
import { PdfEditor } from "./PdfEditor";
|
|
29
29
|
|
|
30
30
|
const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
@@ -42,8 +42,11 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
|
42
42
|
|
|
43
43
|
if (!hasInstructions) {
|
|
44
44
|
return (
|
|
45
|
-
<div className={styles.
|
|
46
|
-
<
|
|
45
|
+
<div className={styles.onlyGradingPedago}>
|
|
46
|
+
<CloseOnlyPedagoCommands />
|
|
47
|
+
<div className={styles.gradingPanel}>
|
|
48
|
+
<Grading />
|
|
49
|
+
</div>
|
|
47
50
|
</div>
|
|
48
51
|
);
|
|
49
52
|
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
.onlyGradingPedago {
|
|
2
|
+
display: flex;
|
|
3
|
+
width: 100%;
|
|
4
|
+
:global(.layout-horizontal) & {
|
|
5
|
+
flex-direction: row-reverse;
|
|
6
|
+
}
|
|
7
|
+
:global(.layout-vertical) & {
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
.pedago {
|
|
2
13
|
display: flex;
|
|
3
14
|
height: 100%;
|
|
@@ -10,6 +21,18 @@
|
|
|
10
21
|
}
|
|
11
22
|
}
|
|
12
23
|
|
|
24
|
+
.closeOnlyPedagoCommands {
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
background-color: var(--surface-200);
|
|
29
|
+
:global(.layout-vertical) & {
|
|
30
|
+
gap: 8px;
|
|
31
|
+
flex-direction: row-reverse;
|
|
32
|
+
align-items: center;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
.pedagoCommands {
|
|
14
37
|
flex-direction: column;
|
|
15
38
|
flex-shrink: 0;
|
package/src/index.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import { ActivitySidebarActionsSetter } from "./features/navbar/ActivitySidebarA
|
|
|
18
18
|
import { ActivityQuickActionsSetter } from "./features/navbar/ActivityQuickActions";
|
|
19
19
|
import ActivitySettingsSetter from "./features/activitySettings/ActivitySettingsSetter";
|
|
20
20
|
import IsDirtySetter from "./features/activityData/IsDirtySetter";
|
|
21
|
+
import AttachedFilesFunctionality from "./features/functionalities/AttachedFilesFunctionality";
|
|
21
22
|
import { Toast } from "./external/prime";
|
|
22
23
|
import type { ToastMessage } from "./external/prime";
|
|
23
24
|
|
|
@@ -39,6 +40,7 @@ export {
|
|
|
39
40
|
ActivityQuickActionsSetter,
|
|
40
41
|
ActivitySettingsSetter,
|
|
41
42
|
IsDirtySetter,
|
|
43
|
+
AttachedFilesFunctionality,
|
|
42
44
|
Toast,
|
|
43
45
|
};
|
|
44
46
|
export type { ToastMessage };
|