@capytale/meta-player 0.5.0 → 0.5.2
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/features/functionalities/AttachedFilesFunctionality.ts +4 -8
- package/src/features/functionalities/functionalitiesSlice.ts +29 -9
- package/src/features/functionalities/hooks.ts +15 -9
- package/src/features/navbar/ActivityMenu.tsx +6 -2
- package/src/features/navbar/AttachedFilesSidebarContent.module.scss +17 -0
- package/src/features/navbar/AttachedFilesSidebarContent.tsx +88 -36
- package/src/features/theming/hooks.ts +7 -0
- package/src/index.tsx +6 -1
package/package.json
CHANGED
|
@@ -2,22 +2,18 @@ import { FC, useEffect } from "react";
|
|
|
2
2
|
import { AttachedFilesOptions, setAttachedFilesOptions } from "./functionalitiesSlice";
|
|
3
3
|
import { useAppDispatch } from "../../app/hooks";
|
|
4
4
|
|
|
5
|
-
type AttachedFilesFunctionalityProps =
|
|
6
|
-
options: Omit<AttachedFilesOptions, "enabled">;
|
|
7
|
-
};
|
|
5
|
+
type AttachedFilesFunctionalityProps = Omit<AttachedFilesOptions, "enabled">;
|
|
8
6
|
|
|
9
|
-
const AttachedFilesFunctionality: FC<AttachedFilesFunctionalityProps> = ({
|
|
10
|
-
options,
|
|
11
|
-
}) => {
|
|
7
|
+
const AttachedFilesFunctionality: FC<AttachedFilesFunctionalityProps> = (props) => {
|
|
12
8
|
const dispatch = useAppDispatch();
|
|
13
9
|
useEffect(() => {
|
|
14
10
|
dispatch(setAttachedFilesOptions({
|
|
15
|
-
...
|
|
11
|
+
...props,
|
|
16
12
|
enabled: true,
|
|
17
13
|
}));
|
|
18
14
|
return () => {
|
|
19
15
|
dispatch(setAttachedFilesOptions({
|
|
20
|
-
...
|
|
16
|
+
...props,
|
|
21
17
|
enabled: false,
|
|
22
18
|
}));
|
|
23
19
|
};
|
|
@@ -4,26 +4,39 @@ import { createAppSlice } from "../../app/createAppSlice";
|
|
|
4
4
|
export type AttachedFileData = {
|
|
5
5
|
name: string;
|
|
6
6
|
isTemporary: boolean;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
downloadMode: "default" | "custom" | "none";
|
|
8
|
+
copyLinkMode: "default" | "custom" | "none";
|
|
9
|
+
interactMode: "download" | "preview" | "custom" | "none";
|
|
10
10
|
urlOrId: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export type UploadedFileInfo = {
|
|
14
|
+
source: URL,
|
|
15
|
+
name: string,
|
|
16
|
+
size: number,
|
|
17
|
+
file: File,
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
export type AttachedFilesOptions = {
|
|
14
21
|
enabled: boolean;
|
|
15
22
|
uploadTemporaryFiles?: {
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
uploadButtonLabel?: string;
|
|
24
|
+
mimeTypes?: string[];
|
|
25
|
+
multiple: boolean;
|
|
26
|
+
handler: (fileInfos: UploadedFileInfo[]) => any;
|
|
18
27
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
listFilesMiddleware?: (originalFiles: AttachedFileData[]) => AttachedFileData[];
|
|
29
|
+
customHandlers?: {
|
|
30
|
+
downloadFile?: ((originalFile: AttachedFileData) => any);
|
|
31
|
+
copyFileLink?: ((originalFile: AttachedFileData) => any);
|
|
32
|
+
interactWithFile?: ((originalFile: AttachedFileData) => any);
|
|
22
33
|
};
|
|
34
|
+
onViewFiles?: () => any;
|
|
23
35
|
};
|
|
24
36
|
|
|
25
37
|
export interface FunctionalitiesState {
|
|
26
38
|
attachedFilesOptions: AttachedFilesOptions;
|
|
39
|
+
attachedFilesRefresher: number;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
export const defaultAttachedFilesOptions: AttachedFilesOptions = {
|
|
@@ -31,7 +44,8 @@ export const defaultAttachedFilesOptions: AttachedFilesOptions = {
|
|
|
31
44
|
};
|
|
32
45
|
|
|
33
46
|
export const initialState: FunctionalitiesState = {
|
|
34
|
-
attachedFilesOptions: defaultAttachedFilesOptions
|
|
47
|
+
attachedFilesOptions: defaultAttachedFilesOptions,
|
|
48
|
+
attachedFilesRefresher: 0,
|
|
35
49
|
};
|
|
36
50
|
|
|
37
51
|
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
@@ -44,22 +58,28 @@ export const functionalitiesSlice = createAppSlice({
|
|
|
44
58
|
setAttachedFilesOptions: create.reducer((state, action: PayloadAction<AttachedFilesOptions>) => {
|
|
45
59
|
state.attachedFilesOptions = action.payload;
|
|
46
60
|
}),
|
|
61
|
+
refreshAttachedFiles: create.reducer((state) => {
|
|
62
|
+
state.attachedFilesRefresher += 1;
|
|
63
|
+
}),
|
|
47
64
|
}),
|
|
48
65
|
// You can define your selectors here. These selectors receive the slice
|
|
49
66
|
// state as their first argument.
|
|
50
67
|
selectors: {
|
|
51
68
|
selectAttachedFilesOptions: (state) => state.attachedFilesOptions,
|
|
52
69
|
selectAttachedFilesEnabled: (state) => state.attachedFilesOptions.enabled,
|
|
70
|
+
selectAttachedFilesRefresher: (state) => state.attachedFilesRefresher,
|
|
53
71
|
},
|
|
54
72
|
});
|
|
55
73
|
|
|
56
74
|
// Action creators are generated for each case reducer function.
|
|
57
75
|
export const {
|
|
58
76
|
setAttachedFilesOptions,
|
|
77
|
+
refreshAttachedFiles,
|
|
59
78
|
} = functionalitiesSlice.actions;
|
|
60
79
|
|
|
61
80
|
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
62
81
|
export const {
|
|
63
82
|
selectAttachedFilesOptions,
|
|
64
83
|
selectAttachedFilesEnabled,
|
|
84
|
+
selectAttachedFilesRefresher,
|
|
65
85
|
} = functionalitiesSlice.selectors;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import { useAppSelector } from "../../app/hooks"
|
|
2
|
+
import { useAppDispatch, useAppSelector } from "../../app/hooks"
|
|
3
3
|
import { useActivityJS } from "../activityJS/ActivityJSProvider";
|
|
4
|
-
import { AttachedFileData, selectAttachedFilesOptions } from "./functionalitiesSlice"
|
|
4
|
+
import { AttachedFileData, refreshAttachedFiles, selectAttachedFilesOptions, selectAttachedFilesRefresher } from "./functionalitiesSlice"
|
|
5
5
|
|
|
6
6
|
export const useAttachedFiles = () => {
|
|
7
7
|
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
8
|
+
const attachedFilesRefresher = useAppSelector(selectAttachedFilesRefresher);
|
|
8
9
|
|
|
9
10
|
const activityJS = useActivityJS();
|
|
10
11
|
const files = useMemo(
|
|
11
12
|
() =>
|
|
12
13
|
activityJS.activitySession?.activityBunch.activityNode.attached_files
|
|
13
14
|
.items || [],
|
|
14
|
-
[activityJS],
|
|
15
|
+
[activityJS, attachedFilesRefresher],
|
|
15
16
|
);
|
|
16
17
|
const filesData = useMemo<AttachedFileData[]>(() => {
|
|
17
18
|
return files.map((file) => {
|
|
@@ -20,19 +21,24 @@ export const useAttachedFiles = () => {
|
|
|
20
21
|
name: name,
|
|
21
22
|
urlOrId: file,
|
|
22
23
|
isTemporary: false,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
downloadMode: "default",
|
|
25
|
+
copyLinkMode: "default",
|
|
26
|
+
interactMode: "preview",
|
|
26
27
|
};
|
|
27
28
|
});
|
|
28
29
|
}, [files]);
|
|
29
30
|
|
|
30
31
|
const treatedFilesData = useMemo(() => {
|
|
31
|
-
if (!attachedFilesOptions.enabled || !attachedFilesOptions.
|
|
32
|
+
if (!attachedFilesOptions.enabled || !attachedFilesOptions.listFilesMiddleware) {
|
|
32
33
|
return filesData;
|
|
33
34
|
}
|
|
34
|
-
return attachedFilesOptions.
|
|
35
|
-
}, [filesData, attachedFilesOptions]);
|
|
35
|
+
return attachedFilesOptions.listFilesMiddleware(filesData);
|
|
36
|
+
}, [filesData, attachedFilesOptions, attachedFilesRefresher]);
|
|
36
37
|
|
|
37
38
|
return treatedFilesData;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const useRefreshAttachedFiles = () => {
|
|
42
|
+
const dispatch = useAppDispatch();
|
|
43
|
+
return () => dispatch(refreshAttachedFiles());
|
|
38
44
|
}
|
|
@@ -15,7 +15,7 @@ import { selectHasAntiCheat } from "../activityData/activityDataSlice";
|
|
|
15
15
|
import AttachedFilesSidebarContent from "./AttachedFilesSidebarContent";
|
|
16
16
|
import { Badge } from "primereact/badge";
|
|
17
17
|
import { useAttachedFiles } from "../functionalities/hooks";
|
|
18
|
-
import { selectAttachedFilesEnabled } from "../functionalities/functionalitiesSlice";
|
|
18
|
+
import { selectAttachedFilesEnabled, selectAttachedFilesOptions } from "../functionalities/functionalitiesSlice";
|
|
19
19
|
|
|
20
20
|
const ActivityMenu: React.FC = memo(() => {
|
|
21
21
|
const [settingsSidebarVisible, setSettingsSidebarVisible] = useState(false);
|
|
@@ -31,6 +31,7 @@ const ActivityMenu: React.FC = memo(() => {
|
|
|
31
31
|
const hasAntiCheat = useAppSelector(selectHasAntiCheat);
|
|
32
32
|
const attachedFiles = useAttachedFiles();
|
|
33
33
|
const attachedFilesEnabled = useAppSelector(selectAttachedFilesEnabled);
|
|
34
|
+
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
34
35
|
return (
|
|
35
36
|
<div className={styles.activityMenu}>
|
|
36
37
|
<ActivityQuickActions />
|
|
@@ -52,7 +53,10 @@ const ActivityMenu: React.FC = memo(() => {
|
|
|
52
53
|
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
53
54
|
}}
|
|
54
55
|
className="p-overlay-badge"
|
|
55
|
-
onClick={() =>
|
|
56
|
+
onClick={() => {
|
|
57
|
+
attachedFilesOptions.onViewFiles?.();
|
|
58
|
+
setAttachedFilesSidebarVisible(true);
|
|
59
|
+
}}
|
|
56
60
|
/>
|
|
57
61
|
)}
|
|
58
62
|
<Button
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
.sidebarContent {
|
|
2
|
+
height: 100%;
|
|
3
|
+
overflow-y: auto;
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
.attachedFiles {
|
|
2
9
|
margin: 4px 0;
|
|
10
|
+
flex-grow: 1;
|
|
11
|
+
flex-shrink: 1;
|
|
3
12
|
}
|
|
4
13
|
|
|
5
14
|
.fileRow {
|
|
@@ -24,3 +33,11 @@
|
|
|
24
33
|
flex-shrink: 0;
|
|
25
34
|
flex-grow: 0;
|
|
26
35
|
}
|
|
36
|
+
|
|
37
|
+
.uploadFilesButtonContainer {
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
flex-grow: 0;
|
|
40
|
+
& > * {
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -11,15 +11,57 @@ import { Toast } from "primereact/toast";
|
|
|
11
11
|
import styles from "./AttachedFilesSidebarContent.module.scss";
|
|
12
12
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
13
13
|
import { downloadFile } from "../../utils/download";
|
|
14
|
+
import { useFileUpload } from "use-file-upload";
|
|
14
15
|
|
|
15
16
|
const AttachedFilesSidebarContent: FC = () => {
|
|
16
17
|
const filesData = useAttachedFiles();
|
|
18
|
+
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
19
|
+
const [_, selectFile] = useFileUpload();
|
|
17
20
|
|
|
18
21
|
return (
|
|
19
|
-
<div className={styles.
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
<div className={styles.sidebarContent}>
|
|
23
|
+
<div className={styles.attachedFiles}>
|
|
24
|
+
{filesData.map((file) => (
|
|
25
|
+
<AttachedFileLinks key={file.name} fileData={file} />
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
{attachedFilesOptions.uploadTemporaryFiles && (
|
|
29
|
+
<div className={styles.uploadFilesButtonContainer}>
|
|
30
|
+
<Button
|
|
31
|
+
icon="pi pi-upload"
|
|
32
|
+
label={
|
|
33
|
+
attachedFilesOptions.uploadTemporaryFiles?.uploadButtonLabel ||
|
|
34
|
+
"Ajouter fichier temporaire"
|
|
35
|
+
}
|
|
36
|
+
onClick={() => {
|
|
37
|
+
selectFile(
|
|
38
|
+
{
|
|
39
|
+
multiple: attachedFilesOptions.uploadTemporaryFiles?.multiple,
|
|
40
|
+
accept:
|
|
41
|
+
attachedFilesOptions.uploadTemporaryFiles?.mimeTypes?.join(
|
|
42
|
+
",",
|
|
43
|
+
) || undefined,
|
|
44
|
+
},
|
|
45
|
+
(filesOrFiles) => {
|
|
46
|
+
if (Array.isArray(filesOrFiles)) {
|
|
47
|
+
attachedFilesOptions.uploadTemporaryFiles?.handler(
|
|
48
|
+
filesOrFiles,
|
|
49
|
+
);
|
|
50
|
+
} else {
|
|
51
|
+
/* Deconstructing the object here so that if there is a
|
|
52
|
+
bug, the error will be caught here and not in the handler */
|
|
53
|
+
const { source, name, size, file } = filesOrFiles;
|
|
54
|
+
attachedFilesOptions.uploadTemporaryFiles?.handler([
|
|
55
|
+
{ source, name, size, file },
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}}
|
|
61
|
+
className={styles.uploadButton}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
23
65
|
</div>
|
|
24
66
|
);
|
|
25
67
|
};
|
|
@@ -36,13 +78,13 @@ const AttachedFileLinks: FC<{ fileData: AttachedFileData }> = ({
|
|
|
36
78
|
<Button
|
|
37
79
|
label={fileData.name}
|
|
38
80
|
severity={fileData.isTemporary ? "warning" : "secondary"}
|
|
39
|
-
disabled={fileData.
|
|
81
|
+
disabled={fileData.interactMode === "none"}
|
|
40
82
|
onClick={() => {
|
|
41
|
-
if (fileData.
|
|
42
|
-
attachedFilesOptions.
|
|
43
|
-
} else if (fileData.
|
|
83
|
+
if (fileData.interactMode === "custom") {
|
|
84
|
+
attachedFilesOptions.customHandlers?.interactWithFile?.(fileData);
|
|
85
|
+
} else if (fileData.interactMode === "download") {
|
|
44
86
|
window.open(fileData.urlOrId, "_blank")?.focus();
|
|
45
|
-
} else if (fileData.
|
|
87
|
+
} else if (fileData.interactMode === "preview") {
|
|
46
88
|
toast.current?.show({
|
|
47
89
|
severity: "info",
|
|
48
90
|
summary: "Aperçu non disponible",
|
|
@@ -54,46 +96,56 @@ const AttachedFileLinks: FC<{ fileData: AttachedFileData }> = ({
|
|
|
54
96
|
className={styles.fileInteraction}
|
|
55
97
|
text
|
|
56
98
|
/>
|
|
57
|
-
{fileData.
|
|
99
|
+
{fileData.downloadMode !== "none" && (
|
|
58
100
|
<Button
|
|
59
101
|
icon="pi pi-download"
|
|
60
102
|
severity="secondary"
|
|
61
103
|
onClick={() => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
104
|
+
if (fileData.downloadMode === "custom") {
|
|
105
|
+
attachedFilesOptions.customHandlers?.downloadFile?.(fileData);
|
|
106
|
+
return;
|
|
107
|
+
} else if (fileData.downloadMode === "default") {
|
|
108
|
+
downloadFile(fileData.urlOrId, fileData.name);
|
|
109
|
+
toast.current?.show({
|
|
110
|
+
severity: "success",
|
|
111
|
+
summary: "Téléchargement lancé",
|
|
112
|
+
detail: fileData.name,
|
|
113
|
+
life: 3000,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
69
116
|
}}
|
|
70
117
|
className={styles.fileSmallInteraction}
|
|
71
118
|
text
|
|
72
119
|
/>
|
|
73
120
|
)}
|
|
74
|
-
{fileData.
|
|
121
|
+
{fileData.copyLinkMode !== "none" && (
|
|
75
122
|
<Button
|
|
76
123
|
icon="pi pi-link"
|
|
77
124
|
severity="secondary"
|
|
78
125
|
onClick={() => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
if (fileData.copyLinkMode === "custom") {
|
|
127
|
+
attachedFilesOptions.customHandlers?.copyFileLink?.(fileData);
|
|
128
|
+
return;
|
|
129
|
+
} else if (fileData.copyLinkMode === "default") {
|
|
130
|
+
// copy link to clipboard
|
|
131
|
+
copyToClipboard(fileData.urlOrId, (success, error) => {
|
|
132
|
+
if (success) {
|
|
133
|
+
toast.current?.show({
|
|
134
|
+
severity: "success",
|
|
135
|
+
summary: "Lien copié",
|
|
136
|
+
detail: fileData.name,
|
|
137
|
+
life: 3000,
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
toast.current?.show({
|
|
141
|
+
severity: "error",
|
|
142
|
+
summary: "Erreur lors de la copie du lien",
|
|
143
|
+
detail: error,
|
|
144
|
+
life: 5000,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
97
149
|
}}
|
|
98
150
|
style={{
|
|
99
151
|
flexGrow: 0,
|
package/src/index.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import BeforeResetAction from "./features/activityJS/BeforeResetAction";
|
|
|
8
8
|
import AfterSaveAction from "./features/activityJS/AfterSaveAction";
|
|
9
9
|
import AfterResetAction from "./features/activityJS/AfterResetAction";
|
|
10
10
|
import { useMode, useWorkflow } from "./hooks";
|
|
11
|
+
import { useThemeType } from "./features/theming/hooks";
|
|
11
12
|
import {
|
|
12
13
|
useActivityJS,
|
|
13
14
|
useActivityJsEssentials,
|
|
@@ -19,8 +20,10 @@ import { ActivityQuickActionsSetter } from "./features/navbar/ActivityQuickActio
|
|
|
19
20
|
import ActivitySettingsSetter from "./features/activitySettings/ActivitySettingsSetter";
|
|
20
21
|
import IsDirtySetter from "./features/activityData/IsDirtySetter";
|
|
21
22
|
import AttachedFilesFunctionality from "./features/functionalities/AttachedFilesFunctionality";
|
|
23
|
+
import { useRefreshAttachedFiles } from "./features/functionalities/hooks";
|
|
22
24
|
import { Toast } from "./external/prime";
|
|
23
25
|
import type { ToastMessage } from "./external/prime";
|
|
26
|
+
import type { AttachedFileData, UploadedFileInfo } from "./features/functionalities/functionalitiesSlice";
|
|
24
27
|
|
|
25
28
|
export {
|
|
26
29
|
MetaPlayer,
|
|
@@ -36,11 +39,13 @@ export {
|
|
|
36
39
|
useNotifyIsDirty,
|
|
37
40
|
useSave,
|
|
38
41
|
useOrientation,
|
|
42
|
+
useThemeType,
|
|
39
43
|
ActivitySidebarActionsSetter,
|
|
40
44
|
ActivityQuickActionsSetter,
|
|
41
45
|
ActivitySettingsSetter,
|
|
42
46
|
IsDirtySetter,
|
|
43
47
|
AttachedFilesFunctionality,
|
|
48
|
+
useRefreshAttachedFiles,
|
|
44
49
|
Toast,
|
|
45
50
|
};
|
|
46
|
-
export type { ToastMessage };
|
|
51
|
+
export type { AttachedFileData, UploadedFileInfo, ToastMessage };
|