@capytale/meta-player 0.0.3 → 0.1.0
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/App.tsx +12 -15
- package/src/MetaPlayer.tsx +1 -12
- package/src/app/store.ts +2 -0
- package/src/app.module.scss +1 -0
- package/src/demo.tsx +10 -8
- package/src/external/prime.ts +5 -0
- package/src/features/activityData/IsDirtySetter.tsx +0 -6
- package/src/features/activityData/MetaPlayerOptionsSetter.tsx +41 -0
- package/src/features/activityData/hooks.ts +9 -0
- package/src/features/activityJS/Saver.tsx +0 -1
- package/src/features/activitySettings/ActivitySettingsSetter.tsx +26 -0
- package/src/features/activitySettings/activitySettingsSlice.ts +43 -0
- package/src/features/activitySettings/hooks.ts +6 -0
- package/src/features/activitySettings/index.tsx +32 -0
- package/src/features/activitySettings/style.module.scss +4 -0
- package/src/features/activitySettings/types.ts +88 -0
- package/src/features/activitySettings/ui.tsx +393 -0
- package/src/features/navbar/CapytaleMenu.tsx +0 -1
- package/src/features/navbar/GradingNav.tsx +0 -1
- package/src/features/navbar/SidebarContent.tsx +12 -10
- package/src/features/navbar/style.module.scss +0 -16
- package/src/features/pedago/index.tsx +0 -2
- package/src/index.css +37 -0
- package/src/utils/equality.ts +32 -0
- package/src/features/activityData/OptionSetter.tsx +0 -35
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -94,21 +94,18 @@ const App: FC<AppProps> = (props) => {
|
|
|
94
94
|
mouseTrack={!isHorizontal}
|
|
95
95
|
/>
|
|
96
96
|
</div>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<div id="meta-player-content">{props.children}</div>
|
|
110
|
-
</SplitterPanel>
|
|
111
|
-
</Splitter>
|
|
97
|
+
<Splitter
|
|
98
|
+
className={styles.appPedagoSplitter}
|
|
99
|
+
layout={isHorizontal ? "vertical" : "horizontal"}
|
|
100
|
+
>
|
|
101
|
+
<SplitterPanel minSize={15} size={30} className={styles.pedagoPanel}>
|
|
102
|
+
<Pedago key="pedago" />
|
|
103
|
+
</SplitterPanel>
|
|
104
|
+
<SplitterPanel minSize={40} size={70}>
|
|
105
|
+
<div id="meta-player-content-cover"></div>
|
|
106
|
+
<div id="meta-player-content">{props.children}</div>
|
|
107
|
+
</SplitterPanel>
|
|
108
|
+
</Splitter>
|
|
112
109
|
</div>
|
|
113
110
|
</div>
|
|
114
111
|
);
|
package/src/MetaPlayer.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { PropsWithChildren
|
|
1
|
+
import React, { PropsWithChildren } from "react";
|
|
2
2
|
|
|
3
3
|
import { Provider } from "react-redux";
|
|
4
4
|
|
|
@@ -12,30 +12,19 @@ import ThemeSwitcher from "./features/theming/ThemeSwitcher";
|
|
|
12
12
|
import { ActivityJSProvider } from "./features/activityJS/ActivityJSProvider";
|
|
13
13
|
import { LoadOptions } from "./features/activityJS/hooks";
|
|
14
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
15
|
import Saver from "./features/activityJS/Saver";
|
|
21
16
|
|
|
22
17
|
type MetaPlayerProps = PropsWithChildren<{
|
|
23
18
|
activityJSOptions?: LoadOptions;
|
|
24
|
-
options?: Partial<MetaPlayerOptions>;
|
|
25
19
|
}>;
|
|
26
20
|
|
|
27
21
|
const MetaPlayer: React.FC<MetaPlayerProps> = (props) => {
|
|
28
22
|
const primeSettings: Partial<APIOptions> = {
|
|
29
23
|
ripple: true,
|
|
30
24
|
};
|
|
31
|
-
const options = useMemo(
|
|
32
|
-
() => ({ ...defaultMetaPlayerOptions, ...props.options }),
|
|
33
|
-
[props.options],
|
|
34
|
-
);
|
|
35
25
|
return (
|
|
36
26
|
<PrimeReactProvider value={primeSettings}>
|
|
37
27
|
<Provider store={store}>
|
|
38
|
-
<OptionSetter options={options} />
|
|
39
28
|
<ThemeSwitcher />
|
|
40
29
|
<ErrorBoundary fallback={<div>Une erreur est survenue</div>}>
|
|
41
30
|
<ActivityJSProvider options={props.activityJSOptions}>
|
package/src/app/store.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { layoutSlice } from "../features/layout/layoutSlice";
|
|
|
6
6
|
import { activityDataSlice } from "../features/activityData/activityDataSlice";
|
|
7
7
|
import { navbarSlice } from "../features/navbar/navbarSlice";
|
|
8
8
|
import { saverSlice } from "../features/activityJS/saverSlice";
|
|
9
|
+
import { activitySettingsSlice } from "../features/activitySettings/activitySettingsSlice";
|
|
9
10
|
|
|
10
11
|
// `combineSlices` automatically combines the reducers using
|
|
11
12
|
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
|
@@ -15,6 +16,7 @@ const rootReducer = combineSlices(
|
|
|
15
16
|
layoutSlice,
|
|
16
17
|
navbarSlice,
|
|
17
18
|
saverSlice,
|
|
19
|
+
activitySettingsSlice,
|
|
18
20
|
);
|
|
19
21
|
// Infer the `RootState` type from the root reducer
|
|
20
22
|
export type RootState = ReturnType<typeof rootReducer>;
|
package/src/app.module.scss
CHANGED
package/src/demo.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { ActivityQuickActionsSetter } from "./features/navbar/ActivityQuickActio
|
|
|
6
6
|
import { ActivitySidebarActionsSetter } from "./features/navbar/ActivitySidebarActions";
|
|
7
7
|
// import { useActivityJS } from "./features/activityJS/ActivityJSProvider";
|
|
8
8
|
import BeforeSaveAction from "./features/activityJS/BeforeSaveAction";
|
|
9
|
+
import MetaPlayerOptionsSetter from "./features/activityData/MetaPlayerOptionsSetter";
|
|
9
10
|
|
|
10
11
|
const DemoActivity: FC = () => {
|
|
11
12
|
// const activityJs = useActivityJS();
|
|
@@ -62,14 +63,15 @@ if (container) {
|
|
|
62
63
|
|
|
63
64
|
root.render(
|
|
64
65
|
<React.StrictMode>
|
|
65
|
-
<MetaPlayer
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
<MetaPlayer>
|
|
67
|
+
<MetaPlayerOptionsSetter
|
|
68
|
+
options={{
|
|
69
|
+
hasInstructions: true,
|
|
70
|
+
pedagoLayout: "default-horizontal",
|
|
71
|
+
supportsLightTheme: true,
|
|
72
|
+
supportsDarkTheme: true,
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
73
75
|
<DemoActivity />
|
|
74
76
|
</MetaPlayer>
|
|
75
77
|
</React.StrictMode>,
|
|
@@ -14,10 +14,4 @@ const IsDirtySetter: FC<IsDirtySetterProps> = (props) => {
|
|
|
14
14
|
return null;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
const notifyIsDirty = (isDirty: boolean = true) => {
|
|
18
|
-
const dispatch = useAppDispatch();
|
|
19
|
-
dispatch(setIsPlayerDirty(isDirty));
|
|
20
|
-
};
|
|
21
|
-
|
|
22
17
|
export default IsDirtySetter;
|
|
23
|
-
export { notifyIsDirty };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FC, useEffect } 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 {
|
|
7
|
+
defaultMetaPlayerOptions,
|
|
8
|
+
MetaPlayerOptions,
|
|
9
|
+
} from "./metaPlayerOptions";
|
|
10
|
+
|
|
11
|
+
type MetaPlayerOptionsSetterProps = {
|
|
12
|
+
options: Partial<MetaPlayerOptions>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const MetaPlayerOptionsSetter: FC<MetaPlayerOptionsSetterProps> = (props) => {
|
|
16
|
+
const dispatch = useAppDispatch();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const options = { ...defaultMetaPlayerOptions, ...props.options };
|
|
19
|
+
if (!options.supportsLightTheme && !options.supportsDarkTheme) {
|
|
20
|
+
throw new Error("At least one theme must be supported");
|
|
21
|
+
}
|
|
22
|
+
dispatch(setPlayerSettings(options));
|
|
23
|
+
dispatch(
|
|
24
|
+
setLayout(
|
|
25
|
+
options.pedagoLayout === "horizontal" ||
|
|
26
|
+
options.pedagoLayout === "default-horizontal"
|
|
27
|
+
? "horizontal"
|
|
28
|
+
: "vertical",
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
if (options.supportsDarkTheme && options.supportsLightTheme) {
|
|
32
|
+
dispatch(setAutoSwitch(true));
|
|
33
|
+
} else {
|
|
34
|
+
dispatch(setAutoSwitch(false));
|
|
35
|
+
dispatch(switchToTheme(options.supportsLightTheme ? "light" : "dark"));
|
|
36
|
+
}
|
|
37
|
+
}, [dispatch, props.options]);
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default MetaPlayerOptionsSetter;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
2
|
+
import { setIsPlayerDirty } from "./activityDataSlice";
|
|
3
|
+
|
|
4
|
+
const useNotifyIsDirty = (isDirty: boolean = true) => {
|
|
5
|
+
const dispatch = useAppDispatch();
|
|
6
|
+
return () => dispatch(setIsPlayerDirty(isDirty));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export { useNotifyIsDirty };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FC, useEffect, useRef } from "react";
|
|
2
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
3
|
+
import { ActivitySettings } from "./types";
|
|
4
|
+
import { setSettings } from "./activitySettingsSlice";
|
|
5
|
+
import { deepEqual } from "../../utils/equality";
|
|
6
|
+
|
|
7
|
+
type ActivitySettingsSetterProps = {
|
|
8
|
+
settings: ActivitySettings;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const ActivitySettingsSetter: FC<ActivitySettingsSetterProps> = ({
|
|
12
|
+
settings,
|
|
13
|
+
}) => {
|
|
14
|
+
const oldSettings = useRef<ActivitySettings | null>(null);
|
|
15
|
+
const dispatch = useAppDispatch();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (deepEqual(oldSettings.current, settings)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
oldSettings.current = settings;
|
|
21
|
+
dispatch(setSettings(settings));
|
|
22
|
+
}, [dispatch, settings]);
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default ActivitySettingsSetter;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
2
|
+
import { createAppSlice } from "../../app/createAppSlice";
|
|
3
|
+
import { ActivitySettings } from "./types";
|
|
4
|
+
|
|
5
|
+
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
6
|
+
export const activitySettingsSlice = createAppSlice({
|
|
7
|
+
name: "activitySettings",
|
|
8
|
+
// `createSlice` will infer the state type from the `initialState` argument
|
|
9
|
+
initialState: {
|
|
10
|
+
settings: undefined as ActivitySettings | undefined,
|
|
11
|
+
},
|
|
12
|
+
// The `reducers` field lets us define reducers and generate associated actions
|
|
13
|
+
reducers: (create) => ({
|
|
14
|
+
setSettings: create.reducer(
|
|
15
|
+
(state, action: PayloadAction<ActivitySettings | undefined>) => {
|
|
16
|
+
state.settings = action.payload;
|
|
17
|
+
},
|
|
18
|
+
),
|
|
19
|
+
updateSettings: create.reducer(
|
|
20
|
+
(
|
|
21
|
+
state,
|
|
22
|
+
action: PayloadAction<
|
|
23
|
+
(
|
|
24
|
+
oldSettings: ActivitySettings | undefined,
|
|
25
|
+
) => ActivitySettings | undefined
|
|
26
|
+
>,
|
|
27
|
+
) => {
|
|
28
|
+
state.settings = action.payload(state.settings);
|
|
29
|
+
},
|
|
30
|
+
),
|
|
31
|
+
}),
|
|
32
|
+
// You can define your selectors here. These selectors receive the slice
|
|
33
|
+
// state as their first argument.
|
|
34
|
+
selectors: {
|
|
35
|
+
selectSettings: (data) => data.settings,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Action creators are generated for each case reducer function.
|
|
40
|
+
export const { setSettings, updateSettings } = activitySettingsSlice.actions;
|
|
41
|
+
|
|
42
|
+
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
43
|
+
export const { selectSettings } = activitySettingsSlice.selectors;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Fieldset } from "primereact/fieldset";
|
|
2
|
+
import { useActivitySettings } from "./hooks";
|
|
3
|
+
import {
|
|
4
|
+
ActivitySettingsFormDisplay,
|
|
5
|
+
ActivitySettingsOptionsDisplay,
|
|
6
|
+
} from "./ui";
|
|
7
|
+
|
|
8
|
+
type ActivitySettingsDisplayProps = {};
|
|
9
|
+
|
|
10
|
+
export function ActivitySettingsDisplay({}: ActivitySettingsDisplayProps) {
|
|
11
|
+
const settings = useActivitySettings();
|
|
12
|
+
if (!settings) return null;
|
|
13
|
+
return (
|
|
14
|
+
<div>
|
|
15
|
+
{Object.keys(settings).map((id) => {
|
|
16
|
+
const section = settings[id];
|
|
17
|
+
return (
|
|
18
|
+
<Fieldset key={id} legend={section.title} className="sidebarFieldset">
|
|
19
|
+
{section.type === "form" ? (
|
|
20
|
+
<ActivitySettingsFormDisplay form={section} sectionId={id} />
|
|
21
|
+
) : (
|
|
22
|
+
<ActivitySettingsOptionsDisplay
|
|
23
|
+
options={section}
|
|
24
|
+
sectionId={id}
|
|
25
|
+
/>
|
|
26
|
+
)}
|
|
27
|
+
</Fieldset>
|
|
28
|
+
);
|
|
29
|
+
})}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type ActivitySettingsSelect = {
|
|
2
|
+
type: "select";
|
|
3
|
+
options: {
|
|
4
|
+
label: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}[];
|
|
7
|
+
selectedOptionName: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ActivitySettingsRange = {
|
|
11
|
+
type: "range";
|
|
12
|
+
min: number;
|
|
13
|
+
max: number;
|
|
14
|
+
step?: number;
|
|
15
|
+
value: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ActivitySettingsTextArea = {
|
|
19
|
+
type: "textarea";
|
|
20
|
+
value: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ActivitySettingsOption = {
|
|
24
|
+
type: "checkbox" | "switch";
|
|
25
|
+
value: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ActivitySettingsInput = {
|
|
29
|
+
type: "input";
|
|
30
|
+
inputType:
|
|
31
|
+
| "color"
|
|
32
|
+
| "date"
|
|
33
|
+
| "datetime-local"
|
|
34
|
+
| "email"
|
|
35
|
+
| "month"
|
|
36
|
+
| "number"
|
|
37
|
+
| "password"
|
|
38
|
+
| "search"
|
|
39
|
+
| "tel"
|
|
40
|
+
| "text"
|
|
41
|
+
| "time"
|
|
42
|
+
| "url"
|
|
43
|
+
| "week";
|
|
44
|
+
value: string | number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ActivitySettingsFormSection = {
|
|
48
|
+
type: "form";
|
|
49
|
+
fields: {
|
|
50
|
+
[name: string]: (
|
|
51
|
+
| ActivitySettingsRange
|
|
52
|
+
| ActivitySettingsInput
|
|
53
|
+
| ActivitySettingsTextArea
|
|
54
|
+
| ActivitySettingsOption
|
|
55
|
+
| ActivitySettingsSelect
|
|
56
|
+
) & {
|
|
57
|
+
label: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type ActivitySettingsMultipleOptionsSection = {
|
|
63
|
+
type: "checkboxes" | "switches";
|
|
64
|
+
options: {
|
|
65
|
+
label: string;
|
|
66
|
+
name: string;
|
|
67
|
+
}[];
|
|
68
|
+
selectedOptionNames: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type ActivitySettingsRadioOptionsSection = {
|
|
72
|
+
type: "radio";
|
|
73
|
+
options: {
|
|
74
|
+
label: string;
|
|
75
|
+
name: string;
|
|
76
|
+
}[];
|
|
77
|
+
selectedOptionName: string | null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type ActivitySettingsSection = (
|
|
81
|
+
| ActivitySettingsMultipleOptionsSection
|
|
82
|
+
| ActivitySettingsRadioOptionsSection
|
|
83
|
+
| ActivitySettingsFormSection
|
|
84
|
+
) & {
|
|
85
|
+
title: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type ActivitySettings = { [id: string]: ActivitySettingsSection };
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActivitySettings,
|
|
3
|
+
ActivitySettingsFormSection,
|
|
4
|
+
ActivitySettingsMultipleOptionsSection,
|
|
5
|
+
ActivitySettingsRadioOptionsSection,
|
|
6
|
+
} from "./types";
|
|
7
|
+
|
|
8
|
+
import styles from "./style.module.scss";
|
|
9
|
+
import { Slider } from "primereact/slider";
|
|
10
|
+
import { InputNumber } from "primereact/inputnumber";
|
|
11
|
+
import { InputText } from "primereact/inputtext";
|
|
12
|
+
import { ColorPicker } from "primereact/colorpicker";
|
|
13
|
+
import { InputTextarea } from "primereact/inputtextarea";
|
|
14
|
+
import { Checkbox } from "primereact/checkbox";
|
|
15
|
+
import { InputSwitch } from "primereact/inputswitch";
|
|
16
|
+
import { Dropdown } from "primereact/dropdown";
|
|
17
|
+
import React from "react";
|
|
18
|
+
import { RadioButton } from "primereact/radiobutton";
|
|
19
|
+
import { useAppDispatch } from "../../app/hooks";
|
|
20
|
+
import { updateSettings } from "./activitySettingsSlice";
|
|
21
|
+
|
|
22
|
+
type ActivitySettingsFormDisplayProps = {
|
|
23
|
+
form: ActivitySettingsFormSection;
|
|
24
|
+
sectionId: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function ActivitySettingsFormDisplay({
|
|
28
|
+
form,
|
|
29
|
+
sectionId,
|
|
30
|
+
}: ActivitySettingsFormDisplayProps) {
|
|
31
|
+
const dispatch = useAppDispatch();
|
|
32
|
+
const selectOnChange = (fieldName: string, value: string) => {
|
|
33
|
+
dispatch(
|
|
34
|
+
updateSettings((oldSettings: ActivitySettings | undefined) => {
|
|
35
|
+
if (!oldSettings) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const oldSection = oldSettings[
|
|
39
|
+
sectionId
|
|
40
|
+
] as ActivitySettingsFormSection;
|
|
41
|
+
const newSettings = {
|
|
42
|
+
...oldSettings,
|
|
43
|
+
[sectionId]: {
|
|
44
|
+
...oldSection,
|
|
45
|
+
type: "form",
|
|
46
|
+
fields: {
|
|
47
|
+
...oldSection.fields,
|
|
48
|
+
[fieldName]: {
|
|
49
|
+
...oldSection.fields[fieldName],
|
|
50
|
+
selectedOptionName: value,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
return newSettings as ActivitySettings;
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
const onChange = (fieldName: string, value: string | number | boolean) => {
|
|
60
|
+
dispatch(
|
|
61
|
+
updateSettings((oldSettings: ActivitySettings | undefined) => {
|
|
62
|
+
if (!oldSettings) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const oldSection = oldSettings[
|
|
66
|
+
sectionId
|
|
67
|
+
] as ActivitySettingsFormSection;
|
|
68
|
+
const newSettings = {
|
|
69
|
+
...oldSettings,
|
|
70
|
+
[sectionId]: {
|
|
71
|
+
...oldSection,
|
|
72
|
+
type: "form",
|
|
73
|
+
fields: {
|
|
74
|
+
...oldSection.fields,
|
|
75
|
+
[fieldName]: {
|
|
76
|
+
...oldSection.fields[fieldName],
|
|
77
|
+
value,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
return newSettings as ActivitySettings;
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
return (
|
|
87
|
+
<div className="mb-3">
|
|
88
|
+
{Object.keys(form.fields).map((name, _) => {
|
|
89
|
+
const field = form.fields[name];
|
|
90
|
+
switch (field.type) {
|
|
91
|
+
case "range":
|
|
92
|
+
return (
|
|
93
|
+
<div className={styles.formGroup}>
|
|
94
|
+
<label
|
|
95
|
+
className={styles.formLabel}
|
|
96
|
+
htmlFor={sectionId + "-" + name}
|
|
97
|
+
>
|
|
98
|
+
Range
|
|
99
|
+
</label>
|
|
100
|
+
<InputNumber
|
|
101
|
+
min={field.min}
|
|
102
|
+
max={field.max}
|
|
103
|
+
step={field.step}
|
|
104
|
+
value={field.value}
|
|
105
|
+
onChange={(e) => onChange(name, e.value as number)}
|
|
106
|
+
/>
|
|
107
|
+
<Slider
|
|
108
|
+
id={sectionId + "-" + name}
|
|
109
|
+
min={field.min}
|
|
110
|
+
max={field.max}
|
|
111
|
+
step={field.step}
|
|
112
|
+
value={field.value}
|
|
113
|
+
onChange={(e) => onChange(name, e.value as number)}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
case "input":
|
|
118
|
+
return (
|
|
119
|
+
<div className={styles.formGroup}>
|
|
120
|
+
<label
|
|
121
|
+
className={styles.formLabel}
|
|
122
|
+
htmlFor={sectionId + "-" + name}
|
|
123
|
+
>
|
|
124
|
+
{field.label}
|
|
125
|
+
</label>
|
|
126
|
+
{field.inputType === "text" && (
|
|
127
|
+
<InputText
|
|
128
|
+
id={sectionId + "-" + name}
|
|
129
|
+
value={field.value as string}
|
|
130
|
+
onChange={(e) => onChange(name, e.target.value)}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
{field.inputType === "number" && (
|
|
134
|
+
<InputNumber
|
|
135
|
+
inputId={sectionId + "-" + name}
|
|
136
|
+
value={field.value as number}
|
|
137
|
+
onChange={(e) => e.value != null && onChange(name, e.value)}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
140
|
+
{field.inputType === "color" && (
|
|
141
|
+
<ColorPicker
|
|
142
|
+
inputId={sectionId + "-" + name}
|
|
143
|
+
value={field.value as string}
|
|
144
|
+
onChange={(e) =>
|
|
145
|
+
e.value && onChange(name, e.value as string)
|
|
146
|
+
}
|
|
147
|
+
/>
|
|
148
|
+
)}
|
|
149
|
+
{field.inputType !== "color" &&
|
|
150
|
+
field.inputType !== "number" &&
|
|
151
|
+
field.inputType !== "text" && (
|
|
152
|
+
<p>Type d'input {field.inputType} pas implémenté.</p>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
case "textarea":
|
|
157
|
+
return (
|
|
158
|
+
<div className={styles.formGroup}>
|
|
159
|
+
<label
|
|
160
|
+
className={styles.formLabel}
|
|
161
|
+
htmlFor={sectionId + "-" + name}
|
|
162
|
+
>
|
|
163
|
+
{field.label}
|
|
164
|
+
</label>
|
|
165
|
+
<InputTextarea
|
|
166
|
+
id={sectionId + "-" + name}
|
|
167
|
+
value={field.value}
|
|
168
|
+
onChange={(e) => onChange(name, e.target.value)}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
case "checkbox":
|
|
173
|
+
return (
|
|
174
|
+
<CheckboxGroup
|
|
175
|
+
inputId={sectionId + "-" + name}
|
|
176
|
+
checked={field.value}
|
|
177
|
+
onChange={(value) => onChange(name, value)}
|
|
178
|
+
label={field.label}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
case "switch":
|
|
182
|
+
return (
|
|
183
|
+
<SwitchGroup
|
|
184
|
+
inputId={sectionId + "-" + name}
|
|
185
|
+
checked={field.value}
|
|
186
|
+
onChange={(value) => onChange(name, value)}
|
|
187
|
+
label={field.label}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
case "select":
|
|
191
|
+
return (
|
|
192
|
+
<div className={styles.formGroup}>
|
|
193
|
+
<label
|
|
194
|
+
className={styles.formLabel}
|
|
195
|
+
htmlFor={sectionId + "-" + name}
|
|
196
|
+
>
|
|
197
|
+
{field.label}
|
|
198
|
+
</label>
|
|
199
|
+
<Dropdown
|
|
200
|
+
inputId={sectionId + "-" + name}
|
|
201
|
+
value={field.selectedOptionName}
|
|
202
|
+
onChange={(e) => selectOnChange(name, e.value)}
|
|
203
|
+
options={field.options}
|
|
204
|
+
optionLabel="label"
|
|
205
|
+
optionValue="name"
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
})}
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
type ActivitySettingsOptionsDisplayProps = {
|
|
216
|
+
options:
|
|
217
|
+
| ActivitySettingsMultipleOptionsSection
|
|
218
|
+
| ActivitySettingsRadioOptionsSection;
|
|
219
|
+
sectionId: string;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export function ActivitySettingsOptionsDisplay({
|
|
223
|
+
options,
|
|
224
|
+
sectionId,
|
|
225
|
+
}: ActivitySettingsOptionsDisplayProps) {
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
{options.type === "radio" ? (
|
|
229
|
+
<ActivitySettingsRadioDisplay options={options} sectionId={sectionId} />
|
|
230
|
+
) : (
|
|
231
|
+
<ActivitySettingsCheckboxesDisplay
|
|
232
|
+
options={options}
|
|
233
|
+
sectionId={sectionId}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
type ActivitySettingsCheckboxesDisplayProps = {
|
|
241
|
+
options: ActivitySettingsMultipleOptionsSection;
|
|
242
|
+
sectionId: string;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
function ActivitySettingsCheckboxesDisplay({
|
|
246
|
+
options,
|
|
247
|
+
sectionId,
|
|
248
|
+
}: ActivitySettingsCheckboxesDisplayProps) {
|
|
249
|
+
const dispatch = useAppDispatch();
|
|
250
|
+
const onChange = (optionName: string, checked: boolean) => {
|
|
251
|
+
dispatch(
|
|
252
|
+
updateSettings((oldSettings: ActivitySettings | undefined) => {
|
|
253
|
+
if (!oldSettings) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
const oldSection = oldSettings[
|
|
257
|
+
sectionId
|
|
258
|
+
] as ActivitySettingsMultipleOptionsSection;
|
|
259
|
+
const newSettings = {
|
|
260
|
+
...oldSettings,
|
|
261
|
+
[sectionId]: {
|
|
262
|
+
...oldSection,
|
|
263
|
+
selectedOptionNames: checked
|
|
264
|
+
? [...oldSection.selectedOptionNames, optionName]
|
|
265
|
+
: oldSection.selectedOptionNames.filter((n) => n !== optionName),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return newSettings as ActivitySettings;
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
return (
|
|
273
|
+
<div className="sidebarRadioButtons">
|
|
274
|
+
{options.options.map((option) => (
|
|
275
|
+
<CheckboxGroup
|
|
276
|
+
key={option.name}
|
|
277
|
+
inputId={sectionId + "-" + option.name}
|
|
278
|
+
checked={options.selectedOptionNames.includes(option.name)}
|
|
279
|
+
onChange={(value) => onChange(option.name, value)}
|
|
280
|
+
label={option.label}
|
|
281
|
+
/>
|
|
282
|
+
))}
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
type ActivitySettingsRadioDisplayProps = {
|
|
288
|
+
options: ActivitySettingsRadioOptionsSection;
|
|
289
|
+
sectionId: string;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
function ActivitySettingsRadioDisplay({
|
|
293
|
+
options,
|
|
294
|
+
sectionId,
|
|
295
|
+
}: ActivitySettingsRadioDisplayProps) {
|
|
296
|
+
const dispatch = useAppDispatch();
|
|
297
|
+
const onChange = (optionName: string, checked: boolean) => {
|
|
298
|
+
if (!checked) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
dispatch(
|
|
302
|
+
updateSettings((oldSettings: ActivitySettings | undefined) => {
|
|
303
|
+
if (!oldSettings) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const oldSection = oldSettings[
|
|
307
|
+
sectionId
|
|
308
|
+
] as ActivitySettingsRadioOptionsSection;
|
|
309
|
+
const newSettings = {
|
|
310
|
+
...oldSettings,
|
|
311
|
+
[sectionId]: {
|
|
312
|
+
...oldSection,
|
|
313
|
+
selectedOptionName: checked ? optionName : null,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
return newSettings as ActivitySettings;
|
|
317
|
+
}),
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
return (
|
|
321
|
+
<div className="sidebarRadioButtons">
|
|
322
|
+
{options.options.map((option) => (
|
|
323
|
+
<RadioGroup
|
|
324
|
+
key={option.name}
|
|
325
|
+
inputId={sectionId + "-" + option.name}
|
|
326
|
+
checked={options.selectedOptionName === option.name}
|
|
327
|
+
onChange={(value) => {
|
|
328
|
+
onChange(option.name, value);
|
|
329
|
+
}}
|
|
330
|
+
label={option.label}
|
|
331
|
+
/>
|
|
332
|
+
))}
|
|
333
|
+
</div>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
type CheckboxGroupProps = {
|
|
338
|
+
inputId: string;
|
|
339
|
+
label: string;
|
|
340
|
+
checked: boolean;
|
|
341
|
+
onChange?: (value: boolean) => void;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const CheckboxGroup: React.FC<CheckboxGroupProps> = (props) => {
|
|
345
|
+
return (
|
|
346
|
+
<div className="sidebarRadioGroup">
|
|
347
|
+
<Checkbox
|
|
348
|
+
inputId={props.inputId}
|
|
349
|
+
checked={props.checked}
|
|
350
|
+
onChange={(e) =>
|
|
351
|
+
props.onChange && e.checked != null && props.onChange(e.checked)
|
|
352
|
+
}
|
|
353
|
+
/>
|
|
354
|
+
<label className={styles.checkboxLabel} htmlFor={props.inputId}>
|
|
355
|
+
{props.label}
|
|
356
|
+
</label>
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const RadioGroup: React.FC<CheckboxGroupProps> = (props) => {
|
|
362
|
+
return (
|
|
363
|
+
<div className="sidebarRadioGroup">
|
|
364
|
+
<RadioButton
|
|
365
|
+
inputId={props.inputId}
|
|
366
|
+
checked={props.checked}
|
|
367
|
+
onChange={(e) => {
|
|
368
|
+
props.onChange && e.checked != null && props.onChange(e.checked);
|
|
369
|
+
}}
|
|
370
|
+
/>
|
|
371
|
+
<label className={styles.checkboxLabel} htmlFor={props.inputId}>
|
|
372
|
+
{props.label}
|
|
373
|
+
</label>
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const SwitchGroup: React.FC<CheckboxGroupProps> = (props) => {
|
|
379
|
+
return (
|
|
380
|
+
<div className="sidebarRadioGroup">
|
|
381
|
+
<InputSwitch
|
|
382
|
+
inputId={props.inputId}
|
|
383
|
+
checked={props.checked}
|
|
384
|
+
onChange={(e) =>
|
|
385
|
+
props.onChange && e.checked != null && props.onChange(e.checked)
|
|
386
|
+
}
|
|
387
|
+
/>
|
|
388
|
+
<label className={styles.checkboxLabel} htmlFor={props.inputId}>
|
|
389
|
+
{props.label}
|
|
390
|
+
</label>
|
|
391
|
+
</div>
|
|
392
|
+
);
|
|
393
|
+
};
|
|
@@ -19,7 +19,6 @@ const GradingNav: React.FC = () => {
|
|
|
19
19
|
const nid = useAppSelector(selectNid) as number;
|
|
20
20
|
const mode = useAppSelector(selectMode);
|
|
21
21
|
const activityNid = useAppSelector(selectActivityNid) as number;
|
|
22
|
-
console.log("Le nid est : ", nid);
|
|
23
22
|
useEffect(() => {
|
|
24
23
|
evalApi.listSa(activityNid).then((j) => {
|
|
25
24
|
setStudentList(j);
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
selectCanChoosePedagoLayout,
|
|
20
20
|
selectCanChooseTheme,
|
|
21
21
|
} from "../activityData/activityDataSlice";
|
|
22
|
+
import { ActivitySettingsDisplay } from "../activitySettings";
|
|
22
23
|
|
|
23
24
|
const SidebarContent: FC = () => {
|
|
24
25
|
const canChooseOrientation = useAppSelector(selectCanChoosePedagoLayout);
|
|
@@ -33,7 +34,7 @@ const SidebarContent: FC = () => {
|
|
|
33
34
|
<Fieldset
|
|
34
35
|
legend="Actions"
|
|
35
36
|
className={classNames(
|
|
36
|
-
|
|
37
|
+
"sidebarFieldset",
|
|
37
38
|
styles.sidebarFieldsetButtons,
|
|
38
39
|
)}
|
|
39
40
|
>
|
|
@@ -41,9 +42,9 @@ const SidebarContent: FC = () => {
|
|
|
41
42
|
</Fieldset>
|
|
42
43
|
)}
|
|
43
44
|
{canChooseOrientation && (
|
|
44
|
-
<Fieldset legend="Disposition" className=
|
|
45
|
-
<div className=
|
|
46
|
-
<div className=
|
|
45
|
+
<Fieldset legend="Disposition" className="sidebarFieldset">
|
|
46
|
+
<div className="sidebarRadioButtons">
|
|
47
|
+
<div className="sidebarRadioGroup">
|
|
47
48
|
<RadioButton
|
|
48
49
|
inputId="rb-horizontal"
|
|
49
50
|
name="horizontal"
|
|
@@ -55,7 +56,7 @@ const SidebarContent: FC = () => {
|
|
|
55
56
|
Horizontale
|
|
56
57
|
</label>
|
|
57
58
|
</div>
|
|
58
|
-
<div className=
|
|
59
|
+
<div className="sidebarRadioGroup">
|
|
59
60
|
<RadioButton
|
|
60
61
|
inputId="rb-vertical"
|
|
61
62
|
name="vertical"
|
|
@@ -71,9 +72,9 @@ const SidebarContent: FC = () => {
|
|
|
71
72
|
</Fieldset>
|
|
72
73
|
)}
|
|
73
74
|
{canChooseTheme && (
|
|
74
|
-
<Fieldset legend="Thème" className=
|
|
75
|
-
<div className=
|
|
76
|
-
<div className=
|
|
75
|
+
<Fieldset legend="Thème" className="sidebarFieldset">
|
|
76
|
+
<div className="sidebarRadioButtons">
|
|
77
|
+
<div className="sidebarRadioGroup">
|
|
77
78
|
<RadioButton
|
|
78
79
|
inputId="rb-light"
|
|
79
80
|
name="light"
|
|
@@ -88,7 +89,7 @@ const SidebarContent: FC = () => {
|
|
|
88
89
|
Clair
|
|
89
90
|
</label>
|
|
90
91
|
</div>
|
|
91
|
-
<div className=
|
|
92
|
+
<div className="sidebarRadioGroup">
|
|
92
93
|
<RadioButton
|
|
93
94
|
inputId="rb-dark"
|
|
94
95
|
name="dark"
|
|
@@ -103,7 +104,7 @@ const SidebarContent: FC = () => {
|
|
|
103
104
|
Sombre
|
|
104
105
|
</label>
|
|
105
106
|
</div>
|
|
106
|
-
<div className=
|
|
107
|
+
<div className="sidebarRadioGroup">
|
|
107
108
|
<RadioButton
|
|
108
109
|
inputId="rb-auto"
|
|
109
110
|
name="auto"
|
|
@@ -118,6 +119,7 @@ const SidebarContent: FC = () => {
|
|
|
118
119
|
</div>
|
|
119
120
|
</Fieldset>
|
|
120
121
|
)}
|
|
122
|
+
<ActivitySettingsDisplay />
|
|
121
123
|
</>
|
|
122
124
|
);
|
|
123
125
|
};
|
|
@@ -139,24 +139,8 @@
|
|
|
139
139
|
align-items: center;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
.sidebarFieldset {
|
|
143
|
-
margin-bottom: 8px;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
142
|
.sidebarFieldsetButtons :global(.p-fieldset-content) {
|
|
147
143
|
display: flex;
|
|
148
144
|
flex-direction: column;
|
|
149
145
|
gap: 6px;
|
|
150
146
|
}
|
|
151
|
-
|
|
152
|
-
.sidebarRadioButtons {
|
|
153
|
-
display: flex;
|
|
154
|
-
flex-direction: column;
|
|
155
|
-
gap: 4px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.sidebarRadioGroup {
|
|
159
|
-
display: flex;
|
|
160
|
-
gap: 8px;
|
|
161
|
-
align-items: center;
|
|
162
|
-
}
|
|
@@ -48,7 +48,6 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
|
48
48
|
) => {
|
|
49
49
|
dispatch(setComments(event.target.value));
|
|
50
50
|
dispatch(setIsMPDirty(true));
|
|
51
|
-
console.log("51");
|
|
52
51
|
};
|
|
53
52
|
|
|
54
53
|
const handleGradingChange: ChangeEventHandler<HTMLTextAreaElement> = (
|
|
@@ -56,7 +55,6 @@ const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
|
56
55
|
) => {
|
|
57
56
|
dispatch(setGrading(event.target.value));
|
|
58
57
|
dispatch(setIsMPDirty(true));
|
|
59
|
-
console.log("59");
|
|
60
58
|
};
|
|
61
59
|
|
|
62
60
|
return (
|
package/src/index.css
CHANGED
|
@@ -33,8 +33,45 @@ body,
|
|
|
33
33
|
color-scheme: light;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
.p-splitter-panel:has(> #meta-player-content-cover) {
|
|
37
|
+
position: relative;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#meta-player-content-cover {
|
|
41
|
+
display: none;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 100%;
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: 0;
|
|
46
|
+
left: 0;
|
|
47
|
+
z-index: 1000;
|
|
48
|
+
background-color: rgba(0, 0, 0, 0);
|
|
49
|
+
.p-splitter-resizing & {
|
|
50
|
+
display: block;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
#meta-player-content,
|
|
37
55
|
:has(> #meta-player-content) {
|
|
38
56
|
height: 100%;
|
|
39
57
|
background-color: var(--surface-a);
|
|
40
58
|
}
|
|
59
|
+
|
|
60
|
+
.sidebarFieldset {
|
|
61
|
+
margin-bottom: 8px;
|
|
62
|
+
& .p-button-label {
|
|
63
|
+
text-align: left;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.sidebarRadioButtons {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
gap: 4px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sidebarRadioGroup {
|
|
74
|
+
display: flex;
|
|
75
|
+
gap: 8px;
|
|
76
|
+
align-items: center;
|
|
77
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// https://medium.com/@stheodorejohn/javascript-object-deep-equality-comparison-in-javascript-7aa227e889d4
|
|
2
|
+
|
|
3
|
+
export function deepEqual(obj1: any, obj2: any): boolean {
|
|
4
|
+
// Base case: If both objects are identical, return true.
|
|
5
|
+
if (obj1 === obj2) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
// Check if both objects are objects and not null.
|
|
9
|
+
if (
|
|
10
|
+
typeof obj1 !== "object" ||
|
|
11
|
+
typeof obj2 !== "object" ||
|
|
12
|
+
obj1 === null ||
|
|
13
|
+
obj2 === null
|
|
14
|
+
) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// Get the keys of both objects.
|
|
18
|
+
const keys1 = Object.keys(obj1);
|
|
19
|
+
const keys2 = Object.keys(obj2);
|
|
20
|
+
// Check if the number of keys is the same.
|
|
21
|
+
if (keys1.length !== keys2.length) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// Iterate through the keys and compare their values recursively.
|
|
25
|
+
for (const key of keys1) {
|
|
26
|
+
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// If all checks pass, the objects are deep equal.
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -1,35 +0,0 @@
|
|
|
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;
|