Skip to content

Commit

Permalink
Merge pull request #171 from FTCHD/dev
Browse files Browse the repository at this point in the history
Merge into main
  • Loading branch information
FTCHD authored Sep 14, 2024
2 parents f2f0027 + 31b52ed commit 963d4ca
Show file tree
Hide file tree
Showing 31 changed files with 600 additions and 516 deletions.
10 changes: 0 additions & 10 deletions app/api/compose/[templateId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ import templates from '@/templates'
import type { InferInsertModel } from 'drizzle-orm'
import { encode } from 'next-auth/jwt'

const SUPPORTED_TEMPLATES = ['cal', 'discourse', 'luma', 'poll', 'medium']

export async function GET(
request: Request,
{ params }: { params: { templateId: keyof typeof templates } }
) {
if (!SUPPORTED_TEMPLATES.includes(params.templateId)) {
throw new Error('This template is not yet supported')
}

const template = templates[params.templateId]

if (!template?.shortDescription) {
Expand All @@ -39,10 +33,6 @@ export async function POST(
request: Request,
{ params }: { params: { templateId: keyof typeof templates } }
) {
if (!SUPPORTED_TEMPLATES.includes(params.templateId)) {
throw new Error('This template is not yet supported')
}

const body = await request.json()

const validatedPayload = await validatePayload(body)
Expand Down
20 changes: 9 additions & 11 deletions app/api/vacuum/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import { notFound } from 'next/navigation'
export const dynamic = 'force-dynamic'

export async function GET() {

return Response.json({ message: 'disabled' }, { status: 200 })

try {
const frames = await client.select().from(frameTable).all()

Expand All @@ -25,7 +22,10 @@ export async function GET() {
},
})
const objects = await s3.listObjects({ Bucket: `${process.env.S3_BUCKET}` })
const files = (objects.Contents ?? []).map((object) => object.Key) as string[]
let files = (objects.Contents ?? []).map((object) => object.Key) as string[]
files = files.map((file) => file.replace('frames/', ''))
// exclude preview images
files = files.filter((file) => !file.endsWith('preview.png'))
const filesFound: string[] = []

console.log(`Found ${files.length} files in R2 and ${frames.length} frames in the database`)
Expand All @@ -39,7 +39,7 @@ export async function GET() {
filesFound.push(...paths)
}

const filesToDelete = files.filter((file) => !filesFound.includes(file))
const filesToDelete = files.filter((file) => !filesFound.find((f) => f.includes(file)))

await deleteFilesFromR2(s3, filesToDelete)

Expand All @@ -55,9 +55,7 @@ export async function GET() {
}

function collectFilePaths(configs: any[]): string[] {
const baseUrl = `${process.env.NEXT_PUBLIC_CDN_HOST}/`

const urls: string[] = []
const urlSet = new Set<string>()
function traverse(obj: any) {
if (typeof obj === 'object' && obj !== null) {
for (const key in obj) {
Expand All @@ -71,7 +69,7 @@ function collectFilePaths(configs: any[]): string[] {
value.includes('.jpeg') ||
value.includes('.png'))
) {
urls.push(value.replace(baseUrl, ''))
urlSet.add(value)
} else if (typeof value === 'object' && value !== null) {
traverse(value)
}
Expand All @@ -84,15 +82,15 @@ function collectFilePaths(configs: any[]): string[] {
traverse(config)
}

return urls
return Array.from(urlSet)
}

async function deleteFilesFromR2(s3: S3, files: string[]) {
if (files.length === 0) return
const deleteParams = {
Bucket: `${process.env.S3_BUCKET}`,
Delete: {
Objects: files.map((file) => ({ Key: file })),
Objects: files.map((file) => ({ Key: `frames/${file}` })),
},
}
await s3.deleteObjects(deleteParams)
Expand Down
43 changes: 16 additions & 27 deletions components/FrameEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { FramePreview } from './FramePreview'
import { InspectorContext } from './editor/Context'
import PublishMenu from './editor/PublishMenu'
import WebhookEventOptions from './editor/WebhookEventOptions'
import BaseSpinner from './shadcn/BaseSpinner'
import { ScrollSectionProvider } from './editor/useScrollSection'
import { Button } from './shadcn/Button'
import { Input } from './shadcn/Input'
import { Popover, PopoverContent, PopoverTrigger } from './shadcn/Popover'
Expand Down Expand Up @@ -180,7 +180,7 @@ export default function FrameEditor({

{/* TODO: consolidate this, like putting a return after postMessage to not trigger toast */}

<div className="flex z-10 flex-row items-center gap-3">
<div className="flex z-10 flex-row gap-3 items-center">
{template.events.length ? (
<TooltipProvider delayDuration={0}>
<Tooltip>
Expand Down Expand Up @@ -303,33 +303,22 @@ export default function FrameEditor({
</div>
)}
<div className="p-4 pb-0 w-full h-full md:w-2/5 md:p-6">
<div className="h-full w-full flex flex-col gap-3 bg-[#0c0c0c] md:border-[#4c3a4e80] md:border-2 md:rounded-xl md:p-4">
<div className="flex flex-row gap-2 justify-between items-center">
<h1 className="mb-4 text-4xl font-bold">Configuration</h1>
{updating && <BaseSpinner />}
</div>

<div
className={`overflow-y-scroll ${
previewOpen
? 'max-h-[calc(100dvh-360px)]'
: 'max-h-[calc(100dvh-150px)]'
} md:max-h-[calc(100dvh-240px)]`}
<div className="h-full w-full flex flex-col gap-3 bg-[#0c0c0c] md:border-[#4c3a4e80] md:border-2 md:rounded-xl pb-2">
<InspectorContext.Provider
value={{
frameId: frame.id,
config: temporaryConfig as typeof template.initialConfig,
storage: frame.storage!,
update: updateConfig,
fid: fid,
fname: fname,
// setLoading
}}
>
<InspectorContext.Provider
value={{
frameId: frame.id,
config: temporaryConfig as typeof template.initialConfig,
storage: frame.storage!,
update: updateConfig,
fid: fid,
fname: fname,
// setLoading
}}
>
<ScrollSectionProvider>
<Inspector />
</InspectorContext.Provider>
</div>
</ScrollSectionProvider>
</InspectorContext.Provider>
</div>
</div>
</div>
Expand Down
24 changes: 9 additions & 15 deletions components/compose/ComposeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { BadgeInfoIcon } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { InspectorContext } from '../editor/Context'
import BaseSpinner from '../shadcn/BaseSpinner'
import { ScrollSectionProvider } from '../editor/useScrollSection'
import { Button } from '../shadcn/Button'
import { ComposePreview } from './ComposePreview'

Expand Down Expand Up @@ -86,12 +86,12 @@ export default function ComposeEditor({
<div className="flex flex-col items-center w-full h-full">
<ComposePreview />

<div className="w-full p-4 dark:bg-[#201629] bg-white flex flex-col gap-4">
<div className="w-full text-base font-medium p-4 border border-[#4c3a4e70] rounded-xl bg-stone-100 dark:bg-[#0c0c0c]">
<BadgeInfoIcon className="w-5 h-5 inline-block mr-2" /> {template.description}
<div className="w-full h-full p-4 dark:bg-[#201629] bg-white flex flex-col items-center gap-2">
<div className="w-full text-xs font-medium p-2 border border-[#4c3a4e70] rounded-xl bg-stone-100 dark:bg-[#0c0c0c]">
<BadgeInfoIcon className="inline-block mr-1 w-4 h-4" /> {template.description}
</div>

<div className="flex flex-col w-full p-3 px-4 bg-stone-100 dark:bg-[#0c0c0c] rounded-xl ">
<div className="flex flex-col h-full w-full bg-stone-100 dark:bg-[#0c0c0c] rounded-xl">
<InspectorContext.Provider
value={{
frameId: frame.id,
Expand All @@ -102,14 +102,14 @@ export default function ComposeEditor({
fname: fname,
}}
>
<Inspector />
<ScrollSectionProvider>
<Inspector />
</ScrollSectionProvider>
</InspectorContext.Provider>
</div>
</div>

<div className="flex flex-row justify-center items-center w-full p-2 pb-5 dark:bg-[#201629] bg-white">
<Button
className="w-[50%] rounded-lg font-semibold border border-transparent bg-[#7c65c1] hover:bg-[#6944ba] active:border-[#ffffff4d] dark:text-white"
className="w-[55%] rounded-lg font-semibold border border-transparent bg-[#7c65c1] hover:bg-[#6944ba] active:border-[#ffffff4d] dark:text-white"
variant={'default'}
onClick={async () => {
await publishConfig()
Expand Down Expand Up @@ -139,12 +139,6 @@ export default function ComposeEditor({
>
ADD TO CAST
</Button>

{updating && (
<div className="fixed right-6 bottom-6 z-20 p-3 rounded-full bg-secondary">
<BaseSpinner />
</div>
)}
</div>
</div>
)
Expand Down
135 changes: 135 additions & 0 deletions components/editor/useScrollSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use client'
import {
type ReactNode,
createContext,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'

const ScrollSectionContext = createContext<{
updateSection: (id: string, element: HTMLDivElement) => void
currentSection: string | null
}>({
updateSection: () => {},
currentSection: null,
})

export const useScrollSectionContext = () => useContext(ScrollSectionContext)

export function ScrollSectionProvider({
children,
}: {
children: ReactNode
}) {
const intersectionObserver = useRef<IntersectionObserver | null>(null)
const sectionElements = useRef<Record<string, HTMLDivElement>>({})
const [intersectingSections, setIntersectingSections] = useState<string[]>([])
const [lastIntersectedSection, setLastIntersectedSection] = useState<string | null>(null)

const currentSection = useMemo(() => {
if (intersectingSections.length) {
return intersectingSections[0]
}

return lastIntersectedSection
}, [intersectingSections, lastIntersectedSection])

const updateSection = (id: string, element: HTMLDivElement) => {
if (sectionElements.current[id] === element) {
return
}

if (sectionElements.current[id]) {
intersectionObserver.current?.unobserve(sectionElements.current[id])
}

sectionElements.current[id] = element
intersectionObserver.current?.observe(element)
}

useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
setIntersectingSections((sections) => {
const entriesId = entries
.map(({ isIntersecting, target }) => ({
isIntersecting,
id: Object.entries(sectionElements.current).find(
([, element]) => element === target
)?.[0],
}))
.filter((entry) => entry.id) as { id: string; isIntersecting: boolean }[]

const newIntersections = entriesId
.filter((entry) => !sections.includes(entry.id) && entry.isIntersecting)
.map((entry) => entry.id)

const notIntersecting = entriesId
.filter((entry) => !entry.isIntersecting)
.map((entry) => entry.id)

const newSections = sections
.filter((section) => !notIntersecting.includes(section))
.concat(newIntersections)

newSections.sort((first, second) => {
const firstElement = sectionElements.current[first]
const secondElement = sectionElements.current[second]

if (!firstElement || !secondElement) {
return 0
}

return (
firstElement.getBoundingClientRect().top -
secondElement.getBoundingClientRect().top
)
})

if (newSections.length) {
setLastIntersectedSection(newSections[0])
}

return newSections
})
},
{
rootMargin: '-140px 0px -40% 0px',
}
)

for (const element of Object.values(sectionElements.current)) {
observer.observe(element)
}
}, [])

return (
<ScrollSectionContext.Provider
value={{
updateSection,
currentSection,
}}
>
{children}
</ScrollSectionContext.Provider>
)
}

export const useScrollSection = (id?: string) => {
const { updateSection } = useScrollSectionContext()
const ref = useRef<HTMLDivElement>(null)

useEffect(() => {
if (id && ref.current) {
updateSection(id, ref.current)
}
}, [id, updateSection])

return {
ref,
}
}

2 changes: 1 addition & 1 deletion lib/farcaster.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type FarcasterUserInfo = NeynarUser
export type FarcasterChannel = NeynarChannel

export interface BuildFrameData {
buttons: FrameButtonMetadata[]
buttons?: FrameButtonMetadata[]
aspectRatio?: '1.91:1' | '1:1' | undefined
inputText?: string
refreshPeriod?: number
Expand Down
Loading

0 comments on commit 963d4ca

Please sign in to comment.