@capytale/meta-player 0.0.3 → 0.1.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.
@@ -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
+ };
@@ -7,6 +7,8 @@ import SidebarContent from "./SidebarContent";
7
7
  import settings from "../../settings";
8
8
  import ActivityQuickActions from "./ActivityQuickActions";
9
9
  import useFullscreen from "../../utils/useFullscreen";
10
+ import { useActivityJS } from "../activityJS/ActivityJSProvider";
11
+ import { Dialog } from "primereact/dialog";
10
12
 
11
13
  const ActivityMenu: React.FC = memo(() => {
12
14
  const [settingsSidebarVisible, setSettingsSidebarVisible] = useState(false);
@@ -14,13 +16,15 @@ const ActivityMenu: React.FC = memo(() => {
14
16
  const isFullscreen = useFullscreen(wantFullscreen, () =>
15
17
  setWantFullscreen(false),
16
18
  );
19
+ const [helpDialogVisible, setHelpDialogVisible] = useState(false);
20
+ const activityJS = useActivityJS();
17
21
  return (
18
22
  <div className={styles.activityMenu}>
19
23
  <ActivityQuickActions />
20
24
  <Button
21
25
  severity="secondary"
22
26
  size="small"
23
- icon={isFullscreen ? "pi pi-expand" : "pi pi-expand" }
27
+ icon={isFullscreen ? "pi pi-expand" : "pi pi-expand"}
24
28
  text
25
29
  tooltip="Plein écran"
26
30
  tooltipOptions={{
@@ -47,8 +51,32 @@ const ActivityMenu: React.FC = memo(() => {
47
51
  position="right"
48
52
  onHide={() => setSettingsSidebarVisible(false)}
49
53
  >
50
- <SidebarContent />
54
+ <SidebarContent
55
+ showHelp={
56
+ activityJS.activitySession?.type.helpUrl
57
+ ? () => {
58
+ setHelpDialogVisible(true);
59
+ setSettingsSidebarVisible(false);
60
+ }
61
+ : undefined
62
+ }
63
+ />
51
64
  </Sidebar>
65
+ {activityJS.activitySession?.type.helpUrl && (
66
+ <Dialog
67
+ id="metaPlayerHelpDialog"
68
+ header="Documentation"
69
+ visible={helpDialogVisible}
70
+ onHide={() => setHelpDialogVisible(false)}
71
+ maximizable={true}
72
+ >
73
+ <iframe
74
+ src={activityJS.activitySession?.type.helpUrl}
75
+ style={{ width: "100%", height: "100%" }}
76
+ title="Documentation"
77
+ />
78
+ </Dialog>
79
+ )}
52
80
  </div>
53
81
  );
54
82
  });