@streamplace/components 0.7.9 → 0.7.13

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 (48) hide show
  1. package/dist/assets/emoji-data.json +19371 -0
  2. package/dist/components/chat/chat-box.js +19 -2
  3. package/dist/components/chat/chat-message.js +12 -4
  4. package/dist/components/chat/chat.js +15 -4
  5. package/dist/components/chat/mod-view.js +15 -8
  6. package/dist/components/dashboard/chat-panel.js +38 -0
  7. package/dist/components/dashboard/header.js +80 -0
  8. package/dist/components/dashboard/index.js +14 -0
  9. package/dist/components/dashboard/information-widget.js +234 -0
  10. package/dist/components/dashboard/mod-actions.js +71 -0
  11. package/dist/components/dashboard/problems.js +74 -0
  12. package/dist/components/mobile-player/ui/viewer-context-menu.js +15 -6
  13. package/dist/components/ui/button.js +2 -2
  14. package/dist/components/ui/dropdown.js +20 -1
  15. package/dist/components/ui/index.js +2 -0
  16. package/dist/components/ui/info-box.js +31 -0
  17. package/dist/components/ui/info-row.js +23 -0
  18. package/dist/components/ui/toast.js +43 -0
  19. package/dist/index.js +3 -1
  20. package/dist/lib/theme/atoms.js +66 -45
  21. package/dist/lib/theme/tokens.js +285 -12
  22. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  23. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  24. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
  25. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
  26. package/package.json +2 -2
  27. package/src/assets/emoji-data.json +19371 -0
  28. package/src/components/chat/chat-box.tsx +19 -1
  29. package/src/components/chat/chat-message.tsx +22 -14
  30. package/src/components/chat/chat.tsx +21 -6
  31. package/src/components/chat/mod-view.tsx +24 -6
  32. package/src/components/dashboard/chat-panel.tsx +80 -0
  33. package/src/components/dashboard/header.tsx +170 -0
  34. package/src/components/dashboard/index.tsx +5 -0
  35. package/src/components/dashboard/information-widget.tsx +526 -0
  36. package/src/components/dashboard/mod-actions.tsx +133 -0
  37. package/src/components/dashboard/problems.tsx +151 -0
  38. package/src/components/mobile-player/ui/viewer-context-menu.tsx +67 -38
  39. package/src/components/ui/button.tsx +2 -2
  40. package/src/components/ui/dropdown.tsx +38 -3
  41. package/src/components/ui/index.ts +2 -0
  42. package/src/components/ui/info-box.tsx +60 -0
  43. package/src/components/ui/info-row.tsx +48 -0
  44. package/src/components/ui/toast.tsx +110 -0
  45. package/src/index.tsx +3 -0
  46. package/src/lib/theme/atoms.ts +97 -43
  47. package/src/lib/theme/tokens.ts +285 -12
  48. package/tsconfig.tsbuildinfo +1 -1
@@ -190,6 +190,14 @@ function ChatBox({ isPopout, chatBoxStyle, emojiData, setIsChatVisible, }) {
190
190
  reply: replyTo || undefined,
191
191
  });
192
192
  setSubmitting(false);
193
+ // if we press "send" button, we want the same action as pressing "Enter"
194
+ // if we're already focused no need to do extra work
195
+ if (textAreaRef.current && !textAreaRef.current.isFocused()) {
196
+ textAreaRef.current.focus();
197
+ requestAnimationFrame(() => {
198
+ textAreaRef.current?.focus();
199
+ });
200
+ }
193
201
  };
