@streamplace/components 0.8.17 → 0.8.18

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 (46) hide show
  1. package/dist/components/mobile-player/video-async.native.d.ts.map +1 -1
  2. package/dist/components/mobile-player/video-async.native.js +1 -1
  3. package/dist/components/mobile-player/video-async.native.js.map +1 -1
  4. package/dist/components/ui/menu.d.ts +14 -0
  5. package/dist/components/ui/menu.d.ts.map +1 -1
  6. package/dist/components/ui/menu.js +81 -2
  7. package/dist/components/ui/menu.js.map +1 -1
  8. package/dist/components/ui/text.d.ts +1 -1
  9. package/dist/lib/theme/atoms.d.ts +7 -7
  10. package/dist/lib/theme/theme.d.ts +1 -1
  11. package/dist/lib/theme/tokens.d.ts +1 -1
  12. package/dist/lib/theme/tokens.js +1 -1
  13. package/dist/livestream-provider/index.d.ts +2 -1
  14. package/dist/livestream-provider/index.d.ts.map +1 -1
  15. package/dist/livestream-provider/index.js +4 -2
  16. package/dist/livestream-provider/index.js.map +1 -1
  17. package/dist/livestream-provider/websocket.d.ts.map +1 -1
  18. package/dist/livestream-provider/websocket.js +15 -1
  19. package/dist/livestream-provider/websocket.js.map +1 -1
  20. package/dist/livestream-store/livestream-state.d.ts +2 -0
  21. package/dist/livestream-store/livestream-state.d.ts.map +1 -1
  22. package/dist/livestream-store/livestream-store.d.ts +1 -0
  23. package/dist/livestream-store/livestream-store.d.ts.map +1 -1
  24. package/dist/livestream-store/livestream-store.js +5 -1
  25. package/dist/livestream-store/livestream-store.js.map +1 -1
  26. package/dist/livestream-store/websocket-consumer.d.ts.map +1 -1
  27. package/dist/livestream-store/websocket-consumer.js +97 -75
  28. package/dist/livestream-store/websocket-consumer.js.map +1 -1
  29. package/locales/en-US/common.ftl +16 -0
  30. package/locales/en-US/settings.ftl +13 -0
  31. package/locales/es-ES/common.ftl +16 -0
  32. package/locales/es-ES/settings.ftl +1 -1
  33. package/locales/fr-FR/common.ftl +16 -0
  34. package/locales/pt-BR/common.ftl +16 -0
  35. package/locales/pt-BR/settings.ftl +1 -1
  36. package/locales/zh-Hant/common.ftl +16 -0
  37. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  38. package/package.json +2 -2
  39. package/src/components/mobile-player/video-async.native.tsx +2 -1
  40. package/src/components/ui/menu.tsx +180 -3
  41. package/src/lib/theme/tokens.ts +1 -1
  42. package/src/livestream-provider/index.tsx +5 -1
  43. package/src/livestream-provider/websocket.tsx +15 -1
  44. package/src/livestream-store/livestream-state.tsx +2 -0
  45. package/src/livestream-store/livestream-store.tsx +5 -0
  46. package/src/livestream-store/websocket-consumer.tsx +95 -73
@@ -9,87 +9,109 @@ const problems_1 = require("./problems");
9
9
  const MAX_RECENT_SEGMENTS = 10;
