@streamplace/components 0.7.34 → 0.7.35
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/dashboard/header.js +16 -2
- package/dist/components/dashboard/problems.js +29 -28
- package/dist/components/mobile-player/ui/viewer-context-menu.js +44 -1
- package/dist/components/ui/button.js +9 -9
- package/dist/components/ui/dropdown.js +15 -10
- package/dist/components/ui/icons.js +6 -0
- package/dist/components/ui/primitives/button.js +0 -7
- package/dist/lib/theme/tokens.js +9 -0
- package/dist/streamplace-store/graph.js +195 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +2 -2
- package/src/components/dashboard/header.tsx +37 -3
- package/src/components/dashboard/index.tsx +1 -1
- package/src/components/dashboard/problems.tsx +57 -46
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +100 -1
- package/src/components/ui/button.tsx +10 -13
- package/src/components/ui/dropdown.tsx +27 -13
- package/src/components/ui/icons.tsx +14 -0
- package/src/components/ui/primitives/button.tsx +0 -7
- package/src/lib/theme/tokens.ts +9 -0
- package/src/streamplace-store/graph.tsx +232 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -59,7 +59,7 @@ function StatusIndicator({ status, isLive }) {
|
|
|
59
59
|
!isLive && text.gray[400],
|
|
60
60
|
], children: getStatusText() })] }));
|
|
61
61
|
}
|
|
62
|
-
function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00:00:00", bitrate = "0 mbps", timeBetweenSegments = 0, connectionStatus = "offline", }) {
|
|
62
|
+
function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00:00:00", bitrate = "0 mbps", timeBetweenSegments = 0, connectionStatus = "offline", problemsCount = 0, onProblemsPress, }) {
|
|
63
63
|
const getConnectionQuality = () => {
|
|
64
64
|
if (timeBetweenSegments <= 1500)
|
|
65
65
|
return "good";
|
|
@@ -76,5 +76,19 @@ function Header({ isLive, streamTitle = "Live Stream", viewers = 0, uptime = "00
|
|
|
76
76
|
bg.neutral[900],
|
|
77
77
|
borders.width.thin,
|
|
78
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.
|
|
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.jsxs)(react_native_1.View, { style: [layout.flex.row, layout.flex.alignCenter, gap.all[3]], children: [(0, jsx_runtime_1.jsx)(StatusIndicator, { status: connectionStatus, isLive: isLive }), problemsCount > 0 && ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onProblemsPress, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
80
|
+
layout.flex.row,
|
|
81
|
+
layout.flex.alignCenter,
|
|
82
|
+
gap.all[1],
|
|
83
|
+
px[2],
|
|
84
|
+
py[1],
|
|
85
|
+
r.md,
|
|
86
|
+
bg.orange[900],
|
|
87
|
+
borders.width.thin,
|
|
88
|
+
borders.color.orange[700],
|
|
89
|
+
{ marginVertical: -8 },
|
|
90
|
+
], children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.AlertCircle, { size: 14, color: "#fb923c" }), (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [
|
|
91
|
+
text.orange[400],
|
|
92
|
+
{ fontSize: 11, fontWeight: "600" },
|
|
93
|
+
], children: [problemsCount, " ", problemsCount === 1 ? "Issue" : "Issues"] })] }) }))] })] }) }), (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
94
|
}
|
|
@@ -8,47 +8,49 @@ const react_1 = require("react");
|
|
|
8
8
|
const react_native_1 = require("react-native");
|
|
9
9
|
const livestream_store_1 = require("../../livestream-store");
|
|
10
10
|
const zero = tslib_1.__importStar(require("../../ui"));
|
|
11
|
+
const ui_1 = require("../ui");
|
|
11
12
|
const { bg, r, borders, p, text, layout, gap } = zero;
|
|
13
|
+
const getIcon = (severity) => {
|
|
14
|
+
switch (severity) {
|
|
15
|
+
case "error":
|
|
16
|
+
return (0, jsx_runtime_1.jsx)(lucide_react_native_1.CircleX, { size: 24, color: "white" });
|
|
17
|
+
case "warning":
|
|
18
|
+
return (0, jsx_runtime_1.jsx)(lucide_react_native_1.CircleAlert, { size: 24, color: "white" });
|
|
19
|
+
case "info":
|
|
20
|
+
return (0, jsx_runtime_1.jsx)(lucide_react_native_1.Info, { size: 24, color: "white" });
|
|
21
|
+
default:
|
|
22
|
+
return (0, jsx_runtime_1.jsx)(lucide_react_native_1.Sparkle, { size: 24, color: "white" });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
12
25
|
const Problems = ({ probs, onIgnore, }) => {
|
|
13
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[4]], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [gap.all[2]], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { size: "2xl", style: [text.white, { fontWeight: "600" }], children: "Optimize Your Stream" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.gray[300]], children: "We've found a few things that could improve your stream's reliability." })] }), probs.map((p) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
14
27
|
gap.all[2],
|
|
15
28
|
layout.flex.row,
|
|
16
29
|
layout.flex.alignCenter,
|
|
17
30
|
{ gap: 8, alignItems: "flex-start" },
|
|
18
|
-
], children: [(0, jsx_runtime_1.jsx)(react_native_1.
|
|
19
|
-
r.
|
|
20
|
-
p[
|
|
31
|
+
], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
32
|
+
zero.r.full,
|
|
33
|
+
zero.p[1],
|
|
21
34
|
{
|
|
22
|
-
width: 82,
|
|
23
|
-
textAlign: "center",
|
|
24
35
|
backgroundColor: p.severity === "error"
|
|
25
36
|
? "#7f1d1d"
|
|
26
37
|
: p.severity === "warning"
|
|
27
38
|
? "#7c2d12"
|
|
28
39
|
: "#1e3a8a",
|
|
29
|
-
color: "white",
|
|
30
|
-
fontSize: 12,
|
|
31
40
|
},
|
|
32
|
-
], children: p.severity }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [{ flex: 1 }, gap.all[1]], children: [(0, jsx_runtime_1.jsx)(
|
|
41
|
+
], children: getIcon(p.severity) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [{ flex: 1 }, gap.all[1]], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.white, { fontWeight: "600" }], children: p.code }), (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.gray[400], { fontSize: 14 }], children: p.message }), p.link && ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => p.link && react_native_1.Linking.openURL(p.link), children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
33
42
|
layout.flex.row,
|
|
34
43
|
layout.flex.alignCenter,
|
|
35
44
|
gap.all[2],
|
|
36
|
-
], children: [(0, jsx_runtime_1.jsx)(
|
|
37
|
-
bg.blue[600],
|
|
38
|
-
r.md,
|
|
39
|
-
p[3],
|
|
40
|
-
layout.flex.center,
|
|
41
|
-
{ marginTop: 16 },
|
|
42
|
-
], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [text.white, { fontWeight: "600" }], children: "Ignore" }) })] }));
|
|
45
|
+
], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { style: [{ color: "#3b82f6", fontSize: 14 }], children: "Learn More" }), (0, jsx_runtime_1.jsx)(lucide_react_native_1.ExternalLink, { size: 12, color: "#3b82f6" })] }) }))] })] }) }, p.message))), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [layout.flex.row, layout.flex.justify.end], children: (0, jsx_runtime_1.jsx)(ui_1.Button, { onPress: onIgnore, variant: "secondary", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [text.white, { fontWeight: "600" }], children: "Acknowledge" }) }) })] }));
|
|
43
46
|
};
|
|
44
|
-
|
|
47
|
+
exports.ProblemsWrapper = (0, react_1.forwardRef)(({ children }, ref) => {
|
|
45
48
|
const problems = (0, livestream_store_1.useLivestreamStore)((x) => x.problems);
|
|
46
49
|
const [dismiss, setDismiss] = (0, react_1.useState)(false);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
], children: [children, problems.length > 0 && !dismiss && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
50
|
+
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
51
|
+
setDismiss,
|
|
52
|
+
}));
|
|
53
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [children, problems.length > 0 && !dismiss && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
52
54
|
{
|
|
53
55
|
position: "absolute",
|
|
54
56
|
top: 0,
|
|
@@ -60,15 +62,14 @@ const ProblemsWrapper = ({ children, }) => {
|
|
|
60
62
|
},
|
|
61
63
|
layout.flex.center,
|
|
62
64
|
{ justifyContent: "flex-start" },
|
|
63
|
-
p[
|
|
65
|
+
p[12],
|
|
64
66
|
], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
65
|
-
bg.
|
|
66
|
-
borders.color.
|
|
67
|
+
bg.neutral[900],
|
|
68
|
+
borders.color.neutral[700],
|
|
67
69
|
borders.width.thin,
|
|
68
70
|
r.lg,
|
|
69
|
-
p[
|
|
71
|
+
p[8],
|
|
70
72
|
{ maxWidth: 700, width: "100%" },
|
|
71
73
|
], children: (0, jsx_runtime_1.jsx)(Problems, { probs: problems, onIgnore: () => setDismiss(true) }) }) }))] }));
|
|
72
|
-
};
|
|
73
|
-
exports.ProblemsWrapper = ProblemsWrapper;
|
|
74
|
+
});
|
|
74
75
|
exports.default = Problems;
|
|
@@ -6,9 +6,11 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
6
6
|
const dropdown_menu_1 = require("@rn-primitives/dropdown-menu");
|
|
7
7
|
const lucide_react_native_1 = require("lucide-react-native");
|
|
8
8
|
const react_native_1 = require("react-native");
|
|
9
|
+
const __1 = require("../../..");
|
|
9
10
|
const theme_1 = require("../../../lib/theme");
|
|
10
11
|
const livestream_store_1 = require("../../../livestream-store");
|
|
11
12
|
const player_store_1 = require("../../../player-store/");
|
|
13
|
+
const graph_1 = require("../../../streamplace-store/graph");
|
|
12
14
|
const ui_1 = require("../../ui");
|
|
13
15
|
function ContextMenu({ dropdownPortalContainer, }) {
|
|
14
16
|
const quality = (0, player_store_1.usePlayerStore)((x) => x.selectedRendition);
|
|
@@ -21,6 +23,10 @@ function ContextMenu({ dropdownPortalContainer, }) {
|
|
|
21
23
|
const livestream = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
|
|
22
24
|
const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
|
|
23
25
|
const setReportSubject = (0, player_store_1.usePlayerStore)((x) => x.setReportSubject);
|
|
26
|
+
const { profile } = (0, __1.useLivestreamInfo)();
|
|
27
|
+
const avatars = (0, __1.useAvatars)(profile?.did ? [profile?.did] : []);
|
|
28
|
+
const ls = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
|
|
29
|
+
let graphManager = (0, graph_1.useGraphManager)(profile?.did);
|
|
24
30
|
const lowLatency = protocol === "webrtc";
|
|
25
31
|
const setLowLatency = (value) => {
|
|
26
32
|
setProtocol(value ? player_store_1.PlayerProtocol.WEBRTC : player_store_1.PlayerProtocol.HLS);
|
|
@@ -33,7 +39,44 @@ function ContextMenu({ dropdownPortalContainer, }) {
|
|
|
33
39
|
const DropdownMenuContent = isMobile
|
|
34
40
|
? ui_1.ResponsiveDropdownMenuContent
|
|
35
41
|
: ui_1.DropdownMenuContentWithoutPortal;
|
|
36
|
-
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.Menu, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsx)(Portal, { container: dropdownPortalContainer, children: (0, jsx_runtime_1.jsxs)(DropdownMenuContent, { side: "top", align: "end", children: [
|
|
42
|
+
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.Menu, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsx)(Portal, { container: dropdownPortalContainer, children: (0, jsx_runtime_1.jsxs)(DropdownMenuContent, { side: "top", align: "end", children: [react_native_1.Platform.OS !== "web" && ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuGroup, { title: "Streamer", children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
43
|
+
__1.zero.layout.flex.row,
|
|
44
|
+
__1.zero.layout.flex.center,
|
|
45
|
+
__1.zero.gap.all[3],
|
|
46
|
+
{ flex: 1, minWidth: 0 },
|
|
47
|
+
], children: [profile?.did && avatars[profile?.did]?.avatar && ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: {
|
|
48
|
+
uri: avatars[profile?.did]?.avatar,
|
|
49
|
+
}, style: { width: 42, height: 42, borderRadius: 999 }, resizeMode: "cover" }, "avatar")), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1, minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
50
|
+
__1.zero.layout.flex.row,
|
|
51
|
+
__1.zero.layout.flex.alignCenter,
|
|
52
|
+
__1.zero.gap.all[2],
|
|
53
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => {
|
|
54
|
+
if (profile?.handle) {
|
|
55
|
+
const url = `https://bsky.app/profile/${profile.handle}`;
|
|
56
|
+
react_native_1.Linking.openURL(url);
|
|
57
|
+
}
|
|
58
|
+
}, children: (0, jsx_runtime_1.jsxs)(ui_1.Text, { children: ["@", profile?.handle || "user"] }) }) }), (0, jsx_runtime_1.jsx)(ui_1.Text, { color: "muted", size: "sm", numberOfLines: 2, ellipsizeMode: "tail", children: ls?.record.title || "Stream Title" })] })] }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { disabled: graphManager.isLoading || !profile?.did, onPress: async () => {
|
|
59
|
+
try {
|
|
60
|
+
if (graphManager.isFollowing) {
|
|
61
|
+
await graphManager.unfollow();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await graphManager.follow();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error("Follow/unfollow error:", err);
|
|
69
|
+
}
|
|
70
|
+
}, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { color: graphManager.isFollowing ? "destructive" : "default", children: graphManager.isLoading
|
|
71
|
+
? "Loading..."
|
|
72
|
+
: graphManager.isFollowing
|
|
73
|
+
? "Unfollow"
|
|
74
|
+
: "Follow" }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuSeparator, {}), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
|
|
75
|
+
if (profile?.handle) {
|
|
76
|
+
const url = `https://bsky.app/profile/${profile.handle}`;
|
|
77
|
+
react_native_1.Linking.openURL(url);
|
|
78
|
+
}
|
|
79
|
+
}, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "View Profile on Bluesky" }) })] })), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: r.name }) })))] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Show Debug Info" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Report", children: (0, jsx_runtime_1.jsx)(ReportButton, { livestream: livestream, setReportModalOpen: setReportModalOpen, setReportSubject: setReportSubject }) })] }) })] }));
|
|
37
80
|
}
|
|
38
81
|
function ReportButton({ livestream, setReportModalOpen, setReportSubject, }) {
|
|
39
82
|
const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
|
|
@@ -85,7 +85,7 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
|
|
|
85
85
|
{ borderRadius: zero.borderRadius.md },
|
|
86
86
|
],
|
|
87
87
|
inner: { gap: 4 },
|
|
88
|
-
text:
|
|
88
|
+
text: zero.typography.universal.sm,
|
|
89
89
|
};
|
|
90
90
|
case "lg":
|
|
91
91
|
return {
|
|
@@ -94,8 +94,8 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
|
|
|
94
94
|
zero.py[3],
|
|
95
95
|
{ borderRadius: zero.borderRadius.md },
|
|
96
96
|
],
|
|
97
|
-
inner: { gap:
|
|
98
|
-
text:
|
|
97
|
+
inner: { gap: 12 },
|
|
98
|
+
text: zero.typography.universal.lg,
|
|
99
99
|
};
|
|
100
100
|
case "xl":
|
|
101
101
|
return {
|
|
@@ -105,17 +105,17 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
|
|
|
105
105
|
{ borderRadius: zero.borderRadius.lg },
|
|
106
106
|
],
|
|
107
107
|
inner: { gap: 12 },
|
|
108
|
-
text:
|
|
108
|
+
text: zero.typography.universal.xl,
|
|
109
109
|
};
|
|
110
110
|
case "pill":
|
|
111
111
|
return {
|
|
112
112
|
button: [
|
|
113
|
-
zero.px[
|
|
114
|
-
zero.py[
|
|
113
|
+
zero.px[2],
|
|
114
|
+
zero.py[1],
|
|
115
115
|
{ borderRadius: zero.borderRadius.full },
|
|
116
116
|
],
|
|
117
117
|
inner: { gap: 4 },
|
|
118
|
-
text:
|
|
118
|
+
text: zero.typography.universal.xs,
|
|
119
119
|
};
|
|
120
120
|
case "md":
|
|
121
121
|
default:
|
|
@@ -126,7 +126,7 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
|
|
|
126
126
|
{ borderRadius: zero.borderRadius.md },
|
|
127
127
|
],
|
|
128
128
|
inner: { gap: 6 },
|
|
129
|
-
text:
|
|
129
|
+
text: zero.typography.universal.sm,
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
}, [size, zt]);
|
|
@@ -170,6 +170,6 @@ exports.Button = (0, react_1.forwardRef)(({ variant = "primary", size = "md", ch
|
|
|
170
170
|
return icons.color.default;
|
|
171
171
|
}
|
|
172
172
|
}, [variant, icons]);
|
|
173
|
-
return ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Root, { ref: ref, disabled: disabled || loading, style: [buttonStyle, sizeStyles.button, style], ...props, children: (0, jsx_runtime_1.jsxs)(button_1.ButtonPrimitive.Content, { style: sizeStyles.inner, children: [loading && !leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left",
|
|
173
|
+
return ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Root, { ref: ref, disabled: disabled || loading, style: [buttonStyle, sizeStyles.button, style], ...props, children: (0, jsx_runtime_1.jsxs)(button_1.ButtonPrimitive.Content, { style: sizeStyles.inner, children: [loading && !leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : leftIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "left", children: leftIcon })) : null, (0, jsx_runtime_1.jsx)(text_1.TextPrimitive.Root, { style: [textStyle, sizeStyles.text], children: loading && loadingText ? loadingText : children }), loading && rightIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "right", children: (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: spinnerSize, color: spinnerColor }) })) : rightIcon ? ((0, jsx_runtime_1.jsx)(button_1.ButtonPrimitive.Icon, { position: "right", style: { width: iconSize, height: iconSize }, children: rightIcon })) : null] }) }));
|
|
174
174
|
});
|
|
175
175
|
exports.Button.displayName = "Button";
|
|
@@ -12,6 +12,7 @@ const react_native_1 = require("react-native");
|
|
|
12
12
|
const atoms_1 = require("../../lib/theme/atoms");
|
|
13
13
|
const ui_1 = require("../../ui");
|
|
14
14
|
const text_1 = require("./primitives/text");
|
|
15
|
+
const text_2 = require("./text");
|
|
15
16
|
exports.DropdownMenu = DropdownMenuPrimitive.Root;
|
|
16
17
|
exports.DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
17
18
|
exports.DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
@@ -29,7 +30,7 @@ exports.DropdownMenuBottomSheet = (0, react_1.forwardRef)(function DropdownMenuB
|
|
|
29
30
|
atoms_1.a.sizes.width[12],
|
|
30
31
|
atoms_1.a.sizes.height[1],
|
|
31
32
|
zt.bg.mutedForeground,
|
|
32
|
-
], children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheetView, { style: [atoms_1.px[
|
|
33
|
+
], children: (0, jsx_runtime_1.jsx)(bottom_sheet_1.BottomSheetView, { style: [atoms_1.px[4]], children: typeof children === "function"
|
|
33
34
|
? children({ pressed: true })
|
|
34
35
|
: children }) }) }));
|
|
35
36
|
});
|
|
@@ -123,9 +124,7 @@ exports.DropdownMenuItem = (0, react_1.forwardRef)(({ inset, disabled, style, ch
|
|
|
123
124
|
atoms_1.py[1],
|
|
124
125
|
atoms_1.pl[2],
|
|
125
126
|
atoms_1.pr[2],
|
|
126
|
-
], children: typeof children === "function"
|
|
127
|
-
? children({ pressed: true })
|
|
128
|
-
: children }) }) }));
|
|
127
|
+
], children: typeof children === "function" ? (children({ pressed: true })) : typeof children === "string" ? ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [inset && atoms_1.gap[2], disabled && { opacity: 0.5 }], children: children })) : (children) }) }) }));
|
|
129
128
|
});
|
|
130
129
|
exports.DropdownMenuCheckboxItem = (0, react_1.forwardRef)(({ children, checked, ...props }, ref) => {
|
|
131
130
|
const { theme } = (0, ui_1.useTheme)();
|
|
@@ -152,21 +151,27 @@ exports.DropdownMenuRadioItem = (0, react_1.forwardRef)(({ children, ...props },
|
|
|
152
151
|
});
|
|
153
152
|
exports.DropdownMenuLabel = (0, react_1.forwardRef)(({ inset, ...props }, ref) => {
|
|
154
153
|
const { theme } = (0, ui_1.useTheme)();
|
|
155
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
154
|
+
return ((0, jsx_runtime_1.jsx)(text_2.Text, { ref: ref, style: [
|
|
156
155
|
atoms_1.px[2],
|
|
157
156
|
atoms_1.py[2],
|
|
158
157
|
{ color: theme.colors.textMuted },
|
|
159
158
|
atoms_1.a.fontSize.base,
|
|
160
|
-
inset && atoms_1.gap[2],
|
|
159
|
+
(inset && atoms_1.gap[2]),
|
|
161
160
|
], ...props }));
|
|
162
161
|
});
|
|
163
162
|
exports.DropdownMenuSeparator = (0, react_1.forwardRef)((props, ref) => {
|
|
164
163
|
const { theme } = (0, ui_1.useTheme)();
|
|
165
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [
|
|
164
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: ref, style: [
|
|
165
|
+
{
|
|
166
|
+
borderBottomWidth: 1,
|
|
167
|
+
borderBottomColor: theme.colors.border,
|
|
168
|
+
marginVertical: -0.5,
|
|
169
|
+
},
|
|
170
|
+
], ...props }));
|
|
166
171
|
});
|
|
167
172
|
function DropdownMenuShortcut(props) {
|
|
168
173
|
const { theme } = (0, ui_1.useTheme)();
|
|
169
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
174
|
+
return ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [
|
|
170
175
|
atoms_1.ml.auto,
|
|
171
176
|
{ color: theme.colors.textMuted },
|
|
172
177
|
atoms_1.a.fontSize.sm,
|
|
@@ -176,7 +181,7 @@ function DropdownMenuShortcut(props) {
|
|
|
176
181
|
exports.DropdownMenuGroup = (0, react_1.forwardRef)((props, ref) => {
|
|
177
182
|
const { theme } = (0, ui_1.useTheme)();
|
|
178
183
|
const { inset, title, children, ...rest } = props;
|
|
179
|
-
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)(
|
|
184
|
+
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)(text_2.Text, { style: [{ color: theme.colors.textMuted }, atoms_1.pb[1], atoms_1.pl[2]], children: title })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
180
185
|
{ backgroundColor: theme.colors.muted },
|
|
181
186
|
react_native_1.Platform.OS === "web" ? [atoms_1.px[2], atoms_1.py[1]] : atoms_1.p[2],
|
|
182
187
|
atoms_1.gap.all[1],
|
|
@@ -185,7 +190,7 @@ exports.DropdownMenuGroup = (0, react_1.forwardRef)((props, ref) => {
|
|
|
185
190
|
});
|
|
186
191
|
exports.DropdownMenuInfo = (0, react_1.forwardRef)(({ description, ...props }, ref) => {
|
|
187
192
|
const { theme } = (0, ui_1.useTheme)();
|
|
188
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
193
|
+
return ((0, jsx_runtime_1.jsx)(text_2.Text, { style: [
|
|
189
194
|
{ color: theme.colors.textMuted },
|
|
190
195
|
atoms_1.pt[1],
|
|
191
196
|
atoms_1.pl[2],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createThemedIcon = createThemedIcon;
|
|
4
|
+
exports.Icon = Icon;
|
|
4
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
6
|
const theme_1 = require("../../lib/theme");
|
|
6
7
|
// Size mapping
|
|
@@ -23,3 +24,8 @@ function createThemedIcon(IconComponent) {
|
|
|
23
24
|
return ((0, jsx_runtime_1.jsx)(IconComponent, { size: iconSize, color: iconColor, ...restProps }));
|
|
24
25
|
};
|
|
25
26
|
}
|
|
27
|
+
// usage of createThemedIcon
|
|
28
|
+
function Icon({ icon, variant = "default", size = "md", color, ...restProps }) {
|
|
29
|
+
const ThemedIcon = createThemedIcon(icon);
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)(ThemedIcon, { variant: variant, size: size, color: color, ...restProps }));
|
|
31
|
+
}
|
|
@@ -78,7 +78,6 @@ const primitiveStyles = react_native_1.StyleSheet.create({
|
|
|
78
78
|
flexDirection: "row",
|
|
79
79
|
alignItems: "center",
|
|
80
80
|
justifyContent: "center",
|
|
81
|
-
minHeight: 44, // iOS minimum touch target
|
|
82
81
|
},
|
|
83
82
|
disabled: {
|
|
84
83
|
opacity: 0.5,
|
|
@@ -98,12 +97,6 @@ const primitiveStyles = react_native_1.StyleSheet.create({
|
|
|
98
97
|
alignItems: "center",
|
|
99
98
|
justifyContent: "center",
|
|
100
99
|
},
|
|
101
|
-
iconLeft: {
|
|
102
|
-
marginRight: 8,
|
|
103
|
-
},
|
|
104
|
-
iconRight: {
|
|
105
|
-
marginLeft: 8,
|
|
106
|
-
},
|
|
107
100
|
iconDisabled: {
|
|
108
101
|
opacity: 0.5,
|
|
109
102
|
},
|
package/dist/lib/theme/tokens.js
CHANGED
|
@@ -580,52 +580,61 @@ exports.typography = {
|
|
|
580
580
|
},
|
|
581
581
|
},
|
|
582
582
|
// Universal typography scale
|
|
583
|
+
// Atkinson's center is weird so the marginBottom is there to correct it?
|
|
583
584
|
universal: {
|
|
584
585
|
xs: {
|
|
585
586
|
fontSize: 12,
|
|
586
587
|
lineHeight: 16,
|
|
588
|
+
marginBottom: -0.7,
|
|
587
589
|
fontWeight: "400",
|
|
588
590
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
589
591
|
},
|
|
590
592
|
sm: {
|
|
591
593
|
fontSize: 14,
|
|
592
594
|
lineHeight: 20,
|
|
595
|
+
marginBottom: -1,
|
|
593
596
|
fontWeight: "400",
|
|
594
597
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
595
598
|
},
|
|
596
599
|
base: {
|
|
597
600
|
fontSize: 16,
|
|
598
601
|
lineHeight: 24,
|
|
602
|
+
marginBottom: -1.2,
|
|
599
603
|
fontWeight: "400",
|
|
600
604
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
601
605
|
},
|
|
602
606
|
lg: {
|
|
603
607
|
fontSize: 18,
|
|
604
608
|
lineHeight: 28,
|
|
609
|
+
marginBottom: -1.5,
|
|
605
610
|
fontWeight: "400",
|
|
606
611
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
607
612
|
},
|
|
608
613
|
xl: {
|
|
609
614
|
fontSize: 20,
|
|
610
615
|
lineHeight: 28,
|
|
616
|
+
marginBottom: -1.75,
|
|
611
617
|
fontWeight: "500",
|
|
612
618
|
fontFamily: "AtkinsonHyperlegibleNext-Medium",
|
|
613
619
|
},
|
|
614
620
|
"2xl": {
|
|
615
621
|
fontSize: 24,
|
|
616
622
|
lineHeight: 32,
|
|
623
|
+
marginBottom: -2,
|
|
617
624
|
fontWeight: "600",
|
|
618
625
|
fontFamily: "AtkinsonHyperlegibleNext-SemiBold",
|
|
619
626
|
},
|
|
620
627
|
"3xl": {
|
|
621
628
|
fontSize: 30,
|
|
622
629
|
lineHeight: 36,
|
|
630
|
+
marginBottom: -2.5,
|
|
623
631
|
fontWeight: "700",
|
|
624
632
|
fontFamily: "AtkinsonHyperlegibleNext-Bold",
|
|
625
633
|
},
|
|
626
634
|
"4xl": {
|
|
627
635
|
fontSize: 36,
|
|
628
636
|
lineHeight: 40,
|
|
637
|
+
marginBottom: -3,
|
|
629
638
|
fontWeight: "700",
|
|
630
639
|
fontFamily: "AtkinsonHyperlegibleNext-ExtraBold",
|
|
631
640
|
},
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useCreateFollowRecord = useCreateFollowRecord;
|
|
4
|
+
exports.useDeleteFollowRecord = useDeleteFollowRecord;
|
|
5
|
+
exports.useGraphManager = useGraphManager;
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const streamplace_store_1 = require("./streamplace-store");
|
|
8
|
+
const xrpc_1 = require("./xrpc");
|
|
9
|
+
function useCreateFollowRecord() {
|
|
10
|
+
let agent = (0, xrpc_1.usePDSAgent)();
|
|
11
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
12
|
+
const createFollow = async (subjectDID) => {
|
|
13
|
+
if (!agent) {
|
|
14
|
+
throw new Error("No PDS agent found");
|
|
15
|
+
}
|
|
16
|
+
if (!agent.did) {
|
|
17
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
18
|
+
}
|
|
19
|
+
setIsLoading(true);
|
|
20
|
+
try {
|
|
21
|
+
const record = {
|
|
22
|
+
$type: "app.bsky.graph.follow",
|
|
23
|
+
subject: subjectDID,
|
|
24
|
+
createdAt: new Date().toISOString(),
|
|
25
|
+
};
|
|
26
|
+
const result = await agent.com.atproto.repo.createRecord({
|
|
27
|
+
repo: agent.did,
|
|
28
|
+
collection: "app.bsky.graph.follow",
|
|
29
|
+
record,
|
|
30
|
+
});
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
setIsLoading(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return { createFollow, isLoading };
|
|
38
|
+
}
|
|
39
|
+
function useDeleteFollowRecord() {
|
|
40
|
+
let agent = (0, xrpc_1.usePDSAgent)();
|
|
41
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
42
|
+
const deleteFollow = async (followRecordUri) => {
|
|
43
|
+
if (!agent) {
|
|
44
|
+
throw new Error("No PDS agent found");
|
|
45
|
+
}
|
|
46
|
+
if (!agent.did) {
|
|
47
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
48
|
+
}
|
|
49
|
+
setIsLoading(true);
|
|
50
|
+
try {
|
|
51
|
+
const result = await agent.com.atproto.repo.deleteRecord({
|
|
52
|
+
repo: agent.did,
|
|
53
|
+
collection: "app.bsky.graph.follow",
|
|
54
|
+
rkey: followRecordUri.split("/").pop(),
|
|
55
|
+
});
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
setIsLoading(false);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return { deleteFollow, isLoading };
|
|
63
|
+
}
|
|
64
|
+
function useGraphManager(subjectDID) {
|
|
65
|
+
const agent = (0, xrpc_1.usePDSAgent)();
|
|
66
|
+
const [isFollowing, setIsFollowing] = (0, react_1.useState)(null);
|
|
67
|
+
const [followUri, setFollowUri] = (0, react_1.useState)(null);
|
|
68
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
69
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
70
|
+
const userDID = agent?.did;
|
|
71
|
+
const streamplaceUrl = (0, streamplace_store_1.useStreamplaceStore)((state) => state.url);
|
|
72
|
+
const fetchFollowStatus = async () => {
|
|
73
|
+
if (!userDID || !subjectDID || !streamplaceUrl) {
|
|
74
|
+
setIsFollowing(null);
|
|
75
|
+
setFollowUri(null);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setIsLoading(true);
|
|
79
|
+
setError(null);
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch(`${streamplaceUrl}/xrpc/place.stream.graph.getFollowingUser?subjectDID=${encodeURIComponent(subjectDID)}&userDID=${encodeURIComponent(userDID)}`, {
|
|
82
|
+
credentials: "include",
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
const errorText = await res.text();
|
|
86
|
+
throw new Error(`Failed to fetch follow status: ${errorText}`);
|
|
87
|
+
}
|
|
88
|
+
const data = await res.json();
|
|
89
|
+
if (data.follow) {
|
|
90
|
+
setIsFollowing(true);
|
|
91
|
+
setFollowUri(data.follow.uri);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
setIsFollowing(false);
|
|
95
|
+
setFollowUri(null);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
setError(`Could not determine follow state: ${err instanceof Error ? err.message : `Unknown error: ${err}`}`);
|
|
100
|
+
setIsFollowing(null);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
setIsLoading(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
(0, react_1.useEffect)(() => {
|
|
107
|
+
if (!userDID || !subjectDID) {
|
|
108
|
+
setIsFollowing(null);
|
|
109
|
+
setFollowUri(null);
|
|
110
|
+
setError(null);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
fetchFollowStatus();
|
|
114
|
+
}, [userDID, subjectDID, streamplaceUrl]);
|
|
115
|
+
const follow = async () => {
|
|
116
|
+
if (!agent || !subjectDID) {
|
|
117
|
+
throw new Error("Cannot follow: not logged in or no subject DID");
|
|
118
|
+
}
|
|
119
|
+
if (!agent.did) {
|
|
120
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
121
|
+
}
|
|
122
|
+
setIsLoading(true);
|
|
123
|
+
setError(null);
|
|
124
|
+
const previousState = isFollowing;
|
|
125
|
+
setIsFollowing(true); // Optimistic
|
|
126
|
+
try {
|
|
127
|
+
const record = {
|
|
128
|
+
$type: "app.bsky.graph.follow",
|
|
129
|
+
subject: subjectDID,
|
|
130
|
+
createdAt: new Date().toISOString(),
|
|
131
|
+
};
|
|
132
|
+
const result = await agent.com.atproto.repo.createRecord({
|
|
133
|
+
repo: agent.did,
|
|
134
|
+
collection: "app.bsky.graph.follow",
|
|
135
|
+
record,
|
|
136
|
+
});
|
|
137
|
+
setFollowUri(result.data.uri);
|
|
138
|
+
setIsFollowing(true);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
setIsFollowing(previousState);
|
|
142
|
+
const errorMsg = `Failed to follow: ${err instanceof Error ? err.message : "Unknown error"}`;
|
|
143
|
+
setError(errorMsg);
|
|
144
|
+
throw new Error(errorMsg);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
setIsLoading(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const unfollow = async () => {
|
|
151
|
+
if (!agent || !subjectDID) {
|
|
152
|
+
throw new Error("Cannot unfollow: not logged in or no subject DID");
|
|
153
|
+
}
|
|
154
|
+
if (!agent.did) {
|
|
155
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
156
|
+
}
|
|
157
|
+
if (!followUri) {
|
|
158
|
+
throw new Error("Cannot unfollow: no follow URI found");
|
|
159
|
+
}
|
|
160
|
+
setIsLoading(true);
|
|
161
|
+
setError(null);
|
|
162
|
+
const previousState = isFollowing;
|
|
163
|
+
const previousUri = followUri;
|
|
164
|
+
setIsFollowing(false); // Optimistic
|
|
165
|
+
setFollowUri(null);
|
|
166
|
+
try {
|
|
167
|
+
await agent.com.atproto.repo.deleteRecord({
|
|
168
|
+
repo: agent.did,
|
|
169
|
+
collection: "app.bsky.graph.follow",
|
|
170
|
+
rkey: followUri.split("/").pop(),
|
|
171
|
+
});
|
|
172
|
+
setIsFollowing(false);
|
|
173
|
+
setFollowUri(null);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
setIsFollowing(previousState);
|
|
177
|
+
setFollowUri(previousUri);
|
|
178
|
+
const errorMsg = `Failed to unfollow: ${err instanceof Error ? err.message : "Unknown error"}`;
|
|
179
|
+
setError(errorMsg);
|
|
180
|
+
throw new Error(errorMsg);
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
setIsLoading(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
isFollowing,
|
|
188
|
+
followUri,
|
|
189
|
+
isLoading,
|
|
190
|
+
error,
|
|
191
|
+
follow,
|
|
192
|
+
unfollow,
|
|
193
|
+
refresh: fetchFollowStatus,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
Binary file
|