194
202
  (0, react_2.useEffect)(() => {
195
203
  if (replyTo && textAreaRef.current) {
@@ -227,7 +235,10 @@ function ChatBox({ isPopout, chatBoxStyle, emojiData, setIsChatVisible, }) {
227
235
  bottom: "100%",
228
236
  left: 0,
229
237
  zIndex: 2001,
230
- }, children: (0, jsx_runtime_1.jsx)(react_1.default, { data: emojiData, onEmojiSelect: (e) => setMessage(message + e.native) }) })] })), (0, jsx_runtime_1.jsxs)(__1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.alignCenter, atoms_1.gap.all[2]], children: [(0, jsx_runtime_1.jsx)(textarea_1.Textarea, { ref: textAreaRef, numberOfLines: 1, value: message, enterKeyHint: "send", onSubmitEditing: submit, multiline: false, onChangeText: (text) => {
238
+ }, children: (0, jsx_runtime_1.jsx)(react_1.default, { data: emojiData, onEmojiSelect: (e) => setMessage(message + e.native) }) })] })), (0, jsx_runtime_1.jsxs)(__1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.alignCenter, atoms_1.gap.all[2]], children: [(0, jsx_runtime_1.jsx)(textarea_1.Textarea, { ref: textAreaRef, numberOfLines: 1, value: message, enterKeyHint: "send", onSubmitEditing: (e) => {
239
+ e.preventDefault();
240
+ submit();
241
+ }, multiline: false, onChangeText: (text) => {
231
242
  setMessage(text);
232
243
  updateSuggestions(text);
233
244
  }, onKeyPress: (k) => {
@@ -245,6 +256,10 @@ function ChatBox({ isPopout, chatBoxStyle, emojiData, setIsChatVisible, }) {
245
256
  handleEmojiSelect(filteredEmojis[highlightedIndex]);
246
257
  }
247
258
  }
259
+ else {
260
+ k.preventDefault();
261
+ submit();
262
+ }
248
263
  }
249
264
  else if (k.nativeEvent.key === "ArrowUp") {
250
265
  if (showSuggestions || showEmojiSuggestions) {
@@ -269,7 +284,9 @@ function ChatBox({ isPopout, chatBoxStyle, emojiData, setIsChatVisible, }) {
269
284
  setShowEmojiSuggestions(false);
270
285
  }
271
286
  }
272
- }, style: [chatBoxStyle] }), (0, jsx_runtime_1.jsx)(__1.Button, { disabled: submitting, variant: "secondary", style: { borderRadius: 16, height: 36, minWidth: 80 }, onPress: submit, children: submitting ? (0, jsx_runtime_1.jsx)(__1.Loader, {}) : "Send" })] }), showSuggestions && ((0, jsx_runtime_1.jsx)(mention_suggestions_1.MentionSuggestions, { authors: filteredAuthors || new Map(), highlightedIndex: highlightedIndex, onSelect: handleMentionSelect })), showEmojiSuggestions && ((0, jsx_runtime_1.jsx)(emoji_suggestions_1.EmojiSuggestions, { emojis: filteredEmojis, highlightedIndex: highlightedIndex, onSelect: handleEmojiSelect })), react_native_1.Platform.OS === "web" && ((0, jsx_runtime_1.jsxs)(__1.View, { style: [
287
+ }, style: [chatBoxStyle],
288
+ // "submit" won't blur on enter
289
+ submitBehavior: "submit", placeholder: "Type a message..." }), (0, jsx_runtime_1.jsx)(__1.Button, { disabled: submitting, variant: "secondary", style: { borderRadius: 16, height: 36, minWidth: 80 }, onPress: submit, children: submitting ? (0, jsx_runtime_1.jsx)(__1.Loader, {}) : "Send" })] }), showSuggestions && ((0, jsx_runtime_1.jsx)(mention_suggestions_1.MentionSuggestions, { authors: filteredAuthors || new Map(), highlightedIndex: highlightedIndex, onSelect: handleMentionSelect })), showEmojiSuggestions && ((0, jsx_runtime_1.jsx)(emoji_suggestions_1.EmojiSuggestions, { emojis: filteredEmojis, highlightedIndex: highlightedIndex, onSelect: handleEmojiSelect })), react_native_1.Platform.OS === "web" && ((0, jsx_runtime_1.jsxs)(__1.View, { style: [
273
290
  atoms_1.layout.flex.row,
274
291
  atoms_1.mb[2],
275
292
  atoms_1.gap.all[2],
@@ -51,22 +51,30 @@ exports.RenderChatMessage = (0, react_1.memo)(function RenderChatMessage({ item,
51
51
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [item.replyTo && showReply && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
52
52
  atoms_1.gap.all[2],
53
53
  ui_1.layout.flex.row,
54
- atoms_1.w.percent[100],
54
+ { minWidth: 0, maxWidth: "100%" },
55
55
  atoms_1.borders.left.width.medium,
56
56
  atoms_1.borders.left.color.gray[700],
57
57
  atoms_1.ml[4],
58
58
  atoms_1.pl[4],
59
59
  atoms_1.opacity[80],
60
- ], children: (0, jsx_runtime_1.jsxs)(text_1.Text, { numberOfLines: 1, style: [atoms_1.flex.shrink[1], atoms_1.mr[4]], children: [(0, jsx_runtime_1.jsxs)(text_1.Text, { style: {
60
+ ], children: (0, jsx_runtime_1.jsxs)(text_1.Text, { numberOfLines: 1, style: [
61
+ atoms_1.flex.shrink[1],
62
+ atoms_1.mr[4],
63
+ { minWidth: 0, overflow: "hidden" },
64
+ ], children: [(0, jsx_runtime_1.jsxs)(text_1.Text, { style: {
61
65
  color: getRgbColor(item.replyTo.chatProfile.color),
62
66
  fontWeight: "thin",
63
67
  }, children: ["@", item.replyTo.author.handle] }), " ", (0, jsx_runtime_1.jsx)(text_1.Text, { style: {
64
68
  color: ui_1.colors.gray[300],
65
69
  fontStyle: "italic",
66
- }, children: item.replyTo.record.text })] }) })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.gap.all[2], ui_1.layout.flex.row, atoms_1.w.percent[100]], children: [showTime && ((0, jsx_runtime_1.jsx)(text_1.Text, { style: {
70
+ }, children: item.replyTo.record.text })] }) })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
71
+ atoms_1.gap.all[2],
72
+ ui_1.layout.flex.row,
73
+ { minWidth: 0, maxWidth: "100%" },
74
+ ], children: [showTime && ((0, jsx_runtime_1.jsx)(text_1.Text, { style: {
67
75
  fontVariant: ["tabular-nums"],
68
76
  color: ui_1.colors.gray[400],
69
- }, children: formatTime(item.record.createdAt) })), (0, jsx_runtime_1.jsxs)(text_1.Text, { weight: "bold", color: "default", style: [atoms_1.flex.shrink[1]], children: [(0, jsx_runtime_1.jsxs)(text_1.Text, { style: [
77
+ }, children: formatTime(item.record.createdAt) })), (0, jsx_runtime_1.jsxs)(text_1.Text, { weight: "bold", color: "default", style: [atoms_1.flex.shrink[1], { minWidth: 0, overflow: "hidden" }], children: [(0, jsx_runtime_1.jsxs)(text_1.Text, { style: [
70
78
  {
71
79
  cursor: "pointer",
72
80
  color: getRgbColor(item.chatProfile?.color),
@@ -51,6 +51,8 @@ const ActionsBar = (0, react_1.memo)(({ item, visible, hoverTimeoutRef, }) => {
51
51
  padding: 1,
52
52
  gap: 4,
53
53
  zIndex: 10,
54
+ maxWidth: 120,
55
+ flexShrink: 0,
54
56
  },
55
57
  ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => setReply(item), style: [
56
58
  {
@@ -110,9 +112,14 @@ const ChatLine = (0, react_1.memo)(({ item, canModerate, }) => {
110
112
  return ((0, jsx_runtime_1.jsxs)(__1.View, { style: [
111
113
  atoms_1.py[1],
112
114
  atoms_1.px[2],
113
- { position: "relative", borderRadius: 8 },
115
+ {
116
+ position: "relative",
117
+ borderRadius: 8,
118
+ minWidth: 0,
119
+ maxWidth: "100%",
120
+ },
114
121
  isHovered && atoms_1.bg.gray[950],
115
- ], onPointerEnter: handleHoverIn, onPointerLeave: handleHoverOut, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { children: (0, jsx_runtime_1.jsx)(chat_message_1.RenderChatMessage, { item: item }) }), (0, jsx_runtime_1.jsx)(ActionsBar, { item: item, visible: isHovered, hoverTimeoutRef: hoverTimeoutRef })] }));
122
+ ], onPointerEnter: handleHoverIn, onPointerLeave: handleHoverOut, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [{ minWidth: 0, maxWidth: "100%" }], children: (0, jsx_runtime_1.jsx)(chat_message_1.RenderChatMessage, { item: item }) }), (0, jsx_runtime_1.jsx)(ActionsBar, { item: item, visible: isHovered, hoverTimeoutRef: hoverTimeoutRef })] }));
116
123
  }
117
124
  return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(ReanimatedSwipeable_1.default, { containerStyle: [atoms_1.py[1]], friction: 2, enableTrackpadTwoFingerGesture: true, rightThreshold: 40, leftThreshold: 40, renderRightActions: react_native_1.Platform.OS === "android" ? undefined : RightAction, renderLeftActions: react_native_1.Platform.OS === "android" ? undefined : LeftAction, overshootFriction: 9, ref: (ref) => {
118
125
  swipeableRef.current = ref;
@@ -133,6 +140,10 @@ const ChatLine = (0, react_1.memo)(({ item, canModerate, }) => {
133
140
  function Chat({ shownMessages = SHOWN_MSGS, style: propsStyle, canModerate = false, ...props }) {
134
141
  const chat = (0, __1.useChat)();
135
142
  if (!chat)
136
- return ((0, jsx_runtime_1.jsx)(__1.View, { style: [atoms_1.flex.shrink[1]], children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Loading chaat..." }) }));
137
- return ((0, jsx_runtime_1.jsxs)(__1.View, { style: [atoms_1.flex.shrink[1]].concat(propsStyle || []), children: [(0, jsx_runtime_1.jsx)(react_native_1.FlatList, { style: [atoms_1.flex.grow[1], atoms_1.flex.shrink[1], atoms_1.w.percent[100]], data: chat.slice(0, shownMessages), inverted: true, keyExtractor: keyExtractor, renderItem: ({ item, index }) => ((0, jsx_runtime_1.jsx)(ChatLine, { item: item, canModerate: canModerate })), removeClippedSubviews: true, maxToRenderPerBatch: 10, initialNumToRender: 10, updateCellsBatchingPeriod: 50 }), (0, jsx_runtime_1.jsx)(mod_view_1.ModView, {})] }));
143
+ return ((0, jsx_runtime_1.jsx)(__1.View, { style: [atoms_1.flex.shrink[1], { minWidth: 0, maxWidth: "100%" }], children: (0, jsx_runtime_1.jsx)(__1.Text, { children: "Loading chaat..." }) }));
144
+ return ((0, jsx_runtime_1.jsxs)(__1.View, { style: [atoms_1.flex.shrink[1], { minWidth: 0, maxWidth: "100%" }].concat(propsStyle || []), children: [(0, jsx_runtime_1.jsx)(react_native_1.FlatList, { style: [
145
+ atoms_1.flex.grow[1],
146
+ atoms_1.flex.shrink[1],
147
+ { minWidth: 0, maxWidth: "100%" },
148
+ ], data: chat.slice(0, shownMessages), inverted: true, keyExtractor: keyExtractor, renderItem: ({ item, index }) => ((0, jsx_runtime_1.jsx)(ChatLine, { item: item, canModerate: canModerate })), removeClippedSubviews: true, maxToRenderPerBatch: 10, initialNumToRender: 10, updateCellsBatchingPeriod: 50 }), (0, jsx_runtime_1.jsx)(mod_view_1.ModView, {})] }));
138
149
  }
@@ -20,6 +20,9 @@ exports.ModView = (0, react_1.forwardRef)(() => {
20
20
  let [messageRemoved, setMessageRemoved] = (0, react_1.useState)(false);
21
21
  let { createBlock, isLoading: isBlockLoading } = (0, block_1.useCreateBlockRecord)();
22
22
  let { createHideChat, isLoading: isHideLoading } = (0, block_1.useCreateHideChatRecord)();
23
+ const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
24
+ const setReportSubject = (0, player_store_1.usePlayerStore)((x) => x.setReportSubject);
25
+ const setModMessage = (0, player_store_1.usePlayerStore)((x) => x.setModMessage);
23
26
  // get the channel did
24
27
  const channelId = (0, player_store_1.usePlayerStore)((state) => state.src);
25
28
  // get the logged in user's identity
@@ -27,18 +30,23 @@ exports.ModView = (0, react_1.forwardRef)(() => {
27
30
  if (!agent?.did) {
28
31
  (0, jsx_runtime_1.jsx)(ui_1.View, { style: [ui_1.layout.flex.row, ui_1.layout.flex.alignCenter, atoms_1.gap.all[2]], children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Log in to submit mod actions" }) });
29
32
  }
33
+ const cleanup = () => {
34
+ setModMessage(null);
35
+ };
30
36
  (0, react_1.useEffect)(() => {
31
37
  if (message) {
32
- console.log("opening mod view");
33
38
  setMessageRemoved(false);
34
39
  triggerRef.current?.open();
35
40
  }
36
41
  else {
37
- console.log("closing mod view");
38
42
  triggerRef.current?.close();
39
43
  }
40
44
  }, [message]);
41
- return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { style: [ui_1.layout.flex.row, ui_1.layout.flex.alignCenter, atoms_1.gap.all[2], atoms_1.w[80]], children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { ref: triggerRef, children: (0, jsx_runtime_1.jsx)(ui_1.View, {}) }), (0, jsx_runtime_1.jsx)(ui_1.ResponsiveDropdownMenuContent, { children: message && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { children: (0, jsx_runtime_1.jsx)(ui_1.View, { style: [
45
+ return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { style: [ui_1.layout.flex.row, ui_1.layout.flex.alignCenter, atoms_1.gap.all[2], atoms_1.w[80]], onOpenChange: (isOpen) => {
46
+ if (!isOpen) {
47
+ cleanup();
48
+ }
49
+ }, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { ref: triggerRef, children: (0, jsx_runtime_1.jsx)(ui_1.View, {}) }), (0, jsx_runtime_1.jsx)(ui_1.ResponsiveDropdownMenuContent, { children: message && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { children: (0, jsx_runtime_1.jsx)(ui_1.View, { style: [
42
50
  ui_1.layout.flex.column,
43
51
  atoms_1.mr[5],
44
52
  { gap: 6, maxWidth: "100%" },
@@ -66,12 +74,10 @@ exports.ModView = (0, react_1.forwardRef)(() => {
66
74
  }, children: message.author.did === agent?.did ? ((0, jsx_runtime_1.jsx)(ui_1.Text, { color: "muted", children: "Block yourself (you can't block yourself)" })) : ((0, jsx_runtime_1.jsx)(ui_1.Text, { color: "destructive", children: isBlockLoading
67
75
  ? "Blocking..."
68
76
  : `Block user @${message.author.handle} from this channel` })) })] })), (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: `User actions`, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
69
- react_native_1.Linking.openURL(`https://${BSKY_FRONTEND_DOMAIN}/profile/${channelId}`);
70
- }, children: (0, jsx_runtime_1.jsxs)(ui_1.Text, { color: "primary", children: ["View user on ", BSKY_FRONTEND_DOMAIN] }) }), (0, jsx_runtime_1.jsx)(ReportButton, { message: message })] })] })) })] }));
77
+ react_native_1.Linking.openURL(`https://${BSKY_FRONTEND_DOMAIN}/profile/${message.author.handle}`);
78
+ }, children: (0, jsx_runtime_1.jsxs)(ui_1.Text, { color: "primary", children: ["View user on ", BSKY_FRONTEND_DOMAIN] }) }), (0, jsx_runtime_1.jsx)(ReportButton, { message: message, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject })] })] })) })] }));
71
79
  });
