Skip to content

Commit

Permalink
feat (session): add realtime lips hook
Browse files Browse the repository at this point in the history
  • Loading branch information
glencoden committed Jan 14, 2025
1 parent 7ad8b8f commit 488f8bc
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 99 deletions.
18 changes: 12 additions & 6 deletions apps/cloud/app/components/DragDropList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,29 @@ const DragDropList = forwardRef<
<div
ref={ref}
className={cn(
'px-main relative flex w-full flex-grow flex-col items-center gap-3 pb-48',
'px-main relative flex w-full flex-grow flex-col items-center gap-3',
{
'overflow-y-scroll': fixTop === null,
},
)}
style={fixTop ? { transform: `translateY(-${fixTop}px)` } : {}}
>
<div
className='sticky left-0 top-0 z-10 h-36 w-full shrink-0 lg:h-40'
className={cn(
'sticky left-0 top-0 z-10 h-36 w-full shrink-0 lg:h-40',
{
absolute: fixTop !== null,
'px-main': fixTop !== null,
},
)}
style={fixTop ? { top: `${fixTop}px` } : {}}
>
<div className='absolute bottom-0 left-1/2 h-64 w-full -translate-x-1/2 bg-blue-800 max-md:w-[100vw]'>
<div className='absolute bottom-0 left-1/2 flex h-28 w-full -translate-x-1/2 items-center justify-center px-6'>
{header}
</div>
<div className='absolute bottom-0 left-1/2 h-64 w-full -translate-x-1/2 bg-blue-800 max-lg:w-[50vw] max-md:w-[100vw]' />
<div className='relative mt-8 flex h-28 w-full items-center justify-center lg:mt-12'>
{header}
</div>
</div>
{fixTop !== null && <div className='h-36 lg:h-40' />}

{springs.map((spring, index) => {
const lip = lips[index]
Expand Down
30 changes: 30 additions & 0 deletions apps/cloud/app/hooks/useLips.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { api } from '@repo/api/client'
import { Session } from '@repo/db'
import { useEffect } from 'react'
import { supabase } from '~/lib/supabase.client'

export const useLips = (sessionId: Session['id']) => {
const utils = api.useUtils()

useEffect(() => {
const channel = supabase
.channel('lip')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'lip',
},
() => {
void utils.lip.getBySessionId.invalidate({ id: sessionId })
},
)
.subscribe()
return () => {
void supabase.removeChannel(channel)
}
}, [utils, sessionId])

return api.lip.getBySessionId.useQuery({ id: sessionId })
}
62 changes: 29 additions & 33 deletions apps/cloud/app/routes/session.$sessionId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import DragDropList from '~/components/DragDropList'
import DragDropListItem from '~/components/DragDropListItem'
import SessionMenu from '~/components/SessionMenu'
import { createSpringEffect } from '~/helpers/create-spring-effect'
import { useLips } from '~/hooks/useLips'

const FULL_LIP_HEIGHT = 108 // px, lip height 96 + list gap 12
const FULL_ACTION_LIP_BOX_HEIGHT = 124 // px, lip box height 112 + list gap 12
Expand Down Expand Up @@ -98,12 +99,7 @@ export default function ActiveSession() {
data,
isLoading: isLipsLoading,
isFetching: isLipsFetching,
} = api.lip.getBySessionId.useQuery(
{ id: session.id },
{
refetchInterval: 1000 * 60,
},
)
} = useLips(session.id)

const lips = data ?? ([] as LipDTO[])

Expand Down Expand Up @@ -300,31 +296,13 @@ export default function ActiveSession() {
return
}

// TODO: add this to move update to avoid having two requests
// ALSO let staged lip be moved away
// ALSO mobile page indicator dots
// Song select loading spinner
// TODO:
// Mobile page indicator dots
// Session start buttons loading spinner rather than page pulse
// Refactor drop shadows by the example of SongLip
// Refactor Button drop shadows by the example of SongLip
// Always five demo lips
// Remove shadow from lips on live stack
// Remove shadow from lips on live stack?
// Hook up stripe and tool ws locally
if (actionLip && status === 'staged') {
const actionStatus =
actionLip.status === 'staged' ? 'no-show' : 'done'
utils.lip.getBySessionId.setData({ id: session.id }, (prevLips) => {
return prevLips?.map((lip) => {
if (lip.id !== actionLip.id) {
return lip
}
return { ...lip, status: actionStatus }
})
})
updateLip({
id: actionLip.id,
status: actionStatus,
})
}

