Skip to content

Commit

Permalink
Refactor to new arch
Browse files Browse the repository at this point in the history
  • Loading branch information
estrattonbailey committed Jan 10, 2025
1 parent 5ffabaf commit 00c0ce6
Show file tree
Hide file tree
Showing 11 changed files with 696 additions and 505 deletions.
81 changes: 81 additions & 0 deletions bskyogcard/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module.exports = {
root: true,
extends: [
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
'react',
'simple-import-sort',
'eslint-plugin-react-compiler',
],
rules: {
'react/no-unescaped-entities': 0,
'react/prop-types': 0,
'react-native/no-inline-styles': 0,
'simple-import-sort/imports': [
'error',
{
groups: [
// Side effect imports.
['^\\u0000'],
// Node.js builtins prefixed with `node:`.
['^node:'],
// Packages.
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
// React/React Native priortized, followed by expo
// Followed by all packages excluding unprefixed relative ones
[
'^(react\\/(.*)$)|^(react$)|^(react-native(.*)$)',
'^(expo(.*)$)|^(expo$)',
'^(?!(?:alf|components|lib|locale|logger|platform|screens|state|view)(?:$|\\/))@?\\w',
],
// Relative imports.
// Ideally, anything that starts with a dot or #
// due to unprefixed relative imports being used, we whitelist the relative paths we use
// (?:$|\\/) matches end of string or /
[
'^(?:#\\/)?(?:lib|state|logger|platform|locale)(?:$|\\/)',
'^(?:#\\/)?view(?:$|\\/)',
'^(?:#\\/)?screens(?:$|\\/)',
'^(?:#\\/)?alf(?:$|\\/)',
'^(?:#\\/)?components(?:$|\\/)',
'^#\\/',
'^\\.',
],
// anything else - hopefully we don't have any of these
['^'],
],
},
],
'simple-import-sort/exports': 'error',
'react-compiler/react-compiler': 'warn',
'no-restricted-imports': [
'error',
{
paths: [
{
name: '@atproto/api',
importNames: ['moderatePost'],
message:
'Please use `moderatePost_wrapped` from `#/lib/moderatePost_wrapped` instead.',
},
],
},
],
},
ignorePatterns: [
'coverage',
'*.lock',
'.husky',
'*.html',
],
parserOptions: {
sourceType: 'module',
ecmaVersion: 'latest',
},
}

74 changes: 74 additions & 0 deletions bskyogcard/src/components/FeedCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {AppBskyFeedDefs, moderateFeedGenerator} from '@atproto/api'

import {ModeratorData} from '../data/getModeratorData.js'
import {PostData} from '../data/getPostData.js'
import {atoms as a, theme as t} from '../theme/index.js'
import {formatCount} from '../util/formatCount.js'
import {getModerationCauseInfo} from '../util/getModerationCauseInfo.js'
import {Box} from './Box.js'
import {Image} from './Image.js'
import {ModeratedEmbed} from './ModeratedEmbed.js'
import {Text} from './Text.js'

export function FeedCard({
embed,
data,
moderatorData,
}: {
embed: AppBskyFeedDefs.GeneratorView
data: PostData
moderatorData: ModeratorData
}) {
const feedModeration = moderateFeedGenerator(
embed,
moderatorData.moderationOptions,
)
const modui = feedModeration.ui('contentList')
const info = getModerationCauseInfo({
cause: modui.blurs.at(0),
moderatorData,
})

if (info) {
return <ModeratedEmbed info={info} />
}

const {avatar, displayName, likeCount = 0, creator} = embed
const image = data.images.get(avatar || '')

return (
<Box
cx={[
a.w_full,
a.gap_sm,
a.rounded_sm,
a.p_md,
a.border,
t.atoms.border_contrast_low,
]}>
<Box cx={[a.flex_row, a.align_center, a.gap_sm]}>
{image && (
<Image
image={image}
cx={[
a.rounded_sm,
{
width: '40px',
},
]}
/>
)}
<Box cx={[a.pt_2xs]}>
<Text cx={[a.text_md, a.font_bold, a.pb_2xs]}>{displayName}</Text>
<Text cx={[a.text_sm, a.leading_snug]}>By @{creator.handle}</Text>
</Box>
</Box>

{likeCount > 1 && (
<Text cx={[a.text_sm, a.leading_snug]}>
Liked by {formatCount(likeCount)} users
</Text>
)}
</Box>
)
}
28 changes: 27 additions & 1 deletion bskyogcard/src/components/Image.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react'

import {Image as ImageSource} from '../data/getPostData.js'
import {style as s} from '../theme/index.js'
import {atoms as a, style as s, theme as t} from '../theme/index.js'
import {Box} from './Box.js'