10
10
  const handleWebSocketMessages = (state, messages) => {
11
11
  for (let message of messages) {
12
- if (streamplace_1.PlaceStreamLivestream.isLivestreamView(message)) {
13
- const newLivestream = message;
14
- const oldLivestream = state.livestream;
15
- // check if this is actually new
16
- if (!oldLivestream || oldLivestream.uri !== newLivestream.uri) {
17
- const streamTitle = newLivestream.record.title || "something cool!";
18
- const systemMessage = system_messages_1.SystemMessages.streamStart(streamTitle);
19
- // set proper times
20
- systemMessage.indexedAt = newLivestream.indexedAt;
21
- systemMessage.record.createdAt = newLivestream.record.createdAt;
22
- state = (0, chat_1.reduceChat)(state, [systemMessage], []);
23
- }
24
- state = {
25
- ...state,
26
- livestream: newLivestream,
27
- };
28
- }
29
- else if (streamplace_1.PlaceStreamLivestream.isViewerCount(message)) {
30
- message = message;
12
+ if (message.$type === "place.stream.error") {
31
13
  state = {
32
14
  ...state,
33
- viewers: message.count,
34
- };
35
- }
36
- else if (streamplace_1.PlaceStreamChatDefs.isMessageView(message)) {
37
- message = message;
38
- // Explicitly map MessageView to MessageViewHydrated
39
- const hydrated = {
40
- uri: message.uri,
41
- cid: message.cid,
42
- author: message.author,
43
- record: message.record,
44
- indexedAt: message.indexedAt,
45
- chatProfile: message.chatProfile,
46
- replyTo: message.replyTo,
47
- deleted: message.deleted,
15
+ problems: [
16
+ ...state.problems,
17
+ {
18
+ code: message.code,
19
+ message: message.message,
20
+ severity: "error",
21
+ },
22
+ ],
48
23
  };
49
- state = (0, chat_1.reduceChat)(state, [hydrated], [], []);
50
24
  }
51
- else if (streamplace_1.PlaceStreamSegment.isRecord(message)) {
52
- const newRecentSegments = [...state.recentSegments];
53
- newRecentSegments.unshift(message);
54
- if (newRecentSegments.length > MAX_RECENT_SEGMENTS) {
55
- newRecentSegments.pop();
25
+ else {
26
+ if (!state.websocketConnected) {
27
+ state = {
28
+ ...state,
29
+ websocketConnected: true,
30
+ };
56
31
  }
57
- state = {
58
- ...state,
59
- segment: message,
60
- recentSegments: newRecentSegments,
61
- problems: (0, problems_1.findProblems)(newRecentSegments),
62
- };
63
- }
64
- else if (streamplace_1.PlaceStreamDefs.isBlockView(message)) {
65
- const block = message;
66
- state = (0, chat_1.reduceChat)(state, [], [block], []);
67
- }
68
- else if (streamplace_1.PlaceStreamDefs.isRenditions(message)) {
69
- message = message;
70
- state = {
71
- ...state,
72
- renditions: message.renditions,
73
- };
74
- }
75
- else if (api_1.AppBskyActorDefs.isProfileViewBasic(message)) {
76
- state = {
77
- ...state,
78
- profile: message,
79
- };
80
- }
81
- else if (streamplace_1.PlaceStreamChatGate.isRecord(message)) {
82
- const hideRecord = message;
83
- const hiddenMessageUri = hideRecord.hiddenMessage;
84
- const newPendingHides = [...state.pendingHides];
85
- if (!newPendingHides.includes(hiddenMessageUri)) {
86
- newPendingHides.push(hiddenMessageUri);
32
+ if (streamplace_1.PlaceStreamLivestream.isLivestreamView(message)) {
33
+ const newLivestream = message;
34
+ const oldLivestream = state.livestream;
35
+ // check if this is actually new
36
+ if (!oldLivestream || oldLivestream.uri !== newLivestream.uri) {
37
+ const streamTitle = newLivestream.record.title || "something cool!";
38
+ const systemMessage = system_messages_1.SystemMessages.streamStart(streamTitle);
39
+ // set proper times
40
+ systemMessage.indexedAt = newLivestream.indexedAt;
41
+ systemMessage.record.createdAt = newLivestream.record.createdAt;
42
+ state = (0, chat_1.reduceChat)(state, [systemMessage], []);
43
+ }
44
+ state = {
45
+ ...state,
46
+ livestream: newLivestream,
47
+ };
48
+ }
49
+ else if (streamplace_1.PlaceStreamLivestream.isViewerCount(message)) {
50
+ message = message;
51
+ state = {
52
+ ...state,
53
+ viewers: message.count,
54
+ };
55
+ }
56
+ else if (streamplace_1.PlaceStreamChatDefs.isMessageView(message)) {
57
+ message = message;
58
+ // Explicitly map MessageView to MessageViewHydrated
59
+ const hydrated = {
60
+ uri: message.uri,
61
+ cid: message.cid,
62
+ author: message.author,
63
+ record: message.record,
64
+ indexedAt: message.indexedAt,
65
+ chatProfile: message.chatProfile,
66
+ replyTo: message.replyTo,
67
+ deleted: message.deleted,
68
+ };
69
+ state = (0, chat_1.reduceChat)(state, [hydrated], [], []);
70
+ }
71
+ else if (streamplace_1.PlaceStreamSegment.isRecord(message)) {
72
+ const newRecentSegments = [...state.recentSegments];
73
+ newRecentSegments.unshift(message);
74
+ if (newRecentSegments.length > MAX_RECENT_SEGMENTS) {
75
+ newRecentSegments.pop();
76
+ }
77
+ state = {
78
+ ...state,
79
+ segment: message,
80
+ recentSegments: newRecentSegments,
81
+ problems: (0, problems_1.findProblems)(newRecentSegments),
82
+ hasReceivedSegment: true,
83
+ };
84
+ }
85
+ else if (streamplace_1.PlaceStreamDefs.isBlockView(message)) {
86
+ const block = message;
87
+ state = (0, chat_1.reduceChat)(state, [], [block], []);
88
+ }
89
+ else if (streamplace_1.PlaceStreamDefs.isRenditions(message)) {
90
+ message = message;
91
+ state = {
92
+ ...state,
93
+ renditions: message.renditions,
94
+ };
95
+ }
96
+ else if (api_1.AppBskyActorDefs.isProfileViewBasic(message)) {
97
+ state = {
98
+ ...state,
99
+ profile: message,
100
+ };
101
+ }
102
+ else if (streamplace_1.PlaceStreamChatGate.isRecord(message)) {
103
+ const hideRecord = message;
104
+ const hiddenMessageUri = hideRecord.hiddenMessage;
105
+ const newPendingHides = [...state.pendingHides];
106
+ if (!newPendingHides.includes(hiddenMessageUri)) {
107
+ newPendingHides.push(hiddenMessageUri);
108
+ }
109
+ state = {
110
+ ...state,
111
+ pendingHides: newPendingHides,
112
+ };
113
+ state = (0, chat_1.reduceChat)(state, [], [], [hiddenMessageUri]);
87
114
  }
88
- state = {
89
- ...state,
90
- pendingHides: newPendingHides,
91
- };
92
- state = (0, chat_1.reduceChat)(state, [], [], [hiddenMessageUri]);
93
115
  }
94
116
  }
95
117
  return (0, chat_1.reduceChat)(state, [], [], []);
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-consumer.js","sourceRoot":"","sources":["../../src/livestream-store/websocket-consumer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"websocket-consumer.js","sourceRoot":"","sources":["../../src/livestream-store/websocket-consumer.tsx"],"names":[],"mappings":"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"}
@@ -44,3 +44,19 @@ notification-count = { $count ->
44
44
  [1] One notification
45
45
  *[other] { $count } notifications
46
46
  }
47
+
48
+ ## Offline User
49
+ user-offline = user is offline
50
+ user-offline-message = { $source ->
51
+ [streamer] Looks like <1>@{ $handle } is offline</1>, but they recommend checking out:
52
+ *[default] Looks like <1>@{ $handle } is offline</1>, but we recommend checking out:
53
+ }
54
+ user-offline-no-recommendations =
55
+ Looks like <1>@{ $handle } is offline</1> right now.
56
+ Check back later.
57
+ streaming-title = streaming { $title }
58
+ viewer-count = { $count ->
59
+ [0] 0 viewers
60
+ [1] 1 viewer
61
+ *[other] { $count } viewers
62
+ }
@@ -80,6 +80,19 @@ keys-count = { $count ->
80
80
  *[other] { $count } keys
81
81
  }
82
82
 
83
+ ## Recommendations
84
+ recommendations = Recommendations
85
+ manage-recommendations = Manage Recommendations
86
+ recommendations-to-others = Recommendations to Others
87
+ recommendations-description = Share up to 8 streamers you recommend to your viewers
88
+ no-recommendations-yet = No recommendations configured yet
89
+ add-recommendation = Add Recommendation
90
+ streamer-did = Streamer DID
91
+ recommendations-count = { $count ->
92
+ [one] { $count } recommendation
93
+ *[other] { $count } recommendations
94
+ }
95
+
83
96
  ## Webhook Management
84
97
  webhooks = Webhooks
85
98
  webhook-integrations = Webhook Integrations
@@ -44,3 +44,19 @@ notification-count = { $count ->
44
44
  [1] Una notificación
45
45
  *[other] { $count } notificaciones
46
46
  }
47
+
48
+ ## Offline User
49
+ user-offline = usuario desconectado
50
+ user-offline-message = { $source ->
51
+ [streamer] Parece que <1>@{ $handle } está desconectado</1>, pero ellos recomiendan ver:
52
+ *[default] Parece que <1>@{ $handle } está desconectado</1>, pero te recomendamos ver:
53
+ }
54
+ user-offline-no-recommendations =
55
+ Parece que <1>@{ $handle } está desconectado</1> ahora mismo.
56
+ Vuelve más tarde.
57
+ streaming-title = transmitiendo { $title }
58
+ viewer-count = { $count ->
59
+ [0] 0 espectadores
60
+ [1] 1 espectador
61
+ *[other] { $count } espectadores
62
+ }
@@ -113,7 +113,7 @@ key-manager = Gestor de Claves
113
113
  manage-keys = Gestionar Claves
114
114
  your-stream-pubkeys = Tus Claves Públicas de Transmisión
115
115
  no-keys = No hay claves configuradas
116
- pubkey-description = Las claves públicas se emparejan con claves de transmisión (usadas en software de streaming) para firmar y verificar tu transmisión
116
+ pubkey-description = Las claves públicas se emparejan con claves de transmisión (usadas en software de transmitiendo) para firmar y verificar tu transmisión
117
117
 
118
118
  keys-count = { $count ->
119
119
  [one] { $count } clave
@@ -44,3 +44,19 @@ notification-count = { $count ->
44
44
  [1] Une notification
45
45
  *[other] { $count } notifications
46
46
  }
47
+
48
+ ## Offline User
49
+ user-offline = utilisateur hors ligne
50
+ user-offline-message = { $source ->
51
+ [streamer] On dirait que <1>@{ $handle } est hors ligne</1>, mais ils recommandent de regarder :
52
+ *[default] On dirait que <1>@{ $handle } est hors ligne</1>, mais nous recommandons de regarder :
53
+ }
54
+ user-offline-no-recommendations =
55
+ On dirait que <1>@{ $handle } est hors ligne</1> maintenant.
56
+ Revenez plus tard.
57
+ streaming-title = diffusion de { $title }
58
+ viewer-count = { $count ->
59
+ [0] 0 spectateurs
60
+ [1] 1 spectateur
61
+ *[other] { $count } spectateurs
62
+ }
@@ -44,3 +44,19 @@ notification-count = { $count ->
44
44
  [1] Uma notificação
45
45
  *[other] { $count } notificações
46
46
  }
47
+
48
+ ## Offline User
49
+ user-offline = usuário offline
50
+ user-offline-message = { $source ->
51
+ [streamer] Parece que <1>@{ $handle } está offline</1>, mas eles recomendam assistir:
52
+ *[default] Parece que <1>@{ $handle } está offline</1>, mas recomendamos assistir:
53
+ }
54
+ user-offline-no-recommendations =
55
+ Parece que <1>@{ $handle } está offline</1> agora.
56
+ Volte mais tarde.
57
+ streaming-title = transmitindo { $title }
58
+ viewer-count = { $count ->
59
+ [0] 0 espectadores
60
+ [1] 1 espectador
61
+ *[other] { $count } espectadores
62
+ }
@@ -111,7 +111,7 @@ key-manager = Gerenciador de Chaves
111
111
  manage-keys = Gerenciar Chaves
112
112
  your-stream-pubkeys = Suas Chaves Públicas de Transmissão
113
113
  no-keys = Nenhuma chave configurada
114
- pubkey-description = Chaves públicas são emparelhadas com chaves de transmissão (usadas em software de streaming) para assinar e verificar sua transmissão
114
+ pubkey-description = Chaves públicas são emparelhadas com chaves de transmissão (usadas em software de transmitindo) para assinar e verificar sua transmissão
115
115
 
116
116
  keys-count = { $count ->
117
117
  [one] { $count } chave
@@ -44,3 +44,19 @@ notification-count = { $count ->
44
44
  [1] 一則通知
45
45
  *[other] { $count } 則通知
46
46
  }
47
+
48
+ ## Offline User
49
+ user-offline = 使用者離線
50
+ user-offline-message = { $source ->
51
+ [streamer] 看起來 <1>@{ $handle } 離線</1> 了,但他們推薦觀看:
52
+ *[default] 看起來 <1>@{ $handle } 離線</1> 了,但我們推薦觀看:
53
+ }
54
+ user-offline-no-recommendations =
55
+ 看起來 <1>@{ $handle } 離線</1> 了。
56
+ 請稍後再來看看。
57
+ streaming-title = 正在直播 { $title }
58
+ viewer-count = { $count ->
59
+ [0] 0 位觀眾
60
+ [1] 1 位觀眾
61
+ *[other] { $count } 位觀眾
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamplace/components",
3
- "version": "0.8.17",
3
+ "version": "0.8.18",
4
4
  "description": "Streamplace React (Native) Components",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.tsx",
@@ -78,5 +78,5 @@
78
78
  "i18n:watch": "nodemon --watch 'locales/**/*.ftl' --exec 'node scripts/compile-translations.js'",
79
79
  "i18n:extract": "i18next-cli extract && node scripts/migrate-i18n.js"
80
80
  },
81
- "gitHead": "80f92d685c83c1065486e447b05a365d0cfc4d01"
81
+ "gitHead": "d9cde37cadad4b378cbdc00c640edcaa4717b005"
82
82
  }
@@ -414,7 +414,8 @@ export function NativeIngestPlayer(props?: {
414
414
  variant="secondary"
415
415
  >
416
416
  <View style={[layout.flex.row, gap.all[1]]}>
417
- <Text>Open Settings</Text> <ArrowRight color="white" size="18" />
417
+ <Text>Open Settings</Text>
418
+ <ArrowRight color="white" size="18" />
418
419
  </View>
419
420
  </Button>
420
421
  )}
@@ -1,5 +1,18 @@
1
- import { forwardRef, ReactNode } from "react";
2
- import { Platform, View, ViewStyle } from "react-native";
1
+ import {
2
+ Children,
3
+ cloneElement,
4
+ forwardRef,
5
+ isValidElement,
6
+ ReactNode,
7
+ } from "react";
8
+ import { Animated, Platform, View, ViewStyle } from "react-native";
9
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
10
+ import {
11
+ runOnJS,
12
+ useAnimatedStyle,
13
+ useSharedValue,
14
+ withSpring,
15
+ } from "react-native-reanimated";
3
16
  import {
4
17
  a,
5
18
  borderRadius,
@@ -64,11 +77,114 @@ export interface MenuItemProps {
64
77
  disabled?: boolean;
65
78
  style?: ViewStyle;
66
79
  onPress?: () => void;
80
+ draggable?: boolean;
81
+ dragHandle?: ReactNode;
82
+ _dragIndex?: number;
83
+ _dragTotalItems?: number;
84
+ _onDragMove?: (fromIndex: number, toIndex: number) => void;
85
+ _onDragEnd?: (fromIndex: number, toIndex: number) => void;
67
86
  }
68
87
 
69
88
  export const MenuItem = forwardRef<View, MenuItemProps>(
70
- ({ children, disabled, style }, ref) => {
89
+ (
90
+ {
91
+ children,
92
+ disabled,
93
+ style,
94
+ draggable,
95
+ dragHandle,
96
+ _dragIndex,
97
+ _dragTotalItems,
98
+ _onDragMove,
99
+ _onDragEnd,
100
+ },
101
+ ref,
102
+ ) => {
71
103
  const { theme } = useTheme();
104
+
105
+ if (
106
+ draggable &&
107
+ _dragIndex !== undefined &&
108
+ _dragTotalItems !== undefined &&
109
+ _onDragMove &&
110
+ _onDragEnd
111
+ ) {
112
+ const translateY = useSharedValue(0);
113
+ const isDragging = useSharedValue(false);
114
+ const ITEM_HEIGHT = 60;
115
+
116
+ const panGesture = Gesture.Pan()
117
+ .onStart(() => {
118
+ isDragging.value = true;
119
+ })
120
+ .onUpdate((event) => {
121
+ translateY.value = event.translationY;
122
+
123
+ const newIndex = Math.round(
124
+ _dragIndex + translateY.value / ITEM_HEIGHT,
125
+ );
126
+ const clampedIndex = Math.max(
127
+ 0,
128
+ Math.min(_dragTotalItems - 1, newIndex),
129
+ );
130
+
131
+ if (clampedIndex !== _dragIndex) {
132
+ runOnJS(_onDragMove)(_dragIndex, clampedIndex);
133
+ }
134
+ })
135
+ .onEnd(() => {
136
+ const newIndex = Math.round(
137
+ _dragIndex + translateY.value / ITEM_HEIGHT,
138
+ );
139
+ const clampedIndex = Math.max(
140
+ 0,
141
+ Math.min(_dragTotalItems - 1, newIndex),
142
+ );
143
+
144
+ runOnJS(_onDragEnd)(_dragIndex, clampedIndex);
145
+
146
+ translateY.value = withSpring(0);
147
+ isDragging.value = false;
148
+ });
149
+
150
+ const animatedStyle = useAnimatedStyle(() => ({
151
+ transform: [{ translateY: translateY.value }],
152
+ zIndex: isDragging.value ? 100 : 1,
153
+ opacity: isDragging.value ? 0.8 : 1,
154
+ }));
155
+
156
+ return (
157
+ <Animated.View style={animatedStyle}>
158
+ <View
159
+ ref={ref}
160
+ style={[
161
+ a.layout.flex.row,
162
+ a.layout.flex.alignCenter,
163
+ a.radius.all.sm,
164
+ py[1],
165
+ pl[3],
166
+ pr[2],
167
+ disabled && { opacity: 0.5 },
168
+ style,
169
+ ]}
170
+ >
171
+ {dragHandle && (
172
+ <GestureDetector gesture={panGesture}>
173
+ <View style={{ marginRight: 8 }}>{dragHandle}</View>
174
+ </GestureDetector>
175
+ )}
176
+ {typeof children === "string" ? (
177
+ <Text style={{ color: theme.colors.popoverForeground }}>
178
+ {children}
179
+ </Text>
180
+ ) : (
181
+ children
182
+ )}
183
+ </View>
184
+ </Animated.View>
185
+ );
186
+ }
187
+
72
188
  return (
73
189
  <View
74
190
  ref={ref}
@@ -169,3 +285,64 @@ export const MenuInfo = forwardRef<View, MenuInfoProps>(
169
285
  );
170
286
  },
171
287
  );
288
+
289
+ export interface MenuDraggableGroupProps {
290
+ children: ReactNode;
291
+ onMove: (fromIndex: number, toIndex: number) => void;
292
+ onDragEnd: (fromIndex: number, toIndex: number) => void;
293
+ dragHandle?: ReactNode;
294
+ style?: ViewStyle;
295
+ }
296
+
297
+ export const MenuDraggableGroup = forwardRef<View, MenuDraggableGroupProps>(
298
+ ({ children, onMove, onDragEnd, dragHandle, style }, ref) => {
299
+ const { theme } = useTheme();
300
+
301
+ const childrenArray = Children.toArray(children);
302
+ const draggableItems = childrenArray.filter(
303
+ (child) =>
304
+ isValidElement(child) &&
305
+ (child.type === MenuItem || child.type === MenuSeparator),
306
+ );
307
+
308
+ let itemIndex = 0;
309
+ const enhancedChildren = Children.map(children, (child) => {
310
+ if (isValidElement(child)) {
311
+ if (child.type === MenuItem) {
312
+ const currentIndex = itemIndex;
313
+ itemIndex++;
314
+
315
+ return cloneElement(child, {
316
+ draggable: true,
317
+ dragHandle: dragHandle || child.props.dragHandle,
318
+ _dragIndex: currentIndex,
319
+ _dragTotalItems: draggableItems.filter(
320
+ (c) => isValidElement(c) && c.type === MenuItem,
321
+ ).length,
322
+ _onDragMove: onMove,
323
+ _onDragEnd: onDragEnd,
324
+ } as any);
325
+ }
326
+ if (child.type === MenuSeparator) {
327
+ return child;
328
+ }
329
+ }
330
+ return child;
331
+ });
332
+
333
+ return (
334
+ <View
335
+ ref={ref}
336
+ style={[
337
+ { backgroundColor: theme.colors.muted + "c0" },
338
+ Platform.OS === "web" ? [px[1], py[1]] : p[1],
339
+ gap.all[1],
340
+ { borderRadius: borderRadius.lg },
341
+ style,
342
+ ]}
343
+ >
344
+ {enhancedChildren}
345
+ </View>
346
+ );
347
+ },
348
+ );
@@ -595,7 +595,7 @@ export const typography = {
595
595
  xs: {
596
596
  fontSize: 12,
597
597
  lineHeight: 16,
598
- marginBottom: -0.7,
598
+ marginBottom: -0.3,
599
599
  fontWeight: "400" as const,
600
600
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
601
601
  },
@@ -5,9 +5,11 @@ import { useLivestreamWebsocket } from "./websocket";
5
5
  export function LivestreamProvider({
6
6
  children,
7
7
  src,
8
+ ignoreOuterContext = false,
8
9
  }: {
9
10
  children: React.ReactNode;
10
11
  src: string;
12
+ ignoreOuterContext?: boolean;
11
13
  }) {
12
14
  const context = useContext(LivestreamContext);
13
15
  const store = useRef(makeLivestreamStore()).current;
@@ -15,7 +17,9 @@ export function LivestreamProvider({
15
17
  // this is ok, there's use cases for having one in another
16
18
  // like having a player component that's independently usable
17
19
  // but can also be embedded within an entire livestream page
18
- return <>{children}</>;
20
+ if (!ignoreOuterContext) {
21
+ return <>{children}</>;
22
+ }
19
23
  }
20
24
  (window as any).livestreamStore = store;
21
25
  return (
@@ -12,17 +12,30 @@ export function useLivestreamWebsocket(src: string) {
12
12
 
13
13
  const ref = useRef<any[]>([]);
14
14
  const handle = useRef<NodeJS.Timeout | null>(null);
15
+ const hasReceivedMessage = useRef(false);
16
+ const hasErrored = useRef(false);
15
17
 
16
18
  const { readyState } = useWebSocket(`${wsUrl}/api/websocket/${src}`, {
17
19
  reconnectInterval: 1000,
18
- shouldReconnect: () => true,
20
+ shouldReconnect: () => !hasErrored.current,
19
21
 
20
22
  onOpen: () => {
21
23
  ref.current = [];
24
+ hasReceivedMessage.current = false;
22
25
  },
23
26
 
24
27
  onError: (e) => {
25
28
  console.log("onError", e);
29
+ if (!hasReceivedMessage.current) {
30
+ hasErrored.current = true;
31
+ handleWebSocketMessages([
32
+ {
33
+ $type: "place.stream.error",
34
+ code: "user_not_found",
35
+ message: "this stream doesn't exist or is unavailable",
36
+ },
37
+ ]);
38
+ }
26
39
  },
27
40
 
28
41
  // spamming the redux store with messages causes a zillion re-renders,
@@ -30,6 +43,7 @@ export function useLivestreamWebsocket(src: string) {
30
43
  onMessage: (msg) => {
31
44
  try {
32
45
  const data = JSON.parse(msg.data);
46
+ hasReceivedMessage.current = true;
33
47
  ref.current.push(data);
34
48
  if (handle.current) {
35
49
  return;
@@ -21,6 +21,8 @@ export interface LivestreamState {
21
21
  replyToMessage: ChatMessageViewHydrated | null;
22
22
  streamKey: string | null;
23
23
  setStreamKey: (key: string | null) => void;
24
+ websocketConnected: boolean;
25
+ hasReceivedSegment: boolean;
24
26
  }
25
27
 
26
28
  export interface LivestreamProblem {