import React from "react"; import { CAL_URL, LOGO } from "./constants"; // Ensures tw prop is typed. declare module "react" { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface HTMLAttributes { tw?: string; } } export interface MeetingImageProps { title: string; profile: { name: string; image?: string | null }; users?: { name: string; username: string }[]; } export interface AppImageProps { name: string; description: string; slug: string; } export interface GenericImageProps { title: string; description: string; } export interface ScreenshotImageProps { image: string; /** * Fallback image to use if the image prop fails to load. */ fallbackImage: string; } interface WrapperProps { children: React.ReactNode; variant?: "light" | "dark"; rotateBackground?: boolean; } const joinMultipleNames = (names: string[] = []) => { const lastName = names.pop(); return `${names.length > 0 ? `${names.join(", ")} & ${lastName}` : lastName}`; }; /** * Test urls: * 1. 1 user http://localhost:3000/api/social/og/image?type=meeting&title=super%20long%20event%20title%20for%20testing%20purposes&meetingProfileName=Pro%20Example&meetingImage=http://localhost:3000/pro/avatar.png&names=Pro%20Example&usernames=pro * 2. Team event (collection), lot's of people, long title http://localhost:3000/api/social/og/image?type=meeting&title=Getting%20to%20know%20us%20and%20have%20a%20beer%20together&meetingProfileName=Seeded%20Team&names=Team%20Pro%20Example%202&names=Team%20Pro%20Example%203&names=Team%20Pro%20Example%204&names=Team%20Free%20Example&names=Team%20Pro%20Example&usernames=teampro2&usernames=teampro3&usernames=teampro4&usernames=teamfree&usernames=teampro * 3. Team event of 2 (collection), http://localhost:3000/api/social/og/image?type=meeting&title=Getting%20to%20know%20each%20other&meetingProfileName=Seeded%20Team&names=Team%20Pro%20Example%202&names=Team%20Pro%20Example%203&usernames=teampro2&usernames=teampro3 * 4. Team event (round robin) http://localhost:3000/api/social/og/image?type=meeting&title=Round%20Robin%20Seeded%20Team%20Event&meetingProfileName=Seeded%20Team * 5. Dynamic collective (2 persons) http://localhost:3000/api/social/og/image?type=meeting&title=15min&meetingProfileName=Team%20Pro%20Example,%20Pro%20Example&names=Team%20Pro%20Example&names=Pro%20Example&usernames=teampro&usernames=pro */ export const constructMeetingImage = ( { title, users = [], profile }: MeetingImageProps, encodeUri = true ): string => { const url = [ `?type=meeting`, `&title=${encodeURIComponent(title)}`, `&meetingProfileName=${encodeURIComponent(profile.name)}`, profile.image && `&meetingImage=${encodeURIComponent(CAL_URL + profile.image)}`, `${users.map((user) => `&names=${encodeURIComponent(user.name)}`).join("")}`, `${users.map((user) => `&usernames=${encodeURIComponent(user.username)}`).join("")}`, // Joining a multiline string for readability. ].join(""); return encodeUri ? encodeURIComponent(url) : url; }; /** * Test url: * http://localhost:3000/api/social/og/image?type=app&name=Huddle01&slug=/api/app-store/huddle01video/icon.svg&description=Huddle01%20is%20a%20new%20video%20conferencing%20software%20native%20to%20Web3%20and%20is%20comparable%20to%20a%20decentralized%20version%20of%20Zoom.%20It%20supports%20conversations%20for... */ export const constructAppImage = ({ name, slug, description }: AppImageProps, encodeUri = true): string => { const url = [ `?type=app`, `&name=${encodeURIComponent(name)}`, `&slug=${encodeURIComponent(slug)}`, `&description=${encodeURIComponent(description)}`, // Joining a multiline string for readability. ].join(""); return encodeUri ? encodeURIComponent(url) : url; }; export const constructGenericImage = ({ title, description }: GenericImageProps, encodeUri = true) => { const url = [ `?type=generic`, `&title=${encodeURIComponent(title)}`, `&description=${encodeURIComponent(description)}`, // Joining a multiline string for readability. ].join(""); return encodeUri ? encodeURIComponent(url) : url; }; const Wrapper = ({ children, variant = "light", rotateBackground }: WrapperProps) => (
background
{children}
); export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => { // We filter attendees here based on whether they have an image and filter duplicates. // Users ALWAYS have an image (albeit a gray empty person avatar), so this mainly filters out // any non existing images for dynamic collectives, while at the same time removing them from // the names list, because the profile name of that event is a concatenation of all names. const attendees = (profile?.image ? [profile, ...users] : users).filter( (value, index, self) => self.findIndex((v) => v.name === value.name) == index ); // Construct list of avatar urls, removes duplicates and empty profile images const avatars = attendees .map((user) => { if ("image" in user && user?.image) return user.image; if ("username" in user && user?.username) return `${CAL_URL}/${user.username}/avatar.png`; return null; }) .filter(Boolean) as string[]; // In case there is NO other attendee than the single meeting profile without an image, we add // that name back in here, since the event probably is a round robin event. const names = attendees.length > 0 ? attendees.map((user) => user.name) : [profile.name]; return (
Logo {avatars.length > 0 && (
/
)}
{avatars.slice(0, 3).map((avatar) => ( Profile picture ))} {avatars.length > 3 && (
+{avatars.length - 3}
)}
Meet {joinMultipleNames(names)}
{title}
); }; const VisualBlur = ({ visualSlug }: { visualSlug: string }) => { // Making a blur of a dark logo is very ugly. We use the filename to indicate, // when we don't want to render these blurry blob backgrounds. if (visualSlug.indexOf("dark") > -1) return null; return (
{/* Blob top left */}
{/* Blob bottom right */}
); }; export const App = ({ name, description, slug }: AppImageProps) => ( Logo
App icon
{name}
{description}
); export const Generic = ({ title, description }: GenericImageProps) => (
Logo
{title}
{description}
); export const ScreenShot = ({ image, fallbackImage }: ScreenshotImageProps) => (
screenshot screenshot
);