72
- function ReportButton({ message, }) {
73
- const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
74
- const setReportSubject = (0, player_store_1.usePlayerStore)((x) => x.setReportSubject);
80
+ function ReportButton({ message, setReportModalOpen, setReportSubject, }) {
75
81
  const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
76
82
  return ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
77
83
  if (!message)
@@ -82,6 +88,7 @@ function ReportButton({ message, }) {
82
88
  $type: "com.atproto.repo.strongRef",
83
89
  uri: message.uri,
84
90
  cid: message.cid,
91
+ context: message,
85
92
  });
86
93
  }, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { color: "warning", children: "Report chat..." }) }));
87
94
  }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = ChatPanel;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_native_1 = require("react-native");
7
+ const emoji_data_json_1 = tslib_1.__importDefault(require("../../assets/emoji-data.json"));
8
+ const zero = tslib_1.__importStar(require("../../ui"));
9
+ const chat_1 = require("../chat/chat");
10
+ const chat_box_1 = require("../chat/chat-box");
11
+ const { flex, bg, r, borders, p, px, py, text, layout } = zero;
12
+ function ChatPanel({ isLive, isConnected, messagesPerMinute = 0, canModerate = false, shownMessages = 50, }) {
13
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
14
+ flex.values[1],
15
+ bg.neutral[900],
16
+ borders.width.thin,
17
+ borders.color.neutral[700],
18
+ layout.flex.column,
19
+ r.lg,
20
+ ], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
21
+ layout.flex.row,
22
+ layout.flex.spaceBetween,
23
+ layout.flex.alignCenter,
24
+ borders.bottom.width.thin,
25
+ borders.bottom.color.neutral[700],
26
+ p[4],
27
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 18, fontWeight: "600" }], children: "Chat" }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
28
+ { width: 6, height: 6, borderRadius: 3 },
29
+ isLive && isConnected ? bg.green[500] : bg.gray[500],
30
+ ] }), (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [text.gray[400], { fontSize: 12, marginLeft: 8 }], children: [messagesPerMinute, " msg/min"] })] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [flex.values[1], px[2], { minHeight: 0 }], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [flex.values[1], { minHeight: 0 }], children: (0, jsx_runtime_1.jsx)(chat_1.Chat, { canModerate: canModerate, shownMessages: shownMessages }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [{ flexShrink: 0 }], children: (0, jsx_runtime_1.jsx)(chat_box_1.ChatBox, { emojiData: emoji_data_json_1.default, chatBoxStyle: [
31
+ bg.gray[700],
32
+ borders.width.thin,
33
+ borders.color.gray[600],
34
+ r.md,
35
+ p[3],
36
+ !isConnected && { opacity: 0.6 },
37
+ ] }) })] })] }));
38
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = Header;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const lucide_react_native_1 = require("lucide-react-native");
7
+ const react_native_1 = require("react-native");
8
+ const zero = tslib_1.__importStar(require("../../ui"));
9
+ const { bg, r, borders, px, py, text, layout, gap } = zero;
10
+ function MetricItem({ icon: Icon, label, value, status }) {
11
+ const statusColors = {
12
+ good: text.green[400],
13
+ warning: text.yellow[400],
14
+ error: text.red[400],
15
+ };
16
+ const statusColor = status ? statusColors[status] : text.gray[300];
17
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(Icon, { size: 16, color: "#9ca3af" }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.column], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[400], { fontSize: 11, fontWeight: "500" }], children: label }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [statusColor, { fontSize: 13, fontWeight: "600" }], children: value })] })] }));
18
+ }
19
+ function StatusIndicator({ status, isLive }) {
20
+ const getStatusColor = () => {
21
+ if (!isLive)
22
+ return bg.gray[500];
23
+ switch (status) {
24
+ case "excellent":
25
+ return bg.green[500];
26
+ case "good":
27
+ return bg.yellow[500];
28
+ case "poor":
29
+ return bg.orange[500];
30
+ case "offline":
31
+ return bg.red[500];
32
+ default:
33
+ return bg.gray[500];
34
+ }
35
+ };
36
+ const getStatusText = () => {
37
+ if (!isLive)
38
+ return "OFFLINE";
39
+ switch (status) {
40
+ case "excellent":
41
+ return "EXCELLENT";
42
+ case "good":
43
+ return "GOOD";
44
+ case "poor":
45
+ return "POOR";
46
+ case "offline":
47
+ return "OFFLINE";
48
+ default:
49
+ return "UNKNOWN";
50
+ }
51
+ };
52
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
53
+ { width: 8, height: 8, borderRadius: 4 },
54
+ getStatusColor(),
55
+ !isLive && { opacity: 0.6 },
56
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
57
+ text.white,
58
+ { fontSize: 12, fontWeight: "700", letterSpacing: 0.5 },
59
+ !isLive && text.gray[400],
60
+ ], children: getStatusText() })] }));
61
+ }
62
+ function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00:00:00", bitrate = "0 mbps", timeBetweenSegments = 0, connectionStatus = "offline", }) {
63
+ const getConnectionQuality = () => {
64
+ if (timeBetweenSegments <= 1500)
65
+ return "good";
66
+ if (timeBetweenSegments <= 3000)
67
+ return "warning";
68
+ return "error";
69
+ };
70
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
71
+ px[4],
72
+ py[3],
73
+ r.lg,
74
+ layout.flex.row,
75
+ layout.flex.spaceBetween,
76
+ bg.neutral[900],
77
+ borders.width.thin,
78
+ borders.color.neutral[700],
79
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[4]], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 18, fontWeight: "600" }], children: streamTitle }), (0, jsx_runtime_1.jsx)(StatusIndicator, { status: connectionStatus, isLive: isLive })] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[6]], children: [isLive && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(MetricItem, { icon: lucide_react_native_1.Users, label: "Viewers", value: viewers.toLocaleString() }), (0, jsx_runtime_1.jsx)(MetricItem, { icon: lucide_react_native_1.Car, label: "Bitrate", value: bitrate })] })), !isLive && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Radio, { size: 16, color: "#6b7280" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[400], { fontSize: 13 }], children: "Stream offline" })] }))] })] }));
80
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Problems = exports.ModActions = exports.InformationWidget = exports.Header = exports.ChatPanel = void 0;
4
+ const tslib_1 = require("tslib");
5
+ var chat_panel_1 = require("./chat-panel");
6
+ Object.defineProperty(exports, "ChatPanel", { enumerable: true, get: function () { return tslib_1.__importDefault(chat_panel_1).default; } });
7
+ var header_1 = require("./header");
8
+ Object.defineProperty(exports, "Header", { enumerable: true, get: function () { return tslib_1.__importDefault(header_1).default; } });
9
+ var information_widget_1 = require("./information-widget");
10
+ Object.defineProperty(exports, "InformationWidget", { enumerable: true, get: function () { return tslib_1.__importDefault(information_widget_1).default; } });
11
+ var mod_actions_1 = require("./mod-actions");
12
+ Object.defineProperty(exports, "ModActions", { enumerable: true, get: function () { return tslib_1.__importDefault(mod_actions_1).default; } });
13
+ var problems_1 = require("./problems");
14
+ Object.defineProperty(exports, "Problems", { enumerable: true, get: function () { return tslib_1.__importDefault(problems_1).default; } });
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = InformationWidget;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const lucide_react_native_1 = require("lucide-react-native");
7
+ const react_1 = tslib_1.__importStar(require("react"));
8
+ const react_native_1 = require("react-native");
9
+ const react_native_svg_1 = tslib_1.__importStar(require("react-native-svg"));
10
+ const livestream_store_1 = require("../../livestream-store");
11
+ const zero = tslib_1.__importStar(require("../../ui"));
12
+ const ui_1 = require("../ui");
13
+ const BITRATE_HISTORY_LENGTH = 30;
14
+ const { bg, r, borders, px, py, text, layout, gap, flex, p } = zero;
15
+ function InformationWidget({ embedMode = false, wideMode, // Optional override
16
+ showChart = true, }) {
17
+ const [bitrateHistory, setBitrateHistory] = (0, react_1.useState)(Array.from({ length: BITRATE_HISTORY_LENGTH }, () => 0));
18
+ const [showViewers, setShowViewers] = (0, react_1.useState)(false);
19
+ const [componentWidth, setComponentWidth] = (0, react_1.useState)(220);
20
+ const [componentHeight, setComponentHeight] = (0, react_1.useState)(400);
21
+ const [streamStartTime, setStreamStartTime] = (0, react_1.useState)(null);
22
+ const [layoutMeasured, setLayoutMeasured] = (0, react_1.useState)(false);
23
+ const isWideMode = wideMode !== undefined ? wideMode : layoutMeasured && componentWidth > 400;
24
+ const isCompactHeight = layoutMeasured && componentHeight < 350;
25
+ const seg = (0, livestream_store_1.useSegment)();
26
+ const livestream = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
27
+ const viewers = (0, livestream_store_1.useViewers)();
28
+ const getBitrate = (0, react_1.useCallback)(() => {
29
+ if (!seg?.size || !seg?.duration)
30
+ return 0;
31
+ const kbps = (seg.size * 8) / ((seg.duration || 1000000000) / 1000000000) / 1000;
32
+ return kbps;
33
+ }, [seg?.size, seg?.duration]);
34
+ const getMediaInfo = (0, react_1.useMemo)(() => {
35
+ const videoTrack = seg?.video?.[0];
36
+ const audioTrack = seg?.audio?.[0];
37
+ return {
38
+ resolution: videoTrack?.width && videoTrack?.height
39
+ ? `${videoTrack.width}x${videoTrack.height}`
40
+ : "Unknown",
41
+ fps: videoTrack?.framerate
42
+ ? `${(videoTrack.framerate.num / videoTrack.framerate.den).toFixed(2)} FPS`
43
+ : "Unknown",
44
+ videoCodec: videoTrack?.codec
45
+ ? videoTrack.codec.toUpperCase()
46
+ : "Unknown",
47
+ };
48
+ }, [seg?.video, seg?.audio]);
49
+ const currentBitrate = getBitrate();
50
+ (0, react_1.useEffect)(() => {
51
+ setBitrateHistory((prev) => [...prev.slice(1), currentBitrate]);
52
+ }, [currentBitrate]);
53
+ (0, react_1.useEffect)(() => {
54
+ if (seg?.startTime && !streamStartTime) {
55
+ setStreamStartTime(new Date(seg.startTime));
56
+ }
57
+ }, [seg?.startTime, streamStartTime]);
58
+ const getBitrateStatus = () => {
59
+ if (currentBitrate > 2000)
60
+ return "good";
61
+ if (currentBitrate > 1000)
62
+ return "warning";
63
+ if (currentBitrate > 0)
64
+ return "error";
65
+ return "neutral";
66
+ };
67
+ const getConnectionStatus = () => {
68
+ if (!seg)
69
+ return "error";
70
+ if (currentBitrate > 1500)
71
+ return "good";
72
+ if (currentBitrate > 500)
73
+ return "warning";
74
+ return "error";
75
+ };
76
+ const avgBitrate = bitrateHistory.length > 0
77
+ ? bitrateHistory.reduce((a, b) => a + b, 0) / bitrateHistory.length
78
+ : 0;
79
+ const peakBitrate = Math.max(...bitrateHistory, 0);
80
+ const uptimeMinutes = streamStartTime
81
+ ? Math.floor((Date.now() - streamStartTime.getTime()) / 60000)
82
+ : 0;
83
+ const estimatedLatency = seg?.duration
84
+ ? Math.floor((seg.duration / 1000000000) * 2)
85
+ : 0;
86
+ const displayBitrate = `${(currentBitrate / 1000).toFixed(2)} Mbps`;
87
+ const displayResolution = getMediaInfo.resolution;
88
+ const displayFps = getMediaInfo.fps;
89
+ const streamTitle = livestream?.record?.title || "Untitled Stream";
90
+ const viewerCount = viewers ?? 0;
91
+ const handleLayout = (0, react_1.useCallback)((event) => {
92
+ const { width, height } = event.nativeEvent.layout;
93
+ console.log("InformationWidget onLayout - size:", `${width}x${height}`);
94
+ if (width > 0 && height > 0) {
95
+ setComponentWidth(width);
96
+ setComponentHeight(height);
97
+ setLayoutMeasured(true);
98
+ }
99
+ }, []);
100
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { onLayout: handleLayout, style: [
101
+ embedMode
102
+ ? { backgroundColor: "rgba(23, 23, 23, 0.9)" }
103
+ : bg.neutral[900],
104
+ embedMode ? undefined : borders.width.thin,
105
+ embedMode ? undefined : borders.color.neutral[700],
106
+ r.lg,
107
+ px[4],
108
+ py[4],
109
+ gap.all[6],
110
+ flex.values[1],
111
+ {
112
+ minWidth: isWideMode ? 500 : 220,
113
+ width: "100%",
114
+ },
115
+ ], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
116
+ layout.flex.row,
117
+ layout.flex.spaceBetween,
118
+ layout.flex.alignCenter,
119
+ ], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[1]], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 18, fontWeight: "700" }], numberOfLines: 1, children: "Stream Health" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
120
+ {
121
+ width: 8,
122
+ height: 8,
123
+ borderRadius: 4,
124
+ backgroundColor: getConnectionStatus() === "good"
125
+ ? "#22c55e"
126
+ : getConnectionStatus() === "warning"
127
+ ? "#f59e0b"
128
+ : "#ef4444",
129
+ },
130
+ ] })] }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: () => setShowViewers(!showViewers), style: [
131
+ layout.flex.column,
132
+ layout.flex.alignCenter,
133
+ gap.all[1],
134
+ { minWidth: 120 },
135
+ ], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [showViewers ? ((0, jsx_runtime_1.jsx)(lucide_react_native_1.Eye, { size: 14, color: "#9ca3af" })) : ((0, jsx_runtime_1.jsx)(lucide_react_native_1.EyeClosed, { size: 14, color: "#9ca3af" })), (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [
136
+ text.white,
137
+ { fontSize: 16, fontWeight: "600", textAlign: "center" },
138
+ ], children: [showViewers ? `${viewerCount}` : "...", " viewer", showViewers && viewerCount !== 1 ? "s" : ""] })] }) })] }), isWideMode ? ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[3]], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(ui_1.InfoBox, { icon: lucide_react_native_1.Car, label: "Bitrate", value: displayBitrate, status: getBitrateStatus() }), (0, jsx_runtime_1.jsx)(ui_1.InfoBox, { icon: lucide_react_native_1.Monitor, label: "Resolution", value: displayResolution }), (0, jsx_runtime_1.jsx)(ui_1.InfoBox, { icon: lucide_react_native_1.Video, label: "FPS", value: displayFps })] }), showChart && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[2]], children: [!isCompactHeight && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
139
+ layout.flex.row,
140
+ layout.flex.spaceBetween,
141
+ layout.flex.alignCenter,
142
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
143
+ text.gray[200],
144
+ { fontSize: 14, fontWeight: "600" },
145
+ ], children: "Live Performance" }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, gap.all[4]], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.alignCenter], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
146
+ text.gray[400],
147
+ { fontSize: 11, fontWeight: "500" },
148
+ ], children: "AVG" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
149
+ text.white,
150
+ { fontSize: 13, fontWeight: "600" },
151
+ ], children: avgBitrate > 0
152
+ ? `${(avgBitrate / 1000).toFixed(1)}M`
153
+ : "0M" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.alignCenter], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
154
+ text.gray[400],
155
+ { fontSize: 11, fontWeight: "500" },
156
+ ], children: "PEAK" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
157
+ text.white,
158
+ { fontSize: 13, fontWeight: "600" },
159
+ ], children: peakBitrate > 0
160
+ ? `${(peakBitrate / 1000).toFixed(1)}M`
161
+ : "0M" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.alignCenter], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
162
+ text.gray[400],
163
+ { fontSize: 11, fontWeight: "500" },
164
+ ], children: "CAPTURED" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
165
+ text.white,
166
+ { fontSize: 13, fontWeight: "600" },
167
+ ], children: uptimeMinutes > 60
168
+ ? `${Math.floor(uptimeMinutes / 60)}h ${uptimeMinutes % 60}m`
169
+ : `${uptimeMinutes}m` })] })] })] })), (0, jsx_runtime_1.jsx)(BitrateChart, { data: bitrateHistory, width: componentWidth - 40, height: 120 })] }))] })) : ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[3]], children: [!isCompactHeight && ((0, jsx_runtime_1.jsxs)(react_native_1.TouchableOpacity, { onPress: () => setShowViewers(!showViewers), style: [
170
+ layout.flex.row,
171
+ layout.flex.spaceBetween,
172
+ layout.flex.alignCenter,
173
+ py[2],
174
+ ], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[3]], children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Eye, { size: 16, color: "#9ca3af" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.gray[300], { fontSize: 13, fontWeight: "500" }], children: "Viewers" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[2]], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
175
+ showViewers ? text.green[400] : text.white,
176
+ { fontSize: 13, fontWeight: "600" },
177
+ ], children: showViewers ? `${viewerCount} watching` : "•••" }), showViewers ? ((0, jsx_runtime_1.jsx)(lucide_react_native_1.ChevronUp, { size: 14, color: "#9ca3af" })) : ((0, jsx_runtime_1.jsx)(lucide_react_native_1.ChevronDown, { size: 14, color: "#9ca3af" }))] })] })), showChart && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[2]], children: [!isCompactHeight && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
178
+ layout.flex.row,
179
+ layout.flex.spaceBetween,
180
+ layout.flex.alignCenter,
181
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
182
+ text.gray[200],
183
+ { fontSize: 14, fontWeight: "600" },
184
+ ], children: "Performance" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
185
+ text.gray[400],
186
+ { fontSize: 11, fontWeight: "500" },
187
+ ], children: avgBitrate > 0
188
+ ? `AVG ${(avgBitrate / 1000).toFixed(1)}M`
189
+ : "No data" })] })), (0, jsx_runtime_1.jsx)(BitrateChart, { data: bitrateHistory, width: componentWidth - 40, height: isCompactHeight ? 80 : 120 })] })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[1]], children: [(0, jsx_runtime_1.jsx)(ui_1.InfoRow, { icon: lucide_react_native_1.Signal, label: "Connection", value: getConnectionStatus() === "good"
190
+ ? "Excellent"
191
+ : getConnectionStatus() === "warning"
192
+ ? "Good"
193
+ : "Poor", status: getConnectionStatus() }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[1], layout.flex.row], children: [(0, jsx_runtime_1.jsx)(ui_1.InfoBox, { icon: lucide_react_native_1.Zap, label: "Bitrate", value: displayBitrate, status: getBitrateStatus() }), (0, jsx_runtime_1.jsx)(ui_1.InfoBox, { icon: lucide_react_native_1.Video, label: "FPS", value: displayFps })] })] })] }))] }));
194
+ }
195
+ function BitrateChart({ data, width, height, }) {
196
+ const maxDataValue = Math.max(...data, 1);
197
+ const minDataValue = Math.min(...data);
198
+ const getSmartRange = (max) => {
199
+ if (max <= 1000)
200
+ return { min: 0, max: 1000, step: 500 };
201
+ if (max <= 2000)
202
+ return { min: 1000, max: 2000, step: 1000 };
203
+ if (max <= 7000)
204
+ return { min: 4000, max: 7000, step: 1500 };
205
+ if (max <= 10000)
206
+ return { min: 4000, max: 10000, step: 5000 };
207
+ const roundedMax = Math.ceil(max / 5000) * 5000;
208
+ return { min: 0, max: roundedMax, step: roundedMax / 2 };
209
+ };
210
+ const { min: minValue, max: maxValue, step } = getSmartRange(maxDataValue);
211
+ const range = maxValue - minValue;
212
+ const chartWidth = width - 40;
213
+ const chartStartX = 40;
214
+ const verticalPadding = 10;
215
+ const chartHeight = height - verticalPadding * 2;
216
+ const points = data
217
+ .map((value, index) => {
218
+ const x = chartStartX + (index / (data.length - 1)) * chartWidth;
219
+ // Clamp value to chart range and plot against the smart scale
220
+ const clampedValue = Math.max(minValue, Math.min(maxValue, value));
221
+ const y = verticalPadding +
222
+ chartHeight -
223
+ ((clampedValue - minValue) / range) * chartHeight;
224
+ return `${x},${y}`;
225
+ })
226
+ .join(" ");
227
+ const pathData = `M ${points.replace(/ /g, " L ")}`;
228
+ const ticks = [
229
+ { value: minValue, y: height - verticalPadding },
230
+ { value: minValue + step, y: height / 2 },
231
+ { value: maxValue, y: verticalPadding },
232
+ ];
233
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { height, width, marginVertical: 8 }, children: (0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: width, height: height, children: [ticks.map((tick, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: chartStartX, y1: tick.y, x2: width, y2: tick.y, stroke: "rgba(255,255,255,0.1)", strokeWidth: "1" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Text, { x: "35", y: tick.y + 4, fontSize: 10, fontFamily: "sans-serif", fill: "#9ca3af", textAnchor: "end", children: (tick.value / 1000).toLocaleString() })] }, index))), (0, jsx_runtime_1.jsx)(react_native_svg_1.Text, { x: 12, y: height / 2, transform: `rotate(-90, 12, ${height / 2})`, fontSize: 10, fontFamily: "sans-serif", fill: "#9ca3af", textAnchor: "middle", children: "mbits/s" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: pathData, stroke: "#22c55e", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" })] }) }));
234
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = ModActions;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const lucide_react_native_1 = require("lucide-react-native");
7
+ const react_native_1 = require("react-native");
8
+ const zero = tslib_1.__importStar(require("../../ui"));
9
+ const { flex, bg, r, borders, p, text, layout, gap, mb } = zero;
10
+ const defaultActions = [
11
+ {
12
+ icon: lucide_react_native_1.Shield,
13
+ label: "Ban User",
14
+ color: "red",
15
+ action: () => console.log("Ban user action"),
16
+ },
17
+ {
18
+ icon: lucide_react_native_1.MessageCircle,
19
+ label: "Timeout",
20
+ color: "yellow",
21
+ action: () => console.log("Timeout user action"),
22
+ },
23
+ {
24
+ icon: lucide_react_native_1.Eye,
25
+ label: "Monitor",
26
+ color: "blue",
27
+ action: () => console.log("Monitor stream action"),
28
+ },
29
+ {
30
+ icon: lucide_react_native_1.AlertTriangle,
31
+ label: "Report",
32
+ color: "orange",
33
+ action: () => console.log("Report content action"),
34
+ },
35
+ ];
36
+ function ModActions({ isLive, isConnected, messageCount = 0, actions = defaultActions, }) {
37
+ const canModerate = isLive && isConnected;
38
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
39
+ flex.values[1],
40
+ bg.gray[800],
41
+ r[3],
42
+ borders.width.thin,
43
+ borders.color.gray[700],
44
+ p[4],
45
+ ], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
46
+ layout.flex.row,
47
+ layout.flex.spaceBetween,
48
+ layout.flex.alignCenter,
49
+ mb[4],
50
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontSize: 18, fontWeight: "600" }], children: "Moderation" }), (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [text.gray[400], { fontSize: 12 }], children: [messageCount, " messages"] })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [layout.flex.row, gap.all[3]], children: actions.map((action, index) => ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { style: [
51
+ flex.grow[1],
52
+ bg.gray[700],
53
+ r[2],
54
+ p[3],
55
+ layout.flex.row,
56
+ layout.flex.alignCenter,
57
+ gap.all[2],
58
+ borders.width.thin,
59
+ borders.color.gray[600],
60
+ ], disabled: !canModerate, onPress: action.action, children: [(0, jsx_runtime_1.jsx)(action.icon, { size: 20, color: canModerate ? "#ffffff" : "#6b7280" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
61
+ canModerate ? text.white : text.gray[400],
62
+ { fontSize: 14, fontWeight: "500" },
63
+ ], children: action.label })] }, index))) }), !canModerate && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
64
+ text.gray[500],
65
+ { fontSize: 12, textAlign: "center", marginTop: 16 },
66
+ ], children: !isLive
67
+ ? "Moderation tools available when live"
68
+ : !isConnected
69
+ ? "Waiting for stream connection..."
70
+ : "Moderation tools unavailable" }))] }));
71
+ }