export type ImageProps = Omit<
React.ImgHTMLAttributes<HTMLImageElement>,
Expand All @@ -21,3 +22,28 @@ export function Image({image, cx, ...rest}: ImageProps) {
/>
)
}

export function SquareImage({image}: {image: ImageSource}) {
return (
<Box
cx={[
a.relative,
a.rounded_sm,
a.overflow_hidden,
a.w_full,
t.atoms.bg_contrast_25,
{paddingTop: '100%'},
]}>
<Image
image={image}
cx={[
a.absolute,
a.inset_0,
{
objectFit: 'cover',
},
]}
/>
</Box>
)
}
59 changes: 59 additions & 0 deletions bskyogcard/src/components/LinkCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {Image as ImageSource} from '../data/getPostData.js'
import {atoms as a, theme as t} from '../theme/index.js'
import {toShortUrl} from '../util/toShortUrl.js'
import {Box} from './Box.js'
import {Image} from './Image.js'
import {Text} from './Text.js'

export function LinkCard({
image,
uri,
title,
description,
}: {
image?: ImageSource
uri?: string
title: string
description: string
}) {
return (
<Box
cx={[
a.w_full,
a.rounded_sm,
a.overflow_hidden,
a.border,
t.atoms.border_contrast_low,
]}>
{image && (
<Box
cx={[
a.relative,
a.w_full,
t.atoms.bg_contrast_25,
{paddingTop: (630 / 1200) * 100 + '%'},
]}>
<Image
image={image}
cx={[
a.absolute,
a.inset_0,
{
objectFit: 'cover',
},
]}
/>
</Box>
)}
<Box cx={[a.p_md, t.atoms.bg]}>
{uri && (
<Text cx={[a.text_xs, a.pb_sm, t.atoms.text_contrast_medium]}>
{toShortUrl(uri)}
</Text>
)}
<Text cx={[a.text_md, a.font_bold, a.pb_xs]}>{title}</Text>
<Text cx={[a.text_sm, a.leading_snug]}>{description}</Text>
</Box>
</Box>
)
}
67 changes: 67 additions & 0 deletions bskyogcard/src/components/ListCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {AppBskyGraphDefs, moderateUserList} from '@atproto/api'

import {ModeratorData} from '../data/getModeratorData.js'
import {PostData} from '../data/getPostData.js'
import {atoms as a, theme as t} from '../theme/index.js'
import {getModerationCauseInfo} from '../util/getModerationCauseInfo.js'
import {Box} from './Box.js'
import {Image} from './Image.js'
import {ModeratedEmbed} from './ModeratedEmbed.js'
import {Text} from './Text.js'

export function ListCard({
embed,
data,
moderatorData,
}: {
embed: AppBskyGraphDefs.ListView
data: PostData
moderatorData: ModeratorData
}) {
const listModeration = moderateUserList(
embed,
moderatorData.moderationOptions,
)
const modui = listModeration.ui('contentList')
const info = getModerationCauseInfo({
cause: modui.blurs.at(0),
moderatorData,
})

if (info) {
return <ModeratedEmbed info={info} />
}

const {avatar, name, creator} = embed
const image = data.images.get(avatar || '')

return (
<Box
cx={[
a.w_full,
a.gap_sm,
a.rounded_sm,
a.p_md,
a.border,
t.atoms.border_contrast_low,
]}>
<Box cx={[a.flex_row, a.align_center, a.gap_sm]}>
{image && (
<Image
image={image}
cx={[
a.rounded_sm,
{
width: '40px',
},
]}
/>
)}
<Box cx={[a.pt_2xs]}>
<Text cx={[a.text_md, a.font_bold, a.pb_2xs]}>{name}</Text>
<Text cx={[a.text_sm, a.leading_snug]}>By @{creator.handle}</Text>
</Box>
</Box>
</Box>
)
}
25 changes: 25 additions & 0 deletions bskyogcard/src/components/ModeratedEmbed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {atoms as a, theme as t} from '../theme/index.js'
import {ModerationCauseInfo} from '../util/getModerationCauseInfo.js'
import {Box} from './Box.js'
import {Text} from './Text.js'

export function ModeratedEmbed({info}: {info: ModerationCauseInfo}) {
return (
<Box
cx={[
a.flex_row,
a.align_center,
a.gap_sm,
a.rounded_sm,
a.p_md,
t.atoms.bg_contrast_25,
]}>
<info.icon size={20} fill={t.atoms.text_contrast_low.color} />
<Box cx={[a.gap_xs]}>
<Text cx={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
{info.name}
</Text>
</Box>
</Box>
)
}
Loading

0 comments on commit 00c0ce6

Please sign in to comment.