@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 CHANGED
@@ -1,27 +1,13 @@
1
- # vite-template-redux
1
+ # Meta Player Capytale
2
2
 
3
- Uses [Vite](https://vitejs.dev/), [Vitest](https://vitest.dev/), and [React Testing Library](https://github.com/testing-library/react-testing-library) to create a modern [React](https://react.dev/) app compatible with [Create React App](https://create-react-app.dev/)
3
+ ## To put in player HTML
4
4
 
5
- ```sh
6
- npx degit reduxjs/redux-templates/packages/vite-template-redux my-app
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
- ## Goals
10
-
11
- - Easy migration from Create React App or Vite
12
- - As beginner friendly as Create React App
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.12",
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 interface FunctionalitiesState {
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 { useAttachedFiles } from "../../functionalities/hooks";
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
- toast.current?.show({
90
- severity: "info",
91
- summary: "Aperçu non disponible",
92
- detail: fileData.name,
93
- life: 3000,
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 { useNotifyIsDirty, useCanSave, useSave } from "./features/activityData/hooks";
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,