~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
230 lines · 8.0 KB Raw History
import { defineOG } from "$hatk";
import type { GrainActorProfile, Photo } from "$hatk";
import { allFonts } from "./fonts.ts";
import { calculateCollageLayout } from "./collage.ts";
import { resolveHandle } from "../helpers/resolveHandle.ts";
import { unlabeledFilter } from "../labels/_hidden.ts";

export default defineOG("/og/profile/:actor", async (ctx) => {
  const { db, params, fetchImage, lookup, blobUrl } = ctx;
  const did = (await resolveHandle(db, params.actor)) ?? params.actor;

  const profiles = await lookup<GrainActorProfile>("social.grain.actor.profile", "did", [did]);
  const author = profiles.get(did);
  const displayName = author?.value.displayName || author?.handle || did.slice(0, 24);
  const handle = author?.handle || did.slice(0, 24);
  const description = author?.value.description || "";
  const avatarRef = author ? blobUrl(did, author.value.avatar) : null;
  const avatarDataUrl = avatarRef ? await fetchImage(avatarRef) : null;

  // Fetch first photo from each of the latest 10 galleries
  const recentRows = (await db.query(
    `SELECT gi.item, g.uri AS gallery FROM "social.grain.gallery.item" gi
       JOIN "social.grain.gallery" g ON g.uri = gi.gallery
       WHERE g.did = $1 AND gi.position = 0
         AND g.space IS NULL AND gi.space IS NULL
         AND ${unlabeledFilter("g.uri")}
         AND ${unlabeledFilter("g.did")}
         AND NOT EXISTS (
           SELECT 1 FROM "social.grain.gallery__labels_self_labels" sl WHERE sl.parent_uri = g.uri
         )
       ORDER BY g.created_at DESC
       LIMIT 10`,
    [did],
  )) as Array<{ item: string; gallery: string }>;

  const imageData: Array<{ url: string; aspectRatio: number }> = [];
  if (recentRows.length > 0) {
    const photoUris = recentRows.map((r) => r.item);
    const photoRecords = await ctx.getRecords<Photo>("social.grain.photo", photoUris);
    // A lone cover is drawn across the whole frame, so it needs more pixels
    // than a thumbnail has.
    const photoSize = photoUris.length === 1 ? "feed_fullsize" : "feed_thumbnail";
    for (const uri of photoUris) {
      const rec = photoRecords.get(uri);
      if (!rec) continue;
      const url = blobUrl(rec.did, rec.value.photo, photoSize);
      if (!url) continue;
      const dataUrl = await fetchImage(url);
      if (!dataUrl) continue;
      const ar = rec.value.aspectRatio
        ? rec.value.aspectRatio.width / rec.value.aspectRatio.height
        : 4 / 3;
      imageData.push({ url: dataUrl, aspectRatio: ar });
    }
  }

  const width = 1200;
  const height = 630;
  const titleBarHeight = 100;
  const collageHeight = height - titleBarHeight;
  const gap = 12;
  const padding = 24;

  const placements = calculateCollageLayout(
    imageData,
    width - padding * 2,
    collageHeight - padding * 2,
    gap,
  );

  return {
    element: {
      type: "div",
      props: {
        style: {
          display: "flex",
          flexDirection: "column",
          width: "100%",
          height: "100%",
          backgroundColor: "#ffffff",
          position: "relative",
        },
        children: [
          // Collage area
          {
            type: "div",
            props: {
              style: {
                display: "flex",
                width: `${width}px`,
                height: `${collageHeight}px`,
                position: "relative",
                padding: `${padding}px`,
              },
              children: placements.map(
                (p: {
                  item: { url: string };
                  x: number;
                  y: number;
                  width: number;
                  height: number;
                }) => ({
                  type: "img",
                  props: {
                    src: p.item.url,
                    style: {
                      position: "absolute",
                      left: `${p.x + padding}px`,
                      top: `${p.y + padding}px`,
                      width: `${p.width}px`,
                      height: `${p.height}px`,
                      objectFit: "cover",
                    },
                  },
                }),
              ),
            },
          },
          // Title bar
          {
            type: "div",
            props: {
              style: {
                position: "absolute",
                bottom: "0",
                left: "0",
                right: "0",
                height: `${titleBarHeight}px`,
                background: "#ffffff",
                display: "flex",
                alignItems: "flex-end",
                padding: "0 24px 20px 24px",
              },
              children: [
                {
                  type: "div",
                  props: {
                    style: {
                      display: "flex",
                      alignItems: "center",
                      gap: "16px",
                      width: "100%",
                    },
                    children: [
                      ...(avatarDataUrl
                        ? [
                            {
                              type: "img",
                              props: {
                                src: avatarDataUrl,
                                style: {
                                  width: "56px",
                                  height: "56px",
                                  borderRadius: "28px",
                                  objectFit: "cover" as const,
                                },
                              },
                            },
                          ]
                        : []),
                      {
                        type: "div",
                        props: {
                          style: {
                            display: "flex",
                            flexDirection: "column",
                            gap: "4px",
                            flex: "1",
                            overflow: "hidden",
                          },
                          children: [
                            {
                              type: "div",
                              props: {
                                children: displayName,
                                style: {
                                  fontSize: 36,
                                  fontWeight: 700,
                                  color: "#171717",
                                  overflow: "hidden",
                                  textOverflow: "ellipsis",
                                  whiteSpace: "nowrap" as const,
                                },
                              },
                            },
                            {
                              type: "div",
                              props: {
                                children: `@${handle}`,
                                style: {
                                  fontSize: 20,
                                  color: "#525252",
                                  overflow: "hidden",
                                  textOverflow: "ellipsis",
                                  whiteSpace: "nowrap" as const,
                                },
                              },
                            },
                          ],
                        },
                      },
                      {
                        type: "div",
                        props: {
                          children: "grain",
                          style: {
                            fontSize: 32,
                            fontFamily: "Syne",
                            fontWeight: 800,
                            color: "#171717",
                            letterSpacing: "-0.02em",
                          },
                        },
                      },
                    ],
                  },
                },
              ],
            },
          },
        ],
      },
    },
    options: { fonts: allFonts() },
    meta: {
      title: `${displayName} (@${handle}) — Grain`,
      description: description || `@${handle} on Grain`,
    },
  };
});