|
1 | | -import { Box, Button, config, Icon, Icons, Scroll, Text } from 'folds'; |
| 1 | +import { Box, Button, config, Icon, Icons, Menu, MenuItem, Scroll, Text, toRem } from 'folds'; |
2 | 2 | import { SyntheticEvent, useCallback, useMemo, useState } from 'react'; |
3 | 3 | import { useNavigate } from 'react-router-dom'; |
4 | 4 | import { useAtomValue } from 'jotai'; |
@@ -35,6 +35,7 @@ import { getSettings, settingsAtom } from '$state/settings'; |
35 | 35 | import { filterPronounsByLanguage } from '$utils/pronouns'; |
36 | 36 | import { useSetting } from '$state/hooks/settings'; |
37 | 37 | import { useSettingsLinkBaseUrl } from '$features/settings/useSettingsLinkBaseUrl'; |
| 38 | +import { TextViewerContent } from '$components/text-viewer'; |
38 | 39 | import { CreatorChip } from './CreatorChip'; |
39 | 40 | import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration'; |
40 | 41 | import { PowerChip } from './PowerChip'; |
@@ -67,11 +68,8 @@ function UserExtendedSection({ |
67 | 68 | htmlReactParserOptions, |
68 | 69 | linkifyOpts, |
69 | 70 | }: Readonly<UserExtendedSectionProps>) { |
70 | | - const clamp = (str: any, len: number) => { |
71 | | - const stringified = String(str ?? ''); |
72 | | - return stringified.length > len ? `${stringified.slice(0, len)}...` : stringified; |
73 | | - }; |
74 | | - const [showMore, setShowMore] = useState(false); |
| 71 | + const [showMisc, setShowMisc] = useState(false); |
| 72 | + const [miscDataIndex, setMiscDataIndex] = useState(-1); |
75 | 73 |
|
76 | 74 | const [renderAnimals] = useSetting(settingsAtom, 'renderAnimals'); |
77 | 75 | const isCat = profile.isCat === true; |
@@ -148,6 +146,73 @@ function UserExtendedSection({ |
148 | 146 | ([key]) => !KNOWN_KEYS.includes(key) |
149 | 147 | ); |
150 | 148 |
|
| 149 | + function handleMiscSelector(index: number) { |
| 150 | + setMiscDataIndex(index); |
| 151 | + setShowMisc(false); |
| 152 | + } |
| 153 | + |
| 154 | + const miscSelector = useMemo(() => { |
| 155 | + if (unknownFields.length === 1 && showMisc) { |
| 156 | + setShowMisc(false); |
| 157 | + setMiscDataIndex(miscDataIndex === -1 ? 0 : -1); |
| 158 | + return null; |
| 159 | + } |
| 160 | + return ( |
| 161 | + <Menu style={{ position: 'absolute', zIndex: '100', transform: `translateY(${toRem(32)})` }}> |
| 162 | + <MenuItem |
| 163 | + size="300" |
| 164 | + radii="300" |
| 165 | + fill="None" |
| 166 | + variant="Primary" |
| 167 | + style={{ justifyContent: 'Center', textAlign: 'center' }} |
| 168 | + onClick={() => handleMiscSelector(-1)} |
| 169 | + > |
| 170 | + <Icon src={Icons.ChevronTop} size="50" /> |
| 171 | + <Text>Show less</Text> |
| 172 | + </MenuItem> |
| 173 | + {unknownFields.map(([key], index) => ( |
| 174 | + <MenuItem |
| 175 | + size="300" |
| 176 | + radii="300" |
| 177 | + fill="None" |
| 178 | + variant="Secondary" |
| 179 | + style={{ justifyContent: 'Center' }} |
| 180 | + onClick={() => handleMiscSelector(index)} |
| 181 | + > |
| 182 | + <Text>{key}</Text> |
| 183 | + </MenuItem> |
| 184 | + ))} |
| 185 | + </Menu> |
| 186 | + ); |
| 187 | + }, [miscDataIndex, showMisc, unknownFields]); |
| 188 | + const miscHeader = useMemo( |
| 189 | + () => ( |
| 190 | + <Box justifyContent="Center" grow="Yes"> |
| 191 | + <Button |
| 192 | + variant="Secondary" |
| 193 | + size="300" |
| 194 | + fill="None" |
| 195 | + onClick={() => setShowMisc(!showMisc)} |
| 196 | + after={miscDataIndex === -1 && <Icon size="50" src={Icons.ChevronBottom} />} |
| 197 | + style={{ |
| 198 | + padding: '1rem', |
| 199 | + justifyContent: 'flex-start', |
| 200 | + width: 'fit-content', |
| 201 | + textAlign: 'center', |
| 202 | + }} |
| 203 | + > |
| 204 | + <Text size="T200" priority="400"> |
| 205 | + {miscDataIndex === -1 |
| 206 | + ? `Show Misc. Data (${unknownFields.length} value${unknownFields.length > 1 ? 's' : ''})` |
| 207 | + : `${unknownFields[miscDataIndex][0]} ${unknownFields.length > 1 ? `(${miscDataIndex + 1}/${unknownFields.length})` : ''}`} |
| 208 | + </Text> |
| 209 | + </Button> |
| 210 | + {showMisc && miscSelector} |
| 211 | + </Box> |
| 212 | + ), |
| 213 | + [miscSelector, miscDataIndex, showMisc, unknownFields] |
| 214 | + ); |
| 215 | + |
151 | 216 | return ( |
152 | 217 | <Box direction="Column" gap="200" style={{ marginBottom: config.space.S100 }}> |
153 | 218 | {(pronouns || localTime) && ( |
@@ -209,39 +274,66 @@ function UserExtendedSection({ |
209 | 274 |
|
210 | 275 | {unknownFields.length > 0 && ( |
211 | 276 | <Box direction="Column" gap="100"> |
212 | | - <Button |
213 | | - variant="Secondary" |
214 | | - size="300" |
215 | | - fill="None" |
216 | | - onClick={() => setShowMore(!showMore)} |
217 | | - after={<Icon size="50" src={showMore ? Icons.ChevronTop : Icons.ChevronBottom} />} |
218 | | - style={{ padding: '1rem', justifyContent: 'flex-start', width: 'fit-content' }} |
219 | | - > |
220 | | - <Text size="T200" priority="400"> |
221 | | - {showMore ? 'Show less' : `+ ${unknownFields.length} more info`} |
222 | | - </Text> |
223 | | - </Button> |
224 | | - |
225 | | - {showMore && ( |
226 | | - <Box |
227 | | - direction="Column" |
| 277 | + {miscDataIndex === -1 && miscHeader} |
| 278 | + {miscDataIndex > -1 && ( |
| 279 | + <div |
228 | 280 | style={{ |
229 | | - padding: config.space.S200, |
230 | | - backgroundColor: 'var(--sable-surface-container)', |
| 281 | + border: '2px solid', |
| 282 | + backgroundColor: 'var(--sable-bg-container)', |
| 283 | + borderColor: 'var(--sable-surface-container-line)', |
231 | 284 | borderRadius: config.radii.R400, |
232 | 285 | }} |
233 | 286 | > |
234 | | - {unknownFields.map(([key, value]) => ( |
235 | | - <Box key={key} direction="Column" style={{ marginBottom: config.space.S100 }}> |
236 | | - <Text size="T200" priority="400" style={{ letterSpacing: '0.05em' }}> |
237 | | - {key} |
238 | | - </Text> |
239 | | - <Text size="T200" priority="300"> |
240 | | - {clamp(renderValue(value), 128)} |
241 | | - </Text> |
| 287 | + <Box |
| 288 | + direction="Row" |
| 289 | + justifyContent="Center" |
| 290 | + alignContent="Center" |
| 291 | + style={{ |
| 292 | + borderRadius: config.radii.R400, |
| 293 | + }} |
| 294 | + > |
| 295 | + {unknownFields.length > 1 && ( |
| 296 | + <Button |
| 297 | + variant="Secondary" |
| 298 | + size="300" |
| 299 | + fill="None" |
| 300 | + onClick={() => |
| 301 | + setMiscDataIndex( |
| 302 | + miscDataIndex === 0 ? unknownFields.length - 1 : miscDataIndex - 1 |
| 303 | + ) |
| 304 | + } |
| 305 | + > |
| 306 | + <Icon src={Icons.ArrowLeft} size="50" /> |
| 307 | + </Button> |
| 308 | + )} |
| 309 | + {miscHeader} |
| 310 | + {unknownFields.length > 1 && ( |
| 311 | + <Button |
| 312 | + variant="Secondary" |
| 313 | + size="300" |
| 314 | + fill="None" |
| 315 | + onClick={() => setMiscDataIndex((miscDataIndex + 1) % unknownFields.length)} |
| 316 | + > |
| 317 | + <Icon src={Icons.ArrowRight} size="50" /> |
| 318 | + </Button> |
| 319 | + )} |
| 320 | + </Box> |
| 321 | + <Scroll size="300" direction="Both"> |
| 322 | + <Box |
| 323 | + direction="Column" |
| 324 | + style={{ |
| 325 | + padding: config.space.S200, |
| 326 | + borderRadius: config.radii.R400, |
| 327 | + maxHeight: toRem(100), |
| 328 | + }} |
| 329 | + > |
| 330 | + <TextViewerContent |
| 331 | + text={renderValue(unknownFields[miscDataIndex][1])} |
| 332 | + langName="json" |
| 333 | + /> |
242 | 334 | </Box> |
243 | | - ))} |
244 | | - </Box> |
| 335 | + </Scroll> |
| 336 | + </div> |
245 | 337 | )} |
246 | 338 | </Box> |
247 | 339 | )} |
@@ -382,7 +474,7 @@ export function UserRoomProfile({ userId, initialProfile }: Readonly<UserRoomPro |
382 | 474 | htmlReactParserOptions={htmlReactParserOptions} |
383 | 475 | linkifyOpts={linkifyOpts} |
384 | 476 | /> |
385 | | - <Box alignItems="Center" gap="100" wrap="Wrap"> |
| 477 | + <Box alignItems="Center" gap="100" wrap="Wrap" justifyContent="Center"> |
386 | 478 | {server && <ServerChip server={server} />} |
387 | 479 | <ShareChip userId={userId} /> |
388 | 480 | {creator ? <CreatorChip /> : <PowerChip userId={userId} />} |
|
0 commit comments