@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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Button } from "primereact/button";
|
|
2
|
+
import { Panel } from "primereact/panel";
|
|
3
|
+
import styles from "./style.module.scss";
|
|
4
|
+
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
|
5
|
+
import {
|
|
6
|
+
selectIsGradingVisible,
|
|
7
|
+
selectOrientation,
|
|
8
|
+
selectPedagoTab,
|
|
9
|
+
toggleIsGradingVisible,
|
|
10
|
+
toggleIsPedagoVisible,
|
|
11
|
+
} from "../layout/layoutSlice";
|
|
12
|
+
|
|
13
|
+
import "@capytale/capytale-rich-text-editor/style.css";
|
|
14
|
+
import { Splitter, SplitterPanel } from "primereact/splitter";
|
|
15
|
+
import { classNames } from "primereact/utils";
|
|
16
|
+
import InstructionsEditor from "./InstructionsEditor";
|
|
17
|
+
import {
|
|
18
|
+
selectComments,
|
|
19
|
+
selectGrading,
|
|
20
|
+
selectHasGradingOrComments,
|
|
21
|
+
selectMode,
|
|
22
|
+
setComments,
|
|
23
|
+
setGrading,
|
|
24
|
+
} from "../activityData/activityDataSlice";
|
|
25
|
+
import { ChangeEventHandler } from "react";
|
|
26
|
+
import settings from "../../settings";
|
|
27
|
+
import { DivProps } from "react-html-props";
|
|
28
|
+
import AnswerSheetEditor from "./AnswerSheetEditor";
|
|
29
|
+
|
|
30
|
+
const Pedago: React.FC<DivProps> = ({ className, ...props }) => {
|
|
31
|
+
const dispatch = useAppDispatch();
|
|
32
|
+
const mode = useAppSelector(selectMode);
|
|
33
|
+
const comments = useAppSelector(selectComments);
|
|
34
|
+
const grading = useAppSelector(selectGrading);
|
|
35
|
+
const pedagoTab = useAppSelector(selectPedagoTab);
|
|
36
|
+
const hasGradingOrComments = useAppSelector(selectHasGradingOrComments);
|
|
37
|
+
const isGradingVisible =
|
|
38
|
+
useAppSelector(selectIsGradingVisible) &&
|
|
39
|
+
(hasGradingOrComments || mode === "review");
|
|
40
|
+
const isHorizontal = useAppSelector(selectOrientation) === "horizontal";
|
|
41
|
+
const mayReverse = isHorizontal
|
|
42
|
+
? (tab: Array<any>) => tab.toReversed()
|
|
43
|
+
: (tab: Array<any>) => tab;
|
|
44
|
+
|
|
45
|
+
const handleCommentsChange: ChangeEventHandler<HTMLTextAreaElement> = (
|
|
46
|
+
event,
|
|
47
|
+
) => {
|
|
48
|
+
console.log("Comments change:", event.target.value);
|
|
49
|
+
dispatch(setComments(event.target.value));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleGradingChange: ChangeEventHandler<HTMLTextAreaElement> = (
|
|
53
|
+
event,
|
|
54
|
+
) => {
|
|
55
|
+
console.log("Grading change:", event.target.value);
|
|
56
|
+
dispatch(setGrading(event.target.value));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
// @ts-ignore - Incompatibility for props in TS
|
|
61
|
+
<div className={classNames(styles.pedago, className)} {...props}>
|
|
62
|
+
<div className={styles.pedagoCommands}>
|
|
63
|
+
<Button
|
|
64
|
+
severity="secondary"
|
|
65
|
+
icon="pi pi-times"
|
|
66
|
+
rounded
|
|
67
|
+
text
|
|
68
|
+
aria-label="Masquer les consignes"
|
|
69
|
+
tooltip="Masquer les consignes"
|
|
70
|
+
tooltipOptions={{
|
|
71
|
+
position: "left",
|
|
72
|
+
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
73
|
+
}}
|
|
74
|
+
onClick={() => dispatch(toggleIsPedagoVisible())}
|
|
75
|
+
/>
|
|
76
|
+
{/*
|
|
77
|
+
<Button
|
|
78
|
+
rounded
|
|
79
|
+
text
|
|
80
|
+
icon="pi pi-times"
|
|
81
|
+
label="H"
|
|
82
|
+
onClick={() => {
|
|
83
|
+
dispatch(
|
|
84
|
+
setPedagoTab(
|
|
85
|
+
pedagoTab === "instructions" ? "answerSheet" : "instructions",
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
*/}
|
|
91
|
+
<div className={styles.pedagoSeparator}></div>
|
|
92
|
+
{(mode === "assignment" || mode === "review") && (
|
|
93
|
+
<ShowHideGradingButton />
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
<div
|
|
97
|
+
className={styles.pedagoContent}
|
|
98
|
+
data-grading-visible={isGradingVisible}
|
|
99
|
+
>
|
|
100
|
+
{!isGradingVisible && (
|
|
101
|
+
<>
|
|
102
|
+
{pedagoTab === "instructions" && <InstructionsEditor />}
|
|
103
|
+
{pedagoTab === "answerSheet" && <AnswerSheetEditor />}
|
|
104
|
+
</>
|
|
105
|
+
)}
|
|
106
|
+
{isGradingVisible && (
|
|
107
|
+
<Splitter
|
|
108
|
+
layout={isHorizontal ? "horizontal" : "vertical"}
|
|
109
|
+
className={styles.pedagoSplitter}
|
|
110
|
+
>
|
|
111
|
+
{mayReverse([
|
|
112
|
+
<SplitterPanel
|
|
113
|
+
key="gradingPanel"
|
|
114
|
+
minSize={30}
|
|
115
|
+
size={40}
|
|
116
|
+
className={styles.gradingPanel}
|
|
117
|
+
>
|
|
118
|
+
<Panel
|
|
119
|
+
className={classNames(
|
|
120
|
+
styles.fullSizePanel,
|
|
121
|
+
styles.pedagoFeedbackPanel,
|
|
122
|
+
)}
|
|
123
|
+
header="Appréciation"
|
|
124
|
+
>
|
|
125
|
+
<div className={styles.pedagoFeedback}>
|
|
126
|
+
{(comments || mode !== "assignment") && (
|
|
127
|
+
<textarea
|
|
128
|
+
value={comments || ""}
|
|
129
|
+
placeholder={
|
|
130
|
+
mode !== "review" ? "" : "Rédigez ici l'appréciation."
|
|
131
|
+
}
|
|
132
|
+
id="comments"
|
|
133
|
+
name="comments"
|
|
134
|
+
rows={2}
|
|
135
|
+
readOnly={mode !== "review"}
|
|
136
|
+
onChange={handleCommentsChange}
|
|
137
|
+
className={styles.fullTextarea}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</Panel>
|
|
142
|
+
<Panel
|
|
143
|
+
className={classNames(
|
|
144
|
+
styles.fullSizePanel,
|
|
145
|
+
styles.pedagoGradePanel,
|
|
146
|
+
)}
|
|
147
|
+
header="Évaluation"
|
|
148
|
+
>
|
|
149
|
+
<div className={styles.pedagoGrade}>
|
|
150
|
+
{(grading || mode !== "assignment") && (
|
|
151
|
+
<textarea
|
|
152
|
+
value={grading || ""}
|
|
153
|
+
placeholder={
|
|
154
|
+
mode !== "review"
|
|
155
|
+
? ""
|
|
156
|
+
: "Écrivez ici l'évaluation libre (chiffrée ou non)."
|
|
157
|
+
}
|
|
158
|
+
id="grading"
|
|
159
|
+
name="grading"
|
|
160
|
+
rows={1}
|
|
161
|
+
readOnly={mode !== "review"}
|
|
162
|
+
onChange={handleGradingChange}
|
|
163
|
+
className={styles.fullTextarea}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</Panel>
|
|
168
|
+
</SplitterPanel>,
|
|
169
|
+
<SplitterPanel
|
|
170
|
+
key="pedagoPanel"
|
|
171
|
+
minSize={50}
|
|
172
|
+
size={60}
|
|
173
|
+
className={styles.pedagoPanel}
|
|
174
|
+
>
|
|
175
|
+
<Panel className={styles.fullSizePanel} header="Consignes">
|
|
176
|
+
{pedagoTab === "instructions" && <InstructionsEditor />}
|
|
177
|
+
{pedagoTab === "answerSheet" && <AnswerSheetEditor />}
|
|
178
|
+
</Panel>
|
|
179
|
+
</SplitterPanel>
|
|
180
|
+
])}
|
|
181
|
+
</Splitter>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const ShowHideGradingButton: React.FC = () => {
|
|
189
|
+
const dispatch = useAppDispatch();
|
|
190
|
+
const mode = useAppSelector(selectMode);
|
|
191
|
+
const hasGradingOrComments = useAppSelector(selectHasGradingOrComments);
|
|
192
|
+
const isGradingVisible = useAppSelector(selectIsGradingVisible);
|
|
193
|
+
const tooltip =
|
|
194
|
+
hasGradingOrComments || mode === "review"
|
|
195
|
+
? isGradingVisible
|
|
196
|
+
? "Masquer la notation"
|
|
197
|
+
: "Afficher la notation"
|
|
198
|
+
: "Pas de note";
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Button
|
|
202
|
+
severity="secondary"
|
|
203
|
+
icon="pi pi-graduation-cap"
|
|
204
|
+
disabled={!(hasGradingOrComments || mode === "review")}
|
|
205
|
+
rounded
|
|
206
|
+
text={!isGradingVisible || !(hasGradingOrComments || mode === "review")}
|
|
207
|
+
aria-label={tooltip}
|
|
208
|
+
tooltip={tooltip}
|
|
209
|
+
tooltipOptions={{
|
|
210
|
+
position: "left",
|
|
211
|
+
showDelay: settings.TOOLTIP_SHOW_DELAY,
|
|
212
|
+
showOnDisabled: true,
|
|
213
|
+
}}
|
|
214
|
+
onClick={() => dispatch(toggleIsGradingVisible())}
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export default Pedago;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
.pedago {
|
|
2
|
+
display: grid;
|
|
3
|
+
height: 100%;
|
|
4
|
+
width: 100%;
|
|
5
|
+
:global(.layout-horizontal) & {
|
|
6
|
+
grid-template-columns: 1fr calc(3rem + 8px);
|
|
7
|
+
grid-template-rows: 1fr;
|
|
8
|
+
}
|
|
9
|
+
:global(.layout-vertical) & {
|
|
10
|
+
grid-template-rows: calc(3rem + 8px) 1fr;
|
|
11
|
+
grid-template-columns: 1fr;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.pedagoCommands {
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
padding: 4px;
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
background-color: var(--surface-200);
|
|
21
|
+
:global(.layout-horizontal) & {
|
|
22
|
+
grid-column-start: 2;
|
|
23
|
+
grid-row-start: 1;
|
|
24
|
+
}
|
|
25
|
+
:global(.layout-vertical) & {
|
|
26
|
+
flex-direction: row-reverse;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.pedagoSeparator {
|
|
31
|
+
flex-grow: 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.pedagoSplitter {
|
|
35
|
+
height: 100%;
|
|
36
|
+
border: none;
|
|
37
|
+
border-radius: 0;
|
|
38
|
+
background-color: var(--surface-0);
|
|
39
|
+
& > :global(.p-splitter-panel) {
|
|
40
|
+
overflow-y: hidden;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.gradingPanel {
|
|
45
|
+
display: flex;
|
|
46
|
+
gap: 4px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
*:has(> .fullSizePanel) {
|
|
50
|
+
padding: 4px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.fullSizePanel {
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 100%;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
& > :global(.p-panel-header) {
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
& > :global(.p-toggleable-content) {
|
|
62
|
+
flex-grow: 1;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
& > :global(.p-toggleable-content) > * {
|
|
66
|
+
height: 100%;
|
|
67
|
+
padding: 0;
|
|
68
|
+
overflow: auto;
|
|
69
|
+
display: flex;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.pedagoContent {
|
|
74
|
+
overflow-y: auto;
|
|
75
|
+
:global(.layout-horizontal) & {
|
|
76
|
+
grid-column-start: 1;
|
|
77
|
+
grid-row-start: 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.pedagoFeedbackPanel {
|
|
82
|
+
flex-grow: 3;
|
|
83
|
+
flex-basis: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.pedagoGradePanel {
|
|
87
|
+
flex-grow: 2;
|
|
88
|
+
flex-basis: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.fullTextarea {
|
|
92
|
+
width: 100%;
|
|
93
|
+
padding: 0.7em;
|
|
94
|
+
border: 1px solid transparent;
|
|
95
|
+
resize: none;
|
|
96
|
+
height: 100%;
|
|
97
|
+
background-color: transparent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
*:has(> .fullTextarea) {
|
|
101
|
+
height: 100%;
|
|
102
|
+
width: 100%;
|
|
103
|
+
overflow: hidden;
|
|
104
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FC, useCallback, useContext, useEffect, useMemo } from "react";
|
|
2
|
+
import { PrimeReactContext } from "primereact/api";
|
|
3
|
+
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
|
4
|
+
import {
|
|
5
|
+
selectAutoSwitch,
|
|
6
|
+
selectPreviousThemePath,
|
|
7
|
+
selectThemePath,
|
|
8
|
+
switchToTheme,
|
|
9
|
+
} from "./themingSlice";
|
|
10
|
+
|
|
11
|
+
const ThemeSwitcher: FC = () => {
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
const mediaMatch = useMemo(
|
|
14
|
+
() => window.matchMedia("(prefers-color-scheme: dark)"),
|
|
15
|
+
[],
|
|
16
|
+
);
|
|
17
|
+
const { changeTheme } = useContext(PrimeReactContext);
|
|
18
|
+
const themePath = useAppSelector(selectThemePath);
|
|
19
|
+
const previousThemePath = useAppSelector(selectPreviousThemePath);
|
|
20
|
+
const autoSwitch = useAppSelector(selectAutoSwitch);
|
|
21
|
+
const handlePrefersColorScheme = useCallback((e: MediaQueryListEvent) => {
|
|
22
|
+
dispatch(switchToTheme(e.matches ? "dark" : "light"));
|
|
23
|
+
}, []);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (themePath !== previousThemePath) {
|
|
26
|
+
changeTheme &&
|
|
27
|
+
changeTheme(
|
|
28
|
+
previousThemePath as string,
|
|
29
|
+
themePath as string,
|
|
30
|
+
"theme-link",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}, [themePath]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (autoSwitch) {
|
|
36
|
+
if (mediaMatch.matches) {
|
|
37
|
+
dispatch(switchToTheme("dark"));
|
|
38
|
+
} else {
|
|
39
|
+
dispatch(switchToTheme("light"));
|
|
40
|
+
}
|
|
41
|
+
mediaMatch.addEventListener("change", handlePrefersColorScheme);
|
|
42
|
+
return () => {
|
|
43
|
+
mediaMatch.removeEventListener("change", handlePrefersColorScheme);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}, [autoSwitch]);
|
|
47
|
+
|
|
48
|
+
return <></>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default ThemeSwitcher;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
2
|
+
import { createAppSlice } from "../../app/createAppSlice";
|
|
3
|
+
|
|
4
|
+
type ThemeData = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
isDark: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface ThemingState {
|
|
12
|
+
themes: ThemeData[];
|
|
13
|
+
currentTheme: number;
|
|
14
|
+
previousTheme: number;
|
|
15
|
+
autoSwitch: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const initialState: ThemingState = {
|
|
19
|
+
themes: [
|
|
20
|
+
{
|
|
21
|
+
id: "light",
|
|
22
|
+
name: "Clair",
|
|
23
|
+
path: "https://cdn.ac-paris.fr/capytale/meta-player/themes/lara-light-blue/theme.css",
|
|
24
|
+
isDark: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "dark",
|
|
28
|
+
name: "Sombre",
|
|
29
|
+
path: "https://cdn.ac-paris.fr/capytale/meta-player/themes/lara-dark-blue/theme.css",
|
|
30
|
+
isDark: true,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
currentTheme: 0,
|
|
34
|
+
previousTheme: 0,
|
|
35
|
+
autoSwitch: false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// If you are not using async thunks you can use the standalone `createSlice`.
|
|
39
|
+
export const themingSlice = createAppSlice({
|
|
40
|
+
name: "theming",
|
|
41
|
+
// `createSlice` will infer the state type from the `initialState` argument
|
|
42
|
+
initialState,
|
|
43
|
+
// The `reducers` field lets us define reducers and generate associated actions
|
|
44
|
+
reducers: (create) => ({
|
|
45
|
+
switchToNextTheme: create.reducer((state) => {
|
|
46
|
+
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
|
47
|
+
// doesn't actually mutate the state because it uses the Immer library,
|
|
48
|
+
// which detects changes to a "draft state" and produces a brand new
|
|
49
|
+
// immutable state based off those changes
|
|
50
|
+
state.previousTheme = state.currentTheme;
|
|
51
|
+
state.currentTheme = (state.currentTheme + 1) % state.themes.length;
|
|
52
|
+
}),
|
|
53
|
+
// Use the `PayloadAction` type to declare the contents of `action.payload`
|
|
54
|
+
switchToTheme: create.reducer((state, action: PayloadAction<string>) => {
|
|
55
|
+
state.previousTheme = state.currentTheme;
|
|
56
|
+
state.currentTheme = state.themes.findIndex(
|
|
57
|
+
(theme) => theme.id === action.payload,
|
|
58
|
+
);
|
|
59
|
+
}),
|
|
60
|
+
setAutoSwitch: create.reducer((state, action: PayloadAction<boolean>) => {
|
|
61
|
+
state.autoSwitch = action.payload;
|
|
62
|
+
}),
|
|
63
|
+
}),
|
|
64
|
+
// You can define your selectors here. These selectors receive the slice
|
|
65
|
+
// state as their first argument.
|
|
66
|
+
selectors: {
|
|
67
|
+
selectThemePath: (theming) => theming.themes[theming.currentTheme].path,
|
|
68
|
+
selectPreviousThemePath: (theming) =>
|
|
69
|
+
theming.themes[theming.previousTheme].path,
|
|
70
|
+
selectThemeName: (theming) => theming.themes[theming.currentTheme].name,
|
|
71
|
+
selectThemeIsDark: (theming) => theming.themes[theming.currentTheme].isDark,
|
|
72
|
+
selectAutoSwitch: (theming) => theming.autoSwitch,
|
|
73
|
+
selectThemeNameOrAuto: (theming) => {
|
|
74
|
+
return theming.autoSwitch
|
|
75
|
+
? "Système (auto)"
|
|
76
|
+
: theming.themes[theming.currentTheme].name;
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Action creators are generated for each case reducer function.
|
|
82
|
+
export const { switchToNextTheme, switchToTheme, setAutoSwitch } =
|
|
83
|
+
themingSlice.actions;
|
|
84
|
+
|
|
85
|
+
// Selectors returned by `slice.selectors` take the root state as their first argument.
|
|
86
|
+
export const {
|
|
87
|
+
selectThemePath,
|
|
88
|
+
selectPreviousThemePath,
|
|
89
|
+
selectThemeName,
|
|
90
|
+
selectThemeIsDark,
|
|
91
|
+
selectAutoSwitch,
|
|
92
|
+
selectThemeNameOrAuto,
|
|
93
|
+
} = themingSlice.selectors;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ActivityMode } from "@capytale/activity.js/activity/activitySession";
|
|
2
|
+
import { useAppSelector } from "../app/hooks";
|
|
3
|
+
import { selectMode } from "../features/activityData/activityDataSlice";
|
|
4
|
+
|
|
5
|
+
export const useMode = () => {
|
|
6
|
+
const mode: ActivityMode = useAppSelector(selectMode);
|
|
7
|
+
return mode;
|
|
8
|
+
};
|
package/src/index.css
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
|
4
|
+
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
|
5
|
+
sans-serif;
|
|
6
|
+
-webkit-font-smoothing: antialiased;
|
|
7
|
+
-moz-osx-font-smoothing: grayscale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
code {
|
|
11
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
|
12
|
+
monospace;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
html, body, #root {
|
|
16
|
+
height: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.p-splitter-gutter {
|
|
20
|
+
background-color: var(--surface-200);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.p-splitter-gutter-handle, .p-splitter-gutter-resizing {
|
|
24
|
+
background-color: var(--surface-400);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
*:has(>.editor-shell) { /* Rich text editor */
|
|
28
|
+
background-color: white;
|
|
29
|
+
color-scheme: light;
|
|
30
|
+
}
|
package/src/index.tsx
ADDED
package/src/logo.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g fill="#764ABC"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></svg>
|