@streamplace/components 0.9.9 → 0.9.10

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.
Files changed (166) hide show
  1. package/assets/badges/live.png +0 -0
  2. package/assets/badges/live_2x.png +0 -0
  3. package/assets/badges/mod.png +0 -0
  4. package/assets/badges/mod_2x.png +0 -0
  5. package/assets/badges/vip.png +0 -0
  6. package/assets/badges/vip_2x.png +0 -0
  7. package/dist/components/chat/badge.d.ts +10 -0
  8. package/dist/components/chat/badge.d.ts.map +1 -0
  9. package/dist/components/chat/badge.js +29 -0
  10. package/dist/components/chat/badge.js.map +1 -0
  11. package/dist/components/chat/chat-box.d.ts +5 -1
  12. package/dist/components/chat/chat-box.d.ts.map +1 -1
  13. package/dist/components/chat/chat-box.js +55 -50
  14. package/dist/components/chat/chat-box.js.map +1 -1
  15. package/dist/components/chat/chat-message.d.ts.map +1 -1
  16. package/dist/components/chat/chat-message.js +9 -11
  17. package/dist/components/chat/chat-message.js.map +1 -1
  18. package/dist/components/chat/chat.d.ts.map +1 -1
  19. package/dist/components/chat/chat.js +35 -41
  20. package/dist/components/chat/chat.js.map +1 -1
  21. package/dist/components/chat/emoji-suggestions.d.ts +7 -18
  22. package/dist/components/chat/emoji-suggestions.d.ts.map +1 -1
  23. package/dist/components/chat/emoji-suggestions.js +6 -2
  24. package/dist/components/chat/emoji-suggestions.js.map +1 -1
  25. package/dist/components/chat/system-message.d.ts.map +1 -1
  26. package/dist/components/chat/system-message.js +9 -1
  27. package/dist/components/chat/system-message.js.map +1 -1
  28. package/dist/components/chat/teleport-modal.d.ts +9 -0
  29. package/dist/components/chat/teleport-modal.d.ts.map +1 -0
  30. package/dist/components/chat/teleport-modal.js +148 -0
  31. package/dist/components/chat/teleport-modal.js.map +1 -0
  32. package/dist/components/chat/user-profile-card.d.ts +12 -0
  33. package/dist/components/chat/user-profile-card.d.ts.map +1 -0
  34. package/dist/components/chat/user-profile-card.js +135 -0
  35. package/dist/components/chat/user-profile-card.js.map +1 -0
  36. package/dist/components/dashboard/chat-panel.d.ts +3 -1
  37. package/dist/components/dashboard/chat-panel.d.ts.map +1 -1
  38. package/dist/components/dashboard/chat-panel.js +2 -2
  39. package/dist/components/dashboard/chat-panel.js.map +1 -1
  40. package/dist/components/dashboard/header.d.ts +1 -1
  41. package/dist/components/dashboard/header.d.ts.map +1 -1
  42. package/dist/components/dashboard/header.js +4 -0
  43. package/dist/components/dashboard/header.js.map +1 -1
  44. package/dist/components/dashboard/information-widget.d.ts.map +1 -1
  45. package/dist/components/dashboard/information-widget.js +13 -11
  46. package/dist/components/dashboard/information-widget.js.map +1 -1
  47. package/dist/components/mobile-player/fullscreen.d.ts.map +1 -1
  48. package/dist/components/mobile-player/fullscreen.js +2 -1
  49. package/dist/components/mobile-player/fullscreen.js.map +1 -1
  50. package/dist/components/mobile-player/fullscreen.native.d.ts.map +1 -1
  51. package/dist/components/mobile-player/fullscreen.native.js +3 -2
  52. package/dist/components/mobile-player/fullscreen.native.js.map +1 -1
  53. package/dist/components/mobile-player/ui/audio-only-overlay.d.ts +2 -0
  54. package/dist/components/mobile-player/ui/audio-only-overlay.d.ts.map +1 -0
  55. package/dist/components/mobile-player/ui/audio-only-overlay.js +29 -0
  56. package/dist/components/mobile-player/ui/audio-only-overlay.js.map +1 -0
  57. package/dist/components/mobile-player/ui/index.d.ts +1 -0
  58. package/dist/components/mobile-player/ui/index.d.ts.map +1 -1
  59. package/dist/components/mobile-player/ui/index.js +1 -0
  60. package/dist/components/mobile-player/ui/index.js.map +1 -1
  61. package/dist/components/mobile-player/ui/input.d.ts +1 -2
  62. package/dist/components/mobile-player/ui/input.d.ts.map +1 -1
  63. package/dist/components/mobile-player/ui/input.js +2 -2
  64. package/dist/components/mobile-player/ui/input.js.map +1 -1
  65. package/dist/components/mobile-player/ui/metrics.d.ts.map +1 -1
  66. package/dist/components/mobile-player/ui/metrics.js +20 -2
  67. package/dist/components/mobile-player/ui/metrics.js.map +1 -1
  68. package/dist/components/mobile-player/ui/viewer-context-menu.d.ts.map +1 -1
  69. package/dist/components/mobile-player/ui/viewer-context-menu.js +29 -1
  70. package/dist/components/mobile-player/ui/viewer-context-menu.js.map +1 -1
  71. package/dist/components/mobile-player/use-webrtc.d.ts +4 -2
  72. package/dist/components/mobile-player/use-webrtc.d.ts.map +1 -1
  73. package/dist/components/mobile-player/use-webrtc.js +89 -15
  74. package/dist/components/mobile-player/use-webrtc.js.map +1 -1
  75. package/dist/components/mobile-player/video-async.native.d.ts.map +1 -1
  76. package/dist/components/mobile-player/video-async.native.js +15 -5
  77. package/dist/components/mobile-player/video-async.native.js.map +1 -1
  78. package/dist/components/mobile-player/video.d.ts.map +1 -1
  79. package/dist/components/mobile-player/video.js +10 -7
  80. package/dist/components/mobile-player/video.js.map +1 -1
  81. package/dist/components/ui/dialog.d.ts.map +1 -1
  82. package/dist/components/ui/dialog.js +8 -0
  83. package/dist/components/ui/dialog.js.map +1 -1
  84. package/dist/hooks/useLivestreamInfo.d.ts +0 -2
  85. package/dist/hooks/useLivestreamInfo.d.ts.map +1 -1
  86. package/dist/hooks/useLivestreamInfo.js +13 -24
  87. package/dist/hooks/useLivestreamInfo.js.map +1 -1
  88. package/dist/hooks/useSegmentTiming.d.ts +1 -1
  89. package/dist/hooks/useSegmentTiming.d.ts.map +1 -1
  90. package/dist/hooks/useSegmentTiming.js +4 -0
  91. package/dist/hooks/useSegmentTiming.js.map +1 -1
  92. package/dist/i18n/i18n-loader.native.d.ts.map +1 -1
  93. package/dist/i18n/i18n-loader.native.js +13 -4
  94. package/dist/i18n/i18n-loader.native.js.map +1 -1
  95. package/dist/lib/slash-commands/teleport.d.ts +5 -1
  96. package/dist/lib/slash-commands/teleport.d.ts.map +1 -1
  97. package/dist/lib/slash-commands/teleport.js +57 -1
  98. package/dist/lib/slash-commands/teleport.js.map +1 -1
  99. package/dist/livestream-store/chat.d.ts +1 -0
  100. package/dist/livestream-store/chat.d.ts.map +1 -1
  101. package/dist/livestream-store/chat.js +10 -1
  102. package/dist/livestream-store/chat.js.map +1 -1
  103. package/dist/livestream-store/livestream-state.d.ts +2 -0
  104. package/dist/livestream-store/livestream-state.d.ts.map +1 -1
  105. package/dist/livestream-store/livestream-store.d.ts +1 -1
  106. package/dist/livestream-store/livestream-store.d.ts.map +1 -1
  107. package/dist/livestream-store/livestream-store.js +10 -1
  108. package/dist/livestream-store/livestream-store.js.map +1 -1
  109. package/dist/livestream-store/websocket-consumer.d.ts.map +1 -1
  110. package/dist/livestream-store/websocket-consumer.js +1 -0
  111. package/dist/livestream-store/websocket-consumer.js.map +1 -1
  112. package/dist/player-store/player-state.d.ts +1 -5
  113. package/dist/player-store/player-state.d.ts.map +1 -1
  114. package/dist/player-store/player-store.d.ts.map +1 -1
  115. package/dist/player-store/player-store.js +16 -5
  116. package/dist/player-store/player-store.js.map +1 -1
  117. package/dist/player-store/single-player-provider.d.ts +0 -2
  118. package/dist/player-store/single-player-provider.d.ts.map +1 -1
  119. package/dist/player-store/single-player-provider.js +0 -2
  120. package/dist/player-store/single-player-provider.js.map +1 -1
  121. package/dist/streamplace-store/stream.d.ts +4 -2
  122. package/dist/streamplace-store/stream.d.ts.map +1 -1
  123. package/dist/streamplace-store/stream.js +36 -74
  124. package/dist/streamplace-store/stream.js.map +1 -1
  125. package/locales/manifest.json +21 -1
  126. package/locales/ro-RO/common.ftl +74 -0
  127. package/locales/ro-RO/settings.ftl +233 -0
  128. package/locales/zh-Hans/common.ftl +57 -0
  129. package/locales/zh-Hans/settings.ftl +222 -0
  130. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  131. package/package.json +2 -2
  132. package/src/components/chat/badge.tsx +45 -0
  133. package/src/components/chat/chat-box.tsx +84 -54
  134. package/src/components/chat/chat-message.tsx +25 -21
  135. package/src/components/chat/chat.tsx +105 -88
  136. package/src/components/chat/emoji-suggestions.tsx +12 -21
  137. package/src/components/chat/system-message.tsx +12 -2
  138. package/src/components/chat/teleport-modal.tsx +310 -0
  139. package/src/components/chat/user-profile-card.tsx +275 -0
  140. package/src/components/dashboard/chat-panel.tsx +8 -0
  141. package/src/components/dashboard/header.tsx +7 -3
  142. package/src/components/dashboard/information-widget.tsx +15 -9
  143. package/src/components/mobile-player/fullscreen.native.tsx +3 -0
  144. package/src/components/mobile-player/fullscreen.tsx +2 -0
  145. package/src/components/mobile-player/ui/audio-only-overlay.tsx +48 -0
  146. package/src/components/mobile-player/ui/index.ts +1 -0
  147. package/src/components/mobile-player/ui/input.tsx +1 -5
  148. package/src/components/mobile-player/ui/metrics.tsx +17 -2
  149. package/src/components/mobile-player/ui/viewer-context-menu.tsx +40 -3
  150. package/src/components/mobile-player/use-webrtc.tsx +118 -17
  151. package/src/components/mobile-player/video-async.native.tsx +18 -5
  152. package/src/components/mobile-player/video.tsx +10 -7
  153. package/src/components/ui/dialog.tsx +8 -0
  154. package/src/hooks/useLivestreamInfo.ts +15 -24
  155. package/src/hooks/useSegmentTiming.tsx +7 -2
  156. package/src/i18n/i18n-loader.native.ts +9 -0
  157. package/src/lib/slash-commands/teleport.ts +68 -0
  158. package/src/livestream-store/chat.tsx +12 -0
  159. package/src/livestream-store/livestream-state.tsx +2 -0
  160. package/src/livestream-store/livestream-store.tsx +9 -1
  161. package/src/livestream-store/websocket-consumer.tsx +1 -0
  162. package/src/player-store/player-state.tsx +1 -7
  163. package/src/player-store/player-store.tsx +16 -7
  164. package/src/player-store/single-player-provider.tsx +0 -4
  165. package/src/streamplace-store/stream.tsx +42 -99
  166. package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
