@rapidmx/meet-plugin 0.11.0 → 0.12.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/apps/meet/_CallControls.tsx +7 -1
- package/apps/meet/_CallView.tsx +98 -93
- package/apps/meet/_ParticipantsDrawer.tsx +62 -64
- package/apps/meet/_icons.tsx +12 -0
- package/dist/apps/meet/_CallControls.d.ts +6 -0
- package/dist/apps/meet/_CallControls.js +7 -1
- package/dist/apps/meet/_CallView.js +5 -4
- package/dist/apps/meet/_ParticipantsDrawer.d.ts +4 -2
- package/dist/apps/meet/_ParticipantsDrawer.js +6 -4
- package/dist/apps/meet/_icons.d.ts +4 -0
- package/dist/apps/meet/_icons.js +5 -1
- package/package.json +1 -1
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* screen, reactions, raise hand, grid/focus and leave. The effects button opens the video filter picker
|
|
10
10
|
* (`_EffectsPanel.tsx`) and lights up while any filter is on.
|
|
11
11
|
*
|
|
12
|
+
* The diagnostics button is the one exception to "opens near its own button": its panel is `fixed` to the
|
|
13
|
+
* viewport's top-left corner rather than anchored above the button, since it reads better as a HUD-style overlay
|
|
14
|
+
* sitting over the call than as a dropdown tucked into the bottom control bar. The outside-click/Escape handling
|
|
15
|
+
* (`barRef`) still closes it the same way, since it's still a DOM descendant of the bar - only its visual position
|
|
16
|
+
* moved, not its open/close wiring.
|
|
17
|
+
*
|
|
12
18
|
* The microphone button shows a live level while it is unmuted - bars that move with the sound the microphone is
|
|
13
19
|
* picking up, so a participant can see their audio is being sent - and the camera button shows a green dot while a
|
|
14
20
|
* camera is sending. Both are driven by `LocalMedia` (`apps/shared/media/useLocalMedia.ts`). The microphone button
|
|
@@ -322,7 +328,7 @@ export default function CallControls({
|
|
|
322
328
|
<div
|
|
323
329
|
role="dialog"
|
|
324
330
|
aria-label="Call diagnostics"
|
|
325
|
-
className="
|
|
331
|
+
className="fixed z-30 top-3 left-3 w-80 max-w-[calc(100vw-1rem)] max-h-[70vh] overflow-y-auto p-3 rounded-2xl bg-[#2b2d30]/95 text-white shadow-xl"
|
|
326
332
|
>
|
|
327
333
|
<DiagnosticsPanel selfName={selfName} micOn={media.micOn} cameraOn={media.cameraOn} participants={participants} />
|
|
328
334
|
</div>
|
package/apps/meet/_CallView.tsx
CHANGED
|
@@ -64,6 +64,7 @@ import type { MeshParticipant, RelayTransportLike } from "../shared/webrtc/types
|
|
|
64
64
|
import { createRelayTransport } from "../shared/relay/RelayTransport.js";
|
|
65
65
|
import { GuestSignalingClient } from "../shared/push/GuestSignalingClient.js";
|
|
66
66
|
import CallControls, { type CallViewMode } from "./_CallControls.js";
|
|
67
|
+
import { BatonIcon } from "./_icons.js";
|
|
67
68
|
import {
|
|
68
69
|
admitParticipant,
|
|
69
70
|
denyParticipant,
|
|
@@ -666,113 +667,117 @@ export default function CallView({
|
|
|
666
667
|
}
|
|
667
668
|
|
|
668
669
|
return (
|
|
669
|
-
<div className="fixed inset-0 z-50 flex
|
|
670
|
+
<div className="fixed inset-0 z-50 flex bg-[#202124] text-white overflow-hidden">
|
|
670
671
|
<style>{`@keyframes meet-float { 0% { transform: translateY(0) scale(.6); opacity: 0; } 12% { opacity: 1; transform: translateY(-4vh) scale(1); } 100% { transform: translateY(-45vh) scale(1); opacity: 0; } }`}</style>
|
|
671
|
-
<
|
|
672
|
-
<
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
672
|
+
<div className="relative flex-1 min-w-0 flex flex-col overflow-hidden">
|
|
673
|
+
<header className="shrink-0 flex items-center justify-between gap-3 px-4 py-3">
|
|
674
|
+
<h1 className="min-w-0 truncate text-base font-medium">{meetingTitle}</h1>
|
|
675
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
676
|
+
{raisedNames.length > 0 && (
|
|
677
|
+
<span className="max-w-[45vw] truncate px-3 py-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-sm font-medium" data-testid="raised-hands">
|
|
678
|
+
✋ {raisedNames.join(", ")}
|
|
679
|
+
</span>
|
|
680
|
+
)}
|
|
681
|
+
{talkingStickActive && (
|
|
682
|
+
<span className="max-w-[45vw] truncate px-3 py-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-sm font-medium" data-testid="talking-stick-status">
|
|
683
|
+
🎙️ {talkingStickHolderName ? `${talkingStickHolderName} ${talkingStickHolderName === "You" ? "have" : "has"} the floor` : "Waiting for the host to choose a speaker"}
|
|
684
|
+
</span>
|
|
685
|
+
)}
|
|
686
|
+
{isHost && (
|
|
687
|
+
<button
|
|
688
|
+
type="button"
|
|
689
|
+
className={`w-9 h-9 flex items-center justify-center rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80 ${talkingStickActive ? "bg-[#a8c7fa] text-[#062e6f] hover:bg-[#8ab4f8]" : "bg-[#3c4043] hover:bg-[#4b4f53]"}`}
|
|
690
|
+
aria-label={talkingStickActive ? "End talking stick" : "Talking stick"}
|
|
691
|
+
aria-pressed={talkingStickActive}
|
|
692
|
+
title={talkingStickActive ? "End talking stick" : "Start talking stick mode"}
|
|
693
|
+
onClick={handleToggleTalkingStick}
|
|
694
|
+
>
|
|
695
|
+
<BatonIcon />
|
|
696
|
+
</button>
|
|
697
|
+
)}
|
|
685
698
|
<button
|
|
686
699
|
type="button"
|
|
687
|
-
className=
|
|
688
|
-
aria-
|
|
689
|
-
|
|
700
|
+
className="px-3 py-1.5 rounded-full bg-[#3c4043] hover:bg-[#4b4f53] text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80"
|
|
701
|
+
aria-label={`${participants.length + 1} participants`}
|
|
702
|
+
aria-haspopup="dialog"
|
|
703
|
+
aria-expanded={drawerOpen}
|
|
704
|
+
onClick={() => setDrawerOpen((prev) => !prev)}
|
|
690
705
|
>
|
|
691
|
-
{
|
|
706
|
+
{participants.length + 1}
|
|
692
707
|
</button>
|
|
693
|
-
|
|
708
|
+
</div>
|
|
709
|
+
</header>
|
|
710
|
+
{connectError && (
|
|
711
|
+
<div role="alert" className="shrink-0 mx-3 mb-2 px-3 py-2 rounded-lg bg-[#601410] text-[#f9dedc] text-sm">
|
|
712
|
+
{connectError}
|
|
713
|
+
</div>
|
|
714
|
+
)}
|
|
715
|
+
{audioBlocked && (
|
|
694
716
|
<button
|
|
695
717
|
type="button"
|
|
696
|
-
className="px-3 py-
|
|
697
|
-
|
|
698
|
-
aria-haspopup="dialog"
|
|
699
|
-
aria-expanded={drawerOpen}
|
|
700
|
-
onClick={() => setDrawerOpen((prev) => !prev)}
|
|
718
|
+
className="shrink-0 mx-3 mb-2 px-3 py-2 rounded-lg bg-[#a8c7fa] text-[#062e6f] text-sm font-medium"
|
|
719
|
+
onClick={handleResumeAudio}
|
|
701
720
|
>
|
|
702
|
-
|
|
721
|
+
Click here to turn on sound
|
|
703
722
|
</button>
|
|
704
|
-
</div>
|
|
705
|
-
</header>
|
|
706
|
-
{connectError && (
|
|
707
|
-
<div role="alert" className="shrink-0 mx-3 mb-2 px-3 py-2 rounded-lg bg-[#601410] text-[#f9dedc] text-sm">
|
|
708
|
-
{connectError}
|
|
709
|
-
</div>
|
|
710
|
-
)}
|
|
711
|
-
{audioBlocked && (
|
|
712
|
-
<button
|
|
713
|
-
type="button"
|
|
714
|
-
className="shrink-0 mx-3 mb-2 px-3 py-2 rounded-lg bg-[#a8c7fa] text-[#062e6f] text-sm font-medium"
|
|
715
|
-
onClick={handleResumeAudio}
|
|
716
|
-
>
|
|
717
|
-
Click here to turn on sound
|
|
718
|
-
</button>
|
|
719
|
-
)}
|
|
720
|
-
<main className="relative flex-1 min-h-0 px-3 pb-3">
|
|
721
|
-
{stage}
|
|
722
|
-
{presenterName && presenterUid !== peerId && (
|
|
723
|
-
<p className="absolute top-2 left-5 px-2 py-0.5 rounded bg-black/60 text-sm">{presenterName} is presenting</p>
|
|
724
723
|
)}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
</div>
|
|
754
|
-
)}
|
|
755
|
-
|
|
756
|
-
<div className="pointer-events-none absolute left-4 bottom-28 w-40 h-[45vh]" aria-hidden="true">
|
|
757
|
-
{reactions.map((reaction) => (
|
|
724
|
+
<main className="relative flex-1 min-h-0 px-3 pb-3">
|
|
725
|
+
{stage}
|
|
726
|
+
{presenterName && presenterUid !== peerId && (
|
|
727
|
+
<p className="absolute top-2 left-5 px-2 py-0.5 rounded bg-black/60 text-sm">{presenterName} is presenting</p>
|
|
728
|
+
)}
|
|
729
|
+
</main>
|
|
730
|
+
<footer className="shrink-0 pt-1 pb-[max(0.75rem,env(safe-area-inset-bottom))]">
|
|
731
|
+
<CallControls
|
|
732
|
+
media={media}
|
|
733
|
+
selfName={selfName}
|
|
734
|
+
participants={participants}
|
|
735
|
+
isPresenting={isPresenting}
|
|
736
|
+
presentingElsewhereName={presenterUid && presenterUid !== peerId ? presenterName : undefined}
|
|
737
|
+
onToggleShare={() => void handleToggleShare()}
|
|
738
|
+
screenTransform={screenTransform}
|
|
739
|
+
onRotateScreen={handleRotateScreen}
|
|
740
|
+
onFlipScreen={handleFlipScreen}
|
|
741
|
+
handRaised={handRaised}
|
|
742
|
+
onToggleHand={handleToggleHand}
|
|
743
|
+
onReaction={handleReaction}
|
|
744
|
+
viewMode={viewMode}
|
|
745
|
+
onToggleViewMode={() => setViewMode((prev) => (prev === "grid" ? "focus" : "grid"))}
|
|
746
|
+
micLocked={micLocked}
|
|
747
|
+
onLeave={onLeave}
|
|
748
|
+
/>
|
|
749
|
+
</footer>
|
|
750
|
+
|
|
751
|
+
{!alone && (
|
|
758
752
|
<div
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
style={{ left: `${(reaction.id % 4) * 36}px`, animation: `meet-float ${REACTION_MS}ms ease-out forwards` }}
|
|
762
|
-
data-testid="reaction"
|
|
753
|
+
className="absolute z-10 right-3 top-14 w-28 sm:top-auto sm:bottom-24 sm:w-52 aspect-video shadow-xl xl:right-4 xl:bottom-4"
|
|
754
|
+
data-testid="self-view"
|
|
763
755
|
>
|
|
764
|
-
|
|
765
|
-
<span className="mt-1 px-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-xs">{reaction.name}</span>
|
|
756
|
+
{selfTile("h-full")}
|
|
766
757
|
</div>
|
|
767
|
-
)
|
|
768
|
-
|
|
758
|
+
)}
|
|
759
|
+
|
|
760
|
+
<div className="pointer-events-none absolute left-4 bottom-28 w-40 h-[45vh]" aria-hidden="true">
|
|
761
|
+
{reactions.map((reaction) => (
|
|
762
|
+
<div
|
|
763
|
+
key={reaction.id}
|
|
764
|
+
className="absolute bottom-0 flex flex-col items-center"
|
|
765
|
+
style={{ left: `${(reaction.id % 4) * 36}px`, animation: `meet-float ${REACTION_MS}ms ease-out forwards` }}
|
|
766
|
+
data-testid="reaction"
|
|
767
|
+
>
|
|
768
|
+
<span className="text-4xl leading-none">{reaction.emoji}</span>
|
|
769
|
+
<span className="mt-1 px-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-xs">{reaction.name}</span>
|
|
770
|
+
</div>
|
|
771
|
+
))}
|
|
772
|
+
</div>
|
|
769
773
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
774
|
+
{participants.map((p) => {
|
|
775
|
+
const stream = remoteStreams[p.uid];
|
|
776
|
+
return stream ? <RemoteAudio key={`${p.uid}:${audioNonce}`} stream={stream} onBlocked={handleAudioBlocked} /> : null;
|
|
777
|
+
})}
|
|
778
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
779
|
+
{announcement}
|
|
780
|
+
</div>
|
|
776
781
|
</div>
|
|
777
782
|
|
|
778
783
|
{drawerOpen && (
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* shows on a tile, as plain rows instead (a list reads better than tiles once there's a reason to scan for one
|
|
9
9
|
* name).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* A docked sidebar, not an overlay: `_CallView.tsx` renders it as an ordinary flex sibling of the call's own
|
|
12
|
+
* content column, which shrinks to make room rather than being covered by a backdrop - meant to stay open and
|
|
13
|
+
* visible for as long as the participant wants, including while the call continues underneath. Closes on Escape or
|
|
14
|
+
* its own close button; there is no backdrop to click, on purpose.
|
|
13
15
|
*
|
|
14
16
|
* A "Host" tag marks the host's own row (`isSelfHost`/`isParticipantHost` - see `_CallView.tsx`'s doc comment on
|
|
15
17
|
* `hostUid` for what that identity is and isn't). Only while `isSelfHost` does every *other* row also get a "Mute"
|
|
@@ -328,70 +330,66 @@ export default function ParticipantsDrawer({
|
|
|
328
330
|
}, [onClose]);
|
|
329
331
|
|
|
330
332
|
return (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
333
|
+
<aside
|
|
334
|
+
role="dialog"
|
|
335
|
+
aria-label="Participants"
|
|
336
|
+
className="relative z-10 shrink-0 w-72 max-w-[85vw] h-full flex flex-col bg-[#2b2d30] text-white shadow-xl border-l border-white/10"
|
|
337
|
+
>
|
|
338
|
+
<div className="flex items-center justify-between gap-2 px-3 py-3 border-b border-white/10">
|
|
339
|
+
<h2 className="text-sm font-medium">Participants ({participants.length + 1})</h2>
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80"
|
|
343
|
+
aria-label="Close participants"
|
|
344
|
+
onClick={onClose}
|
|
345
|
+
>
|
|
346
|
+
✕
|
|
347
|
+
</button>
|
|
348
|
+
</div>
|
|
349
|
+
{isSelfHost && (
|
|
350
|
+
<label className="flex items-center gap-2 px-3 py-2 border-b border-white/10 text-sm">
|
|
351
|
+
<input type="checkbox" checked={forceMuteOnJoin} onChange={onToggleForceMuteOnJoin} className="w-4 h-4" />
|
|
352
|
+
Mute new participants on join
|
|
353
|
+
</label>
|
|
354
|
+
)}
|
|
355
|
+
{isSelfHost && <PasswordSection hasPassword={hasPassword} onSetPassword={onSetPassword} />}
|
|
356
|
+
{isSelfHost && (
|
|
357
|
+
<label className="flex items-center gap-2 px-3 py-2 border-b border-white/10 text-sm">
|
|
358
|
+
<input type="checkbox" checked={waitingRoomEnabled} onChange={onToggleWaitingRoomEnabled} className="w-4 h-4" />
|
|
359
|
+
Require the host to admit participants
|
|
360
|
+
</label>
|
|
361
|
+
)}
|
|
362
|
+
{isSelfHost && waitingRoomEnabled && (
|
|
363
|
+
<WaitingSection waitingParticipants={waitingParticipants} onAdmit={onAdmit} onDeny={onDeny} />
|
|
364
|
+
)}
|
|
365
|
+
<ul className="flex-1 min-h-0 overflow-y-auto p-2 text-sm">
|
|
366
|
+
<Row
|
|
367
|
+
name={selfName}
|
|
368
|
+
isSelf
|
|
369
|
+
isHost={isSelfHost}
|
|
370
|
+
micMuted={!micOn}
|
|
371
|
+
handRaised={handRaised}
|
|
372
|
+
hasStick={talkingStickActive && talkingStickHolder === selfPeerId}
|
|
373
|
+
canGiveStick={isSelfHost && talkingStickActive}
|
|
374
|
+
onGiveStick={() => onGiveTalkingStick(selfPeerId)}
|
|
375
|
+
/>
|
|
376
|
+
{participants.map((p) => (
|
|
367
377
|
<Row
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
isHost={
|
|
371
|
-
micMuted={!
|
|
372
|
-
handRaised={handRaised}
|
|
373
|
-
|
|
378
|
+
key={p.uid}
|
|
379
|
+
name={p.name}
|
|
380
|
+
isHost={isParticipantHost(p.uid)}
|
|
381
|
+
micMuted={!p.audioOn}
|
|
382
|
+
handRaised={p.handRaised}
|
|
383
|
+
transport={p.transport}
|
|
384
|
+
canModerate={isSelfHost}
|
|
385
|
+
onMute={() => onMute(p.uid)}
|
|
386
|
+
onKick={() => onKick(p.uid)}
|
|
387
|
+
hasStick={talkingStickActive && talkingStickHolder === p.uid}
|
|
374
388
|
canGiveStick={isSelfHost && talkingStickActive}
|
|
375
|
-
onGiveStick={() => onGiveTalkingStick(
|
|
389
|
+
onGiveStick={() => onGiveTalkingStick(p.uid)}
|
|
376
390
|
/>
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
name={p.name}
|
|
381
|
-
isHost={isParticipantHost(p.uid)}
|
|
382
|
-
micMuted={!p.audioOn}
|
|
383
|
-
handRaised={p.handRaised}
|
|
384
|
-
transport={p.transport}
|
|
385
|
-
canModerate={isSelfHost}
|
|
386
|
-
onMute={() => onMute(p.uid)}
|
|
387
|
-
onKick={() => onKick(p.uid)}
|
|
388
|
-
hasStick={talkingStickActive && talkingStickHolder === p.uid}
|
|
389
|
-
canGiveStick={isSelfHost && talkingStickActive}
|
|
390
|
-
onGiveStick={() => onGiveTalkingStick(p.uid)}
|
|
391
|
-
/>
|
|
392
|
-
))}
|
|
393
|
-
</ul>
|
|
394
|
-
</aside>
|
|
395
|
-
</>
|
|
391
|
+
))}
|
|
392
|
+
</ul>
|
|
393
|
+
</aside>
|
|
396
394
|
);
|
|
397
395
|
}
|
package/apps/meet/_icons.tsx
CHANGED
|
@@ -68,3 +68,15 @@ export const FlipIcon = () => (
|
|
|
68
68
|
export const DiagnosticsIcon = () => (
|
|
69
69
|
<Icon path="M22 12h-4l-3 9L9 3l-3 9H2v2h5.6l1.4-4.2L13.4 21 17 12h5v-2z" />
|
|
70
70
|
);
|
|
71
|
+
|
|
72
|
+
/** A talking stick: a rounded shaft with a grip knob at one end, drawn (not a Material path - there isn't one for
|
|
73
|
+
* this) as a diagonal bar with a circle, rather than reusing `HandIcon`'s emoji-glyph fallback, since this one
|
|
74
|
+
* needs to light up (`currentColor`) the same way every other control icon here does. */
|
|
75
|
+
export const BatonIcon = () => (
|
|
76
|
+
<svg viewBox="0 0 24 24" className="w-6 h-6" fill="currentColor" aria-hidden="true" focusable="false">
|
|
77
|
+
<g transform="rotate(45 12 12)">
|
|
78
|
+
<rect x="5" y="10.5" width="13" height="3" rx="1.5" />
|
|
79
|
+
<circle cx="19" cy="12" r="3" />
|
|
80
|
+
</g>
|
|
81
|
+
</svg>
|
|
82
|
+
);
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* screen, reactions, raise hand, grid/focus and leave. The effects button opens the video filter picker
|
|
6
6
|
* (`_EffectsPanel.tsx`) and lights up while any filter is on.
|
|
7
7
|
*
|
|
8
|
+
* The diagnostics button is the one exception to "opens near its own button": its panel is `fixed` to the
|
|
9
|
+
* viewport's top-left corner rather than anchored above the button, since it reads better as a HUD-style overlay
|
|
10
|
+
* sitting over the call than as a dropdown tucked into the bottom control bar. The outside-click/Escape handling
|
|
11
|
+
* (`barRef`) still closes it the same way, since it's still a DOM descendant of the bar - only its visual position
|
|
12
|
+
* moved, not its open/close wiring.
|
|
13
|
+
*
|
|
8
14
|
* The microphone button shows a live level while it is unmuted - bars that move with the sound the microphone is
|
|
9
15
|
* picking up, so a participant can see their audio is being sent - and the camera button shows a green dot while a
|
|
10
16
|
* camera is sending. Both are driven by `LocalMedia` (`apps/shared/media/useLocalMedia.ts`). The microphone button
|
|
@@ -10,6 +10,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
10
10
|
* screen, reactions, raise hand, grid/focus and leave. The effects button opens the video filter picker
|
|
11
11
|
* (`_EffectsPanel.tsx`) and lights up while any filter is on.
|
|
12
12
|
*
|
|
13
|
+
* The diagnostics button is the one exception to "opens near its own button": its panel is `fixed` to the
|
|
14
|
+
* viewport's top-left corner rather than anchored above the button, since it reads better as a HUD-style overlay
|
|
15
|
+
* sitting over the call than as a dropdown tucked into the bottom control bar. The outside-click/Escape handling
|
|
16
|
+
* (`barRef`) still closes it the same way, since it's still a DOM descendant of the bar - only its visual position
|
|
17
|
+
* moved, not its open/close wiring.
|
|
18
|
+
*
|
|
13
19
|
* The microphone button shows a live level while it is unmuted - bars that move with the sound the microphone is
|
|
14
20
|
* picking up, so a participant can see their audio is being sent - and the camera button shows a green dot while a
|
|
15
21
|
* camera is sending. Both are driven by `LocalMedia` (`apps/shared/media/useLocalMedia.ts`). The microphone button
|
|
@@ -62,7 +68,7 @@ export default function CallControls({ media, selfName, participants, isPresenti
|
|
|
62
68
|
return (_jsxs("div", { ref: barRef, className: "relative flex flex-wrap items-center justify-center gap-2 px-3", role: "toolbar", "aria-label": "Call controls", children: [_jsxs("div", { className: "sm:relative flex items-stretch gap-px", children: [_jsx("button", { type: "button", className: `${BUTTON} w-9 rounded-r-none ${media.micOn ? NEUTRAL : ALERT}`, "aria-label": "Choose microphone", "aria-haspopup": "menu", "aria-expanded": openMenu === "audio", onClick: () => toggleMenu("audio"), children: _jsx(ChevronUpIcon, {}) }), _jsxs("button", { type: "button", className: `${BUTTON} min-w-14 gap-1.5 px-3 rounded-l-none ${media.micOn ? NEUTRAL : ALERT} disabled:opacity-50 disabled:cursor-not-allowed`, "aria-label": media.micOn ? "Mute microphone" : "Unmute microphone", "aria-pressed": !media.micOn, disabled: micLocked, title: micLocked ? "Only the current talking-stick holder can unmute." : undefined, onClick: () => void media.toggleMic(), children: [media.micOn && _jsx(LevelBars, { level: media.audioLevel }), media.micOn ? _jsx(MicIcon, {}) : _jsx(MicOffIcon, {})] }), openMenu === "audio" && _jsx(DeviceMenu, { kind: "audio", media: media, onClose: () => setOpenMenu(null) })] }), _jsxs("div", { className: "sm:relative flex items-stretch gap-px", children: [_jsx("button", { type: "button", className: `${BUTTON} w-9 rounded-r-none ${media.cameraOn ? NEUTRAL : ALERT}`, "aria-label": "Choose camera", "aria-haspopup": "menu", "aria-expanded": openMenu === "video", onClick: () => toggleMenu("video"), children: _jsx(ChevronUpIcon, {}) }), _jsxs("button", { type: "button", className: `${BUTTON} relative min-w-14 px-3 rounded-l-none ${media.cameraOn ? NEUTRAL : ALERT}`, "aria-label": media.cameraOn ? "Turn off camera" : "Turn on camera", "aria-pressed": !media.cameraOn, onClick: () => void media.toggleCamera(), children: [media.cameraOn ? _jsx(VideoIcon, {}) : _jsx(VideoOffIcon, {}), media.cameraOn && (_jsx("span", { className: "absolute top-2 right-2 w-2 h-2 rounded-full bg-[#34a853] animate-pulse", "data-testid": "camera-live", title: "Your camera is sending" }))] }), openMenu === "video" && _jsx(DeviceMenu, { kind: "video", media: media, onClose: () => setOpenMenu(null) })] }), _jsxs("div", { className: "sm:relative", children: [_jsx("button", { type: "button", className: `${BUTTON} w-12 ${filtersActive(media.filters) ? ACTIVE : NEUTRAL}`, "aria-label": "Video effects", "aria-haspopup": "dialog", "aria-expanded": openMenu === "effects", onClick: () => toggleMenu("effects"), children: _jsx(EffectsIcon, {}) }), openMenu === "effects" && (_jsx("div", { role: "dialog", "aria-label": "Video effects", className: "absolute bottom-full mb-3 left-1/2 -translate-x-1/2 w-96 max-w-[calc(100vw-1rem)] max-h-[60vh] overflow-y-auto p-3 rounded-2xl bg-[#2b2d30] text-white shadow-xl", children: _jsx(EffectsPanel, { media: media, tone: "dark" }) }))] }), _jsx("button", { type: "button", className: `${BUTTON} w-12 ${isPresenting ? ACTIVE : NEUTRAL} disabled:opacity-40 disabled:cursor-not-allowed`, "aria-label": isPresenting ? "Stop sharing" : "Share screen", "aria-pressed": isPresenting, disabled: shareDisabled, title: shareDisabled ? `${presentingElsewhereName} is presenting - stop their share to present yourself.` : undefined, onClick: onToggleShare, children: _jsx(ScreenShareIcon, {}) }), isPresenting && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: `${BUTTON} w-12 ${NEUTRAL}`, "aria-label": "Rotate shared screen", title: "If your shared window looks sideways or upside down to everyone, rotate it here - this is a correction for your own capture, not a browser bug this app can fix automatically.", onClick: onRotateScreen, children: _jsx(RotateIcon, {}) }), _jsx("button", { type: "button", className: `${BUTTON} w-12 ${screenTransform.flipped ? ACTIVE : NEUTRAL}`, "aria-label": screenTransform.flipped ? "Unflip shared screen" : "Flip shared screen", "aria-pressed": screenTransform.flipped, onClick: onFlipScreen, children: _jsx(FlipIcon, {}) })] })), _jsxs("div", { className: "sm:relative", children: [_jsx("button", { type: "button", className: `${BUTTON} w-12 ${NEUTRAL}`, "aria-label": "Send a reaction", "aria-haspopup": "menu", "aria-expanded": openMenu === "emoji", onClick: () => toggleMenu("emoji"), children: _jsx(EmojiIcon, {}) }), openMenu === "emoji" && (_jsx("div", { role: "menu", "aria-label": "Reactions", className: "absolute bottom-full mb-3 left-1/2 -translate-x-1/2 w-max grid grid-cols-4 gap-1 p-2 rounded-2xl bg-[#2b2d30] shadow-xl", children: REACTION_EMOJIS.map((emoji) => (_jsx("button", { type: "button", role: "menuitem", className: "w-11 h-11 rounded-lg text-2xl hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80", "aria-label": `Send ${emoji}`, onClick: () => {
|
|
63
69
|
onReaction(emoji);
|
|
64
70
|
setOpenMenu(null);
|
|
65
|
-
}, children: emoji }, emoji))) }))] }), _jsx("button", { type: "button", className: `${BUTTON} w-12 ${handRaised ? ACTIVE : NEUTRAL}`, "aria-label": handRaised ? "Lower hand" : "Raise hand", "aria-pressed": handRaised, onClick: onToggleHand, children: _jsx(HandIcon, {}) }), _jsx("button", { type: "button", className: `${BUTTON} w-12 ${NEUTRAL}`, "aria-label": viewMode === "grid" ? "Switch to focused view" : "Switch to grid view", onClick: onToggleViewMode, children: viewMode === "grid" ? _jsx(FocusIcon, {}) : _jsx(GridIcon, {}) }), _jsxs("div", { className: "sm:relative", children: [_jsx("button", { type: "button", className: `${BUTTON} w-12 ${openMenu === "diagnostics" ? ACTIVE : NEUTRAL}`, "aria-label": "Call diagnostics", "aria-haspopup": "dialog", "aria-expanded": openMenu === "diagnostics", onClick: () => toggleMenu("diagnostics"), children: _jsx(DiagnosticsIcon, {}) }), openMenu === "diagnostics" && (_jsx("div", { role: "dialog", "aria-label": "Call diagnostics", className: "
|
|
71
|
+
}, children: emoji }, emoji))) }))] }), _jsx("button", { type: "button", className: `${BUTTON} w-12 ${handRaised ? ACTIVE : NEUTRAL}`, "aria-label": handRaised ? "Lower hand" : "Raise hand", "aria-pressed": handRaised, onClick: onToggleHand, children: _jsx(HandIcon, {}) }), _jsx("button", { type: "button", className: `${BUTTON} w-12 ${NEUTRAL}`, "aria-label": viewMode === "grid" ? "Switch to focused view" : "Switch to grid view", onClick: onToggleViewMode, children: viewMode === "grid" ? _jsx(FocusIcon, {}) : _jsx(GridIcon, {}) }), _jsxs("div", { className: "sm:relative", children: [_jsx("button", { type: "button", className: `${BUTTON} w-12 ${openMenu === "diagnostics" ? ACTIVE : NEUTRAL}`, "aria-label": "Call diagnostics", "aria-haspopup": "dialog", "aria-expanded": openMenu === "diagnostics", onClick: () => toggleMenu("diagnostics"), children: _jsx(DiagnosticsIcon, {}) }), openMenu === "diagnostics" && (_jsx("div", { role: "dialog", "aria-label": "Call diagnostics", className: "fixed z-30 top-3 left-3 w-80 max-w-[calc(100vw-1rem)] max-h-[70vh] overflow-y-auto p-3 rounded-2xl bg-[#2b2d30]/95 text-white shadow-xl", children: _jsx(DiagnosticsPanel, { selfName: selfName, micOn: media.micOn, cameraOn: media.cameraOn, participants: participants }) }))] }), _jsx("button", { type: "button", className: `${BUTTON} w-16 bg-[#d93025] text-white hover:bg-[#b3261e]`, "aria-label": "Leave call", onClick: () => onLeave(), children: _jsx(LeaveIcon, {}) })] }));
|
|
66
72
|
}
|
|
67
73
|
/** The device picker opened from the chevron beside the microphone/camera button: the available devices with the
|
|
68
74
|
* one in use ticked, or - while there are none to list - why, with a way to ask for access again. */
|
|
@@ -63,6 +63,7 @@ import { MeshConnectionManager } from "../shared/webrtc/MeshConnectionManager.js
|
|
|
63
63
|
import { createRelayTransport } from "../shared/relay/RelayTransport.js";
|
|
64
64
|
import { GuestSignalingClient } from "../shared/push/GuestSignalingClient.js";
|
|
65
65
|
import CallControls from "./_CallControls.js";
|
|
66
|
+
import { BatonIcon } from "./_icons.js";
|
|
66
67
|
import { admitParticipant, denyParticipant, kickParticipant, listWaitingParticipants, setForceMuteOnJoin as apiSetForceMuteOnJoin, setMeetingPassword, setWaitingRoomEnabled as apiSetWaitingRoomEnabled, } from "./_meetApi.js";
|
|
67
68
|
import ParticipantTile from "./_ParticipantTile.js";
|
|
68
69
|
import ParticipantsDrawer from "./_ParticipantsDrawer.js";
|
|
@@ -501,10 +502,10 @@ export default function CallView({ channel, token, selfUid, selfName, meetingTit
|
|
|
501
502
|
else {
|
|
502
503
|
stage = (_jsx("div", { className: "h-full grid gap-2 auto-rows-fr [grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr))]", children: participants.map((p) => remoteTile(p, "h-full")) }));
|
|
503
504
|
}
|
|
504
|
-
return (_jsxs("div", { className: "fixed inset-0 z-50 flex
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
505
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex bg-[#202124] text-white overflow-hidden", children: [_jsx("style", { children: `@keyframes meet-float { 0% { transform: translateY(0) scale(.6); opacity: 0; } 12% { opacity: 1; transform: translateY(-4vh) scale(1); } 100% { transform: translateY(-45vh) scale(1); opacity: 0; } }` }), _jsxs("div", { className: "relative flex-1 min-w-0 flex flex-col overflow-hidden", children: [_jsxs("header", { className: "shrink-0 flex items-center justify-between gap-3 px-4 py-3", children: [_jsx("h1", { className: "min-w-0 truncate text-base font-medium", children: meetingTitle }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [raisedNames.length > 0 && (_jsxs("span", { className: "max-w-[45vw] truncate px-3 py-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-sm font-medium", "data-testid": "raised-hands", children: ["\u270B ", raisedNames.join(", ")] })), talkingStickActive && (_jsxs("span", { className: "max-w-[45vw] truncate px-3 py-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-sm font-medium", "data-testid": "talking-stick-status", children: ["\uD83C\uDF99\uFE0F ", talkingStickHolderName ? `${talkingStickHolderName} ${talkingStickHolderName === "You" ? "have" : "has"} the floor` : "Waiting for the host to choose a speaker"] })), isHost && (_jsx("button", { type: "button", className: `w-9 h-9 flex items-center justify-center rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80 ${talkingStickActive ? "bg-[#a8c7fa] text-[#062e6f] hover:bg-[#8ab4f8]" : "bg-[#3c4043] hover:bg-[#4b4f53]"}`, "aria-label": talkingStickActive ? "End talking stick" : "Talking stick", "aria-pressed": talkingStickActive, title: talkingStickActive ? "End talking stick" : "Start talking stick mode", onClick: handleToggleTalkingStick, children: _jsx(BatonIcon, {}) })), _jsx("button", { type: "button", className: "px-3 py-1.5 rounded-full bg-[#3c4043] hover:bg-[#4b4f53] text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80", "aria-label": `${participants.length + 1} participants`, "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen((prev) => !prev), children: participants.length + 1 })] })] }), connectError && (_jsx("div", { role: "alert", className: "shrink-0 mx-3 mb-2 px-3 py-2 rounded-lg bg-[#601410] text-[#f9dedc] text-sm", children: connectError })), audioBlocked && (_jsx("button", { type: "button", className: "shrink-0 mx-3 mb-2 px-3 py-2 rounded-lg bg-[#a8c7fa] text-[#062e6f] text-sm font-medium", onClick: handleResumeAudio, children: "Click here to turn on sound" })), _jsxs("main", { className: "relative flex-1 min-h-0 px-3 pb-3", children: [stage, presenterName && presenterUid !== peerId && (_jsxs("p", { className: "absolute top-2 left-5 px-2 py-0.5 rounded bg-black/60 text-sm", children: [presenterName, " is presenting"] }))] }), _jsx("footer", { className: "shrink-0 pt-1 pb-[max(0.75rem,env(safe-area-inset-bottom))]", children: _jsx(CallControls, { media: media, selfName: selfName, participants: participants, isPresenting: isPresenting, presentingElsewhereName: presenterUid && presenterUid !== peerId ? presenterName : undefined, onToggleShare: () => void handleToggleShare(), screenTransform: screenTransform, onRotateScreen: handleRotateScreen, onFlipScreen: handleFlipScreen, handRaised: handRaised, onToggleHand: handleToggleHand, onReaction: handleReaction, viewMode: viewMode, onToggleViewMode: () => setViewMode((prev) => (prev === "grid" ? "focus" : "grid")), micLocked: micLocked, onLeave: onLeave }) }), !alone && (_jsx("div", { className: "absolute z-10 right-3 top-14 w-28 sm:top-auto sm:bottom-24 sm:w-52 aspect-video shadow-xl xl:right-4 xl:bottom-4", "data-testid": "self-view", children: selfTile("h-full") })), _jsx("div", { className: "pointer-events-none absolute left-4 bottom-28 w-40 h-[45vh]", "aria-hidden": "true", children: reactions.map((reaction) => (_jsxs("div", { className: "absolute bottom-0 flex flex-col items-center", style: { left: `${(reaction.id % 4) * 36}px`, animation: `meet-float ${REACTION_MS}ms ease-out forwards` }, "data-testid": "reaction", children: [_jsx("span", { className: "text-4xl leading-none", children: reaction.emoji }), _jsx("span", { className: "mt-1 px-1.5 rounded-full bg-[#a8c7fa] text-[#062e6f] text-xs", children: reaction.name })] }, reaction.id))) }), participants.map((p) => {
|
|
506
|
+
const stream = remoteStreams[p.uid];
|
|
507
|
+
return stream ? _jsx(RemoteAudio, { stream: stream, onBlocked: handleAudioBlocked }, `${p.uid}:${audioNonce}`) : null;
|
|
508
|
+
}), _jsx("div", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement })] }), drawerOpen && (_jsx(ParticipantsDrawer, { selfName: selfName, micOn: media.micOn, handRaised: handRaised, participants: participants, isSelfHost: isHost, isParticipantHost: isParticipantHost, onMute: handleMuteParticipant, onKick: handleKickParticipant, forceMuteOnJoin: forceMuteOnJoin, onToggleForceMuteOnJoin: handleToggleForceMuteOnJoin, hasPassword: hasPassword, onSetPassword: handleSetPassword, waitingRoomEnabled: waitingRoomEnabled, onToggleWaitingRoomEnabled: handleToggleWaitingRoomEnabled, waitingParticipants: waitingParticipants, onAdmit: handleAdmitParticipant, onDeny: handleDenyParticipant, talkingStickActive: talkingStickActive, selfPeerId: peerId, talkingStickHolder: talkingStickHolder, onGiveTalkingStick: handleGiveTalkingStick, onClose: () => setDrawerOpen(false) }))] }));
|
|
508
509
|
}
|
|
509
510
|
/** Plays one remote participant's stream through a hidden `<audio>` element. `onBlocked` fires if the browser
|
|
510
511
|
* refuses to start it (an autoplay policy) - the view then asks for a click. */
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* shows on a tile, as plain rows instead (a list reads better than tiles once there's a reason to scan for one
|
|
5
5
|
* name).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* A docked sidebar, not an overlay: `_CallView.tsx` renders it as an ordinary flex sibling of the call's own
|
|
8
|
+
* content column, which shrinks to make room rather than being covered by a backdrop - meant to stay open and
|
|
9
|
+
* visible for as long as the participant wants, including while the call continues underneath. Closes on Escape or
|
|
10
|
+
* its own close button; there is no backdrop to click, on purpose.
|
|
9
11
|
*
|
|
10
12
|
* A "Host" tag marks the host's own row (`isSelfHost`/`isParticipantHost` - see `_CallView.tsx`'s doc comment on
|
|
11
13
|
* `hostUid` for what that identity is and isn't). Only while `isSelfHost` does every *other* row also get a "Mute"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
///////////////////////////////////////////////////////////////////////////////
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
@@ -9,8 +9,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
9
9
|
* shows on a tile, as plain rows instead (a list reads better than tiles once there's a reason to scan for one
|
|
10
10
|
* name).
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* A docked sidebar, not an overlay: `_CallView.tsx` renders it as an ordinary flex sibling of the call's own
|
|
13
|
+
* content column, which shrinks to make room rather than being covered by a backdrop - meant to stay open and
|
|
14
|
+
* visible for as long as the participant wants, including while the call continues underneath. Closes on Escape or
|
|
15
|
+
* its own close button; there is no backdrop to click, on purpose.
|
|
14
16
|
*
|
|
15
17
|
* A "Host" tag marks the host's own row (`isSelfHost`/`isParticipantHost` - see `_CallView.tsx`'s doc comment on
|
|
16
18
|
* `hostUid` for what that identity is and isn't). Only while `isSelfHost` does every *other* row also get a "Mute"
|
|
@@ -87,5 +89,5 @@ export default function ParticipantsDrawer({ selfName, micOn, handRaised, partic
|
|
|
87
89
|
document.addEventListener("keydown", onKeyDown);
|
|
88
90
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
89
91
|
}, [onClose]);
|
|
90
|
-
return (_jsxs(
|
|
92
|
+
return (_jsxs("aside", { role: "dialog", "aria-label": "Participants", className: "relative z-10 shrink-0 w-72 max-w-[85vw] h-full flex flex-col bg-[#2b2d30] text-white shadow-xl border-l border-white/10", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-3 border-b border-white/10", children: [_jsxs("h2", { className: "text-sm font-medium", children: ["Participants (", participants.length + 1, ")"] }), _jsx("button", { type: "button", className: "w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80", "aria-label": "Close participants", onClick: onClose, children: "\u2715" })] }), isSelfHost && (_jsxs("label", { className: "flex items-center gap-2 px-3 py-2 border-b border-white/10 text-sm", children: [_jsx("input", { type: "checkbox", checked: forceMuteOnJoin, onChange: onToggleForceMuteOnJoin, className: "w-4 h-4" }), "Mute new participants on join"] })), isSelfHost && _jsx(PasswordSection, { hasPassword: hasPassword, onSetPassword: onSetPassword }), isSelfHost && (_jsxs("label", { className: "flex items-center gap-2 px-3 py-2 border-b border-white/10 text-sm", children: [_jsx("input", { type: "checkbox", checked: waitingRoomEnabled, onChange: onToggleWaitingRoomEnabled, className: "w-4 h-4" }), "Require the host to admit participants"] })), isSelfHost && waitingRoomEnabled && (_jsx(WaitingSection, { waitingParticipants: waitingParticipants, onAdmit: onAdmit, onDeny: onDeny })), _jsxs("ul", { className: "flex-1 min-h-0 overflow-y-auto p-2 text-sm", children: [_jsx(Row, { name: selfName, isSelf: true, isHost: isSelfHost, micMuted: !micOn, handRaised: handRaised, hasStick: talkingStickActive && talkingStickHolder === selfPeerId, canGiveStick: isSelfHost && talkingStickActive, onGiveStick: () => onGiveTalkingStick(selfPeerId) }), participants.map((p) => (_jsx(Row, { name: p.name, isHost: isParticipantHost(p.uid), micMuted: !p.audioOn, handRaised: p.handRaised, transport: p.transport, canModerate: isSelfHost, onMute: () => onMute(p.uid), onKick: () => onKick(p.uid), hasStick: talkingStickActive && talkingStickHolder === p.uid, canGiveStick: isSelfHost && talkingStickActive, onGiveStick: () => onGiveTalkingStick(p.uid) }, p.uid)))] })] }));
|
|
91
93
|
}
|
|
@@ -17,3 +17,7 @@ export declare const EffectsIcon: () => React.JSX.Element;
|
|
|
17
17
|
export declare const RotateIcon: () => React.JSX.Element;
|
|
18
18
|
export declare const FlipIcon: () => React.JSX.Element;
|
|
19
19
|
export declare const DiagnosticsIcon: () => React.JSX.Element;
|
|
20
|
+
/** A talking stick: a rounded shaft with a grip knob at one end, drawn (not a Material path - there isn't one for
|
|
21
|
+
* this) as a diagonal bar with a circle, rather than reusing `HandIcon`'s emoji-glyph fallback, since this one
|
|
22
|
+
* needs to light up (`currentColor`) the same way every other control icon here does. */
|
|
23
|
+
export declare const BatonIcon: () => React.JSX.Element;
|
package/dist/apps/meet/_icons.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
function Icon({ path, className = "w-6 h-6" }) {
|
|
3
3
|
return (_jsx("svg", { viewBox: "0 0 24 24", className: className, fill: "currentColor", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: path }) }));
|
|
4
4
|
}
|
|
@@ -18,3 +18,7 @@ export const EffectsIcon = () => (_jsx(Icon, { path: "M19 9l1.25-2.75L23 5l-2.75
|
|
|
18
18
|
export const RotateIcon = () => (_jsx(Icon, { path: "M12 6V2L7 7l5 5V8c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }));
|
|
19
19
|
export const FlipIcon = () => (_jsx(Icon, { path: "M15 21h2v-2h-2v2zm4-12h2V7h-2v2zM3 5v14c0 1.1.9 2 2 2h4v-2H5V5h4V3H5c-1.1 0-2 .9-2 2zm16-2v2h2c0-1.1-.9-2-2-2zm-8 20h2V1h-2v22zm8-6h2v-2h-2v2zM15 5h2V3h-2v2zm4 8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2z" }));
|
|
20
20
|
export const DiagnosticsIcon = () => (_jsx(Icon, { path: "M22 12h-4l-3 9L9 3l-3 9H2v2h5.6l1.4-4.2L13.4 21 17 12h5v-2z" }));
|
|
21
|
+
/** A talking stick: a rounded shaft with a grip knob at one end, drawn (not a Material path - there isn't one for
|
|
22
|
+
* this) as a diagonal bar with a circle, rather than reusing `HandIcon`'s emoji-glyph fallback, since this one
|
|
23
|
+
* needs to light up (`currentColor`) the same way every other control icon here does. */
|
|
24
|
+
export const BatonIcon = () => (_jsx("svg", { viewBox: "0 0 24 24", className: "w-6 h-6", fill: "currentColor", "aria-hidden": "true", focusable: "false", children: _jsxs("g", { transform: "rotate(45 12 12)", children: [_jsx("rect", { x: "5", y: "10.5", width: "13", height: "3", rx: "1.5" }), _jsx("circle", { cx: "19", cy: "12", r: "3" })] }) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rapidmx/meet-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "RapidMX plugin that provides WebRTC video conferencing, joinable from a calendar invite or a shared link.",
|
|
5
5
|
"repository": "https://github.com/rapidmx/meet-plugin.git",
|
|
6
6
|
"author": "RapidREST <rapidrests@gmail.com>",
|