@streamplace/components 0.7.3 → 0.7.7
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/dist/components/chat/chat-box.js +212 -24
- package/dist/components/chat/chat-message.js +5 -5
- package/dist/components/chat/chat.js +83 -5
- package/dist/components/chat/emoji-suggestions.js +35 -0
- package/dist/components/chat/mod-view.js +59 -8
- package/dist/components/chat/system-message.js +19 -0
- package/dist/components/icons/bluesky-icon.js +9 -0
- package/dist/components/keep-awake.js +7 -0
- package/dist/components/keep-awake.native.js +16 -0
- package/dist/components/mobile-player/fullscreen.js +2 -1
- package/dist/components/mobile-player/fullscreen.native.js +3 -3
- package/dist/components/mobile-player/player.js +15 -30
- package/dist/components/mobile-player/ui/index.js +2 -1
- package/dist/components/mobile-player/ui/report-modal.js +90 -0
- package/dist/components/mobile-player/ui/{loading.js → streamer-loading-overlay.js} +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -1
- package/dist/components/mobile-player/ui/viewer-loading-overlay.js +49 -0
- package/dist/components/mobile-player/use-webrtc.js +7 -1
- package/dist/components/mobile-player/video-retry.js +29 -0
- package/dist/components/mobile-player/video.js +84 -9
- package/dist/components/mobile-player/video.native.js +24 -10
- package/dist/components/share/sharesheet.js +91 -0
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/dropdown.js +6 -6
- package/dist/components/ui/index.js +2 -0
- package/dist/components/ui/primitives/modal.js +0 -1
- package/dist/components/ui/slider.js +5 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/usePointerDevice.js +71 -0
- package/dist/index.js +10 -3
- package/dist/lib/system-messages.js +101 -0
- package/dist/livestream-store/chat.js +111 -18
- package/dist/livestream-store/livestream-store.js +3 -0
- package/dist/livestream-store/problems.js +76 -0
- package/dist/livestream-store/websocket-consumer.js +39 -4
- package/dist/player-store/player-store.js +30 -4
- package/dist/streamplace-store/block.js +51 -12
- package/dist/ui/index.js +79 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
- package/package.json +6 -2
- package/src/components/chat/chat-box.tsx +295 -25
- package/src/components/chat/chat-message.tsx +6 -7
- package/src/components/chat/chat.tsx +192 -41
- package/src/components/chat/emoji-suggestions.tsx +94 -0
- package/src/components/chat/mod-view.tsx +119 -40
- package/src/components/chat/system-message.tsx +38 -0
- package/src/components/icons/bluesky-icon.tsx +9 -0
- package/src/components/keep-awake.native.tsx +13 -0
- package/src/components/keep-awake.tsx +3 -0
- package/src/components/mobile-player/fullscreen.native.tsx +12 -3
- package/src/components/mobile-player/fullscreen.tsx +10 -3
- package/src/components/mobile-player/player.tsx +28 -36
- package/src/components/mobile-player/props.tsx +1 -0
- package/src/components/mobile-player/ui/index.ts +2 -1
- package/src/components/mobile-player/ui/report-modal.tsx +195 -0
- package/src/components/mobile-player/ui/{loading.tsx → streamer-loading-overlay.tsx} +1 -1
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +31 -3
- package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +66 -0
- package/src/components/mobile-player/use-webrtc.tsx +10 -2
- package/src/components/mobile-player/video-retry.tsx +28 -0
- package/src/components/mobile-player/video.native.tsx +24 -10
- package/src/components/mobile-player/video.tsx +100 -21
- package/src/components/share/sharesheet.tsx +185 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/dropdown.tsx +13 -13
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/primitives/modal.tsx +0 -1
- package/src/components/ui/slider.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePointerDevice.ts +89 -0
- package/src/index.tsx +11 -2
- package/src/lib/system-messages.ts +135 -0
- package/src/livestream-store/chat.tsx +145 -17
- package/src/livestream-store/livestream-state.tsx +10 -0
- package/src/livestream-store/livestream-store.tsx +3 -0
- package/src/livestream-store/problems.tsx +96 -0
- package/src/livestream-store/websocket-consumer.tsx +44 -4
- package/src/player-store/player-state.tsx +21 -4
- package/src/player-store/player-store.tsx +38 -5
- package/src/streamplace-store/block.tsx +55 -13
- package/src/ui/index.ts +86 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShareSheet = ShareSheet;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const lucide_react_native_1 = require("lucide-react-native");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const react_native_1 = require("react-native");
|
|
8
|
+
const theme_1 = require("../../lib/theme");
|
|
9
|
+
const livestream_store_1 = require("../../livestream-store");
|
|
10
|
+
const streamplace_store_1 = require("../../streamplace-store");
|
|
11
|
+
const bluesky_icon_1 = require("../icons/bluesky-icon");
|
|
12
|
+
const ui_1 = require("../ui");
|
|
13
|
+
function ShareSheet({ onShare } = {}) {
|
|
14
|
+
const profile = (0, livestream_store_1.useLivestreamStore)((x) => x.profile);
|
|
15
|
+
const [isCopying, setIsCopying] = (0, react_1.useState)(false);
|
|
16
|
+
const url = (0, streamplace_store_1.useUrl)();
|
|
17
|
+
// Get the current stream URL
|
|
18
|
+
const getStreamUrl = (0, react_1.useCallback)(() => {
|
|
19
|
+
return url + (profile ? `/@${profile.handle}` : "");
|
|
20
|
+
}, [profile]);
|
|
21
|
+
// Get the embed URL
|
|
22
|
+
const getEmbedUrl = (0, react_1.useCallback)(() => {
|
|
23
|
+
return url + (profile ? `/embed/${profile.handle}` : "");
|
|
24
|
+
}, [profile]);
|
|
25
|
+
// Get embed code
|
|
26
|
+
const getEmbedCode = (0, react_1.useCallback)(() => {
|
|
27
|
+
const embedUrl = getEmbedUrl();
|
|
28
|
+
return `<iframe src="${embedUrl}" width="640" height="360" frameborder="0" allowfullscreen></iframe>`;
|
|
29
|
+
}, [getEmbedUrl]);
|
|
30
|
+
// Copy to clipboard handler
|
|
31
|
+
const copyToClipboard = (0, react_1.useCallback)(async (text, label) => {
|
|
32
|
+
setIsCopying(true);
|
|
33
|
+
try {
|
|
34
|
+
if (react_native_1.Platform.OS === "web") {
|
|
35
|
+
await navigator.clipboard.writeText(text);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
react_native_1.Clipboard.setString(text);
|
|
39
|
+
}
|
|
40
|
+
onShare?.(`copy_${label.toLowerCase().replace(/\s+/g, "_")}`, true);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
onShare?.(`copy_${label.toLowerCase().replace(/\s+/g, "_")}`, false);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
setIsCopying(false);
|
|
47
|
+
}
|
|
48
|
+
}, [onShare]);
|
|
49
|
+
// Share to Bluesky
|
|
50
|
+
const shareToBluesky = (0, react_1.useCallback)(() => {
|
|
51
|
+
const streamUrl = getStreamUrl();
|
|
52
|
+
const text = profile
|
|
53
|
+
? `Check out @${profile.handle} live on Streamplace! ${streamUrl}`
|
|
54
|
+
: `Check out this stream on Streamplace! ${streamUrl}`;
|
|
55
|
+
const blueskyUrl = `https://bsky.app/intent/compose?text=${encodeURIComponent(text)}`;
|
|
56
|
+
react_native_1.Linking.openURL(blueskyUrl);
|
|
57
|
+
onShare?.("share_bluesky", true);
|
|
58
|
+
}, [profile, getStreamUrl, onShare]);
|
|
59
|
+
// Share to Twitter/X
|
|
60
|
+
const shareToTwitter = (0, react_1.useCallback)(() => {
|
|
61
|
+
const streamUrl = getStreamUrl();
|
|
62
|
+
const text = profile
|
|
63
|
+
? `Check out @${profile.handle} live on Streamplace!`
|
|
64
|
+
: `Check out this stream on Streamplace!`;
|
|
65
|
+
const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(streamUrl)}`;
|
|
66
|
+
react_native_1.Linking.openURL(twitterUrl);
|
|
67
|
+
onShare?.("share_twitter", true);
|
|
68
|
+
}, [profile, getStreamUrl, onShare]);
|
|
69
|
+
// Native share (mobile)
|
|
70
|
+
const nativeShare = (0, react_1.useCallback)(async () => {
|
|
71
|
+
const streamUrl = getStreamUrl();
|
|
72
|
+
const text = profile
|
|
73
|
+
? `Check out @${profile.handle} live on Streamplace!`
|
|
74
|
+
: `Check out this stream on Streamplace!`;
|
|
75
|
+
if (react_native_1.Platform.OS === "web" && navigator.share) {
|
|
76
|
+
try {
|
|
77
|
+
await navigator.share({
|
|
78
|
+
title: "Streamplace",
|
|
79
|
+
text: text,
|
|
80
|
+
url: streamUrl,
|
|
81
|
+
});
|
|
82
|
+
onShare?.("share_native", true);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// User cancelled or error occurred
|
|
86
|
+
onShare?.("share_native", false);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}, [profile, getStreamUrl, onShare]);
|
|
90
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Share2, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsxs)(ui_1.ResponsiveDropdownMenuContent, { children: [(0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: "Share", children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: shareToBluesky, closeOnPress: true, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(bluesky_icon_1.BlueskyIcon, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Share to Bluesky" })] }) }), react_native_1.Platform.OS !== "web" || (navigator && navigator.share) ? ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: nativeShare, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Share2, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "More Options..." })] }) })) : null] }), (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: "Copy", children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => copyToClipboard(getStreamUrl(), "Stream link"), disabled: isCopying, closeOnPress: true, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Link2, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Copy Link" })] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => copyToClipboard(getEmbedCode(), "Embed code"), disabled: isCopying, closeOnPress: true, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Code, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Copy Embed Code" })] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { closeOnPress: true, onPress: () => copyToClipboard(getEmbedUrl(), "Embed URL"), disabled: isCopying, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Copy, { size: 20, color: theme_1.colors.gray[400] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Copy Embed URL" })] }) })] })] })] }));
|
|
91
|
+
}
|
|
@@ -103,7 +103,7 @@ exports.DialogFooter = (0, react_1.forwardRef)(({ children, direction = "row", j
|
|
|
103
103
|
exports.DialogFooter.displayName = "DialogFooter";
|
|
104
104
|
// Dialog Close Icon component (Lucide X)
|
|
105
105
|
const DialogCloseIcon = () => {
|
|
106
|
-
return (0, jsx_runtime_1.jsx)(ThemedX, { size: "md", variant: "
|
|
106
|
+
return (0, jsx_runtime_1.jsx)(ThemedX, { size: "md", variant: "default" });
|
|
107
107
|
};
|
|
108
108
|
// Create theme-aware styles
|
|
109
109
|
function createStyles(theme) {
|
|
@@ -23,7 +23,7 @@ exports.DropdownMenuBottomSheet = (0, react_1.forwardRef)(function DropdownMenuB
|
|
|
23
23
|
const sheetRef = (0, react_1.useRef)(null);
|
|
24
24
|
return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Portal, { hostName: portalHost, children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.default, { ref: sheetRef,
|
|
25
25
|
// why the heck is this 1-indexed
|
|
26
|
-
index: open ? 3 : -1, snapPoints: snapPoints, enablePanDownToClose: true, onClose: () => onOpenChange?.(false), style: [overlayStyle], backgroundStyle: [atoms_1.bg.black, atoms_1.a.radius.all.md, atoms_1.a.shadows.md, atoms_1.p[1]], handleIndicatorStyle: [
|
|
26
|
+
index: open ? 3 : -1, snapPoints: snapPoints, enablePanDownToClose: true, enableDynamicSizing: true, enableContentPanningGesture: false, backdropComponent: ({ style }) => ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [style, react_native_1.StyleSheet.absoluteFill], onPress: () => onOpenChange?.(false) })), onClose: () => onOpenChange?.(false), style: [overlayStyle], backgroundStyle: [atoms_1.bg.black, atoms_1.a.radius.all.md, atoms_1.a.shadows.md, atoms_1.p[1]], handleIndicatorStyle: [
|
|
27
27
|
atoms_1.a.sizes.width[12],
|
|
28
28
|
atoms_1.a.sizes.height[1],
|
|
29
29
|
atoms_1.bg.gray[500],
|
|
@@ -129,7 +129,7 @@ exports.DropdownMenuLabel = (0, react_1.forwardRef)(({ inset, ...props }, ref) =
|
|
|
129
129
|
], ...props }));
|
|
130
130
|
});
|
|
131
131
|
exports.DropdownMenuSeparator = (0, react_1.forwardRef)((props, ref) => {
|
|
132
|
-
return (
|
|
132
|
+
return (0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [{ height: 0.5 }, atoms_1.bg.gray[800]], ...props });
|
|
133
133
|
});
|
|
134
134
|
function DropdownMenuShortcut(props) {
|
|
135
135
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
@@ -141,11 +141,11 @@ function DropdownMenuShortcut(props) {
|
|
|
141
141
|
}
|
|
142
142
|
exports.DropdownMenuGroup = (0, react_1.forwardRef)((props, ref) => {
|
|
143
143
|
const { inset, title, children, ...rest } = props;
|
|
144
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.pt[2], inset
|
|
144
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [atoms_1.pt[2], inset && atoms_1.gap[2]], ref: ref, ...rest, children: [title && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [atoms_1.textColors.gray[400], atoms_1.pb[1], atoms_1.pl[2]], children: title })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
145
145
|
atoms_1.bg.gray[900],
|
|
146
|
-
react_native_1.Platform.OS === "web" ? atoms_1.px[2] : atoms_1.p[2],
|
|
147
|
-
atoms_1.gap[
|
|
148
|
-
{ borderRadius: atoms_1.borderRadius.lg
|
|
146
|
+
react_native_1.Platform.OS === "web" ? [atoms_1.px[2], atoms_1.py[1]] : atoms_1.p[2],
|
|
147
|
+
atoms_1.gap.all[1],
|
|
148
|
+
{ borderRadius: atoms_1.borderRadius.lg },
|
|
149
149
|
], children: children })] }));
|
|
150
150
|
});
|
|
151
151
|
exports.DropdownMenuInfo = (0, react_1.forwardRef)(({ description, ...props }, ref) => {
|
|
@@ -15,7 +15,9 @@ tslib_1.__exportStar(require("./icons"), exports);
|
|
|
15
15
|
tslib_1.__exportStar(require("./input"), exports);
|
|
16
16
|
tslib_1.__exportStar(require("./loader"), exports);
|
|
17
17
|
tslib_1.__exportStar(require("./resizeable"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./slider"), exports);
|
|
18
19
|
tslib_1.__exportStar(require("./text"), exports);
|
|
20
|
+
tslib_1.__exportStar(require("./textarea"), exports);
|
|
19
21
|
tslib_1.__exportStar(require("./toast"), exports);
|
|
20
22
|
tslib_1.__exportStar(require("./view"), exports);
|
|
21
23
|
// Component collections for easy importing
|
package/dist/hooks/index.js
CHANGED
|
@@ -9,5 +9,6 @@ tslib_1.__exportStar(require("./useKeyboardSlide"), exports);
|
|
|
9
9
|
tslib_1.__exportStar(require("./useLivestreamInfo"), exports);
|
|
10
10
|
tslib_1.__exportStar(require("./useOuterAndInnerDimensions"), exports);
|
|
11
11
|
tslib_1.__exportStar(require("./usePlayerDimensions"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./usePointerDevice"), exports);
|
|
12
13
|
tslib_1.__exportStar(require("./useSegmentDimensions"), exports);
|
|
13
14
|
tslib_1.__exportStar(require("./useSegmentTiming"), exports);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePointerDevice = usePointerDevice;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
/**
|
|
7
|
+
* Hook to detect if the device is primarily mouse-driven vs touch-driven
|
|
8
|
+
* Uses CSS media queries to detect hover and pointer capabilities
|
|
9
|
+
*/
|
|
10
|
+
function usePointerDevice() {
|
|
11
|
+
const [pointerDevice, setPointerDevice] = (0, react_1.useState)(() => {
|
|
12
|
+
// Default values for non-web platforms
|
|
13
|
+
if (react_native_1.Platform.OS !== "web") {
|
|
14
|
+
return {
|
|
15
|
+
hasHover: false,
|
|
16
|
+
hasFinePointer: false,
|
|
17
|
+
isMouseDriven: false,
|
|
18
|
+
isTouchDriven: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Initial web detection
|
|
22
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
23
|
+
const hasHover = window.matchMedia("(hover: hover)").matches;
|
|
24
|
+
const hasFinePointer = window.matchMedia("(pointer: fine)").matches;
|
|
25
|
+
return {
|
|
26
|
+
hasHover,
|
|
27
|
+
hasFinePointer,
|
|
28
|
+
isMouseDriven: hasHover && hasFinePointer,
|
|
29
|
+
isTouchDriven: !hasHover || !hasFinePointer,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Fallback for SSR or environments without matchMedia
|
|
33
|
+
return {
|
|
34
|
+
hasHover: false,
|
|
35
|
+
hasFinePointer: false,
|
|
36
|
+
isMouseDriven: false,
|
|
37
|
+
isTouchDriven: true,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
(0, react_1.useEffect)(() => {
|
|
41
|
+
// Only run on web platforms
|
|
42
|
+
if (react_native_1.Platform.OS !== "web" ||
|
|
43
|
+
typeof window === "undefined" ||
|
|
44
|
+
!window.matchMedia) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const hoverQuery = window.matchMedia("(hover: hover)");
|
|
48
|
+
const pointerQuery = window.matchMedia("(pointer: fine)");
|
|
49
|
+
const updatePointerDevice = () => {
|
|
50
|
+
const hasHover = hoverQuery.matches;
|
|
51
|
+
const hasFinePointer = pointerQuery.matches;
|
|
52
|
+
setPointerDevice({
|
|
53
|
+
hasHover,
|
|
54
|
+
hasFinePointer,
|
|
55
|
+
isMouseDriven: hasHover && hasFinePointer,
|
|
56
|
+
isTouchDriven: !hasHover || !hasFinePointer,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
// Set up listeners for media query changes
|
|
60
|
+
hoverQuery.addEventListener("change", updatePointerDevice);
|
|
61
|
+
pointerQuery.addEventListener("change", updatePointerDevice);
|
|
62
|
+
// Initial update
|
|
63
|
+
updatePointerDevice();
|
|
64
|
+
// Cleanup
|
|
65
|
+
return () => {
|
|
66
|
+
hoverQuery.removeEventListener("change", updatePointerDevice);
|
|
67
|
+
pointerQuery.removeEventListener("change", updatePointerDevice);
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
return pointerDevice;
|
|
71
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.VideoRetry = exports.zero = exports.ui = exports.PlayerUI = exports.Player = exports.usePlayerContext = exports.withPlayerProvider = exports.PlayerProvider = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
// barrel file :)
|
|
6
6
|
tslib_1.__exportStar(require("./livestream-provider"), exports);
|
|
@@ -18,8 +18,15 @@ Object.defineProperty(exports, "Player", { enumerable: true, get: function () {
|
|
|
18
18
|
Object.defineProperty(exports, "PlayerUI", { enumerable: true, get: function () { return player_1.PlayerUI; } });
|
|
19
19
|
exports.ui = tslib_1.__importStar(require("./components/ui"));
|
|
20
20
|
tslib_1.__exportStar(require("./components/ui"), exports);
|
|
21
|
-
exports.
|
|
22
|
-
exports.atoms = tslib_1.__importStar(require("./lib/theme/atoms"));
|
|
21
|
+
exports.zero = tslib_1.__importStar(require("./ui"));
|
|
23
22
|
tslib_1.__exportStar(require("./hooks"), exports);
|
|
23
|
+
// Theme system exports
|
|
24
|
+
tslib_1.__exportStar(require("./lib/theme"), exports);
|
|
24
25
|
tslib_1.__exportStar(require("./components/chat/chat"), exports);
|
|
25
26
|
tslib_1.__exportStar(require("./components/chat/chat-box"), exports);
|
|
27
|
+
tslib_1.__exportStar(require("./components/chat/system-message"), exports);
|
|
28
|
+
var video_retry_1 = require("./components/mobile-player/video-retry");
|
|
29
|
+
Object.defineProperty(exports, "VideoRetry", { enumerable: true, get: function () { return tslib_1.__importDefault(video_retry_1).default; } });
|
|
30
|
+
tslib_1.__exportStar(require("./lib/system-messages"), exports);
|
|
31
|
+
tslib_1.__exportStar(require("./components/share/sharesheet"), exports);
|
|
32
|
+
tslib_1.__exportStar(require("./components/keep-awake"), exports);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseSystemMessageMetadata = exports.getSystemMessageType = exports.isSystemMessage = exports.SystemMessages = exports.createSystemMessage = exports.SystemMessageType = void 0;
|
|
4
|
+
var SystemMessageType;
|
|
5
|
+
(function (SystemMessageType) {
|
|
6
|
+
SystemMessageType["stream_start"] = "stream_start";
|
|
7
|
+
SystemMessageType["stream_end"] = "stream_end";
|
|
8
|
+
SystemMessageType["notification"] = "notification";
|
|
9
|
+
})(SystemMessageType || (exports.SystemMessageType = SystemMessageType = {}));
|
|
10
|
+
/**
|
|
11
|
+
* Creates a system message with the proper structure
|
|
12
|
+
* @param type The type of system message
|
|
13
|
+
* @param text The message text
|
|
14
|
+
* @param metadata Optional metadata for the message
|
|
15
|
+
* @returns A properly formatted ChatMessageViewHydrated object
|
|
16
|
+
*/
|
|
17
|
+
const createSystemMessage = (type, text, metadata, date = new Date()) => {
|
|
18
|
+
const now = date;
|
|
19
|
+
return {
|
|
20
|
+
uri: `at://did:sys:system/place.stream.chat.message/${now.getTime()}`,
|
|
21
|
+
cid: `system-${now.getTime()}`,
|
|
22
|
+
author: {
|
|
23
|
+
did: "did:sys:system",
|
|
24
|
+
handle: type, // Use handle to specify the type of system message
|
|
25
|
+
},
|
|
26
|
+
record: {
|
|
27
|
+
text,
|
|
28
|
+
createdAt: now.toISOString(),
|
|
29
|
+
streamer: "system",
|
|
30
|
+
$type: "place.stream.chat.message",
|
|
31
|
+
},
|
|
32
|
+
indexedAt: now.toISOString(),
|
|
33
|
+
chatProfile: {
|
|
34
|
+
color: { red: 128, green: 128, blue: 128 }, // Gray color for system messages
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
exports.createSystemMessage = createSystemMessage;
|
|
39
|
+
/**
|
|
40
|
+
* System message factory functions for common scenarios
|
|
41
|
+
*/
|
|
42
|
+
exports.SystemMessages = {
|
|
43
|
+
streamStart: (streamerName) => (0, exports.createSystemMessage)(SystemMessageType.stream_start, `Now streaming - ${streamerName}`, {
|
|
44
|
+
streamerName,
|
|
45
|
+
}),
|
|
46
|
+
// technically, streams can't 'end' on Streamplace
|
|
47
|
+
// possibly we could use deleting or editing streams (`endedAt` param) for this?
|
|
48
|
+
streamEnd: (duration) => (0, exports.createSystemMessage)(SystemMessageType.stream_end, duration ? `Stream has ended. Duration: ${duration}` : "Stream has ended", { duration }),
|
|
49
|
+
notification: (message) => (0, exports.createSystemMessage)(SystemMessageType.notification, message),
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Checks if a message is a system message
|
|
53
|
+
* @param message The message to check
|
|
54
|
+
* @returns True if the message is a system message
|
|
55
|
+
*/
|
|
56
|
+
const isSystemMessage = (message) => {
|
|
57
|
+
return message.author.did === "did:sys:system";
|
|
58
|
+
};
|
|
59
|
+
exports.isSystemMessage = isSystemMessage;
|
|
60
|
+
/**
|
|
61
|
+
* Gets the system message type from a message
|
|
62
|
+
* @param message The message to check
|
|
63
|
+
* @returns The system message type or null if not a system message
|
|
64
|
+
*/
|
|
65
|
+
const getSystemMessageType = (message) => {
|
|
66
|
+
if (!(0, exports.isSystemMessage)(message)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return message.author.handle;
|
|
70
|
+
};
|
|
71
|
+
exports.getSystemMessageType = getSystemMessageType;
|
|
72
|
+
/**
|
|
73
|
+
* Parses metadata from a system message based on its type
|
|
74
|
+
* @param message The system message to parse
|
|
75
|
+
* @returns The parsed metadata
|
|
76
|
+
*/
|
|
77
|
+
const parseSystemMessageMetadata = (message) => {
|
|
78
|
+
const metadata = {};
|
|
79
|
+
const type = (0, exports.getSystemMessageType)(message);
|
|
80
|
+
const text = message.record.text;
|
|
81
|
+
if (!type)
|
|
82
|
+
return metadata;
|
|
83
|
+
switch (type) {
|
|
84
|
+
case "stream_end": {
|
|
85
|
+
const durationMatch = text.match(/Duration:\s*(\d+:\d+(?::\d+)?)/);
|
|
86
|
+
if (durationMatch) {
|
|
87
|
+
metadata.duration = durationMatch[1];
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "stream_start": {
|
|
92
|
+
const streamerMatch = text.match(/^(.+?)\s+is now live!/);
|
|
93
|
+
if (streamerMatch) {
|
|
94
|
+
metadata.streamerName = streamerMatch[1];
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return metadata;
|
|
100
|
+
};
|
|
101
|
+
exports.parseSystemMessageMetadata = parseSystemMessageMetadata;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.reduceChat = exports.reduceChatIncremental = exports.useCreateChatMessage = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
|
|
3
|
+
exports.reduceChat = exports.useReportChatMessage = exports.useSubmitReport = exports.reduceChatIncremental = exports.useCreateChatMessage = exports.useAddPendingHide = exports.usePendingHides = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
|
|
4
4
|
const api_1 = require("@atproto/api");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const streamplace_store_1 = require("../streamplace-store");
|
|
@@ -15,6 +15,23 @@ const useSetReplyToMessage = () => {
|
|
|
15
15
|
}, [store]);
|
|
16
16
|
};
|
|
17
17
|
exports.useSetReplyToMessage = useSetReplyToMessage;
|
|
18
|
+
const usePendingHides = () => (0, livestream_store_1.useLivestreamStore)((state) => state.pendingHides);
|
|
19
|
+
exports.usePendingHides = usePendingHides;
|
|
20
|
+
const useAddPendingHide = () => {
|
|
21
|
+
const store = (0, livestream_store_1.getStoreFromContext)();
|
|
22
|
+
return (0, react_1.useCallback)((messageUri) => {
|
|
23
|
+
const state = store.getState();
|
|
24
|
+
if (!state.pendingHides.includes(messageUri)) {
|
|
25
|
+
const newPendingHides = [...state.pendingHides, messageUri];
|
|
26
|
+
const newState = (0, exports.reduceChat)(state, [], [], [messageUri]);
|
|
27
|
+
store.setState({
|
|
28
|
+
...newState,
|
|
29
|
+
pendingHides: newPendingHides,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, [store]);
|
|
33
|
+
};
|
|
34
|
+
exports.useAddPendingHide = useAddPendingHide;
|
|
18
35
|
const useCreateChatMessage = () => {
|
|
19
36
|
const pdsAgent = (0, xrpc_1.usePDSAgent)();
|
|
20
37
|
const store = (0, livestream_store_1.getStoreFromContext)();
|
|
@@ -63,7 +80,7 @@ const useCreateChatMessage = () => {
|
|
|
63
80
|
indexedAt: new Date().toISOString(),
|
|
64
81
|
chatProfile: chatProfile || undefined,
|
|
65
82
|
};
|
|
66
|
-
state = (0, exports.reduceChat)(state, [localChat], []);
|
|
83
|
+
state = (0, exports.reduceChat)(state, [localChat], [], []);
|
|
67
84
|
store.setState(state);
|
|
68
85
|
await pdsAgent.com.atproto.repo.createRecord({
|
|
69
86
|
repo: userDID,
|
|
@@ -79,7 +96,9 @@ const buildSortedChatList = (chatIndex, existingChatList, newMessages, removedKe
|
|
|
79
96
|
const bTime = parseInt(b.split("-")[0], 10);
|
|
80
97
|
return bTime - aTime;
|
|
81
98
|
});
|
|
82
|
-
return sortedKeys
|
|
99
|
+
return sortedKeys
|
|
100
|
+
.map((key) => chatIndex[key])
|
|
101
|
+
.filter((msg) => !removedKeys.has(msg.uri));
|
|
83
102
|
};
|
|
84
103
|
const profileIsDifferent = (newProfile, oldProfile) => {
|
|
85
104
|
if (!oldProfile) {
|
|
@@ -99,14 +118,25 @@ const profileIsDifferent = (newProfile, oldProfile) => {
|
|
|
99
118
|
const { red: oldRed, green: oldGreen, blue: oldBlue } = oldProfile.color;
|
|
100
119
|
return newRed !== oldRed || newGreen !== oldGreen || newBlue !== oldBlue;
|
|
101
120
|
};
|
|
102
|
-
const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
103
|
-
if (newMessages.length === 0 &&
|
|
121
|
+
const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
|
|
122
|
+
if (newMessages.length === 0 &&
|
|
123
|
+
blocks.length === 0 &&
|
|
124
|
+
hideUris.length === 0) {
|
|
104
125
|
return state;
|
|
105
126
|
}
|
|
106
127
|
const newChatIndex = { ...state.chatIndex };
|
|
107
128
|
const newAuthors = { ...state.authors };
|
|
108
129
|
let hasChanges = false;
|
|
109
130
|
const removedKeys = new Set();
|
|
131
|
+
console.log("newMessages", newMessages);
|
|
132
|
+
for (const msg of newMessages) {
|
|
133
|
+
if (msg.deleted) {
|
|
134
|
+
hasChanges = true;
|
|
135
|
+
console.log("deleted", msg.uri);
|
|
136
|
+
removedKeys.add(msg.uri);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
newMessages = newMessages.filter((msg) => msg.deleted !== true);
|
|
110
140
|
// handle blocks
|
|
111
141
|
if (blocks.length > 0) {
|
|
112
142
|
const blockedDIDs = new Set(blocks.map((block) => block.record.subject));
|
|
@@ -118,13 +148,26 @@ const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
|
118
148
|
}
|
|
119
149
|
}
|
|
120
150
|
}
|
|
151
|
+
if (hideUris.length > 0) {
|
|
152
|
+
for (const [key, message] of Object.entries(newChatIndex)) {
|
|
153
|
+
if (hideUris.includes(message.uri)) {
|
|
154
|
+
delete newChatIndex[key];
|
|
155
|
+
removedKeys.add(key);
|
|
156
|
+
hasChanges = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
121
160
|
const messagesToAdd = [];
|
|
122
161
|
for (const message of newMessages) {
|
|
162
|
+
// don't worry about messages that will be hidden
|
|
163
|
+
if (state.pendingHides.includes(message.uri)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
123
166
|
const date = new Date(message.record.createdAt);
|
|
124
167
|
const key = `${date.getTime()}-${message.uri}`;
|
|
125
168
|
// only change the ref if the profile is different to avoid re-renders elsewhere
|
|
126
|
-
if (profileIsDifferent(message.chatProfile, newAuthors[message.author.
|
|
127
|
-
newAuthors[message.author.
|
|
169
|
+
if (profileIsDifferent(message.chatProfile, newAuthors[message.author.did])) {
|
|
170
|
+
newAuthors[message.author.did] = message.chatProfile;
|
|
128
171
|
}
|
|
129
172
|
// skip messages we already have
|
|
130
173
|
if (newChatIndex[key] && newChatIndex[key].uri === message.uri) {
|
|
@@ -156,17 +199,20 @@ const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
|
156
199
|
const parentMsgKey = Object.keys(newChatIndex).find((k) => newChatIndex[k].uri === parentUri);
|
|
157
200
|
if (parentMsgKey) {
|
|
158
201
|
const parentMsg = newChatIndex[parentMsgKey];
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
// Don't allow replies to system messages
|
|
203
|
+
if (parentMsg.author.did !== "did:sys:system") {
|
|
204
|
+
processedMessage = {
|
|
205
|
+
...message,
|
|
206
|
+
replyTo: {
|
|
207
|
+
cid: parentMsg.cid,
|
|
208
|
+
uri: parentMsg.uri,
|
|
209
|
+
author: parentMsg.author,
|
|
210
|
+
record: parentMsg.record,
|
|
211
|
+
chatProfile: parentMsg.chatProfile,
|
|
212
|
+
indexedAt: parentMsg.indexedAt,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
170
216
|
}
|
|
171
217
|
}
|
|
172
218
|
}
|
|
@@ -183,11 +229,58 @@ const reduceChatIncremental = (state, newMessages, blocks) => {
|
|
|
183
229
|
}
|
|
184
230
|
// Build the new sorted chat list efficiently
|
|
185
231
|
const newChatList = buildSortedChatList(newChatIndex, state.chat, messagesToAdd, removedKeys);
|
|
232
|
+
// Clean up pendingHides - remove URIs that we've now processed
|
|
233
|
+
let newPendingHides = state.pendingHides;
|
|
234
|
+
if (hideUris.length > 0) {
|
|
235
|
+
newPendingHides = state.pendingHides.filter((uri) => !hideUris.includes(uri));
|
|
236
|
+
}
|
|
186
237
|
return {
|
|
187
238
|
...state,
|
|
239
|
+
authors: newAuthors,
|
|
188
240
|
chatIndex: newChatIndex,
|
|
189
241
|
chat: newChatList,
|
|
242
|
+
pendingHides: newPendingHides,
|
|
190
243
|
};
|
|
191
244
|
};
|
|
192
245
|
exports.reduceChatIncremental = reduceChatIncremental;
|
|
246
|
+
const useSubmitReport = () => {
|
|
247
|
+
const pdsAgent = (0, xrpc_1.usePDSAgent)();
|
|
248
|
+
const userDID = (0, streamplace_store_1.useDID)();
|
|
249
|
+
return (0, react_1.useCallback)(async (subject, reasonType, reason,
|
|
250
|
+
// no clue about this
|
|
251
|
+
moderationSvcDid = "did:web:stream.place") => {
|
|
252
|
+
if (!pdsAgent || !userDID) {
|
|
253
|
+
throw new Error("No PDS agent or user DID found");
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const response = await pdsAgent.com.atproto.moderation.createReport({
|
|
257
|
+
reasonType,
|
|
258
|
+
reason,
|
|
259
|
+
subject: subject,
|
|
260
|
+
}, {
|
|
261
|
+
headers: {
|
|
262
|
+
// "atproto-proxy": `${userDID}#atproto_labeler`,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
return response;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error("Failed to submit report:", error);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}, [pdsAgent, userDID]);
|
|
272
|
+
};
|
|
273
|
+
exports.useSubmitReport = useSubmitReport;
|
|
274
|
+
const useReportChatMessage = () => {
|
|
275
|
+
const submitReport = (0, exports.useSubmitReport)();
|
|
276
|
+
return (0, react_1.useCallback)(async (message, reasonType, reason) => {
|
|
277
|
+
const reportSubject = {
|
|
278
|
+
$type: "com.atproto.repo.strongRef",
|
|
279
|
+
uri: message.uri,
|
|
280
|
+
cid: message.cid,
|
|
281
|
+
};
|
|
282
|
+
return await submitReport(reportSubject, reasonType, reason);
|
|
283
|
+
}, [submitReport]);
|
|
284
|
+
};
|
|
285
|
+
exports.useReportChatMessage = useReportChatMessage;
|
|
193
286
|
exports.reduceChat = exports.reduceChatIncremental;
|
|
@@ -14,12 +14,15 @@ const makeLivestreamStore = () => {
|
|
|
14
14
|
chat: [],
|
|
15
15
|
livestream: null,
|
|
16
16
|
viewers: null,
|
|
17
|
+
pendingHides: [],
|
|
17
18
|
segment: null,
|
|
18
19
|
renditions: [],
|
|
19
20
|
replyToMessage: null,
|
|
20
21
|
streamKey: null,
|
|
21
22
|
setStreamKey: (sk) => set({ streamKey: sk }),
|
|
22
23
|
authors: {},
|
|
24
|
+
recentSegments: [],
|
|
25
|
+
problems: [],
|
|
23
26
|
}));
|
|
24
27
|
};
|
|
25
28
|
exports.makeLivestreamStore = makeLivestreamStore;
|