Webhooks for the AT Protocol airglow.run
atproto atprotocol automation webhook
12
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: show more target icons in the automations gallery

Hugo d9172f85 8e2b1fbd

+82 -50
+4 -23
app/components/AutomationCard/index.tsx
··· 1 - import { ArrowRight, Copy, Webhook } from "../../icons.ts"; 2 - import { nsidToDomain } from "../../../lib/lexicons/resolver.ts"; 3 - import { isRecordProducingAction, type Action } from "../../../lib/db/schema.ts"; 4 - import { FOLLOW_TARGETS } from "../../../lib/automations/follow-targets.ts"; 1 + import { Copy } from "../../icons.ts"; 2 + import type { Action } from "../../../lib/db/schema.ts"; 5 3 import { Button } from "../Button/index.tsx"; 6 - import { Favicon, NsidCode } from "../NsidCode/index.tsx"; 4 + import { LexiconFlow } from "../LexiconFlow/index.tsx"; 7 5 import * as s from "./styles.css.ts"; 8 6 9 7 type AutomationCardProps = { ··· 19 17 featured?: boolean; 20 18 }; 21 19 22 - function TargetIcon({ actions }: { actions: Action[] }) { 23 - const pds = actions.find((a) => isRecordProducingAction(a.$type)); 24 - if (pds?.$type === "bsky-post") return <Favicon domain="bsky.app" class={s.targetFavicon} />; 25 - if (pds?.$type === "bookmark") return <Favicon domain="margin.at" class={s.targetFavicon} />; 26 - if (pds?.$type === "follow") 27 - return <Favicon domain={FOLLOW_TARGETS[pds.target].faviconDomain} class={s.targetFavicon} />; 28 - if (pds?.$type === "record" || pds?.$type === "patch-record") 29 - return <Favicon domain={nsidToDomain(pds.targetCollection)} class={s.targetFavicon} />; 30 - return <Webhook size={14} />; 31 - } 32 - 33 20 export function AutomationCard({ 34 21 handle, 35 22 did, ··· 49 36 50 37 return ( 51 38 <article class={featured ? `${s.card} ${s.cardFeatured}` : s.card}> 52 - <div class={s.lexiconRow}> 53 - <NsidCode>{lexicon}</NsidCode> 54 - <ArrowRight size={12} class={s.arrow} /> 55 - <span class={s.target}> 56 - <TargetIcon actions={actions} /> 57 - </span> 58 - </div> 39 + <LexiconFlow lexicon={lexicon} actions={actions} /> 59 40 <h3 class={s.title} title={name}> 60 41 <a href={detailHref} class={s.titleLink}> 61 42 {name}
-27
app/components/AutomationCard/styles.css.ts
··· 25 25 borderColor: `color-mix(in oklch, ${vars.color.accent} 35%, ${vars.color.border})`, 26 26 }); 27 27 28 - export const lexiconRow = style({ 29 - display: "flex", 30 - alignItems: "center", 31 - gap: space[2], 32 - fontSize: fontSize.xs, 33 - color: vars.color.textMuted, 34 - }); 35 - 36 - export const arrow = style({ 37 - color: vars.color.textMuted, 38 - opacity: 0.6, 39 - flexShrink: 0, 40 - }); 41 - 42 - export const target = style({ 43 - display: "inline-flex", 44 - alignItems: "center", 45 - color: vars.color.textSecondary, 46 - }); 47 - 48 - export const targetFavicon = style({ 49 - inlineSize: "16px", 50 - blockSize: "16px", 51 - flexShrink: 0, 52 - borderRadius: "2px", 53 - }); 54 - 55 28 export const title = style({ 56 29 fontSize: fontSize.base, 57 30 fontWeight: fontWeight.semibold,
+44
app/components/LexiconFlow/index.tsx
··· 1 + import { ArrowRight, Webhook } from "../../icons.ts"; 2 + import { nsidToDomain } from "../../../lib/lexicons/resolver.ts"; 3 + import { isRecordProducingAction, type Action } from "../../../lib/db/schema.ts"; 4 + import { FOLLOW_TARGETS } from "../../../lib/automations/follow-targets.ts"; 5 + import { Favicon, NsidCode } from "../NsidCode/index.tsx"; 6 + import * as s from "./styles.css.ts"; 7 + 8 + function TargetIcons({ actions }: { actions: Action[] }) { 9 + const seen = new Set<string>(); 10 + const domains: string[] = []; 11 + for (const a of actions) { 12 + if (!isRecordProducingAction(a.$type)) continue; 13 + let domain: string | null = null; 14 + if (a.$type === "bsky-post") domain = "bsky.app"; 15 + else if (a.$type === "bookmark") domain = "margin.at"; 16 + else if (a.$type === "follow") domain = FOLLOW_TARGETS[a.target].faviconDomain; 17 + else if (a.$type === "record" || a.$type === "patch-record") 18 + domain = nsidToDomain(a.targetCollection); 19 + if (!domain || seen.has(domain)) continue; 20 + seen.add(domain); 21 + domains.push(domain); 22 + if (domains.length === 3) break; 23 + } 24 + if (domains.length === 0) return <Webhook size={14} />; 25 + return ( 26 + <> 27 + {domains.map((d) => ( 28 + <Favicon key={d} domain={d} class={s.targetFavicon} /> 29 + ))} 30 + </> 31 + ); 32 + } 33 + 34 + export function LexiconFlow({ lexicon, actions }: { lexicon: string; actions: Action[] }) { 35 + return ( 36 + <div class={s.row}> 37 + <NsidCode>{lexicon}</NsidCode> 38 + <ArrowRight size={12} class={s.arrow} /> 39 + <span class={s.target}> 40 + <TargetIcons actions={actions} /> 41 + </span> 42 + </div> 43 + ); 44 + }
+32
app/components/LexiconFlow/styles.css.ts
··· 1 + import { style } from "@vanilla-extract/css"; 2 + import { vars } from "../../styles/theme.css.ts"; 3 + import { space } from "../../styles/tokens/spacing.ts"; 4 + import { fontSize } from "../../styles/tokens/typography.ts"; 5 + 6 + export const row = style({ 7 + display: "flex", 8 + alignItems: "center", 9 + gap: space[2], 10 + fontSize: fontSize.xs, 11 + color: vars.color.textMuted, 12 + }); 13 + 14 + export const arrow = style({ 15 + color: vars.color.textMuted, 16 + opacity: 0.6, 17 + flexShrink: 0, 18 + }); 19 + 20 + export const target = style({ 21 + display: "inline-flex", 22 + alignItems: "center", 23 + gap: space[1], 24 + color: vars.color.textSecondary, 25 + }); 26 + 27 + export const targetFavicon = style({ 28 + inlineSize: "16px", 29 + blockSize: "16px", 30 + flexShrink: 0, 31 + borderRadius: "2px", 32 + });
+2
app/routes/u/[handle]/[rkey].tsx
··· 20 20 import { CodeBlock, InlineCode } from "../../../components/CodeBlock/index.js"; 21 21 import { FetchCard } from "../../../components/FetchCard/index.js"; 22 22 import { NsidCode } from "../../../components/NsidCode/index.js"; 23 + import { LexiconFlow } from "../../../components/LexiconFlow/index.js"; 23 24 import { Stack } from "../../../components/Layout/Stack/index.js"; 24 25 import ThemeToggle from "../../../islands/ThemeToggle.js"; 25 26 import { ··· 132 133 /> 133 134 134 135 <Stack gap={6}> 136 + <LexiconFlow lexicon={auto.lexicon} actions={auto.actions} /> 135 137 <Card variant="flat"> 136 138 <DescriptionList> 137 139 <dt>Lexicon</dt>