@edifice.io/react 2.3.1-develop-b2school-actualites.20250911174726 → 2.3.1-develop-b2school.20250916153842
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/dist/editor.js +34 -38
- package/dist/modules/editor/components/Editor/index.d.ts +0 -2
- package/dist/modules/multimedia/VideoRecorder/VideoRecorder.js +36 -145
- package/dist/modules/multimedia/VideoRecorder/VideoRecorderToolbar.d.ts +18 -0
- package/dist/modules/multimedia/VideoRecorder/VideoRecorderToolbar.js +96 -0
- package/dist/modules/multimedia/VideoRecorder/useCameras.d.ts +10 -0
- package/dist/modules/multimedia/VideoRecorder/useCameras.js +86 -0
- package/package.json +6 -6
- package/dist/modules/editor/components/Editor/EditorPreview.d.ts +0 -14
- package/dist/modules/editor/components/Editor/EditorPreview.js +0 -56
- package/dist/modules/editor/components/Editor/EditorPreviewSkeleton.d.ts +0 -8
- package/dist/modules/editor/components/Editor/EditorPreviewSkeleton.js +0 -24
package/dist/editor.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { EditorContent, Editor, useEditor } from "@tiptap/react";
|
|
2
2
|
import { default as default2 } from "@tiptap/starter-kit";
|
|
3
3
|
import { default as default3 } from "./modules/editor/components/Editor/Editor.js";
|
|
4
|
-
import { default as default4 } from "./modules/editor/components/Editor/
|
|
5
|
-
import { default as default5 } from "./modules/editor/components/
|
|
6
|
-
import { default as default6 } from "./modules/editor/components/
|
|
7
|
-
import { default as default7 } from "./modules/editor/components/
|
|
8
|
-
import { default as default8 } from "./modules/editor/components/NodeView/
|
|
9
|
-
import { default as default9 } from "./modules/editor/components/NodeView/
|
|
10
|
-
import { default as default10 } from "./modules/editor/components/NodeView/
|
|
11
|
-
import { default as default11 } from "./modules/editor/components/NodeView/
|
|
12
|
-
import { default as default12 } from "./modules/editor/components/NodeView/
|
|
13
|
-
import { default as default13 } from "./modules/editor/components/
|
|
14
|
-
import { default as default14 } from "./modules/editor/components/
|
|
15
|
-
import { default as default15 } from "./modules/editor/components/Renderer/
|
|
16
|
-
import { default as default16 } from "./modules/editor/components/Renderer/
|
|
17
|
-
import { default as default17 } from "./modules/editor/components/Renderer/
|
|
18
|
-
import { default as default18 } from "./modules/editor/components/Renderer/
|
|
19
|
-
import { default as default19 } from "./modules/editor/components/
|
|
20
|
-
import { default as default20 } from "./modules/editor/components/
|
|
21
|
-
import { default as default21 } from "./modules/editor/components/Toolbar/TableToolbar.js";
|
|
22
|
-
import { default as default22 } from "./modules/editor/components/Toolbar/LinkToolbar.js";
|
|
4
|
+
import { default as default4 } from "./modules/editor/components/Editor/EditorSkeleton.js";
|
|
5
|
+
import { default as default5 } from "./modules/editor/components/BubbleMenuEditImage/BubbleMenuEditImage.js";
|
|
6
|
+
import { default as default6 } from "./modules/editor/components/NodeView/AttachmentNodeView.js";
|
|
7
|
+
import { default as default7 } from "./modules/editor/components/NodeView/AudioNodeView.js";
|
|
8
|
+
import { default as default8 } from "./modules/editor/components/NodeView/ConversationHistoryNodeView.js";
|
|
9
|
+
import { default as default9 } from "./modules/editor/components/NodeView/ImageNodeView.js";
|
|
10
|
+
import { default as default10 } from "./modules/editor/components/NodeView/LinkerNodeView.js";
|
|
11
|
+
import { default as default11 } from "./modules/editor/components/NodeView/VideoNodeView.js";
|
|
12
|
+
import { default as default12 } from "./modules/editor/components/NodeView/InformationPaneNodeView.js";
|
|
13
|
+
import { default as default13 } from "./modules/editor/components/Renderer/AttachmentRenderer.js";
|
|
14
|
+
import { default as default14 } from "./modules/editor/components/Renderer/AudioRenderer.js";
|
|
15
|
+
import { default as default15 } from "./modules/editor/components/Renderer/ConversationHistoryRenderer.js";
|
|
16
|
+
import { default as default16 } from "./modules/editor/components/Renderer/LinkerRenderer.js";
|
|
17
|
+
import { default as default17 } from "./modules/editor/components/Renderer/MediaRenderer.js";
|
|
18
|
+
import { default as default18 } from "./modules/editor/components/Renderer/InformationPaneRenderer.js";
|
|
19
|
+
import { default as default19 } from "./modules/editor/components/Toolbar/TableToolbar.js";
|
|
20
|
+
import { default as default20 } from "./modules/editor/components/Toolbar/LinkToolbar.js";
|
|
23
21
|
import { EditorToolbar } from "./modules/editor/components/EditorToolbar/EditorToolbar.js";
|
|
24
22
|
import { useActionOptions } from "./modules/editor/hooks/useActionOptions.js";
|
|
25
23
|
import { useCommentEditor } from "./modules/editor/hooks/useCommentEditor.js";
|
|
@@ -35,31 +33,29 @@ import { useSpeechSynthetisis } from "./modules/editor/hooks/useSpeechSynthetisi
|
|
|
35
33
|
import { useTipTapEditor } from "./modules/editor/hooks/useTipTapEditor.js";
|
|
36
34
|
import { useCantooEditor } from "./modules/editor/hooks/useCantooEditor.js";
|
|
37
35
|
export {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
default6 as AttachmentNodeView,
|
|
37
|
+
default13 as AttachmentRenderer,
|
|
38
|
+
default7 as AudioNodeView,
|
|
39
|
+
default14 as AudioRenderer,
|
|
40
|
+
default5 as BubbleMenuEditImage,
|
|
41
|
+
default8 as ConversationHistoryNodeView,
|
|
42
|
+
default15 as ConversationHistoryRenderer,
|
|
45
43
|
default3 as Editor,
|
|
46
44
|
EditorContent,
|
|
47
45
|
EditorContext,
|
|
48
46
|
Editor as EditorInstance,
|
|
49
|
-
default4 as
|
|
50
|
-
default5 as EditorPreviewSkeleton,
|
|
51
|
-
default6 as EditorSkeleton,
|
|
47
|
+
default4 as EditorSkeleton,
|
|
52
48
|
EditorToolbar,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
default9 as ImageNodeView,
|
|
50
|
+
default12 as InformationPaneNodeView,
|
|
51
|
+
default18 as InformationPaneRenderer,
|
|
52
|
+
default20 as LinkToolbar,
|
|
53
|
+
default10 as LinkerNodeView,
|
|
54
|
+
default16 as LinkerRenderer,
|
|
55
|
+
default17 as MediaRenderer,
|
|
60
56
|
default2 as StarterKit,
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
default19 as TableToolbar,
|
|
58
|
+
default11 as VideoNodeView,
|
|
63
59
|
useActionOptions,
|
|
64
60
|
useCantooEditor,
|
|
65
61
|
useCommentEditor,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
export * from './Editor';
|
|
2
2
|
export { default as Editor } from './Editor';
|
|
3
|
-
export { default as EditorPreview } from './EditorPreview';
|
|
4
|
-
export { default as EditorPreviewSkeleton } from './EditorPreviewSkeleton';
|
|
5
3
|
export { default as EditorSkeleton } from './EditorSkeleton';
|
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useState, useRef, useImperativeHandle, useEffect, useCallback } from "react";
|
|
3
3
|
import { odeServices } from "@edifice.io/client";
|
|
4
4
|
import { getBestSupportedMimeType, convertMsToMS } from "@edifice.io/utilities";
|
|
5
5
|
import { useTranslation } from "react-i18next";
|
|
6
|
-
import SvgIconPause from "../../icons/components/IconPause.js";
|
|
7
|
-
import SvgIconPlayFilled from "../../icons/components/IconPlayFilled.js";
|
|
8
|
-
import SvgIconRecordStop from "../../icons/components/IconRecordStop.js";
|
|
9
6
|
import SvgIconRecordVideo from "../../icons/components/IconRecordVideo.js";
|
|
10
7
|
import SvgIconRecord from "../../icons/components/IconRecord.js";
|
|
11
|
-
import
|
|
12
|
-
import
|
|
8
|
+
import { useCameras } from "./useCameras.js";
|
|
9
|
+
import { VideoRecorderToolbar } from "./VideoRecorderToolbar.js";
|
|
13
10
|
import useUpload from "../../../hooks/useUpload/useUpload.js";
|
|
14
|
-
import useBrowserInfo from "../../../hooks/useBrowserInfo/useBrowserInfo.js";
|
|
15
11
|
import FormControl from "../../../components/Form/FormControl.js";
|
|
16
12
|
import Label from "../../../components/Label/Label.js";
|
|
17
13
|
import Select from "../../../components/Select/Select.js";
|
|
18
14
|
import LoadingScreen from "../../../components/LoadingScreen/LoadingScreen.js";
|
|
19
|
-
|
|
20
|
-
const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwardRef(({
|
|
15
|
+
const VideoRecorder = /* @__PURE__ */ forwardRef(({
|
|
21
16
|
appCode,
|
|
22
17
|
caption,
|
|
23
18
|
onSuccess,
|
|
@@ -25,17 +20,14 @@ const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwar
|
|
|
25
20
|
onRecordUpdated,
|
|
26
21
|
hideSaveAction = !1
|
|
27
22
|
}, ref) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}), [stream, setStream] = useState(), [mimeType, setMimeType] = useState(""), [recordedChunks, setRecordedChunks] = useState([]), [recordedVideo, setRecordedVideo] = useState(), [recordedTime, setRecordedTime] = useState(0), [playedTime, setPlayedTime] = useState(0), videoRef = useRef(null), recorderRef = useRef(null), {
|
|
23
|
+
const {
|
|
24
|
+
inputDevices,
|
|
25
|
+
setPreferedDevice,
|
|
26
|
+
restartStream,
|
|
27
|
+
stream
|
|
28
|
+
} = useCameras(), [maxDuration, setMaxDuration] = useState(18e4), [recording, setRecording] = useState(!1), [recorded, setRecorded] = useState(!1), [playing, setPlaying] = useState(!1), [saving, setSaving] = useState(!1), [saved, setSaved] = useState(!1), [mimeType, setMimeType] = useState(""), [recordedChunks, setRecordedChunks] = useState([]), [recordedVideo, setRecordedVideo] = useState(), [recordedTime, setRecordedTime] = useState(0), [playedTime, setPlayedTime] = useState(0), videoRef = useRef(null), recorderRef = useRef(null), {
|
|
35
29
|
uploadBlob
|
|
36
|
-
} = useUpload(void 0, appCode)
|
|
37
|
-
device
|
|
38
|
-
} = useBrowserInfo(navigator.userAgent);
|
|
30
|
+
} = useUpload(void 0, appCode);
|
|
39
31
|
useImperativeHandle(ref, () => ({
|
|
40
32
|
save: handleSave
|
|
41
33
|
}));
|
|
@@ -43,10 +35,23 @@ const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwar
|
|
|
43
35
|
t
|
|
44
36
|
} = useTranslation();
|
|
45
37
|
useEffect(() => {
|
|
46
|
-
initMaxDuration()
|
|
47
|
-
}, [])
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
initMaxDuration();
|
|
39
|
+
}, []);
|
|
40
|
+
async function initMaxDuration() {
|
|
41
|
+
try {
|
|
42
|
+
const videoConfResponse = await odeServices.video().getVideoConf();
|
|
43
|
+
setMaxDuration(videoConfResponse.maxDuration * 60 * 1e3);
|
|
44
|
+
} catch {
|
|
45
|
+
setMaxDuration(3 * 60 * 1e3);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
try {
|
|
50
|
+
videoRef.current && (videoRef.current.src && (window.URL.revokeObjectURL(videoRef.current.src), videoRef.current.src = ""), stream && (videoRef.current.srcObject = stream, videoRef.current.autoplay = !0, videoRef.current.volume = 1, videoRef.current.muted = !0));
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(err);
|
|
53
|
+
}
|
|
54
|
+
}, [stream]), useEffect(() => {
|
|
50
55
|
if (recordedChunks.length && !recording && videoRef.current) {
|
|
51
56
|
const finalVideo = new Blob(recordedChunks, {
|
|
52
57
|
type: mimeType
|
|
@@ -76,40 +81,7 @@ const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwar
|
|
|
76
81
|
};
|
|
77
82
|
}
|
|
78
83
|
}, [playing]);
|
|
79
|
-
const
|
|
80
|
-
const videoConfResponse = await odeServices.video().getVideoConf();
|
|
81
|
-
setMaxDuration((videoConfResponse.maxDuration ?? 3) * 60 * 1e3);
|
|
82
|
-
}, initInputDevices = async () => {
|
|
83
|
-
const videoDevices = (await navigator.mediaDevices.enumerateDevices()).filter((device2) => device2.kind === "videoinput");
|
|
84
|
-
switch (device.type) {
|
|
85
|
-
case "mobile":
|
|
86
|
-
case "tablet": {
|
|
87
|
-
const backCamera = {
|
|
88
|
-
deviceId: "environment",
|
|
89
|
-
label: t("video.back.camera"),
|
|
90
|
-
groupId: "",
|
|
91
|
-
kind: "videoinput"
|
|
92
|
-
}, frontCamera = {
|
|
93
|
-
deviceId: "user",
|
|
94
|
-
label: t("video.front.camera"),
|
|
95
|
-
groupId: "",
|
|
96
|
-
kind: "videoinput"
|
|
97
|
-
};
|
|
98
|
-
(videoDevices == null ? void 0 : videoDevices.length) > 1 ? setInputDevices([backCamera, frontCamera]) : setInputDevices([backCamera]);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
default:
|
|
102
|
-
setInputDevices(videoDevices);
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}, enableStream = async (mediaStreamConstraints2) => {
|
|
106
|
-
try {
|
|
107
|
-
const mediaStream = await navigator.mediaDevices.getUserMedia(mediaStreamConstraints2);
|
|
108
|
-
setStream(mediaStream), videoRef.current && (videoRef.current.src && (window.URL.revokeObjectURL(videoRef.current.src), videoRef.current.src = ""), videoRef.current.srcObject = mediaStream, videoRef.current.autoplay = !0, videoRef.current.volume = 1, videoRef.current.muted = !0);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
console.error(err);
|
|
111
|
-
}
|
|
112
|
-
}, handleRecord = useCallback(() => {
|
|
84
|
+
const handleRecord = useCallback(() => {
|
|
113
85
|
setRecording(!0), videoRef && videoRef.current && (videoRef.current.muted = !0);
|
|
114
86
|
const mimeType2 = getBestSupportedMimeType();
|
|
115
87
|
setMimeType(mimeType2), stream && (recorderRef.current = new MediaRecorder(stream, {
|
|
@@ -126,7 +98,7 @@ const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwar
|
|
|
126
98
|
var _a, _b;
|
|
127
99
|
videoRef && videoRef.current && (videoRef.current.muted = !1), playing ? ((_b = videoRef == null ? void 0 : videoRef.current) == null || _b.pause(), setPlaying(!1)) : ((_a = videoRef == null ? void 0 : videoRef.current) == null || _a.play(), setPlaying(!0));
|
|
128
100
|
}, [playing]), handleReset = () => {
|
|
129
|
-
setRecorded(!1), setRecording(!1), setPlaying(!1), setSaved(!1), setRecordedTime(0), setRecordedChunks([]), setRecordedVideo(void 0),
|
|
101
|
+
setRecorded(!1), setRecording(!1), setPlaying(!1), setSaved(!1), setRecordedTime(0), setRecordedChunks([]), setRecordedVideo(void 0), restartStream(), onRecordUpdated && onRecordUpdated();
|
|
130
102
|
}, handleSave = async () => {
|
|
131
103
|
var _a;
|
|
132
104
|
if ((_a = videoRef == null ? void 0 : videoRef.current) == null || _a.pause(), setSaving(!0), !recordedVideo) {
|
|
@@ -141,95 +113,14 @@ const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwar
|
|
|
141
113
|
onError("Error while uploading video"), setSaving(!1), setSaved(!0);
|
|
142
114
|
}, handleEnded = () => {
|
|
143
115
|
setPlaying(!1), setPlayedTime(0), videoRef.current && (videoRef.current.currentTime = 0);
|
|
144
|
-
}, handleInputDeviceChange = (option) => {
|
|
116
|
+
}, handleInputDeviceChange = useCallback((option) => {
|
|
145
117
|
var _a;
|
|
146
118
|
const selectedDevice = inputDevices.find((inputDevice) => inputDevice.label === option);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
video: {
|
|
151
|
-
aspectRatio: VIDEO_WIDTH / VIDEO_HEIGHT,
|
|
152
|
-
facingMode: selectedDevice == null ? void 0 : selectedDevice.deviceId
|
|
153
|
-
}
|
|
154
|
-
} : mediaStreamConstraints2 = {
|
|
155
|
-
audio: !0,
|
|
156
|
-
video: {
|
|
157
|
-
aspectRatio: VIDEO_WIDTH / VIDEO_HEIGHT,
|
|
158
|
-
deviceId: selectedDevice.deviceId
|
|
159
|
-
}
|
|
160
|
-
}, setMediaStreamConstraints(mediaStreamConstraints2), stream && (((_a = recorderRef.current) == null ? void 0 : _a.state) === "recording" && (recorderRef.current.requestData(), recorderRef.current.stop()), stream.getTracks().forEach((track) => track.stop()), setStream(void 0)), enableStream(mediaStreamConstraints2)) : console.error("Selected input device id is null");
|
|
161
|
-
};
|
|
162
|
-
useEffect(() => {
|
|
119
|
+
((_a = recorderRef.current) == null ? void 0 : _a.state) === "recording" && (recorderRef.current.requestData(), recorderRef.current.stop()), setPreferedDevice(selectedDevice);
|
|
120
|
+
}, [inputDevices, stream]);
|
|
121
|
+
return useEffect(() => {
|
|
163
122
|
recordedTime >= maxDuration && handleStop();
|
|
164
|
-
}, [recordedTime, handleStop])
|
|
165
|
-
const toolbarItems = [{
|
|
166
|
-
type: "icon",
|
|
167
|
-
name: "record",
|
|
168
|
-
props: {
|
|
169
|
-
icon: /* @__PURE__ */ jsx(SvgIconRecord, { color: recording || recorded ? "" : "red" }),
|
|
170
|
-
color: "danger",
|
|
171
|
-
disabled: recording || recorded || saving,
|
|
172
|
-
onClick: handleRecord,
|
|
173
|
-
"aria-label": t("bbm.video.record.start")
|
|
174
|
-
},
|
|
175
|
-
tooltip: t("bbm.video.record.start")
|
|
176
|
-
}, {
|
|
177
|
-
type: "icon",
|
|
178
|
-
name: "stop",
|
|
179
|
-
props: {
|
|
180
|
-
icon: /* @__PURE__ */ jsx(SvgIconRecordStop, {}),
|
|
181
|
-
disabled: !recording || recorded || saving,
|
|
182
|
-
onClick: handleStop,
|
|
183
|
-
"aria-label": t("bbm.video.record.stop")
|
|
184
|
-
},
|
|
185
|
-
tooltip: t("bbm.video.record.stop")
|
|
186
|
-
}, {
|
|
187
|
-
type: "icon",
|
|
188
|
-
name: "play",
|
|
189
|
-
visibility: playing ? "hide" : "show",
|
|
190
|
-
props: {
|
|
191
|
-
icon: /* @__PURE__ */ jsx(SvgIconPlayFilled, {}),
|
|
192
|
-
disabled: !recorded || saving,
|
|
193
|
-
onClick: handlePlayPause,
|
|
194
|
-
"aria-label": t("bbm.video.play.start")
|
|
195
|
-
},
|
|
196
|
-
tooltip: t("bbm.video.play.start")
|
|
197
|
-
}, {
|
|
198
|
-
type: "icon",
|
|
199
|
-
name: "pause",
|
|
200
|
-
visibility: playing ? "show" : "hide",
|
|
201
|
-
props: {
|
|
202
|
-
icon: /* @__PURE__ */ jsx(SvgIconPause, {}),
|
|
203
|
-
disabled: !recorded || saving,
|
|
204
|
-
onClick: handlePlayPause,
|
|
205
|
-
"aria-label": t("bbm.video.play.pause")
|
|
206
|
-
},
|
|
207
|
-
tooltip: t("bbm.video.play.pause")
|
|
208
|
-
}, {
|
|
209
|
-
type: "divider"
|
|
210
|
-
}, {
|
|
211
|
-
type: "icon",
|
|
212
|
-
name: "reset",
|
|
213
|
-
props: {
|
|
214
|
-
icon: /* @__PURE__ */ jsx(SvgIconRefresh, {}),
|
|
215
|
-
disabled: !recorded || saving,
|
|
216
|
-
onClick: handleReset,
|
|
217
|
-
"aria-label": t("bbm.video.record.reset")
|
|
218
|
-
},
|
|
219
|
-
tooltip: t("bbm.video.record.reset")
|
|
220
|
-
}, {
|
|
221
|
-
type: "icon",
|
|
222
|
-
name: "save",
|
|
223
|
-
visibility: hideSaveAction ? "hide" : "show",
|
|
224
|
-
props: {
|
|
225
|
-
icon: /* @__PURE__ */ jsx(SvgIconSave, {}),
|
|
226
|
-
disabled: !recorded || saving || saved,
|
|
227
|
-
onClick: handleSave,
|
|
228
|
-
"aria-label": t("bbm.video.record.save")
|
|
229
|
-
},
|
|
230
|
-
tooltip: t("bbm.video.record.save")
|
|
231
|
-
}];
|
|
232
|
-
return /* @__PURE__ */ jsxs("div", { className: "video-recorder d-flex flex-fill flex-column align-items-center pb-8", children: [
|
|
123
|
+
}, [recordedTime, handleStop]), /* @__PURE__ */ jsxs("div", { className: "video-recorder d-flex flex-fill flex-column align-items-center pb-8", children: [
|
|
233
124
|
/* @__PURE__ */ jsx("div", { className: "video-recorder-caption d-none d-md-block", children: caption }),
|
|
234
125
|
inputDevices.length > 1 && /* @__PURE__ */ jsx("div", { className: "video-recorder-devices mb-12", children: /* @__PURE__ */ jsxs(FormControl, { id: "selectInputDevice", children: [
|
|
235
126
|
/* @__PURE__ */ jsx(Label, { className: "d-none d-md-block", children: t("bbm.video.record.select.devices.label") }),
|
|
@@ -255,7 +146,7 @@ const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16, VideoRecorder = /* @__PURE__ */ forwar
|
|
|
255
146
|
] })
|
|
256
147
|
] })
|
|
257
148
|
] }),
|
|
258
|
-
stream && /* @__PURE__ */ jsx(
|
|
149
|
+
stream && /* @__PURE__ */ jsx(VideoRecorderToolbar, { playing, recording, recorded, saving, saved, hideSaveAction, handleRecord, handleStop, handlePlayPause, handleReset, handleSave })
|
|
259
150
|
] }),
|
|
260
151
|
saving && /* @__PURE__ */ jsx(LoadingScreen, { position: !1, caption: t("bbm.video.save.loader.caption") })
|
|
261
152
|
] });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { WorkspaceElement } from '@edifice.io/client';
|
|
2
|
+
export interface VideoRecorderToolbarProps {
|
|
3
|
+
playing: boolean;
|
|
4
|
+
recording: boolean;
|
|
5
|
+
recorded: boolean;
|
|
6
|
+
saving: boolean;
|
|
7
|
+
saved: boolean;
|
|
8
|
+
hideSaveAction: boolean;
|
|
9
|
+
handleRecord: () => void;
|
|
10
|
+
handleStop: () => void;
|
|
11
|
+
handlePlayPause: () => void;
|
|
12
|
+
handleReset: () => void;
|
|
13
|
+
handleSave: () => void;
|
|
14
|
+
}
|
|
15
|
+
export interface VideoRecorderRef {
|
|
16
|
+
save: () => Promise<WorkspaceElement | undefined>;
|
|
17
|
+
}
|
|
18
|
+
export declare const VideoRecorderToolbar: ({ playing, recording, recorded, saving, saved, hideSaveAction, handleRecord, handleStop, handlePlayPause, handleReset, handleSave, }: VideoRecorderToolbarProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import SvgIconPause from "../../icons/components/IconPause.js";
|
|
4
|
+
import SvgIconPlayFilled from "../../icons/components/IconPlayFilled.js";
|
|
5
|
+
import SvgIconRecordStop from "../../icons/components/IconRecordStop.js";
|
|
6
|
+
import SvgIconRecord from "../../icons/components/IconRecord.js";
|
|
7
|
+
import SvgIconRefresh from "../../icons/components/IconRefresh.js";
|
|
8
|
+
import SvgIconSave from "../../icons/components/IconSave.js";
|
|
9
|
+
import { Toolbar } from "../../../components/Toolbar/Toolbar.js";
|
|
10
|
+
const VideoRecorderToolbar = ({
|
|
11
|
+
playing,
|
|
12
|
+
recording,
|
|
13
|
+
recorded,
|
|
14
|
+
saving,
|
|
15
|
+
saved,
|
|
16
|
+
hideSaveAction,
|
|
17
|
+
handleRecord,
|
|
18
|
+
handleStop,
|
|
19
|
+
handlePlayPause,
|
|
20
|
+
handleReset,
|
|
21
|
+
handleSave
|
|
22
|
+
}) => {
|
|
23
|
+
const {
|
|
24
|
+
t
|
|
25
|
+
} = useTranslation(), toolbarItems = [{
|
|
26
|
+
type: "icon",
|
|
27
|
+
name: "record",
|
|
28
|
+
props: {
|
|
29
|
+
icon: /* @__PURE__ */ jsx(SvgIconRecord, { color: recording || recorded ? "" : "red" }),
|
|
30
|
+
color: "danger",
|
|
31
|
+
disabled: recording || recorded || saving,
|
|
32
|
+
onClick: handleRecord,
|
|
33
|
+
"aria-label": t("bbm.video.record.start")
|
|
34
|
+
},
|
|
35
|
+
tooltip: t("bbm.video.record.start")
|
|
36
|
+
}, {
|
|
37
|
+
type: "icon",
|
|
38
|
+
name: "stop",
|
|
39
|
+
props: {
|
|
40
|
+
icon: /* @__PURE__ */ jsx(SvgIconRecordStop, {}),
|
|
41
|
+
disabled: !recording || recorded || saving,
|
|
42
|
+
onClick: handleStop,
|
|
43
|
+
"aria-label": t("bbm.video.record.stop")
|
|
44
|
+
},
|
|
45
|
+
tooltip: t("bbm.video.record.stop")
|
|
46
|
+
}, {
|
|
47
|
+
type: "icon",
|
|
48
|
+
name: "play",
|
|
49
|
+
visibility: playing ? "hide" : "show",
|
|
50
|
+
props: {
|
|
51
|
+
icon: /* @__PURE__ */ jsx(SvgIconPlayFilled, {}),
|
|
52
|
+
disabled: !recorded || saving,
|
|
53
|
+
onClick: handlePlayPause,
|
|
54
|
+
"aria-label": t("bbm.video.play.start")
|
|
55
|
+
},
|
|
56
|
+
tooltip: t("bbm.video.play.start")
|
|
57
|
+
}, {
|
|
58
|
+
type: "icon",
|
|
59
|
+
name: "pause",
|
|
60
|
+
visibility: playing ? "show" : "hide",
|
|
61
|
+
props: {
|
|
62
|
+
icon: /* @__PURE__ */ jsx(SvgIconPause, {}),
|
|
63
|
+
disabled: !recorded || saving,
|
|
64
|
+
onClick: handlePlayPause,
|
|
65
|
+
"aria-label": t("bbm.video.play.pause")
|
|
66
|
+
},
|
|
67
|
+
tooltip: t("bbm.video.play.pause")
|
|
68
|
+
}, {
|
|
69
|
+
type: "divider"
|
|
70
|
+
}, {
|
|
71
|
+
type: "icon",
|
|
72
|
+
name: "reset",
|
|
73
|
+
props: {
|
|
74
|
+
icon: /* @__PURE__ */ jsx(SvgIconRefresh, {}),
|
|
75
|
+
disabled: !recorded || saving,
|
|
76
|
+
onClick: handleReset,
|
|
77
|
+
"aria-label": t("bbm.video.record.reset")
|
|
78
|
+
},
|
|
79
|
+
tooltip: t("bbm.video.record.reset")
|
|
80
|
+
}, {
|
|
81
|
+
type: "icon",
|
|
82
|
+
name: "save",
|
|
83
|
+
visibility: hideSaveAction ? "hide" : "show",
|
|
84
|
+
props: {
|
|
85
|
+
icon: /* @__PURE__ */ jsx(SvgIconSave, {}),
|
|
86
|
+
disabled: !recorded || saving || saved,
|
|
87
|
+
onClick: handleSave,
|
|
88
|
+
"aria-label": t("bbm.video.record.save")
|
|
89
|
+
},
|
|
90
|
+
tooltip: t("bbm.video.record.save")
|
|
91
|
+
}];
|
|
92
|
+
return /* @__PURE__ */ jsx(Toolbar, { items: toolbarItems, className: "position-absolute bottom-0 start-50 bg-white" });
|
|
93
|
+
};
|
|
94
|
+
export {
|
|
95
|
+
VideoRecorderToolbar
|
|
96
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function useCameras(): {
|
|
2
|
+
/** Readonly list (array) of available video input devices. */
|
|
3
|
+
inputDevices: MediaDeviceInfo[];
|
|
4
|
+
/** Select which input video device to use. */
|
|
5
|
+
setPreferedDevice: (device?: MediaDeviceInfo) => void;
|
|
6
|
+
/** The current video stream. */
|
|
7
|
+
stream: MediaStream | undefined;
|
|
8
|
+
/** Start a video stream from the default or prefered device. */
|
|
9
|
+
restartStream: () => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import useBrowserInfo from "../../../hooks/useBrowserInfo/useBrowserInfo.js";
|
|
4
|
+
const VIDEO_HEIGHT = 9, VIDEO_WIDTH = 16;
|
|
5
|
+
function useCameras() {
|
|
6
|
+
const {
|
|
7
|
+
device
|
|
8
|
+
} = useBrowserInfo(navigator.userAgent), {
|
|
9
|
+
t
|
|
10
|
+
} = useTranslation(), [inputDevices, setInputDevices] = useState([]), [mediaStreamConstraints, setMediaStreamConstraints] = useState({
|
|
11
|
+
audio: !0,
|
|
12
|
+
video: {
|
|
13
|
+
facingMode: "environment",
|
|
14
|
+
aspectRatio: VIDEO_WIDTH / VIDEO_HEIGHT
|
|
15
|
+
}
|
|
16
|
+
}), [stream, setStream] = useState();
|
|
17
|
+
async function getVideoInputDevices() {
|
|
18
|
+
return (await navigator.mediaDevices.enumerateDevices()).filter((device2) => device2.kind === "videoinput");
|
|
19
|
+
}
|
|
20
|
+
async function enableStream(mediaStreamConstraints2) {
|
|
21
|
+
try {
|
|
22
|
+
const mediaStream = await navigator.mediaDevices.getUserMedia(mediaStreamConstraints2);
|
|
23
|
+
setStream((previousStream) => (previousStream == null || previousStream.getTracks().forEach((track) => track.stop()), mediaStream));
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const restartStream = () => {
|
|
29
|
+
enableStream(mediaStreamConstraints);
|
|
30
|
+
}, setPreferedDevice = (device2) => {
|
|
31
|
+
let mediaStreamConstraints2 = {};
|
|
32
|
+
device2 != null && device2.deviceId ? ((device2 == null ? void 0 : device2.deviceId) === "environment" || (device2 == null ? void 0 : device2.deviceId) === "user" ? mediaStreamConstraints2 = {
|
|
33
|
+
audio: !0,
|
|
34
|
+
video: {
|
|
35
|
+
aspectRatio: VIDEO_WIDTH / VIDEO_HEIGHT,
|
|
36
|
+
facingMode: device2 == null ? void 0 : device2.deviceId
|
|
37
|
+
}
|
|
38
|
+
} : mediaStreamConstraints2 = {
|
|
39
|
+
audio: !0,
|
|
40
|
+
video: {
|
|
41
|
+
aspectRatio: VIDEO_WIDTH / VIDEO_HEIGHT,
|
|
42
|
+
deviceId: device2.deviceId
|
|
43
|
+
}
|
|
44
|
+
}, setMediaStreamConstraints(mediaStreamConstraints2), restartStream()) : console.error("Selected input device id is null");
|
|
45
|
+
};
|
|
46
|
+
return useEffect(() => {
|
|
47
|
+
async function initInputDevices() {
|
|
48
|
+
await enableStream(mediaStreamConstraints);
|
|
49
|
+
const videoDevices = await getVideoInputDevices();
|
|
50
|
+
switch (device.type) {
|
|
51
|
+
case "mobile":
|
|
52
|
+
case "tablet": {
|
|
53
|
+
const backCamera = {
|
|
54
|
+
deviceId: "environment",
|
|
55
|
+
label: t("video.back.camera"),
|
|
56
|
+
groupId: "",
|
|
57
|
+
kind: "videoinput"
|
|
58
|
+
}, frontCamera = {
|
|
59
|
+
deviceId: "user",
|
|
60
|
+
label: t("video.front.camera"),
|
|
61
|
+
groupId: "",
|
|
62
|
+
kind: "videoinput"
|
|
63
|
+
};
|
|
64
|
+
(videoDevices == null ? void 0 : videoDevices.length) > 1 ? setInputDevices([backCamera, frontCamera]) : setInputDevices([backCamera]);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
setInputDevices(videoDevices);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
initInputDevices();
|
|
73
|
+
}, []), {
|
|
74
|
+
/** Readonly list (array) of available video input devices. */
|
|
75
|
+
inputDevices,
|
|
76
|
+
/** Select which input video device to use. */
|
|
77
|
+
setPreferedDevice,
|
|
78
|
+
/** The current video stream. */
|
|
79
|
+
stream,
|
|
80
|
+
/** Start a video stream from the default or prefered device. */
|
|
81
|
+
restartStream
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
useCameras
|
|
86
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edifice.io/react",
|
|
3
|
-
"version": "2.3.1-develop-b2school
|
|
3
|
+
"version": "2.3.1-develop-b2school.20250916153842",
|
|
4
4
|
"description": "Edifice React Library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
"react-slugify": "^3.0.3",
|
|
131
131
|
"swiper": "^10.1.0",
|
|
132
132
|
"ua-parser-js": "^1.0.36",
|
|
133
|
-
"@edifice.io/bootstrap": "2.3.1-develop-b2school
|
|
134
|
-
"@edifice.io/tiptap-extensions": "2.3.1-develop-b2school
|
|
135
|
-
"@edifice.io/utilities": "2.3.1-develop-b2school
|
|
133
|
+
"@edifice.io/bootstrap": "2.3.1-develop-b2school.20250916153842",
|
|
134
|
+
"@edifice.io/tiptap-extensions": "2.3.1-develop-b2school.20250916153842",
|
|
135
|
+
"@edifice.io/utilities": "2.3.1-develop-b2school.20250916153842"
|
|
136
136
|
},
|
|
137
137
|
"devDependencies": {
|
|
138
138
|
"@babel/plugin-transform-react-pure-annotations": "^7.23.3",
|
|
@@ -163,8 +163,8 @@
|
|
|
163
163
|
"vite": "^5.4.11",
|
|
164
164
|
"vite-plugin-dts": "^4.1.0",
|
|
165
165
|
"vite-tsconfig-paths": "^5.0.1",
|
|
166
|
-
"@edifice.io/client": "2.3.1-develop-b2school
|
|
167
|
-
"@edifice.io/config": "2.3.1-develop-b2school
|
|
166
|
+
"@edifice.io/client": "2.3.1-develop-b2school.20250916153842",
|
|
167
|
+
"@edifice.io/config": "2.3.1-develop-b2school.20250916153842"
|
|
168
168
|
},
|
|
169
169
|
"peerDependencies": {
|
|
170
170
|
"@react-spring/web": "^9.7.5",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Editor component properties
|
|
3
|
-
*/
|
|
4
|
-
export interface EditorPreviewProps {
|
|
5
|
-
/** Rich content to render. */
|
|
6
|
-
content: string;
|
|
7
|
-
/** Display with or without a border */
|
|
8
|
-
variant?: 'outline' | 'ghost';
|
|
9
|
-
onDetailClick?: () => void;
|
|
10
|
-
onMediaClick?: () => void;
|
|
11
|
-
maxMediaDisplayed?: number;
|
|
12
|
-
}
|
|
13
|
-
declare const EditorPreview: ({ content, variant, onDetailClick, onMediaClick, maxMediaDisplayed, }: EditorPreviewProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
-
export default EditorPreview;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from "react";
|
|
3
|
-
import clsx from "clsx";
|
|
4
|
-
import { getThumbnail } from "@edifice.io/utilities";
|
|
5
|
-
import { useTranslation } from "react-i18next";
|
|
6
|
-
import Image from "../../../../components/Image/Image.js";
|
|
7
|
-
const EditorPreview = ({
|
|
8
|
-
content,
|
|
9
|
-
variant = "outline",
|
|
10
|
-
onDetailClick,
|
|
11
|
-
onMediaClick,
|
|
12
|
-
maxMediaDisplayed = 3
|
|
13
|
-
}) => {
|
|
14
|
-
const {
|
|
15
|
-
t
|
|
16
|
-
} = useTranslation(), [summaryContent, setSummaryContent] = useState(""), [medias, setMedias] = useState([]), borderClass = clsx(variant === "outline" && "border rounded-3 py-12 px-16"), hasMediaCallback = onDetailClick || onMediaClick, handleMediaClick = (e) => {
|
|
17
|
-
onMediaClick && (e.stopPropagation(), onMediaClick());
|
|
18
|
-
};
|
|
19
|
-
return useEffect(() => {
|
|
20
|
-
const contentHTML = content;
|
|
21
|
-
if (contentHTML) {
|
|
22
|
-
const doc = new DOMParser().parseFromString(contentHTML, "text/html"), mediaElements = Array.from(doc.querySelectorAll("img, video, iframe, audio, embed"));
|
|
23
|
-
setMedias(mediaElements.filter((el) => el.tagName.toLowerCase() === "img").map((el) => {
|
|
24
|
-
const image = el;
|
|
25
|
-
return image.src ? {
|
|
26
|
-
url: getThumbnail(image.src, 0, 300),
|
|
27
|
-
alt: image.alt
|
|
28
|
-
} : {
|
|
29
|
-
url: "",
|
|
30
|
-
alt: ""
|
|
31
|
-
};
|
|
32
|
-
})), mediaElements.forEach((el) => {
|
|
33
|
-
var _a;
|
|
34
|
-
return (_a = el.parentNode) == null ? void 0 : _a.removeChild(el);
|
|
35
|
-
}), setSummaryContent(doc.body.textContent || "");
|
|
36
|
-
}
|
|
37
|
-
}, [content]), /* @__PURE__ */ jsxs("div", { className: borderClass, "data-testid": "editor-preview", onClick: onDetailClick, tabIndex: onDetailClick ? -1 : void 0, role: onDetailClick ? "button" : void 0, children: [
|
|
38
|
-
/* @__PURE__ */ jsx("div", { className: "flex-fill text-truncate text-truncate-2 post-preview-content overflow-hidden", children: summaryContent }),
|
|
39
|
-
/* @__PURE__ */ jsx("div", { onClick: handleMediaClick, tabIndex: hasMediaCallback ? -1 : void 0, role: hasMediaCallback ? "button" : void 0, className: "d-flex align-items-center justify-content-center gap-24 px-32 pt-16", children: medias.slice(0, maxMediaDisplayed).map((media, index) => /* @__PURE__ */ jsxs("div", { className: clsx("position-relative col-12 col-md-4 ", {
|
|
40
|
-
"d-none d-md-block": index >= 1
|
|
41
|
-
}), style: {
|
|
42
|
-
maxWidth: "150px"
|
|
43
|
-
}, children: [
|
|
44
|
-
/* @__PURE__ */ jsx(Image, { alt: media.alt, objectFit: "cover", ratio: "16", className: "rounded", src: media.url, sizes: "" }),
|
|
45
|
-
(index === 0 || index === 2) && medias.length - (index + 1) > 0 && /* @__PURE__ */ jsx("div", { className: clsx("position-absolute top-0 bottom-0 start-0 end-0 d-flex justify-content-center align-items-center rounded text-light bg-dark bg-opacity-50", {
|
|
46
|
-
"d-flex d-md-none": index === 0,
|
|
47
|
-
"d-none d-md-flex": index === 2
|
|
48
|
-
}), children: t("editor.preview.moreMedia", {
|
|
49
|
-
mediaCount: medias.length - (index + 1)
|
|
50
|
-
}) })
|
|
51
|
-
] }, media.url)) })
|
|
52
|
-
] });
|
|
53
|
-
};
|
|
54
|
-
export {
|
|
55
|
-
EditorPreview as default
|
|
56
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Editor component properties
|
|
3
|
-
*/
|
|
4
|
-
export interface EditorPreviewSkeletonProps {
|
|
5
|
-
variant?: 'outline' | 'ghost';
|
|
6
|
-
}
|
|
7
|
-
declare const EditorPreview: ({ variant }: EditorPreviewSkeletonProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export default EditorPreview;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import clsx from "clsx";
|
|
3
|
-
import TextSkeleton from "../../../../components/Skeleton/TextSkeleton.js";
|
|
4
|
-
import Image from "../../../../components/Image/Image.js";
|
|
5
|
-
const EditorPreview = ({
|
|
6
|
-
variant = "outline"
|
|
7
|
-
}) => {
|
|
8
|
-
const borderClass = clsx(variant === "outline" && "border rounded-3"), contentClass = clsx("mt-16", variant === "outline" && "my-12 mx-16");
|
|
9
|
-
return /* @__PURE__ */ jsx("div", { className: borderClass, "data-testid": "editor-preview", children: /* @__PURE__ */ jsxs("div", { className: contentClass, children: [
|
|
10
|
-
/* @__PURE__ */ jsx(TextSkeleton, { className: "col-12" }),
|
|
11
|
-
/* @__PURE__ */ jsx(TextSkeleton, { className: "col-12" }),
|
|
12
|
-
/* @__PURE__ */ jsxs("div", { className: "d-flex justify-content-center gap-24 px-32 pt-16", children: [
|
|
13
|
-
/* @__PURE__ */ jsx("div", { style: {
|
|
14
|
-
maxWidth: "150px"
|
|
15
|
-
}, className: "col-12 col-md-4", children: /* @__PURE__ */ jsx(Image, { alt: "", objectFit: "cover", ratio: "16", className: "rounded placeholder", src: "", sizes: "" }) }),
|
|
16
|
-
/* @__PURE__ */ jsx("div", { style: {
|
|
17
|
-
maxWidth: "150px"
|
|
18
|
-
}, className: "col-12 col-md-4", children: /* @__PURE__ */ jsx(Image, { alt: "", objectFit: "cover", ratio: "16", className: "rounded placeholder", src: "", sizes: "" }) })
|
|
19
|
-
] })
|
|
20
|
-
] }) });
|
|
21
|
-
};
|
|
22
|
-
export {
|
|
23
|
-
EditorPreview as default
|
|
24
|
-
};
|