@capytale/meta-player 0.0.1
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/.eslintrc.json +35 -0
- package/.prettierrc.json +4 -0
- package/README.md +27 -0
- package/index.html +15 -0
- package/package.json +48 -0
- package/public/themes/lara-dark-blue/fonts/Inter-italic.var.woff2 +0 -0
- package/public/themes/lara-dark-blue/fonts/Inter-roman.var.woff2 +0 -0
- package/public/themes/lara-dark-blue/theme.css +7015 -0
- package/public/themes/lara-light-blue/fonts/Inter-italic.var.woff2 +0 -0
- package/public/themes/lara-light-blue/fonts/Inter-roman.var.woff2 +0 -0
- package/public/themes/lara-light-blue/theme.css +7005 -0
- package/src/App.tsx +116 -0
- package/src/AppRedux.css +39 -0
- package/src/MetaPlayer.tsx +51 -0
- package/src/app/createAppSlice.ts +6 -0
- package/src/app/hooks.ts +12 -0
- package/src/app/store.ts +46 -0
- package/src/app.module.scss +56 -0
- package/src/demo.tsx +81 -0
- package/src/features/activityData/IsDirtySetter.tsx +17 -0
- package/src/features/activityData/OptionSetter.tsx +35 -0
- package/src/features/activityData/activityDataSlice.ts +250 -0
- package/src/features/activityData/metaPlayerOptions.ts +17 -0
- package/src/features/activityJS/ActivityJSProvider.tsx +110 -0
- package/src/features/activityJS/AfterSaveAction.tsx +23 -0
- package/src/features/activityJS/BeforeSaveAction.tsx +23 -0
- package/src/features/activityJS/Saver.tsx +147 -0
- package/src/features/activityJS/hooks.ts +93 -0
- package/src/features/activityJS/saverSlice.ts +58 -0
- package/src/features/layout/layoutSlice.ts +76 -0
- package/src/features/navbar/ActivityInfo.tsx +41 -0
- package/src/features/navbar/ActivityMenu.tsx +56 -0
- package/src/features/navbar/ActivityQuickActions.tsx +52 -0
- package/src/features/navbar/ActivitySidebarActions.tsx +52 -0
- package/src/features/navbar/CapytaleMenu.tsx +183 -0
- package/src/features/navbar/GradingNav.tsx +120 -0
- package/src/features/navbar/ReviewNavbar.tsx +18 -0
- package/src/features/navbar/SidebarContent.tsx +125 -0
- package/src/features/navbar/index.tsx +33 -0
- package/src/features/navbar/navbarSlice.ts +51 -0
- package/src/features/navbar/student-utils.ts +11 -0
- package/src/features/navbar/style.module.scss +162 -0
- package/src/features/pedago/AnswerSheetEditor.tsx +65 -0
- package/src/features/pedago/InstructionsEditor.tsx +82 -0
- package/src/features/pedago/index.tsx +219 -0
- package/src/features/pedago/style.module.scss +104 -0
- package/src/features/theming/ThemeSwitcher.tsx +51 -0
- package/src/features/theming/themingSlice.ts +93 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.css +30 -0
- package/src/index.tsx +6 -0
- package/src/logo.svg +1 -0
- package/src/my_json_data.js +4146 -0
- package/src/settings.ts +6 -0
- package/src/setupTests.ts +1 -0
- package/src/utils/ErrorBoundary.tsx +41 -0
- package/src/utils/PopupButton.tsx +135 -0
- package/src/utils/activity.ts +8 -0
- package/src/utils/breakpoints.ts +5 -0
- package/src/utils/clipboard.ts +11 -0
- package/src/utils/test-utils.tsx +65 -0
- package/src/utils/useFullscreen.ts +65 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +9 -0
- package/vite.config.ts +17 -0
package/src/App.tsx
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Splitter, SplitterPanel } from "primereact/splitter";
|
|
2
|
+
import Navbar from "./features/navbar";
|
|
3
|
+
|
|
4
|
+
import styles from "./app.module.scss";
|
|
5
|
+
import Pedago from "./features/pedago";
|
|
6
|
+
import { useAppDispatch, useAppSelector } from "./app/hooks";
|
|
7
|
+
import { selectThemeIsDark } from "./features/theming/themingSlice";
|
|
8
|
+
|
|
9
|
+
import { Ripple } from "primereact/ripple";
|
|
10
|
+
import { classNames } from "primereact/utils";
|
|
11
|
+
import {
|
|
12
|
+
selectIsPedagoVisible,
|
|
13
|
+
selectOrientation,
|
|
14
|
+
toggleIsPedagoVisible,
|
|
15
|
+
} from "./features/layout/layoutSlice";
|
|
16
|
+
import { FC, PropsWithChildren } from "react";
|
|
17
|
+
import { Tooltip } from "primereact/tooltip";
|
|
18
|
+
import {
|
|
19
|
+
selectHasGradingOrComments,
|
|
20
|
+
selectHasInstructions,
|
|
21
|
+
selectMode,
|
|
22
|
+
} from "./features/activityData/activityDataSlice";
|
|
23
|
+
import settings from "./settings";
|
|
24
|
+
import ReviewNavbar from "./features/navbar/ReviewNavbar";
|
|
25
|
+
|
|
26
|
+
type AppProps = PropsWithChildren<{}>;
|
|
27
|
+
|
|
28
|
+
const App: FC<AppProps> = (props) => {
|
|
29
|
+
const isDark = useAppSelector(selectThemeIsDark) as boolean;
|
|
30
|
+
const mode = useAppSelector(selectMode);
|
|
31
|
+
const isHorizontal = useAppSelector(selectOrientation) === "horizontal";
|
|
32
|
+
const isPedagoVisible = useAppSelector(selectIsPedagoVisible) as boolean;
|
|
33
|
+
const hasInstructions = useAppSelector(selectHasInstructions);
|
|
34
|
+
const hasGradingOrComments = useAppSelector(selectHasGradingOrComments);
|
|
35
|
+
const hasPedago = hasInstructions || hasGradingOrComments;
|
|
36
|
+
const dispatch = useAppDispatch();
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={classNames(
|
|
40
|
+
styles.app,
|
|
41
|
+
isDark ? "dark-theme" : "light-theme",
|
|
42
|
+
isHorizontal ? "layout-horizontal" : "layout-vertical",
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
<div>
|
|
46
|
+
<Navbar />
|
|
47
|
+
{mode === "review" && <ReviewNavbar />}
|
|
48
|
+
</div>
|
|
49
|
+
{hasPedago && isPedagoVisible && (
|
|
50
|
+
<Splitter
|
|
51
|
+
className={styles.appPedagoSplitter}
|
|
52
|
+
layout={isHorizontal ? "vertical" : "horizontal"}
|
|
53
|
+
>
|
|
54
|
+
<SplitterPanel minSize={15} size={30} className={styles.pedagoPanel}>
|
|
55
|
+
<Pedago key="pedago" />
|
|
56
|
+
</SplitterPanel>
|
|
57
|
+
<SplitterPanel minSize={40} size={70}>
|
|
58
|
+
{props.children}
|
|
59
|
+
</SplitterPanel>
|
|
60
|
+
</Splitter>
|
|
61
|
+
)}
|
|
62
|
+
{(!hasPedago || !isPedagoVisible) && (
|
|
63
|
+
<div className={styles.hiddenPedagoContainer}>
|
|
64
|
+
<div
|
|
65
|
+
className={classNames(
|
|
66
|
+
styles.hiddenPedago,
|
|
67
|
+
hasPedago ? null : styles.noPedago,
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
<div
|
|
71
|
+
className={classNames(
|
|
72
|
+
styles.hiddenPedagoButton,
|
|
73
|
+
hasPedago ? "p-ripple" : null,
|
|
74
|
+
)}
|
|
75
|
+
onClick={
|
|
76
|
+
hasPedago ? () => dispatch(toggleIsPedagoVisible()) : undefined
|
|
77
|
+
}
|
|
78
|
+
data-pr-tooltip={
|
|
79
|
+
hasPedago
|
|
80
|
+
? "Afficher les consignes"
|
|
81
|
+
: "Pas de consignes ni de note"
|
|
82
|
+
}
|
|
83
|
+
aria-label={
|
|
84
|
+
hasPedago
|
|
85
|
+
? "Afficher les consignes"
|
|
86
|
+
: "Pas de consignes ni de note"
|
|
87
|
+
}
|
|
88
|
+
role={hasPedago ? "button" : "note"}
|
|
89
|
+
>
|
|
90
|
+
<i
|
|
91
|
+
className={classNames(
|
|
92
|
+
"pi",
|
|
93
|
+
hasPedago
|
|
94
|
+
? isHorizontal
|
|
95
|
+
? "pi-angle-double-down"
|
|
96
|
+
: "pi-angle-double-right"
|
|
97
|
+
: "pi-minus-circle",
|
|
98
|
+
)}
|
|
99
|
+
/>
|
|
100
|
+
<Ripple />
|
|
101
|
+
</div>
|
|
102
|
+
<Tooltip
|
|
103
|
+
target={"." + styles.hiddenPedagoButton}
|
|
104
|
+
showDelay={settings.TOOLTIP_SHOW_DELAY}
|
|
105
|
+
position={isHorizontal ? "bottom" : "right"}
|
|
106
|
+
mouseTrack={!isHorizontal}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
<div className={styles.hiddenPedagoContent}>{props.children}</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default App;
|
package/src/AppRedux.css
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.App {
|
|
2
|
+
text-align: center;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.App-logo {
|
|
6
|
+
height: 40vmin;
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
11
|
+
.App-logo {
|
|
12
|
+
animation: App-logo-float infinite 3s ease-in-out;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.App-header {
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
font-size: calc(10px + 2vmin);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.App-link {
|
|
26
|
+
color: rgb(112, 76, 182);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@keyframes App-logo-float {
|
|
30
|
+
0% {
|
|
31
|
+
transform: translateY(0);
|
|
32
|
+
}
|
|
33
|
+
50% {
|
|
34
|
+
transform: translateY(10px);
|
|
35
|
+
}
|
|
36
|
+
100% {
|
|
37
|
+
transform: translateY(0px);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, { PropsWithChildren, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { Provider } from "react-redux";
|
|
4
|
+
|
|
5
|
+
import { APIOptions, PrimeReactProvider } from "primereact/api";
|
|
6
|
+
import "primeicons/primeicons.css";
|
|
7
|
+
|
|
8
|
+
import App from "./App";
|
|
9
|
+
import { store } from "./app/store";
|
|
10
|
+
import "./index.css";
|
|
11
|
+
import ThemeSwitcher from "./features/theming/ThemeSwitcher";
|
|
12
|
+
import { ActivityJSProvider } from "./features/activityJS/ActivityJSProvider";
|
|
13
|
+
import { LoadOptions } from "./features/activityJS/hooks";
|
|
14
|
+
import { ErrorBoundary } from "./utils/ErrorBoundary";
|
|
15
|
+
import {
|
|
16
|
+
MetaPlayerOptions,
|
|
17
|
+
defaultMetaPlayerOptions,
|
|
18
|
+
} from "./features/activityData/metaPlayerOptions";
|
|
19
|
+
import OptionSetter from "./features/activityData/OptionSetter";
|
|
20
|
+
import Saver from "./features/activityJS/Saver";
|
|
21
|
+
|
|
22
|
+
type MetaPlayerProps = PropsWithChildren<{
|
|
23
|
+
activityJSOptions?: LoadOptions;
|
|
24
|
+
options?: Partial<MetaPlayerOptions>;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
const MetaPlayer: React.FC<MetaPlayerProps> = (props) => {
|
|
28
|
+
const primeSettings: Partial<APIOptions> = {
|
|
29
|
+
ripple: true,
|
|
30
|
+
};
|
|
31
|
+
const options = useMemo(
|
|
32
|
+
() => ({ ...defaultMetaPlayerOptions, ...props.options }),
|
|
33
|
+
[props.options],
|
|
34
|
+
);
|
|
35
|
+
return (
|
|
36
|
+
<PrimeReactProvider value={primeSettings}>
|
|
37
|
+
<Provider store={store}>
|
|
38
|
+
<OptionSetter options={options} />
|
|
39
|
+
<ThemeSwitcher />
|
|
40
|
+
<ErrorBoundary fallback={<div>Une erreur est survenue</div>}>
|
|
41
|
+
<ActivityJSProvider options={props.activityJSOptions}>
|
|
42
|
+
<Saver />
|
|
43
|
+
<App>{props.children}</App>
|
|
44
|
+
</ActivityJSProvider>
|
|
45
|
+
</ErrorBoundary>
|
|
46
|
+
</Provider>
|
|
47
|
+
</PrimeReactProvider>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default MetaPlayer;
|
package/src/app/hooks.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This file serves as a central hub for re-exporting pre-typed Redux hooks.
|
|
2
|
+
// These imports are restricted elsewhere to ensure consistent
|
|
3
|
+
// usage of typed hooks throughout the application.
|
|
4
|
+
// We disable the ESLint rule here because this is the designated place
|
|
5
|
+
// for importing and re-exporting the typed versions of hooks.
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-restricted-imports */
|
|
7
|
+
import { useDispatch, useSelector } from "react-redux"
|
|
8
|
+
import type { AppDispatch, RootState } from "./store"
|
|
9
|
+
|
|
10
|
+
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
|
11
|
+
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
|
12
|
+
export const useAppSelector = useSelector.withTypes<RootState>()
|
package/src/app/store.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Action, ThunkAction } from "@reduxjs/toolkit";
|
|
2
|
+
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
|
3
|
+
import { setupListeners } from "@reduxjs/toolkit/query";
|
|
4
|
+
import { themingSlice } from "../features/theming/themingSlice";
|
|
5
|
+
import { layoutSlice } from "../features/layout/layoutSlice";
|
|
6
|
+
import { activityDataSlice } from "../features/activityData/activityDataSlice";
|
|
7
|
+
import { navbarSlice } from "../features/navbar/navbarSlice";
|
|
8
|
+
import { saverSlice } from "../features/activityJS/saverSlice";
|
|
9
|
+
|
|
10
|
+
// `combineSlices` automatically combines the reducers using
|
|
11
|
+
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
|
12
|
+
const rootReducer = combineSlices(
|
|
13
|
+
activityDataSlice,
|
|
14
|
+
themingSlice,
|
|
15
|
+
layoutSlice,
|
|
16
|
+
navbarSlice,
|
|
17
|
+
saverSlice,
|
|
18
|
+
);
|
|
19
|
+
// Infer the `RootState` type from the root reducer
|
|
20
|
+
export type RootState = ReturnType<typeof rootReducer>;
|
|
21
|
+
|
|
22
|
+
// The store setup is wrapped in `makeStore` to allow reuse
|
|
23
|
+
// when setting up tests that need the same store config
|
|
24
|
+
export const makeStore = (preloadedState?: Partial<RootState>) => {
|
|
25
|
+
const store = configureStore({
|
|
26
|
+
reducer: rootReducer,
|
|
27
|
+
preloadedState,
|
|
28
|
+
});
|
|
29
|
+
// configure listeners using the provided defaults
|
|
30
|
+
// optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
|
|
31
|
+
setupListeners(store.dispatch);
|
|
32
|
+
return store;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const store = makeStore();
|
|
36
|
+
|
|
37
|
+
// Infer the type of `store`
|
|
38
|
+
export type AppStore = typeof store;
|
|
39
|
+
// Infer the `AppDispatch` type from the store itself
|
|
40
|
+
export type AppDispatch = AppStore["dispatch"];
|
|
41
|
+
export type AppThunk<ThunkReturnType = void> = ThunkAction<
|
|
42
|
+
ThunkReturnType,
|
|
43
|
+
RootState,
|
|
44
|
+
unknown,
|
|
45
|
+
Action
|
|
46
|
+
>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
.app {
|
|
2
|
+
height: 100%;
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-rows: auto 1fr;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.appPedagoSplitter {
|
|
8
|
+
height: 100%;
|
|
9
|
+
border: none;
|
|
10
|
+
border-radius: 0;
|
|
11
|
+
overflow-y: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.pedagoPanel {
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
overflow-y: hidden;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.hiddenPedagoContainer {
|
|
21
|
+
display: grid;
|
|
22
|
+
:global(.layout-horizontal) & {
|
|
23
|
+
grid-template-columns: 1fr;
|
|
24
|
+
grid-template-rows: 1.2rem 1fr;
|
|
25
|
+
}
|
|
26
|
+
:global(.layout-vertical) & {
|
|
27
|
+
grid-template-rows: 1fr;
|
|
28
|
+
grid-template-columns: 1.2rem 1fr;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.hiddenPedago {
|
|
33
|
+
background-color: var(--surface-200);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.hiddenPedagoButton {
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
display: flex;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
align-items: center;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
& > i {
|
|
44
|
+
font-size: 1rem;
|
|
45
|
+
}
|
|
46
|
+
&:hover {
|
|
47
|
+
background-color: rgba(128, 128, 128, 0.05);
|
|
48
|
+
}
|
|
49
|
+
&:active {
|
|
50
|
+
background-color: rgba(128, 128, 128, 0.2);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.hiddenPedagoContent {
|
|
55
|
+
background-color: var(--surface-a);
|
|
56
|
+
}
|
package/src/demo.tsx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
import { MetaPlayer } from ".";
|
|
5
|
+
import { ActivityQuickActionsSetter } from "./features/navbar/ActivityQuickActions";
|
|
6
|
+
import { ActivitySidebarActionsSetter } from "./features/navbar/ActivitySidebarActions";
|
|
7
|
+
// import { useActivityJS } from "./features/activityJS/ActivityJSProvider";
|
|
8
|
+
import BeforeSaveAction from "./features/activityJS/BeforeSaveAction";
|
|
9
|
+
|
|
10
|
+
const DemoActivity: FC = () => {
|
|
11
|
+
// const activityJs = useActivityJS();
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<ActivityQuickActionsSetter
|
|
15
|
+
actions={[
|
|
16
|
+
{
|
|
17
|
+
title: "Pause",
|
|
18
|
+
icon: "pi pi-pause",
|
|
19
|
+
action: () => console.log("Pause"),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
title: "Play",
|
|
23
|
+
icon: "pi pi-play",
|
|
24
|
+
action: () => console.log("Play"),
|
|
25
|
+
},
|
|
26
|
+
]}
|
|
27
|
+
/>
|
|
28
|
+
<ActivitySidebarActionsSetter
|
|
29
|
+
actions={[
|
|
30
|
+
{
|
|
31
|
+
title: "Pause",
|
|
32
|
+
icon: "pi pi-pause",
|
|
33
|
+
action: () => console.log("Pause"),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: "Play",
|
|
37
|
+
icon: "pi pi-play",
|
|
38
|
+
action: () => console.log("Play"),
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
<BeforeSaveAction
|
|
43
|
+
name="demo"
|
|
44
|
+
callback={() => console.log("Before save")}
|
|
45
|
+
/>
|
|
46
|
+
<BeforeSaveAction
|
|
47
|
+
name="demo2"
|
|
48
|
+
callback={() => {
|
|
49
|
+
console.log("Before save 2");
|
|
50
|
+
// throw new Error("Error in before save 2");
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
Activité
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const container = document.getElementById("root");
|
|
59
|
+
|
|
60
|
+
if (container) {
|
|
61
|
+
const root = createRoot(container);
|
|
62
|
+
|
|
63
|
+
root.render(
|
|
64
|
+
<React.StrictMode>
|
|
65
|
+
<MetaPlayer
|
|
66
|
+
options={{
|
|
67
|
+
hasInstructions: true,
|
|
68
|
+
pedagoLayout: "default-horizontal",
|
|
69
|
+
supportsLightTheme: true,
|
|
70
|
+
supportsDarkTheme: true,
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<DemoActivity />
|
|
74
|
+
</MetaPlayer>
|
|
75
|
+
</React.StrictMode>,
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FC, useEffect } from "react";
|
|
2
|
+
import { setIsDirty } from "./activityDataSlice";
|
|
3
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
4
|
+
|
|
5
|
+
type IsDirtySetterProps = {
|
|
6
|
+
isDirty: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const IsDirtySetter: FC<IsDirtySetterProps> = (props) => {
|
|
10
|
+
const dispatch = useAppDispatch();
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
dispatch(setIsDirty(props.isDirty));
|
|
13
|
+
}, [props]);
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default IsDirtySetter;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
3
|
+
import { setPlayerSettings } from "./activityDataSlice";
|
|
4
|
+
import { setLayout } from "../layout/layoutSlice";
|
|
5
|
+
import { setAutoSwitch, switchToTheme } from "../theming/themingSlice";
|
|
6
|
+
import { MetaPlayerOptions } from "./metaPlayerOptions";
|
|
7
|
+
|
|
8
|
+
type OptionSetterProps = {
|
|
9
|
+
options: MetaPlayerOptions;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const OptionSetter: FC<OptionSetterProps> = ({ options }) => {
|
|
13
|
+
const dispatch = useAppDispatch();
|
|
14
|
+
if (!options.supportsLightTheme && !options.supportsDarkTheme) {
|
|
15
|
+
throw new Error("At least one theme must be supported");
|
|
16
|
+
}
|
|
17
|
+
dispatch(setPlayerSettings(options));
|
|
18
|
+
dispatch(
|
|
19
|
+
setLayout(
|
|
20
|
+
options.pedagoLayout === "horizontal" ||
|
|
21
|
+
options.pedagoLayout === "default-horizontal"
|
|
22
|
+
? "horizontal"
|
|
23
|
+
: "vertical",
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
if (options.supportsDarkTheme && options.supportsLightTheme) {
|
|
27
|
+
dispatch(setAutoSwitch(true));
|
|
28
|
+
} else {
|
|
29
|
+
dispatch(setAutoSwitch(false));
|
|
30
|
+
dispatch(switchToTheme(options.supportsLightTheme ? "light" : "dark"));
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default OptionSetter;
|