@capytale/meta-player 0.5.12 → 0.5.14

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
+ ```
@@ -0,0 +1,28 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+
7
+ export default tseslint.config(
8
+ { ignores: ['dist'] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ['**/*.{ts,tsx}'],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ 'react-hooks': reactHooks,
18
+ 'react-refresh': reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ 'react-refresh/only-export-components': [
23
+ 'warn',
24
+ { allowConstantExport: true },
25
+ ],
26
+ },
27
+ },
28
+ )
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.14",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -16,11 +16,12 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@capytale/activity.js": "^3.1.14",
19
+ "@capytale/activity.js": "^3.1.21",
20
20
  "@capytale/capytale-anti-triche": "^0.2.1",
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",
@@ -30,6 +31,7 @@
30
31
  "use-file-upload": "^1.0.11"
31
32
  },
32
33
  "devDependencies": {
34
+ "@eslint/js": "^9.19.0",
33
35
  "@testing-library/dom": "^9.3.4",
34
36
  "@testing-library/jest-dom": "^6.2.0",
35
37
  "@testing-library/react": "^14.1.2",
@@ -37,13 +39,18 @@
37
39
  "@types/react": "^18.3.8",
38
40
  "@types/react-dom": "^18.3.0",
39
41
  "@vitejs/plugin-react": "^4.3.4",
42
+ "eslint": "^9.19.0",
43
+ "eslint-plugin-react-hooks": "^5.0.0",
44
+ "eslint-plugin-react-refresh": "^0.4.18",
45
+ "globals": "^15.14.0",
40
46
  "jsdom": "^23.2.0",
41
47
  "prettier": "^3.2.1",
42
48
  "react": "^18.3.1",
43
49
  "react-dom": "^18.3.1",
44
50
  "sass": "^1.75.0",
45
- "typescript": "^5.3.3",
46
- "vite": "^6.0.2"
51
+ "typescript": "^5.7.2",
52
+ "typescript-eslint": "^8.22.0",
53
+ "vite": "^6.1.0"
47
54
  },
48
55
  "peerDependencies": {
49
56
  "react": "^18.3.1",
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
  };
@@ -74,7 +74,22 @@ const MetaPlayer: FC<MetaPlayerProps> = (props) => {
74
74
  <ThemeSwitcher />
75
75
  <ExitWarning />
76
76
  <ErrorBoundary fallback={<div>Une erreur est survenue</div>}>
77
- <ActivityJSProvider options={props.activityJSOptions}>
77
+ <ActivityJSProvider
78
+ options={props.activityJSOptions}
79
+ loadingFallback={<div>Chargement de l'activité...</div>}
80
+ errorFallback={({ children }) => (
81
+ <div style={{ marginLeft: "1rem", marginRight: "1rem" }}>
82
+ <p>Erreur lors du chargement de l'activité : {children}</p>
83
+ <p>
84
+ Êtes-vous bien connecté(e) à Capytale ? Avez-vous bien le
85
+ droit d'accéder à cette ressource ?
86
+ </p>
87
+ <p>
88
+ <a href="/">Retour à l'accueil</a>
89
+ </p>
90
+ </div>
91
+ )}
92
+ >
78
93
  <Saver />
79
94
  <MetaPlayerContent antiCheatOptions={antiCheatOptions}>
80
95
  <App>{props.children}</App>
@@ -28,6 +28,9 @@ const ActivityJSContext = createContext<ActivitySessionLoaderReturnValue>({
28
28
  type ActivityJSProviderProps = PropsWithChildren<{
29
29
  options?: LoadOptions;
30
30
  loadingFallback?: ReactNode;
31
+ errorFallback?:
32
+ | keyof JSX.IntrinsicElements
33
+ | React.JSXElementConstructor<{ children: ReactNode }>;
31
34
  }>;
32
35
 
33
36
  export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
@@ -109,7 +112,14 @@ export const ActivityJSProvider: FC<ActivityJSProviderProps> = (props) => {
109
112
  }
110
113
  const value = useActivitySessionLoader(activityId, props.options, callback);
111
114
  if (!loaded) {
112
- return <>{props.loadingFallback}</>;
115
+ if (value.state === "loading") {
116
+ return <>{props.loadingFallback}</>;
117
+ } else if (value.state === "error" && props.errorFallback) {
118
+ console.error(value.error);
119
+ return (
120
+ <>{<props.errorFallback>{String(value.error)}</props.errorFallback>}</>
121
+ );
122
+ }
113
123
  }
114
124
  return (
115
125
  <ActivityJSContext.Provider value={value}>
@@ -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
+ };
@@ -0,0 +1,74 @@
1
+ import { FC, useCallback, useEffect, useState } from "react";
2
+ import { useAppSelector } from "../../../app/hooks";
3
+ import { Dialog } from "primereact/dialog";
4
+ import { selectActivityInfo } from "../../activityData/activityDataSlice";
5
+ import { Button } from "primereact/button";
6
+ import { apiCloneActivity } from "../../../utils/capytale";
7
+ import { useActivityJsEssentials } from "../../activityJS/ActivityJSProvider";
8
+ import { InputText } from "primereact/inputtext";
9
+
10
+ const CloneDialog: FC<{ visible: boolean; onHide: () => any }> = ({
11
+ visible,
12
+ onHide,
13
+ }) => {
14
+ const { nid } = useActivityJsEssentials();
15
+ const activityTitle = useAppSelector(selectActivityInfo).title;
16
+ const [newTitle, setNewTitle] = useState<string>(activityTitle);
17
+ const clone = useCallback(async () => {
18
+ const clone = await apiCloneActivity(nid, newTitle);
19
+ const newNid = clone.nid;
20
+ const url = `/web/c-act/n/${newNid}/play/create`;
21
+ // redirect to the new activity in a new tab
22
+ window.open(url, "_blank");
23
+ }, [nid, newTitle]);
24
+
25
+ useEffect(() => {
26
+ setNewTitle(activityTitle);
27
+ }, [activityTitle, visible]);
28
+
29
+ return (
30
+ <Dialog
31
+ visible={visible}
32
+ header={"Choisissez le titre du clone"}
33
+ onHide={onHide}
34
+ style={{ width: "750px", maxWidth: "100%" }}
35
+ >
36
+ <div
37
+ style={{
38
+ display: "flex",
39
+ flexDirection: "column",
40
+ gap: "0.5rem",
41
+ marginBottom: "1rem",
42
+ }}
43
+ >
44
+ <label htmlFor="title-input">Titre</label>
45
+ <InputText
46
+ id="title-input"
47
+ value={newTitle}
48
+ onChange={(e) => setNewTitle(e.target.value)}
49
+ />
50
+ <div
51
+ style={{
52
+ display: "flex",
53
+ justifyContent: "flex-end",
54
+ gap: "0.5rem",
55
+ }}
56
+ >
57
+ <Button
58
+ aria-label="Annuler le clonage"
59
+ label="Annuler"
60
+ onClick={onHide}
61
+ severity="secondary"
62
+ />
63
+ <Button
64
+ aria-label="Confirmer le clonage"
65
+ label="Cloner"
66
+ onClick={clone}
67
+ />
68
+ </div>
69
+ </div>
70
+ </Dialog>
71
+ );
72
+ };
73
+
74
+ export default CloneDialog;
@@ -5,7 +5,7 @@ import { Toast } from "primereact/toast";
5
5
  import { OverlayPanel } from "primereact/overlaypanel";
6
6
 
7
7
  import styles from "./style.module.scss";
8
- import { useMemo, useRef } from "react";
8
+ import { useMemo, useRef, useState } from "react";
9
9
  import { useWindowSize } from "@uidotdev/usehooks";
10
10
  import { XL } from "../../../utils/breakpoints";
11
11
  import { useAppDispatch, useAppSelector } from "../../../app/hooks";
@@ -24,10 +24,12 @@ import CardSelector from "../../../utils/CardSelector";
24
24
  import { useActivityJS } from "../../activityJS/ActivityJSProvider";
25
25
  import { selectShowWorkflow } from "../../layout/layoutSlice";
26
26
  import CountdownAndSaveButton from "./CountdownAndSaveButton";
27
+ import CloneDialog from "./CloneDialog";
27
28
 
28
29
  const CapytaleMenu: React.FC = () => {
29
30
  const dispatch = useAppDispatch();
30
31
  const activityJS = useActivityJS();
32
+ const [cloneDialogVisible, setCloneDialogVisible] = useState(false);
31
33
  const sharingInfo = useAppSelector(selectSharingInfo);
32
34
  const windowsSize = useWindowSize();
33
35
  const mode = useAppSelector(selectMode);
@@ -76,6 +78,27 @@ const CapytaleMenu: React.FC = () => {
76
78
  />
77
79
  )}
78
80
  <CountdownAndSaveButton />
81
+ {!isInIframe && mode === "view" && (
82
+ <>
83
+ <Button
84
+ aria-label="Cloner l'activité"
85
+ label="Cloner"
86
+ icon="pi pi-clone"
87
+ size="small"
88
+ outlined
89
+ onClick={() => setCloneDialogVisible(true)}
90
+ tooltip="Cloner l'activité"
91
+ tooltipOptions={{
92
+ position: "bottom",
93
+ showDelay: settings.TOOLTIP_SHOW_DELAY,
94
+ }}
95
+ />
96
+ <CloneDialog
97
+ visible={cloneDialogVisible}
98
+ onHide={() => setCloneDialogVisible(false)}
99
+ />
100
+ </>
101
+ )}
79
102
 
80
103
  {mode === "create" && sharingInfo.code && (
81
104
  <ButtonGroup>
@@ -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}
@@ -37,7 +37,11 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
37
37
  (hasGradingOrComments || mode === "review");
38
38
  const isHorizontal = useAppSelector(selectOrientation) === "horizontal";
39
39
  const mayReverse = isHorizontal
40
- ? (tab: Array<any>) => tab.toReversed()
40
+ ? (tab: Array<any>) => {
41
+ const reversed = [...tab];
42
+ reversed.reverse();
43
+ return reversed;
44
+ }
41
45
  : (tab: Array<any>) => tab;
42
46
 
43
47
  if (!hasInstructions) {
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,
@@ -0,0 +1,11 @@
1
+ import httpClient from '@capytale/activity.js/backend/capytale/http';
2
+
3
+ // Definit le endpoint de l'API
4
+ const myActivitiesApiEp = '/web/c-hdls/api/my-activities';
5
+
6
+ export async function apiCloneActivity(nid: number | string, title?: string) {
7
+ return httpClient.postGetJsonAsync<any>(
8
+ myActivitiesApiEp,
9
+ { action: 'clone', nid, title },
10
+ )
11
+ }
package/tsconfig.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ESNext",
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
4
5
  "useDefineForClassFields": true,
5
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
- "allowJs": false,
7
- "skipLibCheck": true,
8
- "esModuleInterop": false,
9
- "allowSyntheticDefaultImports": true,
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
10
7
  "module": "ESNext",
8
+ "skipLibCheck": true,
9
+ "allowJs": false,
10
+
11
11
  "moduleResolution": "bundler",
12
- "resolveJsonModule": true,
12
+ "allowSyntheticDefaultImports": true,
13
13
  "isolatedModules": true,
14
+ "esModuleInterop": false,
15
+ "resolveJsonModule": true,
14
16
  "noEmit": true,
15
17
  "jsx": "react-jsx",
16
18