// Optimistic update
void utils.lip.getBySessionId.setData(
Expand All @@ -335,13 +313,24 @@ export default function ActiveSession() {
if (lip.sortNumber === null) {
return lip
}
// Update moved lip
if (lip.id === id) {
return {
...lip,
status,
sortNumber,
}
}
// Update current lip on live stack
if (lip.id === actionLip?.id && status === 'staged') {
return {
...lip,
status:
lip.status === 'staged'
? 'no-show'
: ('done' as LipDTO['status']),
}
}
if (fromLip.status === status) {
if (lip.status !== status) {
return lip
Expand Down Expand Up @@ -733,9 +722,6 @@ export default function ActiveSession() {
console.log('DRAG INDEX', dragIndex)
console.log('TARGET INDEX', targetIndex)

// staged - live toggle on lip
// no moving lip away from live stack > implement no-show status

let status: LipDTO['status'] = 'idle'

if (deleteOnDrop) {
Expand Down Expand Up @@ -827,11 +813,21 @@ export default function ActiveSession() {
lip={actionLip}
spring={actionSpring}
bind={bindLipDrag}
isLocked
isLocked={
actionLip.status === 'live'
}
hideTime
hideFavorites
/>
<div className='absolute right-2 top-1/2 flex h-20 w-28 -translate-y-1/2 items-center justify-center'>
<div
className={cn(
'absolute right-2 top-1/2 flex h-20 w-28 -translate-y-1/2 items-center justify-center',
{
'animate-pulse':
isLipUpdatePending,
},
)}
>
{actionLip.status ===
'staged' && (
<Button
Expand Down
107 changes: 49 additions & 58 deletions packages/db/src/queries/createDemoLip.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { and, eq } from 'drizzle-orm'
import {
db,
guestsTable,
Lip,
lipsTable,
Session,
songsTable,
} from '../index.js'
import { and, eq, sql } from 'drizzle-orm'
import { db, guestsTable, lipsTable, Session, songsTable } from '../index.js'

const DEMO_GUEST_NAMES = [
'Bugs Bunny',
Expand Down Expand Up @@ -39,64 +32,62 @@ export const createDemoLip = (sessionId: Session['id']) => {
Math.random() * MAX_NUM_DEMO_LIPS_PER_REQUEST,
)

const songs = await tx.select().from(songsTable)
const guests = await tx.select().from(guestsTable)
const songs = await tx
.select()
.from(songsTable)
.limit(numDemos)
.orderBy(sql`RANDOM()`)

const prevLips = await tx
if (songs.length < numDemos) {
throw new Error(
`There are only ${songs.length} songs and you requested ${numDemos} demo lips.`,
)
}

const guests = await tx
.select()
.from(guestsTable)
.limit(numDemos)
.orderBy(sql`RANDOM()`)

if (guests.length < numDemos) {
throw new Error(
`There are only ${guests.length} guests and you requested ${numDemos} demo lips.`,
)
}

const prevLipsCount = (await tx
.select({ count: sql<number>`count(*)` })
.from(lipsTable)
.where(
and(
eq(lipsTable.sessionId, sessionId),
eq(lipsTable.status, 'idle'),
),
)

const startSortNumber = prevLips.length + 1

const demos: Pick<
Lip,
'songId' | 'guestId' | 'singerName' | 'sortNumber'
>[] = []

for (let i = 0; i < numDemos; i++) {
const demoSong = songs[Math.floor(Math.random() * songs.length)]

if (!demoSong) {
throw new Error(
'There need to be songs in the database to create a demo lip.',
)
}

const demoGuest = guests[Math.floor(Math.random() * guests.length)]!

if (!demoGuest) {
throw new Error(
'There need to be guests in the database to create a demo lip.',
)
}

const demoGuestName =
DEMO_GUEST_NAMES[
Math.floor(Math.random() * DEMO_GUEST_NAMES.length)
]!

demos.push({
songId: demoSong.id,
guestId: demoGuest.id,
singerName: demoGuestName,
sortNumber: startSortNumber + i,
})
}

const inserts = demos.map((demo) => {
return tx.insert(lipsTable).values({
sessionId,
...demo,
})
})

await Promise.all(inserts)
.then((result) => result[0]?.count ?? 0)) as string // prob drizzle type error

const startSortNumber = parseInt(prevLipsCount) + 1

await tx.insert(lipsTable).values(
Array.from({ length: numDemos }, (_, i) => {
const songId = songs[i]?.id
const guestId = guests[i]?.id
if (!songId || !guestId) {
throw new Error('Missing songId or guestId')
}
return {
sessionId,
songId,
guestId,
singerName:
DEMO_GUEST_NAMES[
Math.floor(Math.random() * DEMO_GUEST_NAMES.length)
]!,
sortNumber: startSortNumber + i,
}
}),
)

return true
})
Expand Down
26 changes: 25 additions & 1 deletion packages/db/src/queries/moveLip.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { and, eq } from 'drizzle-orm'
import { and, eq, or } from 'drizzle-orm'
import { db, Lip, lipsTable } from '../index.js'

export const moveLip = (
Expand Down Expand Up @@ -49,6 +49,30 @@ export const moveLip = (
),
)

const [actionLip] = await tx
.select()
.from(lipsTable)
.where(
and(
eq(lipsTable.sessionId, payload.sessionId),
or(
eq(lipsTable.status, 'staged'),
eq(lipsTable.status, 'live'),
),
),
)
.limit(1)

if (actionLip && payload.status === 'staged') {
await tx
.update(lipsTable)
.set({
status: actionLip.status === 'staged' ? 'no-show' : 'done',
sortNumber: 1,
})
.where(eq(lipsTable.id, actionLip.id))
}

const updates =
toList === null
? fromList
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/SongLip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function SongLip({

<section className='absolute right-3 top-0 flex h-full max-w-[25%] flex-col items-end justify-between py-3'>
{!hideTime && (
<Small className='text-pink-700'>
<Small className='text-right text-pink-700'>
{formatDistanceToNow(lip.createdAt!)}
</Small>
)}
Expand Down

0 comments on commit 488f8bc

Please sign in to comment.