@@ -86,19 +127,162 @@ function RoomHeader({ ev }: { ev: TaggedNostrEvent }) {
);
}
-function ParticipantList({ ev }: { ev: TaggedNostrEvent }) {
- const participants = useParticipants();
+function RoomBody({ ev, tab, onSelectTab }: { ev: TaggedNostrEvent; tab: RoomTab; onSelectTab: (t: RoomTab) => void }) {
+ const participants = useParticipants({
+ updateOnlyOn: [
+ RoomEvent.ParticipantConnected,
+ RoomEvent.ParticipantDisconnected,
+ RoomEvent.ParticipantPermissionsChanged,
+ RoomEvent.TrackMuted,
+ RoomEvent.TrackPublished,
+ RoomEvent.TrackUnmuted,
+ RoomEvent.TrackUnmuted,
+ ],
+ });
return (
-
-
-
-
- {participants.map(a => (
-
- ))}
+
+
+
onSelectTab(RoomTab.Participants)}>
+
+
+
onSelectTab(RoomTab.Chat)}>
+
+
+ {tab === RoomTab.Participants && (
+
+ {participants.map(a => (
+
+ ))}
+
+ )}
+ {tab === RoomTab.Chat && (
+ <>
+
+
+ >
+ )}
+
+ );
+}
+
+function MyControls() {
+ const room = useEnsureRoom();
+ const p = room.localParticipant;
+ const permissions = useParticipantPermissions({
+ participant: p,
+ });
+ useEffect(() => {
+ if (permissions && p instanceof LocalParticipant) {
+ const handler = (lt: LocalTrackPublication) => {
+ lt.mute();
+ };
+ p.on("localTrackPublished", handler);
+ if (permissions.canPublish && p.audioTrackPublications.size === 0) {
+ p.setMicrophoneEnabled(true);
+ }
+ return () => {
+ p.off("localTrackPublished", handler);
+ };
+ }
+ }, [p, permissions]);
+ const isMuted = p.getTrackPublication(Track.Source.Microphone)?.isMuted ?? true;
+
+ return (
+
+ {p.permissions?.canPublish && (
+ {
+ if (isMuted) {
+ await p.setMicrophoneEnabled(true);
+ } else {
+ await p.setMicrophoneEnabled(false);
+ }
+ }}
+ />
+ )}
+ {/**/}
+
+ );
+}
+
+function RoomChat({ ev }: { ev: TaggedNostrEvent }) {
+ const link = NostrLink.fromEvent(ev);
+ const sub = useMemo(() => {
+ const sub = new RequestBuilder(`room-chat:${link.tagKey}`);
+ sub.withOptions({ leaveOpen: true, replaceable: true });
+ sub.withFilter().replyToLink([link]).kinds([EventKind.LiveEventChat]).limit(100);
+ return sub;
+ }, [link.tagKey]);
+ const chat = useRequestBuilder(sub);
+
+ return (
+
+
+ {chat
+ .sort((a, b) => b.created_at - a.created_at)
+ .map(e => (
+
+ ))}
+
+
+ );
+}
+
+function ChatMessage({ ev }: { ev: TaggedNostrEvent }) {
+ return (
+
+ );
+}
+
+function WriteChatMessage({ ev }: { ev: TaggedNostrEvent }) {
+ const link = NostrLink.fromEvent(ev);
+ const [chat, setChat] = useState("");
+ const { publisher, system } = useEventPublisher();
+ const { formatMessage } = useIntl();
+
+ async function sendMessage() {
+ if (!publisher || !system || chat.length < 2) return;
+ const eChat = await publisher.generic(eb => eb.kind(EventKind.LiveEventChat).tag(link.toEventTag()!).content(chat));
+ await system.BroadcastEvent(eChat);
+ setChat("");
+ }
+
+ return (
+
+ setChat(e.target.value)}
+ className="grow"
+ onKeyDown={e => {
+ if (e.key === "Enter") {
+ sendMessage();
+ }
+ }}
+ />
+
);
}
@@ -116,16 +300,27 @@ function NostrParticipants({ ev }: { ev: TaggedNostrEvent }) {
}, [link.tagKey]);
const presense = useRequestBuilder(sub);
- return
a.pubkey))} size={32} />;
+ const filteredPresence = presense.filter(ev => ev.created_at > unixNow() - 600);
+ return a.pubkey))} size={32} />;
}
function LiveKitUser({ p }: { p: RemoteParticipant | LocalParticipant }) {
const pubkey = p.identity.startsWith("guest-") ? "anon" : p.identity;
const profile = useUserProfile(pubkey);
+ const mic = p.getTrackPublication(Track.Source.Microphone);
+
return (
-
-
+
+ {mic?.audioTrack?.mediaStreamTrack && (
+
+ )}
+
+
+
+
+ {p.permissions?.canPublish &&
Speaker
}
+
);
}
diff --git a/packages/app/src/Components/User/ProfileImage.tsx b/packages/app/src/Components/User/ProfileImage.tsx
index c46c054f..4dd6b9df 100644
--- a/packages/app/src/Components/User/ProfileImage.tsx
+++ b/packages/app/src/Components/User/ProfileImage.tsx
@@ -118,7 +118,7 @@ export default function ProfileImage({
const classNamesOverInner = classNames(
"min-w-0",
{
- "grid grid-cols-[min-content_auto] gap-3 items-center": showUsername,
+ "grid grid-cols-[min-content_auto] gap-2 items-center": showUsername,
},
className,
);
diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json
index 9ba6eb37..3aa06004 100644
--- a/packages/app/src/lang.json
+++ b/packages/app/src/lang.json
@@ -1431,6 +1431,9 @@
"W9355R": {
"defaultMessage": "Unmute"
},
+ "WTrOy3": {
+ "defaultMessage": "Chat"
+ },
"WeLEuL": {
"defaultMessage": "From Server"
},
@@ -1485,6 +1488,9 @@
"YH2RKk": {
"defaultMessage": "Popular media servers."
},
+ "YLGfQn": {
+ "defaultMessage": "Write message"
+ },
"YQZY/S": {
"defaultMessage": "It looks like you dont follow enough people, take a look at {newUsersPage} to discover people to follow!"
},
diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json
index 34ca47ed..d0d9a91d 100644
--- a/packages/app/src/translations/en.json
+++ b/packages/app/src/translations/en.json
@@ -474,6 +474,7 @@
"W2PiAr": "{n} Blocked",
"W4SaxY": "Local",
"W9355R": "Unmute",
+ "WTrOy3": "Chat",
"WeLEuL": "From Server",
"Wj5TbN": "Issues",
"WmZhfL": "Automatically translate notes to your local language",
@@ -492,6 +493,7 @@
"YDMrKK": "Users",
"YDURw6": "Service URL",
"YH2RKk": "Popular media servers.",
+ "YLGfQn": "Write message",
"YQZY/S": "It looks like you dont follow enough people, take a look at {newUsersPage} to discover people to follow!",
"YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.",
"YU7ZYp": "Public Chat",
diff --git a/packages/system/src/event-kind.ts b/packages/system/src/event-kind.ts
index 842acaa2..25a4a46a 100644
--- a/packages/system/src/event-kind.ts
+++ b/packages/system/src/event-kind.ts
@@ -54,7 +54,8 @@ const enum EventKind {
LongFormTextNote = 30023, // NIP-23
AppData = 30_078, // NIP-78
- LiveEvent = 30311, // NIP-102
+ LiveEvent = 30311, // NIP-53
+ LiveEventChat = 1311, // NIP-53
UserStatus = 30315, // NIP-38
ZapstrTrack = 31337,
ApplicationHandler = 31_990,