@streamplace/components 0.9.7 → 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.
- package/assets/badges/live.png +0 -0
- package/assets/badges/live_2x.png +0 -0
- package/assets/badges/mod.png +0 -0
- package/assets/badges/mod_2x.png +0 -0
- package/assets/badges/vip.png +0 -0
- package/assets/badges/vip_2x.png +0 -0
- package/dist/components/chat/badge.d.ts +10 -0
- package/dist/components/chat/badge.d.ts.map +1 -0
- package/dist/components/chat/badge.js +29 -0
- package/dist/components/chat/badge.js.map +1 -0
- package/dist/components/chat/chat-box.d.ts +5 -1
- package/dist/components/chat/chat-box.d.ts.map +1 -1
- package/dist/components/chat/chat-box.js +55 -50
- package/dist/components/chat/chat-box.js.map +1 -1
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +9 -11
- package/dist/components/chat/chat-message.js.map +1 -1
- package/dist/components/chat/chat.d.ts.map +1 -1
- package/dist/components/chat/chat.js +37 -43
- package/dist/components/chat/chat.js.map +1 -1
- package/dist/components/chat/emoji-suggestions.d.ts +7 -18
- package/dist/components/chat/emoji-suggestions.d.ts.map +1 -1
- package/dist/components/chat/emoji-suggestions.js +6 -2
- package/dist/components/chat/emoji-suggestions.js.map +1 -1
- package/dist/components/chat/system-message.d.ts.map +1 -1
- package/dist/components/chat/system-message.js +9 -1
- package/dist/components/chat/system-message.js.map +1 -1
- package/dist/components/chat/teleport-modal.d.ts +9 -0
- package/dist/components/chat/teleport-modal.d.ts.map +1 -0
- package/dist/components/chat/teleport-modal.js +148 -0
- package/dist/components/chat/teleport-modal.js.map +1 -0
- package/dist/components/chat/user-profile-card.d.ts +12 -0
- package/dist/components/chat/user-profile-card.d.ts.map +1 -0
- package/dist/components/chat/user-profile-card.js +135 -0
- package/dist/components/chat/user-profile-card.js.map +1 -0
- package/dist/components/dashboard/chat-panel.d.ts +3 -1
- package/dist/components/dashboard/chat-panel.d.ts.map +1 -1
- package/dist/components/dashboard/chat-panel.js +2 -2
- package/dist/components/dashboard/chat-panel.js.map +1 -1
- package/dist/components/dashboard/header.d.ts +2 -3
- package/dist/components/dashboard/header.d.ts.map +1 -1
- package/dist/components/dashboard/header.js +6 -2
- package/dist/components/dashboard/header.js.map +1 -1
- package/dist/components/dashboard/information-widget.d.ts.map +1 -1
- package/dist/components/dashboard/information-widget.js +15 -12
- package/dist/components/dashboard/information-widget.js.map +1 -1
- package/dist/components/mobile-player/fullscreen.d.ts.map +1 -1
- package/dist/components/mobile-player/fullscreen.js +2 -1
- package/dist/components/mobile-player/fullscreen.js.map +1 -1
- package/dist/components/mobile-player/fullscreen.native.d.ts.map +1 -1
- package/dist/components/mobile-player/fullscreen.native.js +3 -2
- package/dist/components/mobile-player/fullscreen.native.js.map +1 -1
- package/dist/components/mobile-player/player.d.ts.map +1 -1
- package/dist/components/mobile-player/player.js +15 -0
- package/dist/components/mobile-player/player.js.map +1 -1
- package/dist/components/mobile-player/ui/audio-only-overlay.d.ts +2 -0
- package/dist/components/mobile-player/ui/audio-only-overlay.d.ts.map +1 -0
- package/dist/components/mobile-player/ui/audio-only-overlay.js +29 -0
- package/dist/components/mobile-player/ui/audio-only-overlay.js.map +1 -0
- package/dist/components/mobile-player/ui/index.d.ts +1 -0
- package/dist/components/mobile-player/ui/index.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/index.js +1 -0
- package/dist/components/mobile-player/ui/index.js.map +1 -1
- package/dist/components/mobile-player/ui/input.d.ts +3 -2
- package/dist/components/mobile-player/ui/input.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/input.js +18 -2
- package/dist/components/mobile-player/ui/input.js.map +1 -1
- package/dist/components/mobile-player/ui/metrics.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/metrics.js +20 -2
- package/dist/components/mobile-player/ui/metrics.js.map +1 -1
- package/dist/components/mobile-player/ui/streamer-context-menu.d.ts +3 -1
- package/dist/components/mobile-player/ui/streamer-context-menu.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/streamer-context-menu.js +64 -2
- package/dist/components/mobile-player/ui/streamer-context-menu.js.map +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js +29 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js.map +1 -1
- package/dist/components/mobile-player/use-webrtc.d.ts +4 -2
- package/dist/components/mobile-player/use-webrtc.d.ts.map +1 -1
- package/dist/components/mobile-player/use-webrtc.js +89 -15
- package/dist/components/mobile-player/use-webrtc.js.map +1 -1
- package/dist/components/mobile-player/video-async.native.d.ts.map +1 -1
- package/dist/components/mobile-player/video-async.native.js +15 -5
- package/dist/components/mobile-player/video-async.native.js.map +1 -1
- package/dist/components/mobile-player/video.d.ts.map +1 -1
- package/dist/components/mobile-player/video.js +10 -7
- package/dist/components/mobile-player/video.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +8 -0
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useAQState.d.ts +2 -0
- package/dist/hooks/useAQState.d.ts.map +1 -0
- package/dist/hooks/useAQState.js +37 -0
- package/dist/hooks/useAQState.js.map +1 -0
- package/dist/hooks/useLivestreamInfo.d.ts +1 -2
- package/dist/hooks/useLivestreamInfo.d.ts.map +1 -1
- package/dist/hooks/useLivestreamInfo.js +18 -22
- package/dist/hooks/useLivestreamInfo.js.map +1 -1
- package/dist/hooks/useSegmentTiming.d.ts +1 -1
- package/dist/hooks/useSegmentTiming.d.ts.map +1 -1
- package/dist/hooks/useSegmentTiming.js +4 -0
- package/dist/hooks/useSegmentTiming.js.map +1 -1
- package/dist/i18n/i18n-loader.native.d.ts.map +1 -1
- package/dist/i18n/i18n-loader.native.js +13 -4
- package/dist/i18n/i18n-loader.native.js.map +1 -1
- package/dist/lib/slash-commands/teleport.d.ts +5 -1
- package/dist/lib/slash-commands/teleport.d.ts.map +1 -1
- package/dist/lib/slash-commands/teleport.js +57 -1
- package/dist/lib/slash-commands/teleport.js.map +1 -1
- package/dist/lib/theme/atoms.d.ts +125 -125
- package/dist/livestream-store/chat.d.ts +1 -0
- package/dist/livestream-store/chat.d.ts.map +1 -1
- package/dist/livestream-store/chat.js +10 -1
- package/dist/livestream-store/chat.js.map +1 -1
- package/dist/livestream-store/livestream-state.d.ts +2 -0
- package/dist/livestream-store/livestream-state.d.ts.map +1 -1
- package/dist/livestream-store/livestream-store.d.ts +1 -1
- package/dist/livestream-store/livestream-store.d.ts.map +1 -1
- package/dist/livestream-store/livestream-store.js +10 -1
- package/dist/livestream-store/livestream-store.js.map +1 -1
- package/dist/livestream-store/websocket-consumer.d.ts.map +1 -1
- package/dist/livestream-store/websocket-consumer.js +1 -0
- package/dist/livestream-store/websocket-consumer.js.map +1 -1
- package/dist/player-store/player-state.d.ts +3 -5
- package/dist/player-store/player-state.d.ts.map +1 -1
- package/dist/player-store/player-store.d.ts.map +1 -1
- package/dist/player-store/player-store.js +28 -5
- package/dist/player-store/player-store.js.map +1 -1
- package/dist/player-store/single-player-provider.d.ts +0 -2
- package/dist/player-store/single-player-provider.d.ts.map +1 -1
- package/dist/player-store/single-player-provider.js +0 -2
- package/dist/player-store/single-player-provider.js.map +1 -1
- package/dist/streamplace-store/branding.d.ts.map +1 -1
- package/dist/streamplace-store/branding.js +52 -1
- package/dist/streamplace-store/branding.js.map +1 -1
- package/dist/streamplace-store/stream.d.ts +4 -2
- package/dist/streamplace-store/stream.d.ts.map +1 -1
- package/dist/streamplace-store/stream.js +36 -74
- package/dist/streamplace-store/stream.js.map +1 -1
- package/locales/en-US/common.ftl +13 -1
- package/locales/manifest.json +21 -1
- package/locales/ro-RO/common.ftl +74 -0
- package/locales/ro-RO/settings.ftl +233 -0
- package/locales/zh-Hans/common.ftl +57 -0
- package/locales/zh-Hans/settings.ftl +222 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +2 -2
- package/src/components/chat/badge.tsx +45 -0
- package/src/components/chat/chat-box.tsx +84 -54
- package/src/components/chat/chat-message.tsx +25 -21
- package/src/components/chat/chat.tsx +107 -90
- package/src/components/chat/emoji-suggestions.tsx +12 -21
- package/src/components/chat/system-message.tsx +12 -2
- package/src/components/chat/teleport-modal.tsx +310 -0
- package/src/components/chat/user-profile-card.tsx +275 -0
- package/src/components/dashboard/chat-panel.tsx +8 -0
- package/src/components/dashboard/header.tsx +8 -17
- package/src/components/dashboard/information-widget.tsx +17 -10
- package/src/components/mobile-player/fullscreen.native.tsx +3 -0
- package/src/components/mobile-player/fullscreen.tsx +2 -0
- package/src/components/mobile-player/player.tsx +22 -1
- package/src/components/mobile-player/ui/audio-only-overlay.tsx +48 -0
- package/src/components/mobile-player/ui/index.ts +1 -0
- package/src/components/mobile-player/ui/input.tsx +42 -12
- package/src/components/mobile-player/ui/metrics.tsx +17 -2
- package/src/components/mobile-player/ui/streamer-context-menu.tsx +138 -2
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +40 -3
- package/src/components/mobile-player/use-webrtc.tsx +118 -17
- package/src/components/mobile-player/video-async.native.tsx +18 -5
- package/src/components/mobile-player/video.tsx +10 -7
- package/src/components/ui/dialog.tsx +8 -0
- package/src/components/ui/textarea.tsx +2 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAQState.ts +37 -0
- package/src/hooks/useLivestreamInfo.ts +21 -22
- package/src/hooks/useSegmentTiming.tsx +7 -2
- package/src/i18n/i18n-loader.native.ts +9 -0
- package/src/lib/slash-commands/teleport.ts +68 -0
- package/src/livestream-store/chat.tsx +12 -0
- package/src/livestream-store/livestream-state.tsx +2 -0
- package/src/livestream-store/livestream-store.tsx +9 -1
- package/src/livestream-store/websocket-consumer.tsx +1 -0
- package/src/player-store/player-state.tsx +4 -7
- package/src/player-store/player-store.tsx +33 -7
- package/src/player-store/single-player-provider.tsx +0 -4
- package/src/streamplace-store/branding.tsx +60 -1
- package/src/streamplace-store/stream.tsx +42 -99
- package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
|
@@ -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(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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);
|
|
@@ -261,7 +268,7 @@ export function Chat({
|
|
|
261
268
|
|
|
262
269
|
useEffect(() => {
|
|
263
270
|
buttonOpacity.value = withTiming(isScrolledUp ? 1 : 0, { duration: 200 });
|
|
264
|
-
buttonTranslateY.value = withTiming(isScrolledUp ? 0 :
|
|
271
|
+
buttonTranslateY.value = withTiming(isScrolledUp ? 0 : 50, {
|
|
265
272
|
duration: 200,
|
|
266
273
|
});
|
|
267
274
|
}, [isScrolledUp]);
|
|
@@ -309,25 +316,35 @@ export function Chat({
|
|
|
309
316
|
},
|
|
310
317
|
].concat(propsStyle || [])}
|
|
311
318
|
>
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
{
|
|
@@ -345,7 +362,7 @@ export function Chat({
|
|
|
345
362
|
onPress={scrollToBottom}
|
|
346
363
|
style={[
|
|
347
364
|
{
|
|
348
|
-
pointerEvents: "auto",
|
|
365
|
+
pointerEvents: isScrolledUp ? "auto" : "none",
|
|
349
366
|
backgroundColor: theme.colors.primary,
|
|
350
367
|
opacity: 0.9,
|
|
351
368
|
borderRadius: 20,
|
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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>
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { Check, X } from "lucide-react-native";
|
|
2
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { Image, Pressable, ScrollView, View } from "react-native";
|
|
4
|
+
import { PlaceStreamLivestream } from "streamplace";
|
|
5
|
+
import { useAvatars, zero } from "../..";
|
|
6
|
+
import { useStreamplaceStore } from "../../streamplace-store";
|
|
7
|
+
import { Button, Input, ResponsiveDialog, Text, useTheme } from "../ui";
|
|
8
|
+
|
|
9
|
+
interface TeleportModalProps {
|
|
10
|
+
open: boolean;
|
|
11
|
+
onOpenChange: (open: boolean) => void;
|
|
12
|
+
onSubmit: (targetHandle: string, countdownSeconds: number) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const TeleportModal: React.FC<TeleportModalProps> = ({
|
|
16
|
+
open,
|
|
17
|
+
onOpenChange,
|
|
18
|
+
onSubmit,
|
|
19
|
+
}) => {
|
|
20
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
21
|
+
const [selectedStream, setSelectedStream] =
|
|
22
|
+
useState<PlaceStreamLivestream.LivestreamView | null>(null);
|
|
23
|
+
const [countdownSeconds, setCountdownSeconds] = useState("10");
|
|
24
|
+
|
|
25
|
+
const { theme } = useTheme();
|
|
26
|
+
|
|
27
|
+
const liveUsersCache = useStreamplaceStore((state) => state.liveUsers);
|
|
28
|
+
const liveUsersLoading = useStreamplaceStore(
|
|
29
|
+
(state) => state.liveUsersLoading,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const [liveUsers, setLiveUsers] = useState(liveUsersCache);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setLiveUsers(liveUsersCache);
|
|
36
|
+
}, [liveUsersCache]);
|
|
37
|
+
|
|
38
|
+
const profiles = useAvatars(liveUsers?.map((u) => u.author?.did || "") || []);
|
|
39
|
+
|
|
40
|
+
const filteredStreams = useMemo(() => {
|
|
41
|
+
if (!liveUsers) return [];
|
|
42
|
+
if (!searchQuery.trim()) return liveUsers;
|
|
43
|
+
|
|
44
|
+
const query = searchQuery.toLowerCase();
|
|
45
|
+
// filter by handle or stream title
|
|
46
|
+
return liveUsers.filter(
|
|
47
|
+
(stream) =>
|
|
48
|
+
stream.author?.handle?.toLowerCase().includes(query) ||
|
|
49
|
+
stream.record.title?.toString().toLowerCase().includes(query),
|
|
50
|
+
);
|
|
51
|
+
}, [liveUsers, searchQuery]);
|
|
52
|
+
|
|
53
|
+
const handleCancel = () => {
|
|
54
|
+
setSearchQuery("");
|
|
55
|
+
setSelectedStream(null);
|
|
56
|
+
setCountdownSeconds("10");
|
|
57
|
+
onOpenChange(false);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleSubmit = () => {
|
|
61
|
+
if (!selectedStream?.author?.handle) return;
|
|
62
|
+
|
|
63
|
+
const countdown = parseInt(countdownSeconds, 10);
|
|
64
|
+
if (isNaN(countdown) || countdown < 5 || countdown > 300) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onSubmit(selectedStream.author.handle, countdown);
|
|
69
|
+
handleCancel();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<ResponsiveDialog
|
|
74
|
+
open={open}
|
|
75
|
+
onOpenChange={onOpenChange}
|
|
76
|
+
showCloseButton={false}
|
|
77
|
+
variant="default"
|
|
78
|
+
size="xl"
|
|
79
|
+
dismissible={false}
|
|
80
|
+
>
|
|
81
|
+
<View style={[zero.py[2]]}>
|
|
82
|
+
<View style={[zero.layout.flex.row, zero.layout.flex.justify.between]}>
|
|
83
|
+
<View style={[zero.mb[4], zero.gap.all[1], zero.layout.flex.column]}>
|
|
84
|
+
<Text size="2xl">Teleport to another live streamer</Text>
|
|
85
|
+
<Text color="muted">
|
|
86
|
+
Select a streamer to teleport your viewers to their stream.
|
|
87
|
+
</Text>
|
|
88
|
+
</View>
|
|
89
|
+
<Pressable onPress={handleCancel} style={[{ padding: 8 }]}>
|
|
90
|
+
<X color={theme.colors.mutedForeground} />
|
|
91
|
+
</Pressable>
|
|
92
|
+
</View>
|
|
93
|
+
<View style={[zero.mb[4]]}>
|
|
94
|
+
<Input
|
|
95
|
+
value={searchQuery}
|
|
96
|
+
onChangeText={setSearchQuery}
|
|
97
|
+
placeholder="Search by handle..."
|
|
98
|
+
autoCapitalize="none"
|
|
99
|
+
autoCorrect={false}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
|
|
103
|
+
{liveUsersLoading && !liveUsers ? (
|
|
104
|
+
<View style={[zero.py[8], { alignItems: "center" }]}>
|
|
105
|
+
<Text color="muted">Loading live users...</Text>
|
|
106
|
+
</View>
|
|
107
|
+
) : filteredStreams.length === 0 ? (
|
|
108
|
+
<View style={[zero.py[8], { alignItems: "center" }]}>
|
|
109
|
+
<Text color="muted">
|
|
110
|
+
{searchQuery
|
|
111
|
+
? "No matching live users found"
|
|
112
|
+
: "No live users found"}
|
|
113
|
+
</Text>
|
|
114
|
+
</View>
|
|
115
|
+
) : (
|
|
116
|
+
<ScrollView style={[{ maxHeight: 400 }]}>
|
|
117
|
+
<View
|
|
118
|
+
style={[
|
|
119
|
+
{
|
|
120
|
+
flexDirection: "row",
|
|
121
|
+
flexWrap: "wrap",
|
|
122
|
+
gap: 12,
|
|
123
|
+
},
|
|
124
|
+
]}
|
|
125
|
+
>
|
|
126
|
+
{filteredStreams.map((stream) => {
|
|
127
|
+
const isSelected = selectedStream?.uri === stream.uri;
|
|
128
|
+
const profile = profiles[stream.author?.did];
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Pressable
|
|
132
|
+
key={stream.uri}
|
|
133
|
+
onPress={() => setSelectedStream(stream)}
|
|
134
|
+
style={[
|
|
135
|
+
{
|
|
136
|
+
width: "49.2%",
|
|
137
|
+
minWidth: 200,
|
|
138
|
+
},
|
|
139
|
+
]}
|
|
140
|
+
>
|
|
141
|
+
<View
|
|
142
|
+
style={[
|
|
143
|
+
{
|
|
144
|
+
backgroundColor: theme.colors.muted,
|
|
145
|
+
borderRadius: 12,
|
|
146
|
+
overflow: "hidden",
|
|
147
|
+
borderWidth: 2,
|
|
148
|
+
borderColor: isSelected
|
|
149
|
+
? theme.colors.primary
|
|
150
|
+
: "transparent",
|
|
151
|
+
},
|
|
152
|
+
]}
|
|
153
|
+
>
|
|
154
|
+
<View
|
|
155
|
+
style={[
|
|
156
|
+
{
|
|
157
|
+
width: "100%",
|
|
158
|
+
aspectRatio: 16 / 9,
|
|
159
|
+
backgroundColor: theme.colors.card,
|
|
160
|
+
position: "relative",
|
|
161
|
+
},
|
|
162
|
+
]}
|
|
163
|
+
>
|
|
164
|
+
<Image
|
|
165
|
+
source={{
|
|
166
|
+
uri:
|
|
167
|
+
"/api/playback/" +
|
|
168
|
+
stream.author.did +
|
|
169
|
+
"/stream.jpg",
|
|
170
|
+
}}
|
|
171
|
+
style={{
|
|
172
|
+
width: "100%",
|
|
173
|
+
height: "100%",
|
|
174
|
+
}}
|
|
175
|
+
resizeMode="cover"
|
|
176
|
+
/>
|
|
177
|
+
{isSelected && (
|
|
178
|
+
<View
|
|
179
|
+
style={[
|
|
180
|
+
{
|
|
181
|
+
position: "absolute",
|
|
182
|
+
top: 8,
|
|
183
|
+
right: 8,
|
|
184
|
+
backgroundColor: theme.colors.primary,
|
|
185
|
+
borderRadius: 999,
|
|
186
|
+
width: 24,
|
|
187
|
+
height: 24,
|
|
188
|
+
alignItems: "center",
|
|
189
|
+
justifyContent: "center",
|
|
190
|
+
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.6)",
|
|
191
|
+
},
|
|
192
|
+
]}
|
|
193
|
+
>
|
|
194
|
+
<Check size={16} color="white" />
|
|
195
|
+
</View>
|
|
196
|
+
)}
|
|
197
|
+
{stream.viewerCount && (
|
|
198
|
+
<View
|
|
199
|
+
style={[
|
|
200
|
+
{
|
|
201
|
+
position: "absolute",
|
|
202
|
+
top: 8,
|
|
203
|
+
left: 8,
|
|
204
|
+
backgroundColor: "rgba(0, 0, 0, 0.75)",
|
|
205
|
+
borderRadius: 999,
|
|
206
|
+
paddingHorizontal: 8,
|
|
207
|
+
paddingVertical: 4,
|
|
208
|
+
},
|
|
209
|
+
]}
|
|
210
|
+
>
|
|
211
|
+
<Text style={[{ fontSize: 12, color: "white" }]}>
|
|
212
|
+
{stream.viewerCount.count} viewer
|
|
213
|
+
{stream.viewerCount.count !== 1 ? "s" : ""}
|
|
214
|
+
</Text>
|
|
215
|
+
</View>
|
|
216
|
+
)}
|
|
217
|
+
</View>
|
|
218
|
+
<View
|
|
219
|
+
style={[
|
|
220
|
+
{
|
|
221
|
+
padding: 12,
|
|
222
|
+
flexDirection: "row",
|
|
223
|
+
gap: 8,
|
|
224
|
+
alignItems: "center",
|
|
225
|
+
},
|
|
226
|
+
]}
|
|
227
|
+
>
|
|
228
|
+
<View
|
|
229
|
+
style={[
|
|
230
|
+
{
|
|
231
|
+
width: 40,
|
|
232
|
+
height: 40,
|
|
233
|
+
borderRadius: 20,
|
|
234
|
+
overflow: "hidden",
|
|
235
|
+
backgroundColor: theme.colors.card,
|
|
236
|
+
flexShrink: 0,
|
|
237
|
+
},
|
|
238
|
+
]}
|
|
239
|
+
>
|
|
240
|
+
{profile?.avatar ? (
|
|
241
|
+
<Image
|
|
242
|
+
source={{
|
|
243
|
+
uri: profile.avatar,
|
|
244
|
+
}}
|
|
245
|
+
style={{ width: "100%", height: "100%" }}
|
|
246
|
+
resizeMode="cover"
|
|
247
|
+
/>
|
|
248
|
+
) : (
|
|
249
|
+
<View
|
|
250
|
+
style={{
|
|
251
|
+
width: "100%",
|
|
252
|
+
height: "100%",
|
|
253
|
+
backgroundColor: theme.colors.muted,
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
)}
|
|
257
|
+
</View>
|
|
258
|
+
|
|
259
|
+
{/* Text */}
|
|
260
|
+
<View style={[{ flex: 1, minWidth: 0 }]}>
|
|
261
|
+
<Text numberOfLines={1} ellipsizeMode="tail">
|
|
262
|
+
{stream.author?.handle}
|
|
263
|
+
</Text>
|
|
264
|
+
{stream.record.title ? (
|
|
265
|
+
<Text
|
|
266
|
+
numberOfLines={1}
|
|
267
|
+
ellipsizeMode="tail"
|
|
268
|
+
style={[
|
|
269
|
+
{
|
|
270
|
+
fontSize: 12,
|
|
271
|
+
color: theme.colors.textMuted,
|
|
272
|
+
},
|
|
273
|
+
]}
|
|
274
|
+
>
|
|
275
|
+
{stream.record.title as any}
|
|
276
|
+
</Text>
|
|
277
|
+
) : null}
|
|
278
|
+
</View>
|
|
279
|
+
</View>
|
|
280
|
+
</View>
|
|
281
|
+
</Pressable>
|
|
282
|
+
);
|
|
283
|
+
})}
|
|
284
|
+
</View>
|
|
285
|
+
</ScrollView>
|
|
286
|
+
)}
|
|
287
|
+
</View>
|
|
288
|
+
<View
|
|
289
|
+
style={[
|
|
290
|
+
zero.mt[8],
|
|
291
|
+
zero.layout.flex.row,
|
|
292
|
+
zero.layout.flex.justify.end,
|
|
293
|
+
zero.gap.all[2],
|
|
294
|
+
]}
|
|
295
|
+
>
|
|
296
|
+
<Button width="min" variant="secondary" onPress={handleCancel}>
|
|
297
|
+
<Text>Cancel</Text>
|
|
298
|
+
</Button>
|
|
299
|
+
<Button
|
|
300
|
+
width="min"
|
|
301
|
+
variant="primary"
|
|
302
|
+
onPress={handleSubmit}
|
|
303
|
+
disabled={!selectedStream}
|
|
304
|
+
>
|
|
305
|
+
<Text>Teleport</Text>
|
|
306
|
+
</Button>
|
|
307
|
+
</View>
|
|
308
|
+
</ResponsiveDialog>
|
|
309
|
+
);
|
|
310
|
+
};
|