@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,131 @@
|
|
|
1
|
+
import { forwardRef, useState } from "react";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
|
+
import { useTheme } from "../../lib/theme/theme";
|
|
4
|
+
import { Text } from "../ui/text";
|
|
5
|
+
|
|
6
|
+
export interface TooltipProps {
|
|
7
|
+
content: string;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
position?: "top" | "bottom" | "left" | "right";
|
|
10
|
+
style?: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Tooltip = forwardRef<any, TooltipProps>(
|
|
14
|
+
({ content, children, position = "top", style }, ref) => {
|
|
15
|
+
const { theme } = useTheme();
|
|
16
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
17
|
+
const styles = createStyles(theme, position);
|
|
18
|
+
|
|
19
|
+
const handleHoverIn = () => {
|
|
20
|
+
setIsVisible(true);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const handleHoverOut = () => {
|
|
24
|
+
setIsVisible(false);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View
|
|
29
|
+
ref={ref}
|
|
30
|
+
style={[styles.container, style]}
|
|
31
|
+
onPointerEnter={handleHoverIn}
|
|
32
|
+
onPointerLeave={handleHoverOut}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
{isVisible && (
|
|
36
|
+
<View style={styles.tooltip}>
|
|
37
|
+
<Text style={styles.tooltipText}>{content}</Text>
|
|
38
|
+
</View>
|
|
39
|
+
)}
|
|
40
|
+
</View>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
Tooltip.displayName = "Tooltip";
|
|
46
|
+
|
|
47
|
+
function createStyles(theme: any, position: string) {
|
|
48
|
+
const positionStyles = {
|
|
49
|
+
top: {
|
|
50
|
+
tooltip: {
|
|
51
|
+
bottom: "100%",
|
|
52
|
+
left: "50%",
|
|
53
|
+
transform: [{ translateX: -50 }],
|
|
54
|
+
marginBottom: theme.spacing[1],
|
|
55
|
+
},
|
|
56
|
+
arrow: {
|
|
57
|
+
top: "100%",
|
|
58
|
+
left: "50%",
|
|
59
|
+
transform: [{ translateX: -50 }],
|
|
60
|
+
borderTopColor: theme.colors.card,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
bottom: {
|
|
64
|
+
tooltip: {
|
|
65
|
+
top: "100%",
|
|
66
|
+
left: "50%",
|
|
67
|
+
transform: [{ translateX: -50 }],
|
|
68
|
+
marginTop: theme.spacing[1],
|
|
69
|
+
},
|
|
70
|
+
arrow: {
|
|
71
|
+
bottom: "100%",
|
|
72
|
+
left: "50%",
|
|
73
|
+
transform: [{ translateX: -50 }],
|
|
74
|
+
borderBottomColor: theme.colors.card,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
left: {
|
|
78
|
+
tooltip: {
|
|
79
|
+
right: "100%",
|
|
80
|
+
top: "50%",
|
|
81
|
+
transform: [{ translateY: -50 }],
|
|
82
|
+
marginRight: theme.spacing[1],
|
|
83
|
+
},
|
|
84
|
+
arrow: {
|
|
85
|
+
left: "100%",
|
|
86
|
+
top: "50%",
|
|
87
|
+
transform: [{ translateY: -50 }],
|
|
88
|
+
borderLeftColor: theme.colors.card,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
right: {
|
|
92
|
+
tooltip: {
|
|
93
|
+
left: "100%",
|
|
94
|
+
top: "50%",
|
|
95
|
+
transform: [{ translateY: -50 }],
|
|
96
|
+
marginLeft: theme.spacing[1],
|
|
97
|
+
},
|
|
98
|
+
arrow: {
|
|
99
|
+
right: "100%",
|
|
100
|
+
top: "50%",
|
|
101
|
+
transform: [{ translateY: -50 }],
|
|
102
|
+
borderRightColor: theme.colors.card,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const currentPosition =
|
|
108
|
+
positionStyles[position as keyof typeof positionStyles];
|
|
109
|
+
|
|
110
|
+
return StyleSheet.create({
|
|
111
|
+
container: {
|
|
112
|
+
position: "relative",
|
|
113
|
+
},
|
|
114
|
+
tooltip: {
|
|
115
|
+
position: "absolute",
|
|
116
|
+
backgroundColor: theme.colors.card,
|
|
117
|
+
borderRadius: theme.borderRadius.md,
|
|
118
|
+
padding: theme.spacing[2],
|
|
119
|
+
maxWidth: 200,
|
|
120
|
+
...theme.shadows.lg,
|
|
121
|
+
...currentPosition.tooltip,
|
|
122
|
+
zIndex: 1000,
|
|
123
|
+
},
|
|
124
|
+
tooltipText: {
|
|
125
|
+
color: theme.colors.text,
|
|
126
|
+
fontSize: 12,
|
|
127
|
+
lineHeight: 16,
|
|
128
|
+
textAlign: "left",
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -43,3 +43,6 @@ export * as Dashboard from "./components/dashboard";
|
|
|
43
43
|
// Storage exports
|
|
44
44
|
export { default as storage } from "./storage";
|
|
45
45
|
export type { AQStorage } from "./storage/storage.shared";
|
|
46
|
+
|
|
47
|
+
// Content metadata components
|
|
48
|
+
export * from "./components/content-metadata";
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { schemas } from "streamplace";
|
|
2
|
+
|
|
3
|
+
// Content warnings derived from lexicon schema
|
|
4
|
+
export const CONTENT_WARNINGS = (() => {
|
|
5
|
+
// Find the content warnings schema
|
|
6
|
+
const contentWarningsSchema = schemas.find(
|
|
7
|
+
(schema) => schema.id === "place.stream.metadata.contentWarnings",
|
|
8
|
+
);
|
|
9
|
+
if (!contentWarningsSchema?.defs) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"Could not find place.stream.metadata.contentWarnings schema",
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const contentWarningConstants = [
|
|
16
|
+
{ constant: "place.stream.metadata.contentWarnings#death", label: "Death" },
|
|
17
|
+
{
|
|
18
|
+
constant: "place.stream.metadata.contentWarnings#drugUse",
|
|
19
|
+
label: "Drug Use",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
constant: "place.stream.metadata.contentWarnings#fantasyViolence",
|
|
23
|
+
label: "Fantasy Violence",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
constant: "place.stream.metadata.contentWarnings#flashingLights",
|
|
27
|
+
label: "Flashing Lights",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
constant: "place.stream.metadata.contentWarnings#language",
|
|
31
|
+
label: "Language",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
constant: "place.stream.metadata.contentWarnings#nudity",
|
|
35
|
+
label: "Nudity",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
constant: "place.stream.metadata.contentWarnings#PII",
|
|
39
|
+
label: "Personally Identifiable Information",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
constant: "place.stream.metadata.contentWarnings#sexuality",
|
|
43
|
+
label: "Sexuality",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
constant: "place.stream.metadata.contentWarnings#suffering",
|
|
47
|
+
label: "Upsetting or Disturbing",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
constant: "place.stream.metadata.contentWarnings#violence",
|
|
51
|
+
label: "Violence",
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
return contentWarningConstants.map(({ constant, label }) => {
|
|
56
|
+
// Extract the key from the constant by splitting on '#'
|
|
57
|
+
const key = constant.split("#")[1];
|
|
58
|
+
const def = contentWarningsSchema.defs[key];
|
|
59
|
+
const description = def?.description || `Description for ${label}`;
|
|
60
|
+
return {
|
|
61
|
+
value: constant,
|
|
62
|
+
label: label,
|
|
63
|
+
description: description,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
})();
|
|
67
|
+
|
|
68
|
+
// License options derived from lexicon schema
|
|
69
|
+
export const LICENSE_OPTIONS = (() => {
|
|
70
|
+
// Find the content rights schema
|
|
71
|
+
const contentRightsSchema = schemas.find(
|
|
72
|
+
(schema) => schema.id === "place.stream.metadata.contentRights",
|
|
73
|
+
);
|
|
74
|
+
if (!contentRightsSchema?.defs) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"Could not find place.stream.metadata.contentRights schema",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const licenseConstants = [
|
|
81
|
+
{
|
|
82
|
+
constant: "place.stream.metadata.contentRights#all-rights-reserved",
|
|
83
|
+
label: "All Rights Reserved",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
constant: "place.stream.metadata.contentRights#cc0_1__0",
|
|
87
|
+
label: "CC0 (Public Domain) 1.0",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
constant: "place.stream.metadata.contentRights#cc-by_4__0",
|
|
91
|
+
label: "CC BY 4.0",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
constant: "place.stream.metadata.contentRights#cc-by-sa_4__0",
|
|
95
|
+
label: "CC BY-SA 4.0",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
constant: "place.stream.metadata.contentRights#cc-by-nc_4__0",
|
|
99
|
+
label: "CC BY-NC 4.0",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
constant: "place.stream.metadata.contentRights#cc-by-nc-sa_4__0",
|
|
103
|
+
label: "CC BY-NC-SA 4.0",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
constant: "place.stream.metadata.contentRights#cc-by-nd_4__0",
|
|
107
|
+
label: "CC BY-ND 4.0",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
constant: "place.stream.metadata.contentRights#cc-by-nc-nd_4__0",
|
|
111
|
+
label: "CC BY-NC-ND 4.0",
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const options = licenseConstants.map(({ constant, label }) => {
|
|
116
|
+
// Extract the key from the constant by splitting on '#'
|
|
117
|
+
const key = constant.split("#")[1];
|
|
118
|
+
const def = contentRightsSchema.defs[key];
|
|
119
|
+
const description = def?.description || `Description for ${label}`;
|
|
120
|
+
return {
|
|
121
|
+
value: constant,
|
|
122
|
+
label: label,
|
|
123
|
+
description: description,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Add custom license option
|
|
128
|
+
options.push({
|
|
129
|
+
value: "custom",
|
|
130
|
+
label: "Custom License",
|
|
131
|
+
description:
|
|
132
|
+
"Custom license. Define your own terms for how others can use, adapt, or share your content.",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return options;
|
|
136
|
+
})();
|
|
137
|
+
|
|
138
|
+
// License URL labels for C2PA manifests
|
|
139
|
+
export const LICENSE_URL_LABELS: Record<string, string> = {
|
|
140
|
+
"http://creativecommons.org/publicdomain/zero/1.0/":
|
|
141
|
+
"CC0 - Public Domain 1.0",
|
|
142
|
+
"http://creativecommons.org/licenses/by/4.0/": "CC BY - Attribution 4.0",
|
|
143
|
+
"http://creativecommons.org/licenses/by-sa/4.0/":
|
|
144
|
+
"CC BY-SA - Attribution ShareAlike 4.0",
|
|
145
|
+
"http://creativecommons.org/licenses/by-nc/4.0/":
|
|
146
|
+
"CC BY-NC - Attribution NonCommercial 4.0",
|
|
147
|
+
"http://creativecommons.org/licenses/by-nc-sa/4.0/":
|
|
148
|
+
"CC BY-NC-SA - Attribution NonCommercial ShareAlike 4.0",
|
|
149
|
+
"http://creativecommons.org/licenses/by-nd/4.0/":
|
|
150
|
+
"CC BY-ND - Attribution NoDerivatives 4.0",
|
|
151
|
+
"http://creativecommons.org/licenses/by-nc-nd/4.0/":
|
|
152
|
+
"CC BY-NC-ND - Attribution NonCommercial NoDerivatives 4.0",
|
|
153
|
+
"All rights reserved": "All Rights Reserved",
|
|
154
|
+
} as const;
|
|
155
|
+
|
|
156
|
+
// C2PA warning labels for content warnings
|
|
157
|
+
export const C2PA_WARNING_LABELS: Record<string, string> = {
|
|
158
|
+
"cwarn:death": "Death",
|
|
159
|
+
"cwarn:drugUse": "Drug Use",
|
|
160
|
+
"cwarn:fantasyViolence": "Fantasy Violence",
|
|
161
|
+
"cwarn:flashingLights": "Flashing Lights",
|
|
162
|
+
"cwarn:language": "Language",
|
|
163
|
+
"cwarn:nudity": "Nudity",
|
|
164
|
+
"cwarn:PII": "Personally Identifiable Information",
|
|
165
|
+
"cwarn:sexuality": "Sexuality",
|
|
166
|
+
"cwarn:suffering": "Upsetting or Disturbing",
|
|
167
|
+
"cwarn:violence": "Violence",
|
|
168
|
+
// Also support lexicon constants for backward compatibility
|
|
169
|
+
"place.stream.metadata.contentWarnings#death": "Death",
|
|
170
|
+
"place.stream.metadata.contentWarnings#drugUse": "Drug Use",
|
|
171
|
+
"place.stream.metadata.contentWarnings#fantasyViolence": "Fantasy Violence",
|
|
172
|
+
"place.stream.metadata.contentWarnings#flashingLights": "Flashing Lights",
|
|
173
|
+
"place.stream.metadata.contentWarnings#language": "Language",
|
|
174
|
+
"place.stream.metadata.contentWarnings#nudity": "Nudity",
|
|
175
|
+
"place.stream.metadata.contentWarnings#PII":
|
|
176
|
+
"Personally Identifiable Information",
|
|
177
|
+
"place.stream.metadata.contentWarnings#sexuality": "Sexuality",
|
|
178
|
+
"place.stream.metadata.contentWarnings#suffering": "Upsetting or Disturbing",
|
|
179
|
+
"place.stream.metadata.contentWarnings#violence": "Violence",
|
|
180
|
+
} as const;
|
package/src/lib/theme/theme.tsx
CHANGED
|
@@ -84,6 +84,10 @@ export interface Theme {
|
|
|
84
84
|
warning: string;
|
|
85
85
|
warningForeground: string;
|
|
86
86
|
|
|
87
|
+
// Info colors
|
|
88
|
+
info: string;
|
|
89
|
+
infoForeground: string;
|
|
90
|
+
|
|
87
91
|
// Border and input colors
|
|
88
92
|
border: string;
|
|
89
93
|
input: string;
|
|
@@ -344,18 +348,18 @@ function generateThemeColorsFromPalette(
|
|
|
344
348
|
accent: isDark ? palette[800] : palette[100],
|
|
345
349
|
accentForeground: isDark ? palette[50] : palette[900],
|
|
346
350
|
|
|
347
|
-
destructive:
|
|
348
|
-
Platform.OS === "ios" ? colors.ios.systemRed : colors.destructive[500],
|
|
351
|
+
destructive: colors.destructive[700],
|
|
349
352
|
destructiveForeground: colors.white,
|
|
350
353
|
|
|
351
|
-
success:
|
|
352
|
-
Platform.OS === "ios" ? colors.ios.systemGreen : colors.success[500],
|
|
354
|
+
success: colors.success[700],
|
|
353
355
|
successForeground: colors.white,
|
|
354
356
|
|
|
355
|
-
warning:
|
|
356
|
-
Platform.OS === "ios" ? colors.ios.systemOrange : colors.warning[500],
|
|
357
|
+
warning: colors.warning[700],
|
|
357
358
|
warningForeground: colors.white,
|
|
358
359
|
|
|
360
|
+
info: colors.blue[700],
|
|
361
|
+
infoForeground: isDark ? palette[50] : palette[900],
|
|
362
|
+
|
|
359
363
|
border: isDark ? palette[500] + "30" : palette[200] + "30",
|
|
360
364
|
input: isDark ? palette[800] : palette[200],
|
|
361
365
|
ring: Platform.OS === "ios" ? colors.ios.systemBlue : colors.primary[500],
|
package/src/lib/theme/tokens.ts
CHANGED
|
@@ -590,52 +590,61 @@ export const typography = {
|
|
|
590
590
|
},
|
|
591
591
|
|
|
592
592
|
// Universal typography scale
|
|
593
|
+
// Atkinson's center is weird so the marginBottom is there to correct it?
|
|
593
594
|
universal: {
|
|
594
595
|
xs: {
|
|
595
596
|
fontSize: 12,
|
|
596
597
|
lineHeight: 16,
|
|
598
|
+
marginBottom: -0.7,
|
|
597
599
|
fontWeight: "400" as const,
|
|
598
600
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
599
601
|
},
|
|
600
602
|
sm: {
|
|
601
603
|
fontSize: 14,
|
|
602
604
|
lineHeight: 20,
|
|
605
|
+
marginBottom: -1,
|
|
603
606
|
fontWeight: "400" as const,
|
|
604
607
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
605
608
|
},
|
|
606
609
|
base: {
|
|
607
610
|
fontSize: 16,
|
|
608
611
|
lineHeight: 24,
|
|
612
|
+
marginBottom: -1.2,
|
|
609
613
|
fontWeight: "400" as const,
|
|
610
614
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
611
615
|
},
|
|
612
616
|
lg: {
|
|
613
617
|
fontSize: 18,
|
|
614
618
|
lineHeight: 28,
|
|
619
|
+
marginBottom: -1.5,
|
|
615
620
|
fontWeight: "400" as const,
|
|
616
621
|
fontFamily: "AtkinsonHyperlegibleNext-Regular",
|
|
617
622
|
},
|
|
618
623
|
xl: {
|
|
619
624
|
fontSize: 20,
|
|
620
625
|
lineHeight: 28,
|
|
626
|
+
marginBottom: -1.75,
|
|
621
627
|
fontWeight: "500" as const,
|
|
622
628
|
fontFamily: "AtkinsonHyperlegibleNext-Medium",
|
|
623
629
|
},
|
|
624
630
|
"2xl": {
|
|
625
631
|
fontSize: 24,
|
|
626
632
|
lineHeight: 32,
|
|
633
|
+
marginBottom: -2,
|
|
627
634
|
fontWeight: "600" as const,
|
|
628
635
|
fontFamily: "AtkinsonHyperlegibleNext-SemiBold",
|
|
629
636
|
},
|
|
630
637
|
"3xl": {
|
|
631
638
|
fontSize: 30,
|
|
632
639
|
lineHeight: 36,
|
|
640
|
+
marginBottom: -2.5,
|
|
633
641
|
fontWeight: "700" as const,
|
|
634
642
|
fontFamily: "AtkinsonHyperlegibleNext-Bold",
|
|
635
643
|
},
|
|
636
644
|
"4xl": {
|
|
637
645
|
fontSize: 36,
|
|
638
646
|
lineHeight: 40,
|
|
647
|
+
marginBottom: -3,
|
|
639
648
|
fontWeight: "700" as const,
|
|
640
649
|
fontFamily: "AtkinsonHyperlegibleNext-ExtraBold",
|
|
641
650
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SessionManager } from "@atproto/api/dist/session-manager";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
|
+
import { useGetChatProfile } from "../streamplace-store";
|
|
3
4
|
import { makeStreamplaceStore } from "../streamplace-store/streamplace-store";
|
|
4
5
|
import { StreamplaceContext } from "./context";
|
|
5
6
|
import Poller from "./poller";
|
|
@@ -13,7 +14,6 @@ export function StreamplaceProvider({
|
|
|
13
14
|
url: string;
|
|
14
15
|
oauthSession?: SessionManager | null;
|
|
15
16
|
}) {
|
|
16
|
-
console.log("session in provider is", oauthSession);
|
|
17
17
|
// todo: handle url changes?
|
|
18
18
|
const store = useRef(makeStreamplaceStore({ url })).current;
|
|
19
19
|
|
|
@@ -27,7 +27,25 @@ export function StreamplaceProvider({
|
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<StreamplaceContext.Provider value={{ store: store }}>
|
|
30
|
-
<
|
|
30
|
+
<ChatProfileCreator oauthSession={oauthSession}>
|
|
31
|
+
<Poller>{children}</Poller>
|
|
32
|
+
</ChatProfileCreator>
|
|
31
33
|
</StreamplaceContext.Provider>
|
|
32
34
|
);
|
|
33
35
|
}
|
|
36
|
+
|
|
37
|
+
export function ChatProfileCreator({
|
|
38
|
+
oauthSession,
|
|
39
|
+
children,
|
|
40
|
+
}: {
|
|
41
|
+
oauthSession?: SessionManager | null;
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
}) {
|
|
44
|
+
const getChatProfile = useGetChatProfile();
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (oauthSession) {
|
|
47
|
+
getChatProfile();
|
|
48
|
+
}
|
|
49
|
+
}, [oauthSession]);
|
|
50
|
+
return <>{children}</>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContentMetadataResult,
|
|
3
|
+
useDID,
|
|
4
|
+
useSetContentMetadata,
|
|
5
|
+
} from "./streamplace-store";
|
|
6
|
+
import { usePDSAgent } from "./xrpc";
|
|
7
|
+
|
|
8
|
+
export const useSaveContentMetadata = () => {
|
|
9
|
+
const pdsAgent = usePDSAgent();
|
|
10
|
+
const did = useDID();
|
|
11
|
+
const setContentMetadata = useSetContentMetadata();
|
|
12
|
+
|
|
13
|
+
return async (params: {
|
|
14
|
+
contentWarnings?: string[];
|
|
15
|
+
distributionPolicy?: { deleteAfter?: number };
|
|
16
|
+
contentRights?: Record<string, any>;
|
|
17
|
+
rkey?: string;
|
|
18
|
+
}) => {
|
|
19
|
+
if (!pdsAgent || !did) {
|
|
20
|
+
throw new Error("No PDS agent or DID available");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const metadataRecord = {
|
|
24
|
+
$type: "place.stream.metadata.configuration",
|
|
25
|
+
createdAt: new Date().toISOString(),
|
|
26
|
+
...(params.contentWarnings &&
|
|
27
|
+
params.contentWarnings.length > 0 && {
|
|
28
|
+
contentWarnings: { warnings: params.contentWarnings },
|
|
29
|
+
}),
|
|
30
|
+
...(params.distributionPolicy &&
|
|
31
|
+
params.distributionPolicy.deleteAfter && {
|
|
32
|
+
distributionPolicy: params.distributionPolicy,
|
|
33
|
+
}),
|
|
34
|
+
...(params.contentRights &&
|
|
35
|
+
Object.keys(params.contentRights).length > 0 && {
|
|
36
|
+
contentRights: params.contentRights,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const rkey = params.rkey || "self";
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Try to update existing record first
|
|
44
|
+
const result = await (pdsAgent as any).com.atproto.repo.putRecord({
|
|
45
|
+
repo: did,
|
|
46
|
+
collection: "place.stream.metadata.configuration",
|
|
47
|
+
rkey,
|
|
48
|
+
record: metadataRecord,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const contentMetadata: ContentMetadataResult = {
|
|
52
|
+
record: metadataRecord as any,
|
|
53
|
+
uri: result.data.uri,
|
|
54
|
+
cid: result.data.cid || "",
|
|
55
|
+
rkey,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
setContentMetadata(contentMetadata);
|
|
59
|
+
return contentMetadata;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// If record doesn't exist, create it
|
|
62
|
+
if (
|
|
63
|
+
error instanceof Error &&
|
|
64
|
+
(error.message?.includes("not found") ||
|
|
65
|
+
error.message?.includes("RecordNotFound") ||
|
|
66
|
+
error.message?.includes("mst: not found") ||
|
|
67
|
+
(error as any)?.status === 404)
|
|
68
|
+
) {
|
|
69
|
+
const createResult = await (
|
|
70
|
+
pdsAgent as any
|
|
71
|
+
).com.atproto.repo.createRecord({
|
|
72
|
+
repo: did,
|
|
73
|
+
collection: "place.stream.metadata.configuration",
|
|
74
|
+
rkey,
|
|
75
|
+
record: metadataRecord,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const contentMetadata: ContentMetadataResult = {
|
|
79
|
+
record: metadataRecord as any,
|
|
80
|
+
uri: createResult.data.uri,
|
|
81
|
+
cid: createResult.data.cid || "",
|
|
82
|
+
rkey,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
setContentMetadata(contentMetadata);
|
|
86
|
+
return contentMetadata;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Simple get function
|
|
94
|
+
export const useGetContentMetadata = () => {
|
|
95
|
+
const pdsAgent = usePDSAgent();
|
|
96
|
+
const did = useDID();
|
|
97
|
+
const setContentMetadata = useSetContentMetadata();
|
|
98
|
+
|
|
99
|
+
return async (params?: { userDid?: string; rkey?: string }) => {
|
|
100
|
+
if (!pdsAgent) {
|
|
101
|
+
throw new Error("No PDS agent available");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const targetDid = params?.userDid || did;
|
|
105
|
+
if (!targetDid) {
|
|
106
|
+
throw new Error("No DID provided or user not authenticated");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = await (pdsAgent as any).com.atproto.repo.getRecord({
|
|
111
|
+
repo: targetDid,
|
|
112
|
+
collection: "place.stream.metadata.configuration",
|
|
113
|
+
rkey: params?.rkey || "self",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
throw new Error("Failed to get content metadata record");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const contentMetadata: ContentMetadataResult = {
|
|
121
|
+
record: result.data.value,
|
|
122
|
+
uri: result.data.uri,
|
|
123
|
+
cid: result.data.cid || "",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
setContentMetadata(contentMetadata);
|
|
127
|
+
return contentMetadata;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// Handle record not found - this is normal for new users
|
|
130
|
+
if (
|
|
131
|
+
error instanceof Error &&
|
|
132
|
+
(error.message?.includes("not found") ||
|
|
133
|
+
error.message?.includes("RecordNotFound") ||
|
|
134
|
+
error.message?.includes("mst: not found") ||
|
|
135
|
+
(error as any)?.status === 404)
|
|
136
|
+
) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
};
|