diff options
author | devin ivy <devinivy@gmail.com> | 2024-06-20 17:45:52 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-20 17:45:52 -0400 |
commit | 51f5e6bf900685ef92191f22949d09035733c682 (patch) | |
tree | 7e613992c1b131f4fe082a794ae9c32555d87b12 /bskyogcard/src/components | |
parent | eac4668d7312b35721e147e808c181b2be0256bf (diff) | |
download | voidsky-51f5e6bf900685ef92191f22949d09035733c682.tar.zst |
Bsky link card service (#4547)
* setup bskycard * quick proof of concept for png card generation * bskycard: use jsx * bskycard: 3x5 profile layout * bskycard: add butterfly overlay * bskycard: tidy * bskycard: separate and reorganize * bskycard: tidy * bskycard: tidy * bskycard: tidy * bskycard: poc of transparent overlay and box shadow * bskycard: reorg impl into src/ directory * bskycard: use more standard app structure * bskycard: setup dockerfile, fix build * bskycard: support for x-origin-verify * bskycard: card layout, filter images based on labels * bskycard: tidy * bskycard: support cluster mode * bskycard: handle error fetching starter pack info * bskycard: tidy * bskycard: fix leak on failed image fetch * bskycard: build workflow * bskyogcard: rename from bskycard * bskyogcard: fix some express plumbing * bskyogcard: add cdn tags, tidy
Diffstat (limited to 'bskyogcard/src/components')
-rw-r--r-- | bskyogcard/src/components/Butterfly.tsx | 16 | ||||
-rw-r--r-- | bskyogcard/src/components/Img.tsx | 10 | ||||
-rw-r--r-- | bskyogcard/src/components/StarterPack.tsx | 149 |
3 files changed, 175 insertions, 0 deletions
diff --git a/bskyogcard/src/components/Butterfly.tsx b/bskyogcard/src/components/Butterfly.tsx new file mode 100644 index 000000000..5a4124975 --- /dev/null +++ b/bskyogcard/src/components/Butterfly.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +export function Butterfly(props: React.SVGAttributes<SVGSVGElement>) { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 568 501" + {...props}> + <path + fill="currentColor" + d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z" + /> + </svg> + ) +} diff --git a/bskyogcard/src/components/Img.tsx b/bskyogcard/src/components/Img.tsx new file mode 100644 index 000000000..dac223180 --- /dev/null +++ b/bskyogcard/src/components/Img.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +export function Img( + props: Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src'> & {src: Buffer}, +) { + const {src, ...others} = props + return ( + <img {...others} src={`data:image/jpeg;base64,${src.toString('base64')}`} /> + ) +} diff --git a/bskyogcard/src/components/StarterPack.tsx b/bskyogcard/src/components/StarterPack.tsx new file mode 100644 index 000000000..f73442190 --- /dev/null +++ b/bskyogcard/src/components/StarterPack.tsx @@ -0,0 +1,149 @@ +/* eslint-disable bsky-internal/avoid-unwrapped-text */ +import React from 'react' +import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' + +import {Butterfly} from './Butterfly.js' +import {Img} from './Img.js' + +export const STARTERPACK_HEIGHT = 630 +export const STARTERPACK_WIDTH = 1200 +export const TILE_SIZE = STARTERPACK_HEIGHT / 3 + +const GRADIENT_TOP = '#0A7AFF' +const GRADIENT_BOTTOM = '#59B9FF' +const IMAGE_STROKE = '#359CFF' + +export function StarterPack(props: { + starterPack: AppBskyGraphDefs.StarterPackView + images: Map<string, Buffer> +}) { + const {starterPack, images} = props + const record = AppBskyGraphStarterpack.isRecord(starterPack.record) + ? starterPack.record + : null + const imagesArray = [...images.values()] + const imageOfCreator = images.get(starterPack.creator.did) + const imagesExceptCreator = [...images.entries()] + .filter(([did]) => did !== starterPack.creator.did) + .map(([, image]) => image) + const imagesAcross: Buffer[] = [] + if (imageOfCreator) { + if (imagesExceptCreator.length >= 6) { + imagesAcross.push(...imagesExceptCreator.slice(0, 3)) + imagesAcross.push(imageOfCreator) + imagesAcross.push(...imagesExceptCreator.slice(3, 6)) + } else { + const firstHalf = Math.floor(imagesExceptCreator.length / 2) + imagesAcross.push(...imagesExceptCreator.slice(0, firstHalf)) + imagesAcross.push(imageOfCreator) + imagesAcross.push( + ...imagesExceptCreator.slice(firstHalf, imagesExceptCreator.length), + ) + } + } else { + imagesAcross.push(...imagesExceptCreator.slice(0, 7)) + } + return ( + <div + style={{ + display: 'flex', + justifyContent: 'center', + width: STARTERPACK_WIDTH, + height: STARTERPACK_HEIGHT, + backgroundColor: 'black', + color: 'white', + fontFamily: 'Inter', + }}> + {/* image tiles */} + <div + style={{ + display: 'flex', + flexWrap: 'wrap', + alignItems: 'stretch', + width: TILE_SIZE * 6, + height: TILE_SIZE * 3, + }}> + {[...Array(18)].map((_, i) => { + const image = imagesArray.at(i % imagesArray.length) + return ( + <div + key={i} + style={{ + display: 'flex', + height: TILE_SIZE, + width: TILE_SIZE, + }}> + {image && <Img height="100%" width="100%" src={image} />} + </div> + ) + })} + {/* background overlay */} + <div + style={{ + display: 'flex', + width: '100%', + height: '100%', + position: 'absolute', + backgroundImage: `linear-gradient(to bottom, ${GRADIENT_TOP}, ${GRADIENT_BOTTOM})`, + opacity: 0.9, + }} + /> + </div> + {/* foreground text & images */} + <div + style={{ + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + width: '100%', + height: '100%', + position: 'absolute', + color: 'white', + }}> + <div + style={{ + color: 'white', + padding: 60, + fontSize: 40, + }}> + JOIN THE CONVERSATION + </div> + <div style={{display: 'flex'}}> + {imagesAcross.map((image, i) => { + return ( + <div + key={i} + style={{ + display: 'flex', + height: 172 + 15 * 2, + width: 172 + 15 * 2, + margin: -15, + border: `15px solid ${IMAGE_STROKE}`, + borderRadius: '50%', + overflow: 'hidden', + }}> + <Img height="100%" width="100%" src={image} /> + </div> + ) + })} + </div> + <div + style={{ + padding: '75px 30px 0px', + fontSize: 65, + }}> + {record?.name || 'Starter Pack'} + </div> + <div + style={{ + display: 'flex', + fontSize: 40, + justifyContent: 'center', + padding: '30px 30px 10px', + }}> + on <Butterfly width="65" style={{margin: '-7px 10px 0'}} /> Bluesky + </div> + </div> + </div> + ) +} |