@natoe/colab 0.1.19 → 0.1.22
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/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +232 -108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +232 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3088,6 +3088,121 @@ var styles6 = {
|
|
|
3088
3088
|
animation: "natoe-colab-message-in 180ms ease-out"
|
|
3089
3089
|
}
|
|
3090
3090
|
};
|
|
3091
|
+
function useAudioRecorder() {
|
|
3092
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
3093
|
+
const [duration, setDuration] = useState(0);
|
|
3094
|
+
const [error, setError] = useState(null);
|
|
3095
|
+
const mediaRecorderRef = useRef(null);
|
|
3096
|
+
const chunksRef = useRef([]);
|
|
3097
|
+
const streamRef = useRef(null);
|
|
3098
|
+
const timerRef = useRef(null);
|
|
3099
|
+
const startTimeRef = useRef(0);
|
|
3100
|
+
const resolveRef = useRef(null);
|
|
3101
|
+
const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
|
|
3102
|
+
const cleanup = useCallback(() => {
|
|
3103
|
+
if (timerRef.current) {
|
|
3104
|
+
clearInterval(timerRef.current);
|
|
3105
|
+
timerRef.current = null;
|
|
3106
|
+
}
|
|
3107
|
+
if (streamRef.current) {
|
|
3108
|
+
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
3109
|
+
streamRef.current = null;
|
|
3110
|
+
}
|
|
3111
|
+
mediaRecorderRef.current = null;
|
|
3112
|
+
chunksRef.current = [];
|
|
3113
|
+
setIsRecording(false);
|
|
3114
|
+
setDuration(0);
|
|
3115
|
+
}, []);
|
|
3116
|
+
const start = useCallback(async () => {
|
|
3117
|
+
if (!isSupported) {
|
|
3118
|
+
setError("Audio recording is not supported in this browser");
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
try {
|
|
3122
|
+
setError(null);
|
|
3123
|
+
chunksRef.current = [];
|
|
3124
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
3125
|
+
streamRef.current = stream;
|
|
3126
|
+
const mimeType = MediaRecorder.isTypeSupported(AUDIO_MIME_TYPE) ? AUDIO_MIME_TYPE : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
|
|
3127
|
+
const recorder = new MediaRecorder(stream, { mimeType });
|
|
3128
|
+
mediaRecorderRef.current = recorder;
|
|
3129
|
+
recorder.ondataavailable = (event) => {
|
|
3130
|
+
if (event.data.size > 0) {
|
|
3131
|
+
chunksRef.current.push(event.data);
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
recorder.onerror = () => {
|
|
3135
|
+
setError("Recording failed");
|
|
3136
|
+
cleanup();
|
|
3137
|
+
resolveRef.current?.(null);
|
|
3138
|
+
resolveRef.current = null;
|
|
3139
|
+
};
|
|
3140
|
+
recorder.onstop = () => {
|
|
3141
|
+
const finalDuration = Math.round((Date.now() - startTimeRef.current) / 1e3);
|
|
3142
|
+
const blob = new Blob(chunksRef.current, { type: mimeType });
|
|
3143
|
+
cleanup();
|
|
3144
|
+
resolveRef.current?.({ blob, duration: finalDuration });
|
|
3145
|
+
resolveRef.current = null;
|
|
3146
|
+
};
|
|
3147
|
+
recorder.start(250);
|
|
3148
|
+
startTimeRef.current = Date.now();
|
|
3149
|
+
setIsRecording(true);
|
|
3150
|
+
timerRef.current = setInterval(() => {
|
|
3151
|
+
const elapsed = Math.round((Date.now() - startTimeRef.current) / 1e3);
|
|
3152
|
+
setDuration(elapsed);
|
|
3153
|
+
}, 1e3);
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
if (err instanceof DOMException && err.name === "NotAllowedError") {
|
|
3156
|
+
setError("Microphone access denied. Please allow microphone permissions.");
|
|
3157
|
+
} else {
|
|
3158
|
+
setError("Failed to start recording");
|
|
3159
|
+
}
|
|
3160
|
+
cleanup();
|
|
3161
|
+
}
|
|
3162
|
+
}, [isSupported, cleanup]);
|
|
3163
|
+
const stop = useCallback(async () => {
|
|
3164
|
+
return new Promise((resolve) => {
|
|
3165
|
+
if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
|
|
3166
|
+
resolve(null);
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
resolveRef.current = resolve;
|
|
3170
|
+
mediaRecorderRef.current.stop();
|
|
3171
|
+
});
|
|
3172
|
+
}, []);
|
|
3173
|
+
const cancel = useCallback(() => {
|
|
3174
|
+
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
3175
|
+
mediaRecorderRef.current.onstop = null;
|
|
3176
|
+
mediaRecorderRef.current.stop();
|
|
3177
|
+
}
|
|
3178
|
+
cleanup();
|
|
3179
|
+
resolveRef.current?.(null);
|
|
3180
|
+
resolveRef.current = null;
|
|
3181
|
+
}, [cleanup]);
|
|
3182
|
+
return {
|
|
3183
|
+
isRecording,
|
|
3184
|
+
duration,
|
|
3185
|
+
start,
|
|
3186
|
+
stop,
|
|
3187
|
+
cancel,
|
|
3188
|
+
isSupported,
|
|
3189
|
+
error
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
function MicIcon({ size = 20, color = "currentColor" }) {
|
|
3193
|
+
return /* @__PURE__ */ jsx(
|
|
3194
|
+
"svg",
|
|
3195
|
+
{
|
|
3196
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3197
|
+
width: size,
|
|
3198
|
+
height: size,
|
|
3199
|
+
viewBox: "0 0 24 24",
|
|
3200
|
+
fill: color,
|
|
3201
|
+
"aria-hidden": "true",
|
|
3202
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" })
|
|
3203
|
+
}
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3091
3206
|
function ReplyPreview({ message, onCancel, className }) {
|
|
3092
3207
|
const preview = renderQuotedPreview(message);
|
|
3093
3208
|
return /* @__PURE__ */ jsxs("div", { className, style: styles7.container, children: [
|
|
@@ -3224,8 +3339,7 @@ function CloseIcon({ size = 18, color = "currentColor" }) {
|
|
|
3224
3339
|
}
|
|
3225
3340
|
function MessageInput({
|
|
3226
3341
|
onSendText,
|
|
3227
|
-
|
|
3228
|
-
// onSendAudio,
|
|
3342
|
+
onSendAudio,
|
|
3229
3343
|
onSendFile,
|
|
3230
3344
|
onTyping,
|
|
3231
3345
|
replyTo,
|
|
@@ -3242,6 +3356,15 @@ function MessageInput({
|
|
|
3242
3356
|
const typingTimeoutRef = useRef(null);
|
|
3243
3357
|
const isTypingRef = useRef(false);
|
|
3244
3358
|
const sendingRef = useRef(false);
|
|
3359
|
+
const {
|
|
3360
|
+
isRecording,
|
|
3361
|
+
duration,
|
|
3362
|
+
start: startRecording,
|
|
3363
|
+
stop: stopRecording,
|
|
3364
|
+
cancel: cancelRecording,
|
|
3365
|
+
isSupported: micSupported,
|
|
3366
|
+
error: micError
|
|
3367
|
+
} = useAudioRecorder();
|
|
3245
3368
|
const handleTyping = useCallback(() => {
|
|
3246
3369
|
if (!isTypingRef.current) {
|
|
3247
3370
|
isTypingRef.current = true;
|
|
@@ -3290,6 +3413,16 @@ function MessageInput({
|
|
|
3290
3413
|
},
|
|
3291
3414
|
[handleSendText]
|
|
3292
3415
|
);
|
|
3416
|
+
const handleAudioStop = useCallback(async () => {
|
|
3417
|
+
const result = await stopRecording();
|
|
3418
|
+
if (!result || !onSendAudio) return;
|
|
3419
|
+
setIsSending(true);
|
|
3420
|
+
try {
|
|
3421
|
+
await onSendAudio(result.blob, result.duration);
|
|
3422
|
+
} finally {
|
|
3423
|
+
setIsSending(false);
|
|
3424
|
+
}
|
|
3425
|
+
}, [stopRecording, onSendAudio]);
|
|
3293
3426
|
const handleFileSelect = useCallback(
|
|
3294
3427
|
async (e) => {
|
|
3295
3428
|
const file = e.target.files?.[0];
|
|
@@ -3330,7 +3463,18 @@ function MessageInput({
|
|
|
3330
3463
|
}
|
|
3331
3464
|
)
|
|
3332
3465
|
] }),
|
|
3333
|
-
/* @__PURE__ */
|
|
3466
|
+
micError && /* @__PURE__ */ jsx("div", { style: styles8.error, role: "alert", children: /* @__PURE__ */ jsx("span", { style: styles8.errorText, children: micError }) }),
|
|
3467
|
+
isRecording ? /* @__PURE__ */ jsxs("div", { style: styles8.recordingBar, children: [
|
|
3468
|
+
/* @__PURE__ */ jsxs("div", { style: styles8.recordingIndicator, children: [
|
|
3469
|
+
/* @__PURE__ */ jsx("span", { style: styles8.recordingDot }),
|
|
3470
|
+
"Recording ",
|
|
3471
|
+
formatDuration2(duration)
|
|
3472
|
+
] }),
|
|
3473
|
+
/* @__PURE__ */ jsxs("div", { style: styles8.recordingActions, children: [
|
|
3474
|
+
/* @__PURE__ */ jsx("button", { onClick: cancelRecording, style: styles8.cancelButton, type: "button", children: "Cancel" }),
|
|
3475
|
+
/* @__PURE__ */ jsx("button", { onClick: handleAudioStop, style: styles8.stopButton, type: "button", children: "Send" })
|
|
3476
|
+
] })
|
|
3477
|
+
] }) : /* @__PURE__ */ jsxs("div", { style: styles8.inputBar, children: [
|
|
3334
3478
|
/* @__PURE__ */ jsx(
|
|
3335
3479
|
"button",
|
|
3336
3480
|
{
|
|
@@ -3349,6 +3493,7 @@ function MessageInput({
|
|
|
3349
3493
|
ref: fileInputRef,
|
|
3350
3494
|
type: "file",
|
|
3351
3495
|
onChange: handleFileSelect,
|
|
3496
|
+
"aria-label": "Choose a file to upload",
|
|
3352
3497
|
style: { display: "none" },
|
|
3353
3498
|
accept: [
|
|
3354
3499
|
...SUPPORTED_IMAGE_TYPES,
|
|
@@ -3379,7 +3524,19 @@ function MessageInput({
|
|
|
3379
3524
|
style: styles8.textarea
|
|
3380
3525
|
}
|
|
3381
3526
|
),
|
|
3382
|
-
/* @__PURE__ */ jsx(
|
|
3527
|
+
onSendAudio && micSupported && !text.trim() && /* @__PURE__ */ jsx(
|
|
3528
|
+
"button",
|
|
3529
|
+
{
|
|
3530
|
+
onClick: startRecording,
|
|
3531
|
+
disabled: disabled || isSending,
|
|
3532
|
+
"aria-label": "Record voice message",
|
|
3533
|
+
title: "Record voice message",
|
|
3534
|
+
style: styles8.micButton,
|
|
3535
|
+
type: "button",
|
|
3536
|
+
children: /* @__PURE__ */ jsx(MicIcon, { size: 22, color: COLOR.neutral700 })
|
|
3537
|
+
}
|
|
3538
|
+
),
|
|
3539
|
+
text.trim() && /* @__PURE__ */ jsx(
|
|
3383
3540
|
"button",
|
|
3384
3541
|
{
|
|
3385
3542
|
onClick: handleSendText,
|
|
@@ -3397,6 +3554,11 @@ function MessageInput({
|
|
|
3397
3554
|
] })
|
|
3398
3555
|
] });
|
|
3399
3556
|
}
|
|
3557
|
+
function formatDuration2(seconds) {
|
|
3558
|
+
const m = Math.floor(seconds / 60);
|
|
3559
|
+
const s = seconds % 60;
|
|
3560
|
+
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
3561
|
+
}
|
|
3400
3562
|
var styles8 = {
|
|
3401
3563
|
container: {
|
|
3402
3564
|
borderTop: `1px solid ${COLOR.neutral200}`,
|
|
@@ -3473,6 +3635,23 @@ var styles8 = {
|
|
|
3473
3635
|
color: COLOR.neutral700,
|
|
3474
3636
|
transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
|
|
3475
3637
|
},
|
|
3638
|
+
// Same 48-square chrome as attach. Sits between the textarea and send
|
|
3639
|
+
// button when there's no draft text — replaces send while idle so the
|
|
3640
|
+
// input row stays one row tall.
|
|
3641
|
+
micButton: {
|
|
3642
|
+
width: "48px",
|
|
3643
|
+
height: "48px",
|
|
3644
|
+
display: "flex",
|
|
3645
|
+
alignItems: "center",
|
|
3646
|
+
justifyContent: "center",
|
|
3647
|
+
backgroundColor: COLOR.neutral100,
|
|
3648
|
+
border: `1.5px solid ${COLOR.neutral200}`,
|
|
3649
|
+
borderRadius: "14px",
|
|
3650
|
+
cursor: "pointer",
|
|
3651
|
+
flexShrink: 0,
|
|
3652
|
+
color: COLOR.neutral700,
|
|
3653
|
+
transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
|
|
3654
|
+
},
|
|
3476
3655
|
textarea: {
|
|
3477
3656
|
flex: 1,
|
|
3478
3657
|
minHeight: "48px",
|
|
@@ -3505,7 +3684,53 @@ var styles8 = {
|
|
|
3505
3684
|
cursor: "pointer",
|
|
3506
3685
|
flexShrink: 0,
|
|
3507
3686
|
transition: "opacity 120ms ease, background-color 120ms ease"
|
|
3508
|
-
}
|
|
3687
|
+
},
|
|
3688
|
+
recordingBar: {
|
|
3689
|
+
display: "flex",
|
|
3690
|
+
justifyContent: "space-between",
|
|
3691
|
+
alignItems: "center",
|
|
3692
|
+
padding: `${SPACE.S3} ${SPACE.S4}`,
|
|
3693
|
+
backgroundColor: COLOR.dangerBg
|
|
3694
|
+
},
|
|
3695
|
+
recordingIndicator: {
|
|
3696
|
+
display: "flex",
|
|
3697
|
+
alignItems: "center",
|
|
3698
|
+
gap: SPACE.S2,
|
|
3699
|
+
fontSize: FONT_SIZE.sm,
|
|
3700
|
+
color: COLOR.danger,
|
|
3701
|
+
fontWeight: FONT_WEIGHT.medium
|
|
3702
|
+
},
|
|
3703
|
+
recordingDot: {
|
|
3704
|
+
width: SPACE.S2,
|
|
3705
|
+
height: SPACE.S2,
|
|
3706
|
+
borderRadius: RADIUS.full,
|
|
3707
|
+
backgroundColor: COLOR.danger,
|
|
3708
|
+
animation: "pulse 1.5s infinite"
|
|
3709
|
+
},
|
|
3710
|
+
recordingActions: {
|
|
3711
|
+
display: "flex",
|
|
3712
|
+
gap: SPACE.S2
|
|
3713
|
+
},
|
|
3714
|
+
cancelButton: {
|
|
3715
|
+
padding: `${SPACE.S1} ${SPACE.S4}`,
|
|
3716
|
+
fontSize: FONT_SIZE.sm,
|
|
3717
|
+
color: COLOR.neutral500,
|
|
3718
|
+
backgroundColor: COLOR.white,
|
|
3719
|
+
border: `1px solid ${COLOR.neutral300}`,
|
|
3720
|
+
borderRadius: RADIUS.md,
|
|
3721
|
+
cursor: "pointer"
|
|
3722
|
+
},
|
|
3723
|
+
stopButton: {
|
|
3724
|
+
padding: `${SPACE.S1} ${SPACE.S4}`,
|
|
3725
|
+
fontSize: FONT_SIZE.sm,
|
|
3726
|
+
fontWeight: FONT_WEIGHT.medium,
|
|
3727
|
+
color: COLOR.white,
|
|
3728
|
+
backgroundColor: COLOR.danger,
|
|
3729
|
+
border: "none",
|
|
3730
|
+
borderRadius: RADIUS.md,
|
|
3731
|
+
cursor: "pointer"
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3509
3734
|
var ROLE_AVATAR_BG = {
|
|
3510
3735
|
radiologist: "#4f46e5",
|
|
3511
3736
|
// indigo
|
|
@@ -5188,7 +5413,7 @@ function InlineAudioPlayer({
|
|
|
5188
5413
|
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "70%", backgroundColor: pal.audioFg } }),
|
|
5189
5414
|
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } })
|
|
5190
5415
|
] }),
|
|
5191
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.audioDuration, color: pal.audioFg }, children:
|
|
5416
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.audioDuration, color: pal.audioFg }, children: formatDuration3(displayTime) }),
|
|
5192
5417
|
/* @__PURE__ */ jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
|
|
5193
5418
|
] });
|
|
5194
5419
|
}
|
|
@@ -5204,7 +5429,7 @@ function renderMessagePreview(message) {
|
|
|
5204
5429
|
return message.body;
|
|
5205
5430
|
}
|
|
5206
5431
|
}
|
|
5207
|
-
function
|
|
5432
|
+
function formatDuration3(seconds) {
|
|
5208
5433
|
const safe = Math.max(0, Math.floor(seconds));
|
|
5209
5434
|
const m = Math.floor(safe / 60);
|
|
5210
5435
|
const s = safe % 60;
|
|
@@ -6360,107 +6585,6 @@ function useMessages({
|
|
|
6360
6585
|
reset
|
|
6361
6586
|
};
|
|
6362
6587
|
}
|
|
6363
|
-
function useAudioRecorder() {
|
|
6364
|
-
const [isRecording, setIsRecording] = useState(false);
|
|
6365
|
-
const [duration, setDuration] = useState(0);
|
|
6366
|
-
const [error, setError] = useState(null);
|
|
6367
|
-
const mediaRecorderRef = useRef(null);
|
|
6368
|
-
const chunksRef = useRef([]);
|
|
6369
|
-
const streamRef = useRef(null);
|
|
6370
|
-
const timerRef = useRef(null);
|
|
6371
|
-
const startTimeRef = useRef(0);
|
|
6372
|
-
const resolveRef = useRef(null);
|
|
6373
|
-
const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
|
|
6374
|
-
const cleanup = useCallback(() => {
|
|
6375
|
-
if (timerRef.current) {
|
|
6376
|
-
clearInterval(timerRef.current);
|
|
6377
|
-
timerRef.current = null;
|
|
6378
|
-
}
|
|
6379
|
-
if (streamRef.current) {
|
|
6380
|
-
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
6381
|
-
streamRef.current = null;
|
|
6382
|
-
}
|
|
6383
|
-
mediaRecorderRef.current = null;
|
|
6384
|
-
chunksRef.current = [];
|
|
6385
|
-
setIsRecording(false);
|
|
6386
|
-
setDuration(0);
|
|
6387
|
-
}, []);
|
|
6388
|
-
const start = useCallback(async () => {
|
|
6389
|
-
if (!isSupported) {
|
|
6390
|
-
setError("Audio recording is not supported in this browser");
|
|
6391
|
-
return;
|
|
6392
|
-
}
|
|
6393
|
-
try {
|
|
6394
|
-
setError(null);
|
|
6395
|
-
chunksRef.current = [];
|
|
6396
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
6397
|
-
streamRef.current = stream;
|
|
6398
|
-
const mimeType = MediaRecorder.isTypeSupported(AUDIO_MIME_TYPE) ? AUDIO_MIME_TYPE : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
|
|
6399
|
-
const recorder = new MediaRecorder(stream, { mimeType });
|
|
6400
|
-
mediaRecorderRef.current = recorder;
|
|
6401
|
-
recorder.ondataavailable = (event) => {
|
|
6402
|
-
if (event.data.size > 0) {
|
|
6403
|
-
chunksRef.current.push(event.data);
|
|
6404
|
-
}
|
|
6405
|
-
};
|
|
6406
|
-
recorder.onerror = () => {
|
|
6407
|
-
setError("Recording failed");
|
|
6408
|
-
cleanup();
|
|
6409
|
-
resolveRef.current?.(null);
|
|
6410
|
-
resolveRef.current = null;
|
|
6411
|
-
};
|
|
6412
|
-
recorder.onstop = () => {
|
|
6413
|
-
const finalDuration = Math.round((Date.now() - startTimeRef.current) / 1e3);
|
|
6414
|
-
const blob = new Blob(chunksRef.current, { type: mimeType });
|
|
6415
|
-
cleanup();
|
|
6416
|
-
resolveRef.current?.({ blob, duration: finalDuration });
|
|
6417
|
-
resolveRef.current = null;
|
|
6418
|
-
};
|
|
6419
|
-
recorder.start(250);
|
|
6420
|
-
startTimeRef.current = Date.now();
|
|
6421
|
-
setIsRecording(true);
|
|
6422
|
-
timerRef.current = setInterval(() => {
|
|
6423
|
-
const elapsed = Math.round((Date.now() - startTimeRef.current) / 1e3);
|
|
6424
|
-
setDuration(elapsed);
|
|
6425
|
-
}, 1e3);
|
|
6426
|
-
} catch (err) {
|
|
6427
|
-
if (err instanceof DOMException && err.name === "NotAllowedError") {
|
|
6428
|
-
setError("Microphone access denied. Please allow microphone permissions.");
|
|
6429
|
-
} else {
|
|
6430
|
-
setError("Failed to start recording");
|
|
6431
|
-
}
|
|
6432
|
-
cleanup();
|
|
6433
|
-
}
|
|
6434
|
-
}, [isSupported, cleanup]);
|
|
6435
|
-
const stop = useCallback(async () => {
|
|
6436
|
-
return new Promise((resolve) => {
|
|
6437
|
-
if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
|
|
6438
|
-
resolve(null);
|
|
6439
|
-
return;
|
|
6440
|
-
}
|
|
6441
|
-
resolveRef.current = resolve;
|
|
6442
|
-
mediaRecorderRef.current.stop();
|
|
6443
|
-
});
|
|
6444
|
-
}, []);
|
|
6445
|
-
const cancel = useCallback(() => {
|
|
6446
|
-
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
6447
|
-
mediaRecorderRef.current.onstop = null;
|
|
6448
|
-
mediaRecorderRef.current.stop();
|
|
6449
|
-
}
|
|
6450
|
-
cleanup();
|
|
6451
|
-
resolveRef.current?.(null);
|
|
6452
|
-
resolveRef.current = null;
|
|
6453
|
-
}, [cleanup]);
|
|
6454
|
-
return {
|
|
6455
|
-
isRecording,
|
|
6456
|
-
duration,
|
|
6457
|
-
start,
|
|
6458
|
-
stop,
|
|
6459
|
-
cancel,
|
|
6460
|
-
isSupported,
|
|
6461
|
-
error
|
|
6462
|
-
};
|
|
6463
|
-
}
|
|
6464
6588
|
function useUnreadCount() {
|
|
6465
6589
|
const { socket, totalUnread } = useCollab();
|
|
6466
6590
|
const [counts, setCounts] = useState({});
|