@@ -1,13 +1,15 @@
1
- import Picker from "@emoji-mart/react";
2
1
  import Graphemer from "graphemer";
3
2
  import { AtSignIcon, ExternalLink, X } from "lucide-react-native";
4
3
  import { env } from "process";
5
- import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
6
5
  import { Platform, Pressable, TextInput } from "react-native";
7
6
  import { ChatMessageViewHydrated } from "streamplace";
8
7
  import { Button, Loader, Text, toast, useTheme, View } from "../../";
9
8
  import { handleSlashCommand } from "../../lib/slash-commands";
10
- import { registerTeleportCommand } from "../../lib/slash-commands/teleport";
9
+ import {
10
+ createTeleport,
11
+ registerTeleportCommand,
12
+ } from "../../lib/slash-commands/teleport";
11
13
  import { StreamNotifications } from "../../lib/stream-notifications";
12
14
  import { SystemMessages } from "../../lib/system-messages";
13
15
  import {
@@ -24,6 +26,7 @@ import {
24
26
  w,
25
27
  } from "../../lib/theme/atoms";
26
28
  import {
29
+ useAddSystemMessage,
27
30
  useChat,
28
31
  useCreateChatMessage,
29
32
  useLivestream,
@@ -34,8 +37,13 @@ import {
34
37
  import { useDID, usePDSAgent } from "../../streamplace-store";
35
38
  import { Textarea } from "../ui/textarea";
36
39
  import { RenderChatMessage } from "./chat-message";
37
- import { EmojiData, EmojiSuggestions } from "./emoji-suggestions";
40
+ import {
41
+ EmojiData,
42
+ EmojiSuggestions,
43
+ getSkinNative,
44
+ } from "./emoji-suggestions";
38
45
  import { MentionSuggestions } from "./mention-suggestions";
46
+ import { TeleportModal } from "./teleport-modal";
39
47
 
40
48
  const COOL_EMOJI_LIST = [
41
49
  // @ts-ignore we can iterate through this just fine it seems
@@ -49,11 +57,21 @@ export function ChatBox({
49
57
  chatBoxStyle,
50
58
  emojiData,
51
59
  setIsChatVisible,
60
+ onEmojiPickerToggle,
61
+ emojiPicker,
62
+ skinTone = 0,
52
63
  }: {
53
64
  isPopout?: boolean;
54
65
  chatBoxStyle?: any;
55
66
  emojiData: EmojiData | null;
56
67
  setIsChatVisible?: (visible: boolean) => void;
68
+ onEmojiPickerToggle?: () => void;
69
+ emojiPicker?: (
70
+ isOpen: boolean,
71
+ onClose: () => void,
72
+ onSelect: (emoji: any) => void,
73
+ ) => ReactNode;
74
+ skinTone?: number;
57
75
  }) {
58
76
  const [submitting, setSubmitting] = useState(false);
59
77
  const [message, setMessage] = useState("");
@@ -68,6 +86,7 @@ export function ChatBox({
68
86
  new Map(),
69
87
  );
70
88
  const [filteredEmojis, setFilteredEmojis] = useState<any[]>([]);
89
+ const [showTeleportModal, setShowTeleportModal] = useState(false);
71
90
  const isOverLimit = graphemer.countGraphemes(message) > 300;
72
91
 
73
92
  let linfo = useLivestream();
@@ -76,6 +95,7 @@ export function ChatBox({
76
95
 
77
96
  const chat = useChat();
78
97
  const createChatMessage = useCreateChatMessage();
98
+ const addSystemMessage = useAddSystemMessage();
79
99
  const replyTo = useReplyToMessage();
80
100
  const setReplyToMessage = useSetReplyToMessage();
81
101
  const textAreaRef = useRef<TextInput>(null);
@@ -88,7 +108,9 @@ export function ChatBox({
88
108
 
89
109
  useEffect(() => {
90
110
  if (pdsAgent && userDID) {
91
- registerTeleportCommand(pdsAgent, userDID, setActiveTeleportUri);
111
+ registerTeleportCommand(pdsAgent, userDID, setActiveTeleportUri, () =>
112
+ setShowTeleportModal(true),
113
+ );
92
114
  }
93
115
  }, [pdsAgent, userDID, setActiveTeleportUri]);
94
116
 
@@ -105,7 +127,12 @@ export function ChatBox({
105
127
 
106
128
  useEffect(() => {
107
129
  if (pdsAgent && linfo?.author?.did && pdsAgent.did === linfo.author.did) {
108
- registerTeleportCommand(pdsAgent, pdsAgent.did, setActiveTeleportUri);
130
+ registerTeleportCommand(
131
+ pdsAgent,
132
+ pdsAgent.did,
133
+ setActiveTeleportUri,
134
+ () => setShowTeleportModal(true),
135
+ );
109
136
  }
110
137
  }, [pdsAgent, linfo?.author?.did, setActiveTeleportUri]);
111
138
 
@@ -116,11 +143,37 @@ export function ChatBox({
116
143
  };
117
144
 
118
145
  const handleEmojiSelect = (emoji: any) => {
119
- const beforeColon = message.slice(0, message.lastIndexOf(":"));
120
- setMessage(`${beforeColon}${emoji.skins[0]?.native} `);
146
+ console.log("[ChatBox] handleEmojiSelect", emoji);
147
+ if (emoji.s) {
148
+ const beforeColon = message.slice(0, message.lastIndexOf(":"));
149
+ setMessage(`${beforeColon}${getSkinNative(emoji, skinTone)} `);
150
+ } else if (emoji.type === "standard") {
151
+ setMessage(message + emoji.native);
152
+ } else if (emoji.type === "custom") {
153
+ setMessage(message + `:${emoji.name}: `);
154
+ }
121
155
  setShowEmojiSuggestions(false);
122
156
  };
123
157
 
158
+ const handleTeleportSubmit = async (
159
+ targetHandle: string,
160
+ countdownSeconds: number,
161
+ ) => {
162
+ if (!pdsAgent || !userDID) return;
163
+
164
+ const result = await createTeleport(
165
+ pdsAgent,
166
+ userDID,
167
+ targetHandle,
168
+ countdownSeconds,
169
+ setActiveTeleportUri,
170
+ );
171
+
172
+ if (!result.success && result.error) {
173
+ SystemMessages.commandError(result.error);
174
+ }
175
+ };
176
+
124
177
  const updateSuggestions = (text: string) => {
125
178
  // Handle mentions
126
179
  const atIndex = text.lastIndexOf("@");
@@ -205,15 +258,15 @@ export function ChatBox({
205
258
  sort: [5, emoji.id.toLowerCase().indexOf(searchText), 0],
206
259
  }; // includes id
207
260
  }
208
- if (emoji.name.toLowerCase().includes(searchText)) {
261
+ if (emoji.m.toLowerCase().includes(searchText)) {
209
262
  return {
210
263
  emoji,
211
- sort: [6, emoji.name.toLowerCase().indexOf(searchText), 0],
264
+ sort: [6, emoji.m.toLowerCase().indexOf(searchText), 0],
212
265
  };
213
266
  }
214
267
  if (
215
- emoji.keywords &&
216
- emoji.keywords.some((keyword: string) =>
268
+ emoji.k &&
269
+ emoji.k.some((keyword: string) =>
217
270
  keyword.toLowerCase().includes(searchText),
218
271
  )
219
272
  ) {
@@ -280,7 +333,7 @@ export function ChatBox({
280
333
  if (result.handled) {
281
334
  if (result.error) {
282
335
  console.error("Slash command error:", result.error);
283
- SystemMessages.commandError(result.error);
336
+ addSystemMessage(SystemMessages.commandError(result.error));
284
337
  }
285
338
  return;
286
339
  }
@@ -321,6 +374,11 @@ export function ChatBox({
321
374
 
322
375
  return (
323
376
  <View style={[layout.flex.column, flex.shrink[1], gap.all[2]]}>
377
+ <TeleportModal
378
+ open={showTeleportModal}
379
+ onOpenChange={setShowTeleportModal}
380
+ onSubmit={handleTeleportSubmit}
381
+ />
324
382
  {replyTo && (
325
383
  <View
326
384
  style={[
@@ -360,45 +418,6 @@ export function ChatBox({
360
418
  </Pressable>
361
419
  </View>
362
420
  )}
363
- {showEmojiSelector && (
364
- <View
365
- style={{
366
- position: "absolute",
367
- top: 0,
368
- left: 0,
369
- right: 0,
370
- bottom: 0,
371
- zIndex: 200,
372
- }}
373
- pointerEvents="box-none"
374
- >
375
- {/* Overlay to catch outside clicks */}
376
- <Pressable
377
- style={{
378
- position: "absolute",
379
- top: 0,
380
- left: 0,
381
- right: 0,
382
- bottom: 0,
383
- }}
384
- onPress={() => setShowEmojiSelector(false)}
385
- />
386
- <View
387
- style={{
388
- position: "absolute",
389
- bottom: "100%",
390
- left: 0,
391
- zIndex: 2001,
392
- }}
393
- pointerEvents="auto"
394
- >
395
- <Picker
396
- data={emojiData}
397
- onEmojiSelect={(e) => setMessage(message + e.native)}
398
- />
399
- </View>
400
- </View>
401
- )}
402
421
  <View style={[layout.flex.row, layout.flex.alignCenter, gap.all[2]]}>
403
422
  <Textarea
404
423
  ref={textAreaRef}
@@ -508,6 +527,7 @@ export function ChatBox({
508
527
  emojis={filteredEmojis}
509
528
  highlightedIndex={highlightedIndex}
510
529
  onSelect={handleEmojiSelect}
530
+ skinTone={skinTone}
511
531
  />
512
532
  )}
513
533
  {Platform.OS === "web" && (
@@ -516,9 +536,14 @@ export function ChatBox({
516
536
  layout.flex.row,
517
537
  mb[2],
518
538
  gap.all[2],
519
- { justifyContent: "flex-end" },
539
+ { justifyContent: "flex-end", position: "relative" },
520
540
  ]}
521
541
  >
542
+ {emojiPicker?.(
543
+ showEmojiSelector,
544
+ () => setShowEmojiSelector(false),
545
+ handleEmojiSelect,
546
+ )}
522
547
  {env.NODE_ENV === "development" && (
523
548
  <Button
524
549
  variant="secondary"
@@ -562,9 +587,14 @@ export function ChatBox({
562
587
  >
563
588
  <Button
564
589
  variant="secondary"
590
+ id="web-emoji-picker-btn"
565
591
  aria-label="Insert Emoji"
566
592
  style={{ borderRadius: 16, maxWidth: 44, aspectRatio: 1 }}
567
- onPress={() => setShowEmojiSelector(!showEmojiSelector)}
593
+ onPress={() => {
594
+ onEmojiPickerToggle
595
+ ? onEmojiPickerToggle()
596
+ : setShowEmojiSelector(!showEmojiSelector);
597
+ }}
568
598
  >
569
599
  <Text>{COOL_EMOJI_LIST[emojiIconIndex]}</Text>
570
600
  </Button>
@@ -25,6 +25,8 @@ interface Facet {
25
25
 
26
26
  import { useLivestreamStore } from "../../livestream-store";
27
27
  import { Text } from "../ui/text";
28
+ import { BadgeDisplayRow } from "./badge";
29
+ import { UserProfileCard } from "./user-profile-card";
28
30
 
29
31
  const getRgbColor = (color?: { red: number; green: number; blue: number }) =>
30
32
  color ? `rgb(${color.red}, ${color.green}, ${color.blue})` : colors.gray[500];
@@ -152,39 +154,41 @@ export const RenderChatMessage = memo(
152
154
  </Text>
153
155
  </View>
154
156
  )}
155
- <View
156
- style={[
157
- gap.all[2],
158
- layout.flex.row,
159
- { minWidth: 0, maxWidth: "100%" },
160
- ]}
161
- >
157
+ <View style={[layout.flex.row, { minWidth: 0, maxWidth: "100%" }]}>
162
158
  {showTime && (
163
159
  <Text
164
160
  style={{
165
161
  fontVariant: ["tabular-nums"],
166
162
  color: colors.gray[400],
163
+ width: 44,
164
+ marginRight: 8,
167
165
  }}
168
166
  >
169
167
  {formatTime(item.record.createdAt)}
170
168
  </Text>
171
169
  )}
172
- <Text
173
- weight="bold"
174
- color="default"
175
- style={[flex.shrink[1], { minWidth: 0, overflow: "hidden" }]}
176
- >
177
- <Text
178
- style={[
179
- {
180
- cursor: "pointer",
181
- color: getRgbColor(item.chatProfile?.color),
182
- },
183
- ]}
170
+ <Text style={[flex.shrink[1], { minWidth: 0 }]}>
171
+ <UserProfileCard
172
+ uri={item.uri}
173
+ author={item.author}
174
+ badges={item.badges}
184
175
  >
185
- {formatHandleWithAt(item.author)}
176
+ <Text>
177
+ <BadgeDisplayRow badges={item.badges} />
178
+ <Text
179
+ weight="bold"
180
+ style={{
181
+ cursor: "pointer",
182
+ color: getRgbColor(item.chatProfile?.color),
183
+ }}
184
+ >
185
+ {formatHandleWithAt(item.author)}
186
+ </Text>
187
+ </Text>
188
+ </UserProfileCard>
189
+ <Text weight="bold" color="default">
190
+ {": "}
186
191
  </Text>
187
- :{" "}
188
192
  <RichTextMessage
189
193
  text={item.record.text}
190
194
  facets={item.record.facets || []}
@@ -26,6 +26,7 @@ import {
26
26
  import { bg, flex, layout, mr, px, py } from "../../lib/theme/atoms";
27
27
  import { RenderChatMessage } from "./chat-message";
28
28
  import { ModView } from "./mod-view";
29
+ import { ProfileCardProvider } from "./user-profile-card";
29
30
 
30
31
  function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
31
32
  const styleAnimation = useAnimatedStyle(() => {
@@ -141,77 +142,65 @@ const ActionsBar = memo(
141
142
  },
142
143
  );
143
144
 
144
- const ChatLine = memo(({ item }: { item: ChatMessageViewHydrated }) => {
145
- const setReply = useSetReplyToMessage();
146
- const setModMsg = usePlayerStore((state) => state.setModMessage);
147
- const swipeableRef = useRef<SwipeableMethods | null>(null);
148
- const [isHovered, setIsHovered] = useState(false);
149
- const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
145
+ const ChatLine = memo(
146
+ ({
147
+ item,
148
+ isHovered,
149
+ onHoverIn,
150
+ onHoverOut,
151
+ hoverTimeoutRef,
152
+ }: {
153
+ item: ChatMessageViewHydrated;
154
+ isHovered?: boolean;
155
+ onHoverIn?: () => void;
156
+ onHoverOut?: () => void;
157
+ hoverTimeoutRef?: React.MutableRefObject<NodeJS.Timeout | null>;
158
+ }) => {
159
+ const setReply = useSetReplyToMessage();
160
+ const setModMsg = usePlayerStore((state) => state.setModMessage);
161
+ const swipeableRef = useRef<SwipeableMethods | null>(null);
150
162
 
151
- const handleHoverIn = () => {
152
- if (hoverTimeoutRef.current) {
153
- clearTimeout(hoverTimeoutRef.current);
154
- hoverTimeoutRef.current = null;
163
+ if (item.author.did === "did:sys:system") {
164
+ return (
165
+ <SystemMessage
166
+ variant={getSystemMessageType(item) || SystemMessageType.notification}
167
+ timestamp={new Date(item.record.createdAt)}
168
+ title={item.record.text}
169
+ facets={item.record.facets}
170
+ />
171
+ );
155
172
  }
156
- setIsHovered(true);
157
- };
158
-
159
- const handleHoverOut = () => {
160
- hoverTimeoutRef.current = setTimeout(() => {
161
- setIsHovered(false);
162
- }, 50);
163
- };
164
173
 
165
- useEffect(() => {
166
- return () => {
167
- if (hoverTimeoutRef.current) {
168
- clearTimeout(hoverTimeoutRef.current);
169
- }
170
- };
171
- }, []);
172
-
173
- if (item.author.did === "did:sys:system") {
174
- return (
175
- <SystemMessage
176
- variant={getSystemMessageType(item) || SystemMessageType.notification}
177
- timestamp={new Date(item.record.createdAt)}
178
- title={item.record.text}
179
- facets={item.record.facets}
180
- />
181
- );
182
- }
174
+ if (Platform.OS === "web") {
175
+ return (
176
+ <View
177
+ style={[
178
+ py[1],
179
+ px[2],
180
+ {
181
+ position: "relative",
182
+ borderRadius: 8,
183
+ minWidth: 0,
184
+ maxWidth: "100%",
185
+ },
186
+ isHovered && bg.gray[950],
187
+ ]}
188
+ onPointerEnter={onHoverIn}
189
+ onPointerLeave={onHoverOut}
190
+ >
191
+ <Pressable style={[{ minWidth: 0, maxWidth: "100%" }]}>
192
+ <RenderChatMessage item={item} />
193
+ </Pressable>
194
+ <ActionsBar
195
+ item={item}
196
+ visible={!!isHovered}
197
+ hoverTimeoutRef={hoverTimeoutRef!}
198
+ />
199
+ </View>
200
+ );
201
+ }
183
202
 
184
- if (Platform.OS === "web") {
185
203
  return (
186
- <View
187
- style={[
188
- py[1],
189
- px[2],
190
- {
191
- position: "relative",
192
- borderRadius: 8,
193
- minWidth: 0,
194
- maxWidth: "100%",
195
- },
196
- isHovered && bg.gray[950],
197
- ]}
198
- onPointerEnter={handleHoverIn}
199
- onPointerLeave={handleHoverOut}
200
- >
201
- <Pressable style={[{ minWidth: 0, maxWidth: "100%" }]}>
202
- <RenderChatMessage item={item} />
203
- </Pressable>
204
- <ActionsBar
205
- item={item}
206
- visible={isHovered}
207
- hoverTimeoutRef={hoverTimeoutRef}
208
- />
209
- </View>
210
- );
211
- }
212
-
213
- return (
214
- <>
215
204
  <Swipeable
216
205
  containerStyle={[py[1]]}
217
206
  friction={2}
@@ -238,9 +227,9 @@ const ChatLine = memo(({ item }: { item: ChatMessageViewHydrated }) => {
238
227
  >
239
228
  <RenderChatMessage item={item} />
240
229
  </Swipeable>
241
- </>
242
- );
243
- });
230
+ );
231
+ },
232
+ );
244
233
 
245
234
  export function Chat({
246
235
  shownMessages = SHOWN_MSGS,
@@ -254,6 +243,24 @@ export function Chat({
254
243
  const chat = useChat();
255
244
  const [isScrolledUp, setIsScrolledUp] = useState(false);
256
245
  const flatListRef = useRef<FlatList>(null);
246
+ const [hoveredMessageUri, setHoveredMessageUri] = useState<string | null>(
247
+ null,
248
+ );
249
+ const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
250
+
251
+ const handleHoverIn = (uri: string) => {
252
+ if (hoverTimeoutRef.current) {
253
+ clearTimeout(hoverTimeoutRef.current);
254
+ hoverTimeoutRef.current = null;
255
+ }
256
+ setHoveredMessageUri(uri);
257
+ };
258
+
259
+ const handleHoverOut = () => {
260
+ hoverTimeoutRef.current = setTimeout(() => {
261
+ setHoveredMessageUri(null);
262
+ }, 50);
263
+ };
257
264
 
258
265
  // Animation for scroll-to-bottom button
259
266
  const buttonOpacity = useSharedValue(0);
@@ -309,25 +316,35 @@ export function Chat({
309
316
  },
310
317
  ].concat(propsStyle || [])}
311
318
  >
312
- <FlatList
313
- ref={flatListRef}
314
- style={[
315
- flex.grow[1],
316
- flex.shrink[1],
317
- { minWidth: 0, maxWidth: "100%" },
318
- ]}
319
- data={chat.slice(0, shownMessages)}
320
- inverted={true}
321
- keyExtractor={keyExtractor}
322
- renderItem={({ item, index }) => <ChatLine item={item} />}
323
- removeClippedSubviews={true}
324
- maxToRenderPerBatch={10}
325
- initialNumToRender={10}
326
- updateCellsBatchingPeriod={50}
327
- onScroll={handleScroll}
328
- scrollEventThrottle={16}
329
- nestedScrollEnabled={true}
330
- />
319
+ <ProfileCardProvider>
320
+ <FlatList
321
+ ref={flatListRef}
322
+ style={[
323
+ flex.grow[1],
324
+ flex.shrink[1],
325
+ { minWidth: 0, maxWidth: "100%" },
326
+ ]}
327
+ data={chat.slice(0, shownMessages)}
328
+ inverted={true}
329
+ keyExtractor={keyExtractor}
330
+ renderItem={({ item }) => (
331
+ <ChatLine
332
+ item={item}
333
+ isHovered={hoveredMessageUri === item.uri}
334
+ onHoverIn={() => handleHoverIn(item.uri)}
335
+ onHoverOut={handleHoverOut}
336
+ hoverTimeoutRef={hoverTimeoutRef}
337
+ />
338
+ )}
339
+ removeClippedSubviews={true}
340
+ maxToRenderPerBatch={10}
341
+ initialNumToRender={10}
342
+ updateCellsBatchingPeriod={50}
343
+ onScroll={handleScroll}
344
+ scrollEventThrottle={16}
345
+ nestedScrollEnabled={true}
346
+ />
347
+ </ProfileCardProvider>
331
348
  <Reanimated.View
332
349
  style={[
333
350
  {
@@ -4,46 +4,37 @@ import { Code, Text, View } from "../..";
4
4
  import { bg, layout, left, right, zIndex } from "../../lib/theme/atoms";
5
5
 
6
6
  export interface EmojiData {
7
- categories: Category[];
8
7
  emojis: { [key: string]: Emoji };
9
8
  aliases: { [key: string]: string };
10
- sheet: Sheet;
11
- }
12
-
13
- export interface Category {
14
- id: string;
15
- emojis: string[];
16
9
  }
17
10
 
18
11
  export interface Emoji {
19
12
  id: string;
20
- name: string;
21
- keywords: string[];
22
- skins: Skin[];
23
- version: number;
24
- emoticons?: string[];
13
+ m: string;
14
+ k: string[];
15
+ s: Skin[];
25
16
  }
26
17
 
27
18
  export interface Skin {
28
- unified: string;
29
- native: string;
30
- }
31
-
32
- export interface Sheet {
33
- cols: number;
34
- rows: number;
19
+ n: string;
35
20
  }
36
21
 
37
22
  interface EmojiSuggestionsProps {
38
23
  emojis: Emoji[];
39
24
  onSelect: (emoji: Emoji) => void;
40
25
  highlightedIndex: number;
26
+ skinTone?: number;
27
+ }
28
+
29
+ export function getSkinNative(emoji: Emoji, skinTone: number = 0): string {
30
+ return (emoji.s[skinTone] ?? emoji.s[0]).n;
41
31
  }
42
32
 
43
33
  export function EmojiSuggestions({
44
34
  emojis,
45
35
  onSelect,
46
36
  highlightedIndex,
37
+ skinTone = 0,
47
38
  }: EmojiSuggestionsProps) {
48
39
  if (!emojis || emojis.length === 0) {
49
40
  return null;
@@ -83,10 +74,10 @@ export function EmojiSuggestions({
83
74
  ]}
84
75
  >
85
76
  <Text style={{ fontSize: 16, marginRight: 8 }}>
86
- {emoji.skins[0]?.native}
77
+ {getSkinNative(emoji, skinTone)}
87
78
  </Text>
88
79
  <Text style={{ color: "white", fontSize: 14 }}>
89
- <Code style={[bg.gray[950]]}>:{emoji.id}:</Code> {emoji.name}
80
+ <Code style={[bg.gray[950]]}>:{emoji.id}:</Code> {emoji.m}
90
81
  </Text>
91
82
  </Pressable>
92
83
  ))}
@@ -1,7 +1,7 @@
1
1
  import { View } from "react-native";
2
2
  import { Main } from "streamplace/src/lexicons/types/place/stream/richtext/facet";
3
3
  import { SystemMessageType } from "../../lib/system-messages";
4
- import { colors, flex, gap, layout, ml, pb, pl, px, w } from "../../ui";
4
+ import { bg, colors, flex, gap, layout, ml, pb, pl, px, r, w } from "../../ui";
5
5
  import { Code, Text } from "../ui/text";
6
6
  import { RichTextMessage } from "./chat-message";
7
7
 
@@ -18,8 +18,18 @@ export function SystemMessage({
18
18
  timestamp,
19
19
  facets,
20
20
  }: SystemMessageProps) {
21
+ const isError = variant === SystemMessageType.command_error;
22
+
21
23
  return (
22
- <View style={[w.percent[100], px[2], pb[2]]}>
24
+ <View
25
+ style={[
26
+ w.percent[100],
27
+ px[2],
28
+ pb[2],
29
+ isError && bg.red[950],
30
+ isError && r.md,
31
+ ]}
32
+ >
23
33
  <Code color="muted" tracking="widest" style={[pl[12], ml[1]]}>
24
34
  SYSTEM MESSAGE
25
35
  </Code>