@streamplace/components 0.7.34 → 0.8.0
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/content-metadata/content-metadata-form.js +404 -0
- package/dist/components/content-metadata/content-rights.js +78 -0
- package/dist/components/content-metadata/content-warnings.js +68 -0
- package/dist/components/content-metadata/index.js +11 -0
- package/dist/components/dashboard/header.js +16 -2
- package/dist/components/dashboard/problems.js +29 -28
- package/dist/components/mobile-player/player.js +4 -0
- package/dist/components/mobile-player/ui/report-modal.js +3 -2
- package/dist/components/mobile-player/ui/viewer-context-menu.js +44 -1
- package/dist/components/ui/button.js +9 -9
- package/dist/components/ui/checkbox.js +87 -0
- package/dist/components/ui/dialog.js +188 -83
- 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/components/ui/primitives/input.js +13 -1
- package/dist/components/ui/primitives/modal.js +2 -2
- package/dist/components/ui/select.js +89 -0
- package/dist/components/ui/textarea.js +23 -4
- package/dist/components/ui/toast.js +464 -114
- package/dist/components/ui/tooltip.js +103 -0
- package/dist/index.js +2 -0
- package/dist/lib/metadata-constants.js +157 -0
- package/dist/lib/theme/theme.js +5 -3
- package/dist/lib/theme/tokens.js +9 -0
- package/dist/streamplace-provider/index.js +14 -4
- package/dist/streamplace-store/content-metadata-actions.js +118 -0
- package/dist/streamplace-store/graph.js +195 -0
- package/dist/streamplace-store/streamplace-store.js +18 -5
- package/dist/streamplace-store/user.js +67 -7
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +3 -3
- package/src/components/content-metadata/content-metadata-form.tsx +761 -0
- package/src/components/content-metadata/content-rights.tsx +104 -0
- package/src/components/content-metadata/content-warnings.tsx +100 -0
- package/src/components/content-metadata/index.tsx +18 -0
- 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/player.tsx +5 -0
- package/src/components/mobile-player/ui/report-modal.tsx +13 -7
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +100 -1
- package/src/components/ui/button.tsx +10 -13
- package/src/components/ui/checkbox.tsx +147 -0
- package/src/components/ui/dialog.tsx +319 -99
- 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/components/ui/primitives/input.tsx +19 -2
- package/src/components/ui/primitives/modal.tsx +4 -2
- package/src/components/ui/select.tsx +175 -0
- package/src/components/ui/textarea.tsx +47 -29
- package/src/components/ui/toast.tsx +785 -179
- package/src/components/ui/tooltip.tsx +131 -0
- package/src/index.tsx +3 -0
- package/src/lib/metadata-constants.ts +180 -0
- package/src/lib/theme/theme.tsx +10 -6
- package/src/lib/theme/tokens.ts +9 -0
- package/src/streamplace-provider/index.tsx +20 -2
- package/src/streamplace-store/content-metadata-actions.tsx +142 -0
- package/src/streamplace-store/graph.tsx +232 -0
- package/src/streamplace-store/streamplace-store.tsx +30 -4
- package/src/streamplace-store/user.tsx +71 -7
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
|
+
import { LICENSE_URL_LABELS } from "../../lib/metadata-constants";
|
|
4
|
+
import { useTheme } from "../../lib/theme/theme";
|
|
5
|
+
import { Text } from "../ui/text";
|
|
6
|
+
|
|
7
|
+
export interface ContentRightsProps {
|
|
8
|
+
contentRights: {
|
|
9
|
+
creator?: string;
|
|
10
|
+
copyrightNotice?: string;
|
|
11
|
+
copyrightYear?: string | number;
|
|
12
|
+
license?: string;
|
|
13
|
+
creditLine?: string;
|
|
14
|
+
};
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ContentRights = forwardRef<any, ContentRightsProps>(
|
|
19
|
+
({ contentRights }, ref) => {
|
|
20
|
+
const { theme } = useTheme();
|
|
21
|
+
|
|
22
|
+
if (!contentRights || Object.keys(contentRights).length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const styles = createStyles(theme);
|
|
27
|
+
|
|
28
|
+
const formatLicense = (license: string) => {
|
|
29
|
+
return LICENSE_URL_LABELS[license] || license;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Display rights in bottom metadata view
|
|
33
|
+
const elements: string[] = [];
|
|
34
|
+
|
|
35
|
+
// TODO: Map DID to handle creator
|
|
36
|
+
// if (contentRights.creator) {
|
|
37
|
+
// elements.push(`Creator: ${contentRights.creator}`);
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
if (contentRights.copyrightYear) {
|
|
41
|
+
elements.push(`© ${contentRights.copyrightYear.toString()}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (contentRights.license) {
|
|
45
|
+
elements.push(formatLicense(contentRights.license));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (contentRights.copyrightNotice) {
|
|
49
|
+
elements.push(contentRights.copyrightNotice);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (contentRights.creditLine) {
|
|
53
|
+
elements.push(contentRights.creditLine);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View ref={ref} style={styles.compactContainer}>
|
|
58
|
+
<Text style={styles.compactText}>{elements.join(" • ")}</Text>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
ContentRights.displayName = "ContentRights";
|
|
65
|
+
|
|
66
|
+
function createStyles(theme: any) {
|
|
67
|
+
return StyleSheet.create({
|
|
68
|
+
container: {
|
|
69
|
+
paddingVertical: theme.spacing[3],
|
|
70
|
+
},
|
|
71
|
+
title: {
|
|
72
|
+
fontSize: 14,
|
|
73
|
+
fontWeight: "600",
|
|
74
|
+
color: theme.colors.text,
|
|
75
|
+
marginBottom: theme.spacing[2],
|
|
76
|
+
},
|
|
77
|
+
content: {
|
|
78
|
+
gap: theme.spacing[2],
|
|
79
|
+
},
|
|
80
|
+
row: {
|
|
81
|
+
flexDirection: "row",
|
|
82
|
+
gap: theme.spacing[2],
|
|
83
|
+
},
|
|
84
|
+
label: {
|
|
85
|
+
fontSize: 13,
|
|
86
|
+
color: theme.colors.textMuted,
|
|
87
|
+
},
|
|
88
|
+
value: {
|
|
89
|
+
fontSize: 13,
|
|
90
|
+
color: theme.colors.text,
|
|
91
|
+
},
|
|
92
|
+
compactContainer: {
|
|
93
|
+
flexDirection: "row",
|
|
94
|
+
gap: theme.spacing[2],
|
|
95
|
+
flexWrap: "wrap",
|
|
96
|
+
marginTop: theme.spacing[1],
|
|
97
|
+
},
|
|
98
|
+
compactText: {
|
|
99
|
+
fontSize: 14,
|
|
100
|
+
fontWeight: "500",
|
|
101
|
+
color: theme.colors.text,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
|
+
import { C2PA_WARNING_LABELS } from "../../lib/metadata-constants";
|
|
4
|
+
import { useTheme } from "../../lib/theme/theme";
|
|
5
|
+
import { Text } from "../ui/text";
|
|
6
|
+
|
|
7
|
+
export interface ContentWarningsProps {
|
|
8
|
+
warnings: string[];
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ContentWarnings = forwardRef<any, ContentWarningsProps>(
|
|
13
|
+
({ warnings, compact = false }, ref) => {
|
|
14
|
+
const { theme } = useTheme();
|
|
15
|
+
|
|
16
|
+
if (!warnings || warnings.length === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const styles = createStyles(theme, compact);
|
|
21
|
+
|
|
22
|
+
const getWarningLabel = (warning: string): string => {
|
|
23
|
+
return C2PA_WARNING_LABELS[warning] || warning;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (compact) {
|
|
27
|
+
return (
|
|
28
|
+
<View ref={ref} style={styles.compactContainer}>
|
|
29
|
+
{warnings.map((warning, index) => (
|
|
30
|
+
<View key={index} style={styles.compactWarning}>
|
|
31
|
+
<Text style={styles.compactWarningText}>
|
|
32
|
+
{getWarningLabel(warning)}
|
|
33
|
+
</Text>
|
|
34
|
+
</View>
|
|
35
|
+
))}
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View ref={ref} style={styles.container}>
|
|
42
|
+
<Text style={styles.title}>Content Warnings</Text>
|
|
43
|
+
<View style={styles.warningsContainer}>
|
|
44
|
+
{warnings.map((warning, index) => (
|
|
45
|
+
<View key={index} style={styles.warning}>
|
|
46
|
+
<Text style={styles.warningText}>{getWarningLabel(warning)}</Text>
|
|
47
|
+
</View>
|
|
48
|
+
))}
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
ContentWarnings.displayName = "ContentWarnings";
|
|
56
|
+
|
|
57
|
+
function createStyles(theme: any, compact: boolean) {
|
|
58
|
+
return StyleSheet.create({
|
|
59
|
+
container: {
|
|
60
|
+
flexDirection: "column",
|
|
61
|
+
gap: theme.spacing[2],
|
|
62
|
+
},
|
|
63
|
+
title: {
|
|
64
|
+
fontSize: 14,
|
|
65
|
+
fontWeight: "600",
|
|
66
|
+
color: theme.colors.text,
|
|
67
|
+
},
|
|
68
|
+
warningsContainer: {
|
|
69
|
+
flexDirection: "row",
|
|
70
|
+
flexWrap: "wrap",
|
|
71
|
+
gap: theme.spacing[2],
|
|
72
|
+
},
|
|
73
|
+
warning: {
|
|
74
|
+
backgroundColor: theme.colors.warning,
|
|
75
|
+
borderRadius: theme.borderRadius.md,
|
|
76
|
+
padding: theme.spacing[2],
|
|
77
|
+
},
|
|
78
|
+
warningText: {
|
|
79
|
+
color: theme.colors.warningForeground,
|
|
80
|
+
fontSize: 12,
|
|
81
|
+
fontWeight: "500",
|
|
82
|
+
},
|
|
83
|
+
compactContainer: {
|
|
84
|
+
flexDirection: "row",
|
|
85
|
+
flexWrap: "wrap",
|
|
86
|
+
gap: theme.spacing[1],
|
|
87
|
+
},
|
|
88
|
+
compactWarning: {
|
|
89
|
+
backgroundColor: theme.colors.warning,
|
|
90
|
+
borderRadius: theme.borderRadius.full,
|
|
91
|
+
paddingHorizontal: 10,
|
|
92
|
+
paddingVertical: 4,
|
|
93
|
+
},
|
|
94
|
+
compactWarningText: {
|
|
95
|
+
color: theme.colors.warningForeground,
|
|
96
|
+
fontSize: 14,
|
|
97
|
+
fontWeight: "600",
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Main form component
|
|
2
|
+
export { ContentMetadataForm } from "./content-metadata-form";
|
|
3
|
+
|
|
4
|
+
// Display components
|
|
5
|
+
export { ContentRights } from "./content-rights";
|
|
6
|
+
export { ContentWarnings } from "./content-warnings";
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
export type {
|
|
10
|
+
ContentMetadata,
|
|
11
|
+
ContentMetadataFormProps,
|
|
12
|
+
DistributionPolicy,
|
|
13
|
+
Rights,
|
|
14
|
+
} from "./content-metadata-form";
|
|
15
|
+
|
|
16
|
+
export type { ContentRightsProps } from "./content-rights";
|
|
17
|
+
|
|
18
|
+
export type { ContentWarningsProps } from "./content-warnings";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Car, Radio, Users } from "lucide-react-native";
|
|
2
|
-
import { Text, View } from "react-native";
|
|
1
|
+
import { AlertCircle, Car, Radio, Users } from "lucide-react-native";
|
|
2
|
+
import { Pressable, Text, View } from "react-native";
|
|
3
3
|
import * as zero from "../../ui";
|
|
4
4
|
|
|
5
5
|
const { bg, r, borders, px, py, text, layout, gap } = zero;
|
|
@@ -103,6 +103,8 @@ interface HeaderProps {
|
|
|
103
103
|
bitrate?: string;
|
|
104
104
|
timeBetweenSegments?: number;
|
|
105
105
|
connectionStatus?: "excellent" | "good" | "poor" | "offline";
|
|
106
|
+
problemsCount?: number;
|
|
107
|
+
onProblemsPress?: () => void;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
export default function Header({
|
|
@@ -113,6 +115,8 @@ export default function Header({
|
|
|
113
115
|
bitrate = "0 mbps",
|
|
114
116
|
timeBetweenSegments = 0,
|
|
115
117
|
connectionStatus = "offline",
|
|
118
|
+
problemsCount = 0,
|
|
119
|
+
onProblemsPress,
|
|
116
120
|
}: HeaderProps) {
|
|
117
121
|
const getConnectionQuality = (): "good" | "warning" | "error" => {
|
|
118
122
|
if (timeBetweenSegments <= 1500) return "good";
|
|
@@ -139,7 +143,37 @@ export default function Header({
|
|
|
139
143
|
<Text style={[text.white, { fontSize: 18, fontWeight: "600" }]}>
|
|
140
144
|
{streamTitle}
|
|
141
145
|
</Text>
|
|
142
|
-
<
|
|
146
|
+
<View style={[layout.flex.row, layout.flex.alignCenter, gap.all[3]]}>
|
|
147
|
+
<StatusIndicator status={connectionStatus} isLive={isLive} />
|
|
148
|
+
{problemsCount > 0 && (
|
|
149
|
+
<Pressable onPress={onProblemsPress}>
|
|
150
|
+
<View
|
|
151
|
+
style={[
|
|
152
|
+
layout.flex.row,
|
|
153
|
+
layout.flex.alignCenter,
|
|
154
|
+
gap.all[1],
|
|
155
|
+
px[2],
|
|
156
|
+
py[1],
|
|
157
|
+
r.md,
|
|
158
|
+
bg.orange[900],
|
|
159
|
+
borders.width.thin,
|
|
160
|
+
borders.color.orange[700],
|
|
161
|
+
{ marginVertical: -8 },
|
|
162
|
+
]}
|
|
163
|
+
>
|
|
164
|
+
<AlertCircle size={14} color="#fb923c" />
|
|
165
|
+
<Text
|
|
166
|
+
style={[
|
|
167
|
+
text.orange[400],
|
|
168
|
+
{ fontSize: 11, fontWeight: "600" },
|
|
169
|
+
]}
|
|
170
|
+
>
|
|
171
|
+
{problemsCount} {problemsCount === 1 ? "Issue" : "Issues"}
|
|
172
|
+
</Text>
|
|
173
|
+
</View>
|
|
174
|
+
</Pressable>
|
|
175
|
+
)}
|
|
176
|
+
</View>
|
|
143
177
|
</View>
|
|
144
178
|
</View>
|
|
145
179
|
|
|
@@ -2,4 +2,4 @@ export { default as ChatPanel } from "./chat-panel";
|
|
|
2
2
|
export { default as Header } from "./header";
|
|
3
3
|
export { default as InformationWidget } from "./information-widget";
|
|
4
4
|
export { default as ModActions } from "./mod-actions";
|
|
5
|
-
export { default as Problems } from "./problems";
|
|
5
|
+
export { default as Problems, ProblemsWrapperRef } from "./problems";
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
CircleAlert,
|
|
3
|
+
CircleX,
|
|
4
|
+
ExternalLink,
|
|
5
|
+
Info,
|
|
6
|
+
Sparkle,
|
|
7
|
+
} from "lucide-react-native";
|
|
8
|
+
import { forwardRef, useImperativeHandle, useState } from "react";
|
|
9
|
+
import { Linking, Pressable, View } from "react-native";
|
|
4
10
|
import { useLivestreamStore } from "../../livestream-store";
|
|
5
11
|
import { LivestreamProblem } from "../../livestream-store/livestream-state";
|
|
6
12
|
import * as zero from "../../ui";
|
|
13
|
+
import { Button, Text } from "../ui";
|
|
7
14
|
|
|
8
15
|
const { bg, r, borders, p, text, layout, gap } = zero;
|
|
9
16
|
|
|
17
|
+
const getIcon = (severity: string) => {
|
|
18
|
+
switch (severity) {
|
|
19
|
+
case "error":
|
|
20
|
+
return <CircleX size={24} color="white" />;
|
|
21
|
+
case "warning":
|
|
22
|
+
return <CircleAlert size={24} color="white" />;
|
|
23
|
+
case "info":
|
|
24
|
+
return <Info size={24} color="white" />;
|
|
25
|
+
default:
|
|
26
|
+
return <Sparkle size={24} color="white" />;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
10
30
|
const Problems = ({
|
|
11
31
|
probs,
|
|
12
32
|
onIgnore,
|
|
@@ -15,9 +35,9 @@ const Problems = ({
|
|
|
15
35
|
onIgnore: () => void;
|
|
16
36
|
}) => {
|
|
17
37
|
return (
|
|
18
|
-
<View style={[gap.all[
|
|
19
|
-
<View>
|
|
20
|
-
<Text style={[text.white, {
|
|
38
|
+
<View style={[gap.all[4]]}>
|
|
39
|
+
<View style={[gap.all[2]]}>
|
|
40
|
+
<Text size="2xl" style={[text.white, { fontWeight: "600" }]}>
|
|
21
41
|
Optimize Your Stream
|
|
22
42
|
</Text>
|
|
23
43
|
<Text style={[text.gray[300]]}>
|
|
@@ -34,26 +54,22 @@ const Problems = ({
|
|
|
34
54
|
{ gap: 8, alignItems: "flex-start" },
|
|
35
55
|
]}
|
|
36
56
|
>
|
|
37
|
-
<
|
|
57
|
+
<View
|
|
38
58
|
style={[
|
|
39
|
-
r.
|
|
40
|
-
p[
|
|
59
|
+
zero.r.full,
|
|
60
|
+
zero.p[1],
|
|
41
61
|
{
|
|
42
|
-
width: 82,
|
|
43
|
-
textAlign: "center",
|
|
44
62
|
backgroundColor:
|
|
45
63
|
p.severity === "error"
|
|
46
64
|
? "#7f1d1d"
|
|
47
65
|
: p.severity === "warning"
|
|
48
66
|
? "#7c2d12"
|
|
49
67
|
: "#1e3a8a",
|
|
50
|
-
color: "white",
|
|
51
|
-
fontSize: 12,
|
|
52
68
|
},
|
|
53
69
|
]}
|
|
54
70
|
>
|
|
55
|
-
{p.severity}
|
|
56
|
-
</
|
|
71
|
+
{getIcon(p.severity)}
|
|
72
|
+
</View>
|
|
57
73
|
<View style={[{ flex: 1 }, gap.all[1]]}>
|
|
58
74
|
<Text style={[text.white, { fontWeight: "600" }]}>{p.code}</Text>
|
|
59
75
|
<Text style={[text.gray[400], { fontSize: 14 }]}>
|
|
@@ -79,39 +95,34 @@ const Problems = ({
|
|
|
79
95
|
</View>
|
|
80
96
|
</View>
|
|
81
97
|
))}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
r.md,
|
|
88
|
-
p[3],
|
|
89
|
-
layout.flex.center,
|
|
90
|
-
{ marginTop: 16 },
|
|
91
|
-
]}
|
|
92
|
-
>
|
|
93
|
-
<Text style={[text.white, { fontWeight: "600" }]}>Ignore</Text>
|
|
94
|
-
</Pressable>
|
|
98
|
+
<View style={[layout.flex.row, layout.flex.justify.end]}>
|
|
99
|
+
<Button onPress={onIgnore} variant="secondary">
|
|
100
|
+
<Text style={[text.white, { fontWeight: "600" }]}>Acknowledge</Text>
|
|
101
|
+
</Button>
|
|
102
|
+
</View>
|
|
95
103
|
</View>
|
|
96
104
|
);
|
|
97
105
|
};
|
|
98
106
|
|
|
99
|
-
export
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
export interface ProblemsWrapperRef {
|
|
108
|
+
setDismiss: (value: boolean) => void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const ProblemsWrapper = forwardRef<
|
|
112
|
+
ProblemsWrapperRef,
|
|
113
|
+
{
|
|
114
|
+
children: React.ReactElement;
|
|
115
|
+
}
|
|
116
|
+
>(({ children }, ref) => {
|
|
104
117
|
const problems = useLivestreamStore((x) => x.problems);
|
|
105
118
|
const [dismiss, setDismiss] = useState(false);
|
|
106
119
|
|
|
120
|
+
useImperativeHandle(ref, () => ({
|
|
121
|
+
setDismiss,
|
|
122
|
+
}));
|
|
123
|
+
|
|
107
124
|
return (
|
|
108
|
-
|
|
109
|
-
style={[
|
|
110
|
-
{ position: "relative", flex: 1 },
|
|
111
|
-
layout.flex.center,
|
|
112
|
-
{ flexBasis: 0 },
|
|
113
|
-
]}
|
|
114
|
-
>
|
|
125
|
+
<>
|
|
115
126
|
{children}
|
|
116
127
|
{problems.length > 0 && !dismiss && (
|
|
117
128
|
<View
|
|
@@ -127,16 +138,16 @@ export const ProblemsWrapper = ({
|
|
|
127
138
|
},
|
|
128
139
|
layout.flex.center,
|
|
129
140
|
{ justifyContent: "flex-start" },
|
|
130
|
-
p[
|
|
141
|
+
p[12],
|
|
131
142
|
]}
|
|
132
143
|
>
|
|
133
144
|
<View
|
|
134
145
|
style={[
|
|
135
|
-
bg.
|
|
136
|
-
borders.color.
|
|
146
|
+
bg.neutral[900],
|
|
147
|
+
borders.color.neutral[700],
|
|
137
148
|
borders.width.thin,
|
|
138
149
|
r.lg,
|
|
139
|
-
p[
|
|
150
|
+
p[8],
|
|
140
151
|
{ maxWidth: 700, width: "100%" },
|
|
141
152
|
]}
|
|
142
153
|
>
|
|
@@ -144,8 +155,8 @@ export const ProblemsWrapper = ({
|
|
|
144
155
|
</View>
|
|
145
156
|
</View>
|
|
146
157
|
)}
|
|
147
|
-
|
|
158
|
+
</>
|
|
148
159
|
);
|
|
149
|
-
};
|
|
160
|
+
});
|
|
150
161
|
|
|
151
162
|
export default Problems;
|
|
@@ -23,6 +23,7 @@ export function Player(
|
|
|
23
23
|
const clearControlsTimeout = usePlayerStore((x) => x.clearControlsTimeout);
|
|
24
24
|
|
|
25
25
|
const setReportingURL = usePlayerStore((x) => x.setReportingURL);
|
|
26
|
+
const setEmbedded = usePlayerStore((x) => x.setEmbedded);
|
|
26
27
|
|
|
27
28
|
const reportModalOpen = usePlayerStore((x) => x.reportModalOpen);
|
|
28
29
|
const setReportModalOpen = usePlayerStore((x) => x.setReportModalOpen);
|
|
@@ -32,6 +33,10 @@ export function Player(
|
|
|
32
33
|
setReportingURL(props.reportingURL ?? null);
|
|
33
34
|
}, [props.reportingURL]);
|
|
34
35
|
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setEmbedded(props.embedded ?? false);
|
|
38
|
+
}, [props.embedded]);
|
|
39
|
+
|
|
35
40
|
// Will call back every few seconds to send health updates
|
|
36
41
|
usePlayerStatus();
|
|
37
42
|
|
|
@@ -9,11 +9,11 @@ import { zero } from "../../..";
|
|
|
9
9
|
import { useSubmitReport } from "../../../livestream-store";
|
|
10
10
|
import {
|
|
11
11
|
Button,
|
|
12
|
-
Dialog,
|
|
13
12
|
DialogFooter,
|
|
14
|
-
|
|
13
|
+
ResponsiveDialog,
|
|
15
14
|
Text,
|
|
16
15
|
Textarea,
|
|
16
|
+
useTheme,
|
|
17
17
|
} from "../../ui";
|
|
18
18
|
|
|
19
19
|
// AT Protocol moderation reason types with proper labels
|
|
@@ -72,6 +72,8 @@ export const ReportModal: React.FC<ReportModalProps> = ({
|
|
|
72
72
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
73
73
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
74
74
|
|
|
75
|
+
const { theme } = useTheme();
|
|
76
|
+
|
|
75
77
|
const submitReport = useSubmitReport();
|
|
76
78
|
|
|
77
79
|
const handleCancel = () => {
|
|
@@ -107,7 +109,7 @@ export const ReportModal: React.FC<ReportModalProps> = ({
|
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
return (
|
|
110
|
-
<
|
|
112
|
+
<ResponsiveDialog
|
|
111
113
|
open={open}
|
|
112
114
|
onOpenChange={onOpenChange}
|
|
113
115
|
title={title}
|
|
@@ -118,7 +120,7 @@ export const ReportModal: React.FC<ReportModalProps> = ({
|
|
|
118
120
|
dismissible={false}
|
|
119
121
|
position="center"
|
|
120
122
|
>
|
|
121
|
-
<
|
|
123
|
+
<View style={[zero.pb[2]]}>
|
|
122
124
|
{REPORT_REASONS.map((reason) => (
|
|
123
125
|
<TouchableOpacity
|
|
124
126
|
key={reason.value}
|
|
@@ -136,7 +138,11 @@ export const ReportModal: React.FC<ReportModalProps> = ({
|
|
|
136
138
|
]}
|
|
137
139
|
>
|
|
138
140
|
<View>
|
|
139
|
-
{selectedReason === reason.value ?
|
|
141
|
+
{selectedReason === reason.value ? (
|
|
142
|
+
<CheckCircle color={theme.colors.foreground} />
|
|
143
|
+
) : (
|
|
144
|
+
<Circle color={theme.colors.foreground} />
|
|
145
|
+
)}
|
|
140
146
|
</View>
|
|
141
147
|
<View
|
|
142
148
|
style={[zero.layout.flex.column, zero.gap.all[1], zero.flex[1]]}
|
|
@@ -164,7 +170,7 @@ export const ReportModal: React.FC<ReportModalProps> = ({
|
|
|
164
170
|
</Text>
|
|
165
171
|
)}
|
|
166
172
|
</View>
|
|
167
|
-
</
|
|
173
|
+
</View>
|
|
168
174
|
<DialogFooter>
|
|
169
175
|
<Button
|
|
170
176
|
variant="secondary"
|
|
@@ -188,7 +194,7 @@ export const ReportModal: React.FC<ReportModalProps> = ({
|
|
|
188
194
|
)}
|
|
189
195
|
</Button>
|
|
190
196
|
</DialogFooter>
|
|
191
|
-
</
|
|
197
|
+
</ResponsiveDialog>
|
|
192
198
|
);
|
|
193
199
|
};
|
|
194
200
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useRootContext } from "@rn-primitives/dropdown-menu";
|
|
2
2
|
import { Menu } from "lucide-react-native";
|
|
3
|
-
import { Platform, View } from "react-native";
|
|
3
|
+
import { Image, Linking, Platform, Pressable, View } from "react-native";
|
|
4
|
+
import { useAvatars, useLivestreamInfo, zero } from "../../..";
|
|
4
5
|
import { colors } from "../../../lib/theme";
|
|
5
6
|
import { useLivestreamStore } from "../../../livestream-store";
|
|
6
7
|
import { PlayerProtocol, usePlayerStore } from "../../../player-store/";
|
|
8
|
+
import { useGraphManager } from "../../../streamplace-store/graph";
|
|
7
9
|
import {
|
|
8
10
|
DropdownMenu,
|
|
9
11
|
DropdownMenuCheckboxItem,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
DropdownMenuPortal,
|
|
15
17
|
DropdownMenuRadioGroup,
|
|
16
18
|
DropdownMenuRadioItem,
|
|
19
|
+
DropdownMenuSeparator,
|
|
17
20
|
DropdownMenuTrigger,
|
|
18
21
|
ResponsiveDropdownMenuContent,
|
|
19
22
|
Text,
|
|
@@ -38,6 +41,12 @@ export function ContextMenu({
|
|
|
38
41
|
const setReportModalOpen = usePlayerStore((x) => x.setReportModalOpen);
|
|
39
42
|
const setReportSubject = usePlayerStore((x) => x.setReportSubject);
|
|
40
43
|
|
|
44
|
+
const { profile } = useLivestreamInfo();
|
|
45
|
+
const avatars = useAvatars(profile?.did ? [profile?.did] : []);
|
|
46
|
+
const ls = useLivestreamStore((x) => x.livestream);
|
|
47
|
+
|
|
48
|
+
let graphManager = useGraphManager(profile?.did);
|
|
49
|
+
|
|
41
50
|
const lowLatency = protocol === "webrtc";
|
|
42
51
|
const setLowLatency = (value: boolean) => {
|
|
43
52
|
setProtocol(value ? PlayerProtocol.WEBRTC : PlayerProtocol.HLS);
|
|
@@ -61,6 +70,96 @@ export function ContextMenu({
|
|
|
61
70
|
</DropdownMenuTrigger>
|
|
62
71
|
<Portal container={dropdownPortalContainer}>
|
|
63
72
|
<DropdownMenuContent side="top" align="end">
|
|
73
|
+
{Platform.OS !== "web" && (
|
|
74
|
+
<DropdownMenuGroup title="Streamer">
|
|
75
|
+
<View
|
|
76
|
+
style={[
|
|
77
|
+
zero.layout.flex.row,
|
|
78
|
+
zero.layout.flex.center,
|
|
79
|
+
zero.gap.all[3],
|
|
80
|
+
{ flex: 1, minWidth: 0 },
|
|
81
|
+
]}
|
|
82
|
+
>
|
|
83
|
+
{profile?.did && avatars[profile?.did]?.avatar && (
|
|
84
|
+
<Image
|
|
85
|
+
key="avatar"
|
|
86
|
+
source={{
|
|
87
|
+
uri: avatars[profile?.did]?.avatar,
|
|
88
|
+
}}
|
|
89
|
+
style={{ width: 42, height: 42, borderRadius: 999 }}
|
|
90
|
+
resizeMode="cover"
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
94
|
+
<View
|
|
95
|
+
style={[
|
|
96
|
+
zero.layout.flex.row,
|
|
97
|
+
zero.layout.flex.alignCenter,
|
|
98
|
+
zero.gap.all[2],
|
|
99
|
+
]}
|
|
100
|
+
>
|
|
101
|
+
<Pressable
|
|
102
|
+
onPress={() => {
|
|
103
|
+
if (profile?.handle) {
|
|
104
|
+
const url = `https://bsky.app/profile/${profile.handle}`;
|
|
105
|
+
Linking.openURL(url);
|
|
106
|
+
}
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<Text>@{profile?.handle || "user"}</Text>
|
|
110
|
+
</Pressable>
|
|
111
|
+
{/*{did && profile && (
|
|
112
|
+
<FollowButton streamerDID={profile?.did} currentUserDID={did} />
|
|
113
|
+
)}*/}
|
|
114
|
+
</View>
|
|
115
|
+
<Text
|
|
116
|
+
color="muted"
|
|
117
|
+
size="sm"
|
|
118
|
+
numberOfLines={2}
|
|
119
|
+
ellipsizeMode="tail"
|
|
120
|
+
>
|
|
121
|
+
{ls?.record.title || "Stream Title"}
|
|
122
|
+
</Text>
|
|
123
|
+
</View>
|
|
124
|
+
</View>
|
|
125
|
+
<DropdownMenuSeparator />
|
|
126
|
+
<DropdownMenuItem
|
|
127
|
+
disabled={graphManager.isLoading || !profile?.did}
|
|
128
|
+
onPress={async () => {
|
|
129
|
+
try {
|
|
130
|
+
if (graphManager.isFollowing) {
|
|
131
|
+
await graphManager.unfollow();
|
|
132
|
+
} else {
|
|
133
|
+
await graphManager.follow();
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error("Follow/unfollow error:", err);
|
|
137
|
+
}
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<Text
|
|
141
|
+
color={graphManager.isFollowing ? "destructive" : "default"}
|
|
142
|
+
>
|
|
143
|
+
{graphManager.isLoading
|
|
144
|
+
? "Loading..."
|
|
145
|
+
: graphManager.isFollowing
|
|
146
|
+
? "Unfollow"
|
|
147
|
+
: "Follow"}
|
|
148
|
+
</Text>
|
|
149
|
+
</DropdownMenuItem>
|
|
150
|
+
<DropdownMenuSeparator />
|
|
151
|
+
<DropdownMenuItem
|
|
152
|
+
onPress={() => {
|
|
153
|
+
if (profile?.handle) {
|
|
154
|
+
const url = `https://bsky.app/profile/${profile.handle}`;
|
|
155
|
+
Linking.openURL(url);
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<Text>View Profile on Bluesky</Text>
|
|
160
|
+
</DropdownMenuItem>
|
|
161
|
+
</DropdownMenuGroup>
|
|
162
|
+
)}
|
|
64
163
|
<DropdownMenuGroup title="Resolution">
|
|
65
164
|
<DropdownMenuRadioGroup value={quality} onValueChange={setQuality}>
|
|
66
165
|
<DropdownMenuRadioItem value="source">
|