mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-15 00:04:59 +08:00
Clean up remaining room components
This commit is contained in:
parent
73eacdb23f
commit
ebf61511f1
@ -1,437 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2021 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import EventEmitter from "events";
|
|
||||||
|
|
||||||
export class ConferenceCallDebugger extends EventEmitter {
|
|
||||||
constructor(client, groupCall) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.client = client;
|
|
||||||
this.groupCall = groupCall;
|
|
||||||
|
|
||||||
this.debugState = {
|
|
||||||
users: new Map(),
|
|
||||||
calls: new Map(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bufferedEvents = [];
|
|
||||||
|
|
||||||
client.on("event", this._onEvent);
|
|
||||||
groupCall.on("call", this._onCall);
|
|
||||||
groupCall.on("debugstate", this._onDebugStateChanged);
|
|
||||||
groupCall.on("entered", this._onEntered);
|
|
||||||
groupCall.on("left", this._onLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onEntered = () => {
|
|
||||||
const eventCount = this.bufferedEvents.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < eventCount; i++) {
|
|
||||||
const event = this.bufferedEvents.pop();
|
|
||||||
this._onEvent(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_onLeft = () => {
|
|
||||||
this.bufferedEvents = [];
|
|
||||||
this.debugState = {
|
|
||||||
users: new Map(),
|
|
||||||
calls: new Map(),
|
|
||||||
};
|
|
||||||
this.emit("debug");
|
|
||||||
};
|
|
||||||
|
|
||||||
_onEvent = (event) => {
|
|
||||||
if (!this.groupCall.entered) {
|
|
||||||
this.bufferedEvents.push(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomId = event.getRoomId();
|
|
||||||
const type = event.getType();
|
|
||||||
|
|
||||||
if (
|
|
||||||
roomId === this.groupCall.room.roomId &&
|
|
||||||
(type.startsWith("m.call.") ||
|
|
||||||
type === "me.robertlong.call.info" ||
|
|
||||||
type === "m.room.member")
|
|
||||||
) {
|
|
||||||
const sender = event.getSender();
|
|
||||||
const { call_id } = event.getContent();
|
|
||||||
|
|
||||||
if (call_id) {
|
|
||||||
if (this.debugState.calls.has(call_id)) {
|
|
||||||
const callState = this.debugState.calls.get(call_id);
|
|
||||||
callState.events.push(event);
|
|
||||||
} else {
|
|
||||||
this.debugState.calls.set(call_id, {
|
|
||||||
state: "unknown",
|
|
||||||
events: [event],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.debugState.users.has(sender)) {
|
|
||||||
const userState = this.debugState.users.get(sender);
|
|
||||||
userState.events.push(event);
|
|
||||||
} else {
|
|
||||||
this.debugState.users.set(sender, {
|
|
||||||
state: "unknown",
|
|
||||||
events: [event],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("debug");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_onDebugStateChanged = (userId, callId, state) => {
|
|
||||||
if (userId) {
|
|
||||||
const userState = this.debugState.users.get(userId);
|
|
||||||
|
|
||||||
if (userState) {
|
|
||||||
userState.state = state;
|
|
||||||
} else {
|
|
||||||
this.debugState.users.set(userId, {
|
|
||||||
state,
|
|
||||||
events: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callId) {
|
|
||||||
const callState = this.debugState.calls.get(callId);
|
|
||||||
|
|
||||||
if (callState) {
|
|
||||||
callState.state = state;
|
|
||||||
} else {
|
|
||||||
this.debugState.calls.set(callId, {
|
|
||||||
state,
|
|
||||||
events: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("debug");
|
|
||||||
};
|
|
||||||
|
|
||||||
_onCall = (call) => {
|
|
||||||
const peerConnection = call.peerConn;
|
|
||||||
|
|
||||||
if (!peerConnection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendWebRTCInfoEvent = async (eventType) => {
|
|
||||||
const event = {
|
|
||||||
call_id: call.callId,
|
|
||||||
eventType,
|
|
||||||
iceConnectionState: peerConnection.iceConnectionState,
|
|
||||||
iceGatheringState: peerConnection.iceGatheringState,
|
|
||||||
signalingState: peerConnection.signalingState,
|
|
||||||
selectedCandidatePair: null,
|
|
||||||
localCandidate: null,
|
|
||||||
remoteCandidate: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// getStats doesn't support selectors in Firefox so get all stats by passing null.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getStats#browser_compatibility
|
|
||||||
const stats = await peerConnection.getStats(null);
|
|
||||||
|
|
||||||
const statsArr = Array.from(stats.values());
|
|
||||||
|
|
||||||
// Matrix doesn't support floats so we convert time in seconds to ms
|
|
||||||
function secToMs(time) {
|
|
||||||
if (time === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(time * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function processTransportStats(transportStats) {
|
|
||||||
if (!transportStats) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
packetsSent: transportStats.packetsSent,
|
|
||||||
packetsReceived: transportStats.packetsReceived,
|
|
||||||
bytesSent: transportStats.bytesSent,
|
|
||||||
bytesReceived: transportStats.bytesReceived,
|
|
||||||
iceRole: transportStats.iceRole,
|
|
||||||
iceState: transportStats.iceState,
|
|
||||||
dtlsState: transportStats.dtlsState,
|
|
||||||
dtlsCipher: transportStats.dtlsCipher,
|
|
||||||
tlsVersion: transportStats.tlsVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processCandidateStats(candidateStats) {
|
|
||||||
if (!candidateStats) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Figure out how to normalize ip and address across browsers
|
|
||||||
// networkType property excluded for privacy reasons:
|
|
||||||
// https://www.w3.org/TR/webrtc-stats/#sotd
|
|
||||||
return {
|
|
||||||
priority:
|
|
||||||
candidateStats.priority && candidateStats.priority.toString(),
|
|
||||||
candidateType: candidateStats.candidateType,
|
|
||||||
protocol: candidateStats.protocol,
|
|
||||||
address: !!candidateStats.address
|
|
||||||
? candidateStats.address
|
|
||||||
: candidateStats.ip,
|
|
||||||
port: candidateStats.port,
|
|
||||||
url: candidateStats.url,
|
|
||||||
relayProtocol: candidateStats.relayProtocol,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processCandidatePair(candidatePairStats) {
|
|
||||||
if (!candidatePairStats) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localCandidateStats = statsArr.find(
|
|
||||||
(stat) => stat.id === candidatePairStats.localCandidateId
|
|
||||||
);
|
|
||||||
event.localCandidate = processCandidateStats(localCandidateStats);
|
|
||||||
|
|
||||||
const remoteCandidateStats = statsArr.find(
|
|
||||||
(stat) => stat.id === candidatePairStats.remoteCandidateId
|
|
||||||
);
|
|
||||||
event.remoteCandidate = processCandidateStats(remoteCandidateStats);
|
|
||||||
|
|
||||||
const transportStats = statsArr.find(
|
|
||||||
(stat) => stat.id === candidatePairStats.transportId
|
|
||||||
);
|
|
||||||
event.transport = processTransportStats(transportStats);
|
|
||||||
|
|
||||||
return {
|
|
||||||
state: candidatePairStats.state,
|
|
||||||
bytesSent: candidatePairStats.bytesSent,
|
|
||||||
bytesReceived: candidatePairStats.bytesReceived,
|
|
||||||
requestsSent: candidatePairStats.requestsSent,
|
|
||||||
requestsReceived: candidatePairStats.requestsReceived,
|
|
||||||
responsesSent: candidatePairStats.responsesSent,
|
|
||||||
responsesReceived: candidatePairStats.responsesReceived,
|
|
||||||
currentRoundTripTime: secToMs(
|
|
||||||
candidatePairStats.currentRoundTripTime
|
|
||||||
),
|
|
||||||
totalRoundTripTime: secToMs(candidatePairStats.totalRoundTripTime),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firefox uses the deprecated "selected" property for the nominated ice candidate.
|
|
||||||
const selectedCandidatePair = statsArr.find(
|
|
||||||
(stat) =>
|
|
||||||
stat.type === "candidate-pair" && (stat.selected || stat.nominated)
|
|
||||||
);
|
|
||||||
|
|
||||||
event.selectedCandidatePair = processCandidatePair(selectedCandidatePair);
|
|
||||||
|
|
||||||
function processCodecStats(codecStats) {
|
|
||||||
if (!codecStats) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payload type enums and MIME types listed here:
|
|
||||||
// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml
|
|
||||||
return {
|
|
||||||
mimeType: codecStats.mimeType,
|
|
||||||
clockRate: codecStats.clockRate,
|
|
||||||
payloadType: codecStats.payloadType,
|
|
||||||
channels: codecStats.channels,
|
|
||||||
sdpFmtpLine: codecStats.sdpFmtpLine,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processRTPStreamStats(rtpStreamStats) {
|
|
||||||
const codecStats = statsArr.find(
|
|
||||||
(stat) => stat.id === rtpStreamStats.codecId
|
|
||||||
);
|
|
||||||
const codec = processCodecStats(codecStats);
|
|
||||||
|
|
||||||
return {
|
|
||||||
kind: rtpStreamStats.kind,
|
|
||||||
codec,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processInboundRTPStats(inboundRTPStats) {
|
|
||||||
const rtpStreamStats = processRTPStreamStats(inboundRTPStats);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rtpStreamStats,
|
|
||||||
decoderImplementation: inboundRTPStats.decoderImplementation,
|
|
||||||
bytesReceived: inboundRTPStats.bytesReceived,
|
|
||||||
packetsReceived: inboundRTPStats.packetsReceived,
|
|
||||||
packetsLost: inboundRTPStats.packetsLost,
|
|
||||||
jitter: secToMs(inboundRTPStats.jitter),
|
|
||||||
frameWidth: inboundRTPStats.frameWidth,
|
|
||||||
frameHeight: inboundRTPStats.frameHeight,
|
|
||||||
frameBitDepth: inboundRTPStats.frameBitDepth,
|
|
||||||
framesPerSecond:
|
|
||||||
inboundRTPStats.framesPerSecond &&
|
|
||||||
inboundRTPStats.framesPerSecond.toString(),
|
|
||||||
framesReceived: inboundRTPStats.framesReceived,
|
|
||||||
framesDecoded: inboundRTPStats.framesDecoded,
|
|
||||||
framesDropped: inboundRTPStats.framesDropped,
|
|
||||||
totalSamplesDecoded: inboundRTPStats.totalSamplesDecoded,
|
|
||||||
totalDecodeTime: secToMs(inboundRTPStats.totalDecodeTime),
|
|
||||||
totalProcessingDelay: secToMs(inboundRTPStats.totalProcessingDelay),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processOutboundRTPStats(outboundRTPStats) {
|
|
||||||
const rtpStreamStats = processRTPStreamStats(outboundRTPStats);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rtpStreamStats,
|
|
||||||
encoderImplementation: outboundRTPStats.encoderImplementation,
|
|
||||||
bytesSent: outboundRTPStats.bytesSent,
|
|
||||||
packetsSent: outboundRTPStats.packetsSent,
|
|
||||||
frameWidth: outboundRTPStats.frameWidth,
|
|
||||||
frameHeight: outboundRTPStats.frameHeight,
|
|
||||||
frameBitDepth: outboundRTPStats.frameBitDepth,
|
|
||||||
framesPerSecond:
|
|
||||||
outboundRTPStats.framesPerSecond &&
|
|
||||||
outboundRTPStats.framesPerSecond.toString(),
|
|
||||||
framesSent: outboundRTPStats.framesSent,
|
|
||||||
framesEncoded: outboundRTPStats.framesEncoded,
|
|
||||||
qualityLimitationReason: outboundRTPStats.qualityLimitationReason,
|
|
||||||
qualityLimitationResolutionChanges:
|
|
||||||
outboundRTPStats.qualityLimitationResolutionChanges,
|
|
||||||
totalEncodeTime: secToMs(outboundRTPStats.totalEncodeTime),
|
|
||||||
totalPacketSendDelay: secToMs(outboundRTPStats.totalPacketSendDelay),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processRemoteInboundRTPStats(remoteInboundRTPStats) {
|
|
||||||
const rtpStreamStats = processRTPStreamStats(remoteInboundRTPStats);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rtpStreamStats,
|
|
||||||
packetsReceived: remoteInboundRTPStats.packetsReceived,
|
|
||||||
packetsLost: remoteInboundRTPStats.packetsLost,
|
|
||||||
jitter: secToMs(remoteInboundRTPStats.jitter),
|
|
||||||
framesDropped: remoteInboundRTPStats.framesDropped,
|
|
||||||
roundTripTime: secToMs(remoteInboundRTPStats.roundTripTime),
|
|
||||||
totalRoundTripTime: secToMs(remoteInboundRTPStats.totalRoundTripTime),
|
|
||||||
fractionLost:
|
|
||||||
remoteInboundRTPStats.fractionLost !== undefined &&
|
|
||||||
remoteInboundRTPStats.fractionLost.toString(),
|
|
||||||
reportsReceived: remoteInboundRTPStats.reportsReceived,
|
|
||||||
roundTripTimeMeasurements:
|
|
||||||
remoteInboundRTPStats.roundTripTimeMeasurements,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function processRemoteOutboundRTPStats(remoteOutboundRTPStats) {
|
|
||||||
const rtpStreamStats = processRTPStreamStats(remoteOutboundRTPStats);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rtpStreamStats,
|
|
||||||
encoderImplementation: remoteOutboundRTPStats.encoderImplementation,
|
|
||||||
bytesSent: remoteOutboundRTPStats.bytesSent,
|
|
||||||
packetsSent: remoteOutboundRTPStats.packetsSent,
|
|
||||||
roundTripTime: secToMs(remoteOutboundRTPStats.roundTripTime),
|
|
||||||
totalRoundTripTime: secToMs(
|
|
||||||
remoteOutboundRTPStats.totalRoundTripTime
|
|
||||||
),
|
|
||||||
reportsSent: remoteOutboundRTPStats.reportsSent,
|
|
||||||
roundTripTimeMeasurements:
|
|
||||||
remoteOutboundRTPStats.roundTripTimeMeasurements,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
event.inboundRTP = statsArr
|
|
||||||
.filter((stat) => stat.type === "inbound-rtp")
|
|
||||||
.map(processInboundRTPStats);
|
|
||||||
|
|
||||||
event.outboundRTP = statsArr
|
|
||||||
.filter((stat) => stat.type === "outbound-rtp")
|
|
||||||
.map(processOutboundRTPStats);
|
|
||||||
|
|
||||||
event.remoteInboundRTP = statsArr
|
|
||||||
.filter((stat) => stat.type === "remote-inbound-rtp")
|
|
||||||
.map(processRemoteInboundRTPStats);
|
|
||||||
|
|
||||||
event.remoteOutboundRTP = statsArr
|
|
||||||
.filter((stat) => stat.type === "remote-outbound-rtp")
|
|
||||||
.map(processRemoteOutboundRTPStats);
|
|
||||||
|
|
||||||
this.client.sendEvent(
|
|
||||||
this.groupCall.room.roomId,
|
|
||||||
"me.robertlong.call.info",
|
|
||||||
event
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let statsTimeout;
|
|
||||||
|
|
||||||
const sendStats = () => {
|
|
||||||
if (
|
|
||||||
call.state === "ended" ||
|
|
||||||
peerConnection.connectionState === "closed"
|
|
||||||
) {
|
|
||||||
clearTimeout(statsTimeout);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendWebRTCInfoEvent("stats");
|
|
||||||
statsTimeout = setTimeout(sendStats, 30 * 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(sendStats, 30 * 1000);
|
|
||||||
|
|
||||||
peerConnection.addEventListener("iceconnectionstatechange", () => {
|
|
||||||
sendWebRTCInfoEvent("iceconnectionstatechange");
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener("icegatheringstatechange", () => {
|
|
||||||
sendWebRTCInfoEvent("icegatheringstatechange");
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener("negotiationneeded", () => {
|
|
||||||
sendWebRTCInfoEvent("negotiationneeded");
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener("track", () => {
|
|
||||||
sendWebRTCInfoEvent("track");
|
|
||||||
});
|
|
||||||
// NOTE: Not available on Firefox
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1561441
|
|
||||||
peerConnection.addEventListener(
|
|
||||||
"icecandidateerror",
|
|
||||||
({ errorCode, url, errorText }) => {
|
|
||||||
this.client.sendEvent(
|
|
||||||
this.groupCall.room.roomId,
|
|
||||||
"me.robertlong.call.ice_error",
|
|
||||||
{
|
|
||||||
call_id: call.callId,
|
|
||||||
errorCode,
|
|
||||||
url,
|
|
||||||
errorText,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
peerConnection.addEventListener("signalingstatechange", () => {
|
|
||||||
sendWebRTCInfoEvent("signalingstatechange");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,13 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button } from "./button";
|
import { Button } from "../button";
|
||||||
import { PopoverMenuTrigger } from "./PopoverMenu";
|
import { PopoverMenuTrigger } from "../PopoverMenu";
|
||||||
import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg";
|
import { ReactComponent as SpotlightIcon } from "../icons/Spotlight.svg";
|
||||||
import { ReactComponent as FreedomIcon } from "./icons/Freedom.svg";
|
import { ReactComponent as FreedomIcon } from "../icons/Freedom.svg";
|
||||||
import { ReactComponent as CheckIcon } from "./icons/Check.svg";
|
import { ReactComponent as CheckIcon } from "../icons/Check.svg";
|
||||||
import styles from "./GridLayoutMenu.module.css";
|
import styles from "./GridLayoutMenu.module.css";
|
||||||
import { Menu } from "./Menu";
|
import { Menu } from "../Menu";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
import { Tooltip, TooltipTrigger } from "./Tooltip";
|
import { Tooltip, TooltipTrigger } from "../Tooltip";
|
||||||
|
|
||||||
export function GridLayoutMenu({ layout, setLayout }) {
|
export function GridLayoutMenu({ layout, setLayout }) {
|
||||||
return (
|
return (
|
@ -13,9 +13,9 @@ import VideoGrid, {
|
|||||||
import SimpleVideoGrid from "matrix-react-sdk/src/components/views/voip/GroupCallView/SimpleVideoGrid";
|
import SimpleVideoGrid from "matrix-react-sdk/src/components/views/voip/GroupCallView/SimpleVideoGrid";
|
||||||
import "matrix-react-sdk/res/css/views/voip/GroupCallView/_VideoGrid.scss";
|
import "matrix-react-sdk/res/css/views/voip/GroupCallView/_VideoGrid.scss";
|
||||||
import { getAvatarUrl } from "../ConferenceCallManagerHooks";
|
import { getAvatarUrl } from "../ConferenceCallManagerHooks";
|
||||||
import { GroupCallInspector } from "../GroupCallInspector";
|
import { GroupCallInspector } from "./GroupCallInspector";
|
||||||
import { OverflowMenu } from "./OverflowMenu";
|
import { OverflowMenu } from "./OverflowMenu";
|
||||||
import { GridLayoutMenu } from "../GridLayoutMenu";
|
import { GridLayoutMenu } from "./GridLayoutMenu";
|
||||||
import { Avatar } from "../Avatar";
|
import { Avatar } from "../Avatar";
|
||||||
import { UserMenuContainer } from "../UserMenuContainer";
|
import { UserMenuContainer } from "../UserMenuContainer";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Modal, ModalContent } from "./Modal";
|
import { Modal, ModalContent } from "../Modal";
|
||||||
import { CopyButton } from "./button";
|
import { CopyButton } from "../button";
|
||||||
import { getRoomUrl } from "./ConferenceCallManagerHooks";
|
import { getRoomUrl } from "../ConferenceCallManagerHooks";
|
||||||
import styles from "./InviteModal.module.css";
|
import styles from "./InviteModal.module.css";
|
||||||
|
|
||||||
export function InviteModal({ roomId, ...rest }) {
|
export function InviteModal({ roomId, ...rest }) {
|
@ -8,7 +8,7 @@ import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
|||||||
import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
|
import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { SettingsModal } from "../settings/SettingsModal";
|
import { SettingsModal } from "../settings/SettingsModal";
|
||||||
import { InviteModal } from "../InviteModal";
|
import { InviteModal } from "./InviteModal";
|
||||||
import { Tooltip, TooltipTrigger } from "../Tooltip";
|
import { Tooltip, TooltipTrigger } from "../Tooltip";
|
||||||
|
|
||||||
export function OverflowMenu({
|
export function OverflowMenu({
|
||||||
|
Loading…
Reference in New Issue
Block a user