@capytale/meta-player 0.5.12 → 0.5.13
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/README.md +10 -24
- package/package.json +2 -1
- package/src/App.tsx +2 -0
- package/src/features/functionalities/PreviewDialog.tsx +76 -0
- package/src/features/functionalities/functionalitiesSlice.ts +14 -1
- package/src/features/functionalities/hooks.ts +29 -3
- package/src/features/navbar/sidebars/AttachedFilesSidebarContent.tsx +28 -8
- package/src/index.tsx +11 -1
package/README.md
CHANGED
|
@@ -1,27 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Meta Player Capytale
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## To put in player HTML
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
- CSS used by the meta-player:
|
|
6
|
+
```html
|
|
7
|
+
<link id="theme-link" rel="stylesheet" href="https://cdn.ac-paris.fr/capytale/meta-player/themes/lara-light-blue/theme.css">
|
|
7
8
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
- Optimized performance compared to Create React App
|
|
14
|
-
- Customizable without ejecting
|
|
15
|
-
|
|
16
|
-
## Scripts
|
|
17
|
-
|
|
18
|
-
- `dev`/`start` - start dev server and open browser
|
|
19
|
-
- `build` - build for production
|
|
20
|
-
- `preview` - locally preview production build
|
|
21
|
-
- `test` - launch test runner
|
|
22
|
-
|
|
23
|
-
## Inspiration
|
|
24
|
-
|
|
25
|
-
- [Create React App](https://github.com/facebook/create-react-app/tree/main/packages/cra-template)
|
|
26
|
-
- [Vite](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react)
|
|
27
|
-
- [Vitest](https://github.com/vitest-dev/vitest/tree/main/examples/react-testing-lib)
|
|
9
|
+
- If using the attached files preview capability:
|
|
10
|
+
```html
|
|
11
|
+
<link rel="stylesheet" href="https://cdn.ac-paris.fr/highlight/styles/default.css">
|
|
12
|
+
<script src="https://cdn.ac-paris.fr/highlight/highlight.min.js"></script>
|
|
13
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capytale/meta-player",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"@capytale/capytale-rich-text-editor": "^0.4.3",
|
|
22
22
|
"@reduxjs/toolkit": "^2.0.1",
|
|
23
23
|
"@uidotdev/usehooks": "^2.4.1",
|
|
24
|
+
"mime": "^4.0.6",
|
|
24
25
|
"primeicons": "^7.0.0",
|
|
25
26
|
"primereact": "^10.8.3",
|
|
26
27
|
"react-dropzone": "^14.2.9",
|
package/src/App.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import settings from "./settings";
|
|
27
27
|
import ReviewNavbar from "./features/navbar/review-navbar";
|
|
28
28
|
import { useSave } from "./features/activityData/hooks";
|
|
29
|
+
import PreviewDialog from "./features/functionalities/PreviewDialog";
|
|
29
30
|
|
|
30
31
|
type AppProps = PropsWithChildren<{}>;
|
|
31
32
|
|
|
@@ -130,6 +131,7 @@ const App: FC<AppProps> = (props) => {
|
|
|
130
131
|
</SplitterPanel>
|
|
131
132
|
</Splitter>
|
|
132
133
|
</div>
|
|
134
|
+
<PreviewDialog />
|
|
133
135
|
</div>
|
|
134
136
|
);
|
|
135
137
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { FC, memo, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
|
3
|
+
import {
|
|
4
|
+
PreviewFile,
|
|
5
|
+
selectPreviewFile,
|
|
6
|
+
setPreviewFile,
|
|
7
|
+
} from "./functionalitiesSlice";
|
|
8
|
+
import { Dialog } from "primereact/dialog";
|
|
9
|
+
|
|
10
|
+
const HighlightedCode = memo(({ code }: { code: string }) => {
|
|
11
|
+
const codeRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (codeRef.current != null) {
|
|
14
|
+
if ((window as any).hljs) {
|
|
15
|
+
(window as any).hljs.highlightBlock(codeRef.current);
|
|
16
|
+
} else {
|
|
17
|
+
console.warn("Highlight.js not loaded");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}, [code]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<pre>
|
|
24
|
+
<code
|
|
25
|
+
ref={codeRef}
|
|
26
|
+
style={{ padding: 0, background: "white", maxWidth: "100%" }}
|
|
27
|
+
>
|
|
28
|
+
{code}
|
|
29
|
+
</code>
|
|
30
|
+
</pre>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const PreviewDialog: FC = () => {
|
|
35
|
+
const previewFile = useAppSelector(selectPreviewFile);
|
|
36
|
+
const [persistentPreviewFile, setPersistentPreviewFile] =
|
|
37
|
+
useState<PreviewFile | null>(null);
|
|
38
|
+
|
|
39
|
+
const dispatch = useAppDispatch();
|
|
40
|
+
const visible = previewFile != null;
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (previewFile != null) {
|
|
44
|
+
setPersistentPreviewFile(previewFile);
|
|
45
|
+
}
|
|
46
|
+
}, [previewFile]);
|
|
47
|
+
|
|
48
|
+
if (persistentPreviewFile == null) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Dialog
|
|
54
|
+
visible={visible}
|
|
55
|
+
header={persistentPreviewFile.title}
|
|
56
|
+
onHide={() => {
|
|
57
|
+
dispatch(setPreviewFile(null));
|
|
58
|
+
}}
|
|
59
|
+
style={{ maxWidth: "100%" }}
|
|
60
|
+
>
|
|
61
|
+
{persistentPreviewFile.type === "text" ? (
|
|
62
|
+
<HighlightedCode code={persistentPreviewFile.content} />
|
|
63
|
+
) : (
|
|
64
|
+
<img
|
|
65
|
+
src={persistentPreviewFile.url}
|
|
66
|
+
alt={persistentPreviewFile.title}
|
|
67
|
+
style={{
|
|
68
|
+
maxWidth: "100%",
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
</Dialog>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default PreviewDialog;
|
|
@@ -34,9 +34,15 @@ export type AttachedFilesOptions = {
|
|
|
34
34
|
onViewFiles?: () => any;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
export
|
|
37
|
+
export type PreviewFile = { title: string } & (
|
|
38
|
+
{ type: "text", content: string } |
|
|
39
|
+
{ type: "image", url: string }
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export type FunctionalitiesState = {
|
|
38
43
|
attachedFilesOptions: AttachedFilesOptions;
|
|
39
44
|
attachedFilesRefresher: number;
|
|
45
|
+
previewFile: PreviewFile | null;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export const defaultAttachedFilesOptions: AttachedFilesOptions = {
|
|
@@ -46,6 +52,7 @@ export const defaultAttachedFilesOptions: AttachedFilesOptions = {
|
|
|
46
52
|
export const initialState: FunctionalitiesState = {
|
|
47
53
|
attachedFilesOptions: defaultAttachedFilesOptions,
|
|
48
54
|
attachedFilesRefresher: 0,
|
|
55
|
+
previewFile: null,
|
|
49
56
|
};
|
|
50
57
|
|
|
51
58
|
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
@@ -61,6 +68,9 @@ export const functionalitiesSlice = createAppSlice({
|
|
|
61
68
|
refreshAttachedFiles: create.reducer((state) => {
|
|
62
69
|
state.attachedFilesRefresher += 1;
|
|
63
70
|
}),
|
|
71
|
+
setPreviewFile: create.reducer((state, action: PayloadAction<PreviewFile | null>) => {
|
|
72
|
+
state.previewFile = action.payload;
|
|
73
|
+
}),
|
|
64
74
|
}),
|
|
65
75
|
// You can define your selectors here. These selectors receive the slice
|
|
66
76
|
// state as their first argument.
|
|
@@ -68,6 +78,7 @@ export const functionalitiesSlice = createAppSlice({
|
|
|
68
78
|
selectAttachedFilesOptions: (state) => state.attachedFilesOptions,
|
|
69
79
|
selectAttachedFilesEnabled: (state) => state.attachedFilesOptions.enabled,
|
|
70
80
|
selectAttachedFilesRefresher: (state) => state.attachedFilesRefresher,
|
|
81
|
+
selectPreviewFile: (state) => state.previewFile,
|
|
71
82
|
},
|
|
72
83
|
});
|
|
73
84
|
|
|
@@ -75,6 +86,7 @@ export const functionalitiesSlice = createAppSlice({
|
|
|
75
86
|
export const {
|
|
76
87
|
setAttachedFilesOptions,
|
|
77
88
|
refreshAttachedFiles,
|
|
89
|
+
setPreviewFile,
|
|
78
90
|
} = functionalitiesSlice.actions;
|
|
79
91
|
|
|
80
92
|
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
@@ -82,4 +94,5 @@ export const {
|
|
|
82
94
|
selectAttachedFilesOptions,
|
|
83
95
|
selectAttachedFilesEnabled,
|
|
84
96
|
selectAttachedFilesRefresher,
|
|
97
|
+
selectPreviewFile,
|
|
85
98
|
} = functionalitiesSlice.selectors;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useAppDispatch, useAppSelector } from "../../app/hooks"
|
|
3
3
|
import { useActivityJS } from "../activityJS/ActivityJSProvider";
|
|
4
|
-
import { AttachedFileData, refreshAttachedFiles, selectAttachedFilesOptions, selectAttachedFilesRefresher } from "./functionalitiesSlice"
|
|
4
|
+
import { AttachedFileData, refreshAttachedFiles, selectAttachedFilesOptions, selectAttachedFilesRefresher, setPreviewFile } from "./functionalitiesSlice"
|
|
5
5
|
|
|
6
6
|
export const useAttachedFiles = () => {
|
|
7
7
|
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
@@ -36,9 +36,35 @@ export const useAttachedFiles = () => {
|
|
|
36
36
|
}, [filesData, attachedFilesOptions, attachedFilesRefresher]);
|
|
37
37
|
|
|
38
38
|
return treatedFilesData;
|
|
39
|
-
}
|
|
39
|
+
};
|
|
40
40
|
|
|
41
41
|
export const useRefreshAttachedFiles = () => {
|
|
42
42
|
const dispatch = useAppDispatch();
|
|
43
43
|
return () => dispatch(refreshAttachedFiles());
|
|
44
|
-
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const usePreviewTextFile = () => {
|
|
47
|
+
const dispatch = useAppDispatch();
|
|
48
|
+
return (title: string, content: string) => {
|
|
49
|
+
dispatch(
|
|
50
|
+
setPreviewFile({
|
|
51
|
+
title,
|
|
52
|
+
type: "text",
|
|
53
|
+
content,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const usePreviewImageFile = () => {
|
|
60
|
+
const dispatch = useAppDispatch();
|
|
61
|
+
return (title: string, url: string) => {
|
|
62
|
+
dispatch(
|
|
63
|
+
setPreviewFile({
|
|
64
|
+
title,
|
|
65
|
+
type: "image",
|
|
66
|
+
url,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { FC, useRef } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useAttachedFiles,
|
|
4
|
+
usePreviewImageFile,
|
|
5
|
+
usePreviewTextFile,
|
|
6
|
+
} from "../../functionalities/hooks";
|
|
3
7
|
import { useAppSelector } from "../../../app/hooks";
|
|
4
8
|
import {
|
|
5
9
|
AttachedFileData,
|
|
@@ -12,6 +16,7 @@ import styles from "./AttachedFilesSidebarContent.module.scss";
|
|
|
12
16
|
import { copyToClipboard } from "../../../utils/clipboard";
|
|
13
17
|
import { downloadFile } from "../../../utils/download";
|
|
14
18
|
import { useFileUpload } from "use-file-upload";
|
|
19
|
+
import mime from "mime";
|
|
15
20
|
|
|
16
21
|
const AttachedFilesSidebarContent: FC = () => {
|
|
17
22
|
const filesData = useAttachedFiles();
|
|
@@ -72,6 +77,8 @@ const AttachedFileLinks: FC<{ fileData: AttachedFileData }> = ({
|
|
|
72
77
|
}) => {
|
|
73
78
|
const attachedFilesOptions = useAppSelector(selectAttachedFilesOptions);
|
|
74
79
|
const toast = useRef<Toast>(null);
|
|
80
|
+
const previewTextFile = usePreviewTextFile();
|
|
81
|
+
const previewImageFile = usePreviewImageFile();
|
|
75
82
|
|
|
76
83
|
return (
|
|
77
84
|
<div className={styles.fileRow}>
|
|
@@ -80,18 +87,31 @@ const AttachedFileLinks: FC<{ fileData: AttachedFileData }> = ({
|
|
|
80
87
|
label={fileData.name}
|
|
81
88
|
severity={fileData.isTemporary ? "warning" : "secondary"}
|
|
82
89
|
disabled={fileData.interactMode === "none"}
|
|
83
|
-
onClick={() => {
|
|
90
|
+
onClick={async () => {
|
|
84
91
|
if (fileData.interactMode === "custom") {
|
|
85
92
|
attachedFilesOptions.customHandlers?.interactWithFile?.(fileData);
|
|
86
93
|
} else if (fileData.interactMode === "download") {
|
|
87
94
|
window.open(fileData.urlOrId, "_blank")?.focus();
|
|
88
95
|
} else if (fileData.interactMode === "preview") {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
const mimeType = mime.getType(fileData.name);
|
|
97
|
+
if (
|
|
98
|
+
mimeType?.startsWith("text") ||
|
|
99
|
+
mimeType === "application/json"
|
|
100
|
+
) {
|
|
101
|
+
// fetch file content
|
|
102
|
+
const response = await fetch(fileData.urlOrId);
|
|
103
|
+
const content = await response.text();
|
|
104
|
+
previewTextFile(fileData.name, content);
|
|
105
|
+
} else if (mimeType?.startsWith("image")) {
|
|
106
|
+
previewImageFile(fileData.name, fileData.urlOrId);
|
|
107
|
+
} else {
|
|
108
|
+
toast.current?.show({
|
|
109
|
+
severity: "info",
|
|
110
|
+
summary: `Impossible de prévisualiser {fileData.name}`,
|
|
111
|
+
detail: `Aperçu non disponible pour ce type de fichier (${mimeType})`,
|
|
112
|
+
life: 3000,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
}}
|
|
97
117
|
className={styles.fileInteraction}
|
package/src/index.tsx
CHANGED
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
useActivityJS,
|
|
14
14
|
useActivityJsEssentials,
|
|
15
15
|
} from "./features/activityJS/ActivityJSProvider";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
useNotifyIsDirty,
|
|
18
|
+
useCanSave,
|
|
19
|
+
useSave,
|
|
20
|
+
} from "./features/activityData/hooks";
|
|
17
21
|
import { useOrientation } from "./features/layout/hooks";
|
|
18
22
|
import { ActivitySidebarActionsSetter } from "./features/navbar/sidebars/ActivitySidebarActions";
|
|
19
23
|
import { ActivityQuickActionsSetter } from "./features/navbar/activity-menu/ActivityQuickActions";
|
|
@@ -22,6 +26,10 @@ import IsDirtySetter from "./features/activityData/IsDirtySetter";
|
|
|
22
26
|
import AttachedFilesFunctionality from "./features/functionalities/AttachedFilesFunctionality";
|
|
23
27
|
import { useRefreshAttachedFiles } from "./features/functionalities/hooks";
|
|
24
28
|
import { useActivitySettings } from "./features/activitySettings/hooks";
|
|
29
|
+
import {
|
|
30
|
+
usePreviewTextFile,
|
|
31
|
+
usePreviewImageFile,
|
|
32
|
+
} from "./features/functionalities/hooks";
|
|
25
33
|
import { Toast } from "./external/prime";
|
|
26
34
|
import type { ToastMessage } from "./external/prime";
|
|
27
35
|
import type {
|
|
@@ -46,6 +54,8 @@ export {
|
|
|
46
54
|
useOrientation,
|
|
47
55
|
useThemeType,
|
|
48
56
|
useActivitySettings,
|
|
57
|
+
usePreviewTextFile,
|
|
58
|
+
usePreviewImageFile,
|
|
49
59
|
ActivitySidebarActionsSetter,
|
|
50
60
|
ActivityQuickActionsSetter,
|
|
51
61
|
ActivitySettingsSetter,
|