Skip to content

Commit

Permalink
Merge pull request #26 from dddwa/fix/agenda-display
Browse files Browse the repository at this point in the history
Fixed agenda display for sponsor sessions
  • Loading branch information
JakeGinnivan authored Nov 11, 2024
2 parents 013a8a9 + 264ec4e commit 6f0a188
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 128 deletions.
8 changes: 8 additions & 0 deletions website/app/lib/http.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export const CACHE_CONTROL = {
* but people get typos/fixes and stuff too.
*/
doc: 'max-age=300, stale-while-revalidate=604800',

/**
* Keep it in the browser (and CDN) for 5 minutes so when they click
* back/forward/etc. it's super fast. SWR for 1 week on CDN so it stays fast,
* but people get typos/fixes and stuff too.
*/
schedule: 'max-age=300, stale-while-revalidate=604800',

/**
* Keep it in the browser (and CDN) for 1 day, we won't be updating these as
* often until the conf is a bit closer, and we can prevent over-fetching from
Expand Down
326 changes: 198 additions & 128 deletions website/app/routes/_layout.agenda.($year).tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { LoaderFunctionArgs } from '@remix-run/node'
import type { LoaderFunctionArgs, SerializeFrom } from '@remix-run/node'
import { json, redirect } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { DateTime } from 'luxon'
import { Fragment } from 'react'
import { $path } from 'remix-routes'
import { Box, Flex, styled } from 'styled-system/jsx'
import { TypeOf } from 'zod'
import { TypeOf, z } from 'zod'
import { AppLink } from '~/components/app-link'
import { SponsorSection } from '~/components/page-components/SponsorSection'
import { Year } from '~/lib/config-types'
import { CACHE_CONTROL } from '~/lib/http.server'
import { conferenceConfig } from '../config/conference-config'
import { getYearConfig } from '../lib/get-year-config'
import { formatDate, getScheduleGrid, gridSmartSchema } from '../lib/sessionize.server'
import { formatDate, getScheduleGrid, gridSmartSchema, roomSchema, timeSlotSchema } from '../lib/sessionize.server'
import { slugify } from '../lib/slugify'

export async function loader({ params, context }: LoaderFunctionArgs) {
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function loader({ params, context }: LoaderFunctionArgs) {
}
: undefined,
},
{ headers: { 'Cache-Control': CACHE_CONTROL.conf } },
{ headers: { 'Cache-Control': CACHE_CONTROL.schedule } },
)
}

Expand Down Expand Up @@ -196,132 +196,19 @@ export default function Agenda() {
</styled.h2>

{timeSlot.rooms.map((room) => {
const fullSession = schedule.rooms
.find((r) => r.id === room.id)
?.sessions.find((session) => session.id === room.session.id)
const endsAtTime = fullSession?.endsAt.replace(/\d{4}-\d{2}-\d{2}T/, '')
const endTime12 = fullSession?.endsAt
? DateTime.fromISO(fullSession.endsAt).toFormat('h:mm a').toLowerCase()
: undefined

const timeSlotEnd = endsAtTime?.replace(/:/g, '') ?? ''
const earliestEnd = !availableTimeSlots?.includes(timeSlotEnd)
? nextTimeSlotStart
: (timeSlotEnd ?? nextTimeSlotStart)

return (
<styled.div
<RoomTimeSlot
key={room.id}
marginBottom="0"
xl={{ marginBottom: 1 }}
style={{
gridRow: `time-${timeSlotSimple} / time-${earliestEnd}`,
gridColumn:
timeSlot.rooms.length === 1
? `room-${schedule.rooms.at(0)?.id} / room-${schedule.rooms.at(-1)?.id}`
: `room-${room.id}`,
}}
>
<Box
rounded="sm"
bgColor="#1F1F4E"
fontSize="sm"
height="full"
padding={2}
mt="2"
xl={{
mt: 0,
}}
>
<styled.h3
wordWrap="break-word"
color="white"
fontSize="md"
fontWeight="semibold"
lineHeight="tight"
mb={2}
>
{fullSession?.isServiceSession ? (
fullSession?.title
) : (
<AppLink
to={$path('/agenda/:year/talk/:sessionId', {
year,
sessionId: fullSession?.id ?? '#',
})}
>
{fullSession?.title}
</AppLink>
)}
</styled.h3>
<styled.span
display="none"
xl={{
display: 'flex',
}}
alignItems="center"
gap={2}
color="#C2C2FF"
textWrap="nowrap"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
style={{ width: '16px', height: '16px' }}
>
<path
fillRule="evenodd"
d="M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8Zm7.75-4.25a.75.75 0 0 0-1.5 0V8c0 .414.336.75.75.75h3.25a.75.75 0 0 0 0-1.5h-2.5v-3.5Z"
clipRule="evenodd"
/>
</svg>
{startTime12} - {endTime12}
</styled.span>
{fullSession?.isServiceSession ? null : (
<Flex
alignItems="center"
gap={2}
color="#C2C2FF"
textOverflow="ellipsis"
textWrap="nowrap"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
style={{ width: '16px', height: '16px' }}
>
<path
fillRule="evenodd"
d="m7.539 14.841.003.003.002.002a.755.755 0 0 0 .912 0l.002-.002.003-.003.012-.009a5.57 5.57 0 0 0 .19-.153 15.588 15.588 0 0 0 2.046-2.082c1.101-1.362 2.291-3.342 2.291-5.597A5 5 0 0 0 3 7c0 2.255 1.19 4.235 2.292 5.597a15.591 15.591 0 0 0 2.046 2.082 8.916 8.916 0 0 0 .189.153l.012.01ZM8 8.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
clipRule="evenodd"
/>
</svg>
{room.name}
</Flex>
)}
{fullSession?.speakers?.length ? (
<Flex alignItems="center" gap={2} color="#C2C2FF">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
style={{ width: '16px', height: '16px' }}
>
<path
fillRule="evenodd"
d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
clipRule="evenodd"
/>
</svg>
{fullSession?.speakers
.map((speaker) => speaker.name)
?.join(', ')}
</Flex>
) : null}
</Box>
</styled.div>
schedule={schedule}
room={room}
availableTimeSlots={availableTimeSlots}
nextTimeSlotStart={nextTimeSlotStart}
timeSlotSimple={timeSlotSimple}
timeSlot={timeSlot}
year={year}
startTime12={startTime12}
timeSlotIndex={timeSlotIndex}
/>
)
})}
</Fragment>
Expand All @@ -335,6 +222,189 @@ export default function Agenda() {
)
}

function RoomTimeSlot({
schedule,
room,
availableTimeSlots,
nextTimeSlotStart,
timeSlotSimple,
timeSlot,
year,
startTime12,
timeSlotIndex,
}: {
schedule: NonNullable<SerializeFrom<typeof loader>['schedule']>
room: z.infer<typeof roomSchema>
availableTimeSlots: string[] | undefined
nextTimeSlotStart: string
timeSlotSimple: string
timeSlot: z.infer<typeof timeSlotSchema>
year: string
startTime12: string
timeSlotIndex: number
}) {
const fullSession = schedule.rooms
.find((r) => r.id === room.id)
?.sessions.find((session) => session.id === room.session.id)
const endsAtTime = fullSession?.endsAt.replace(/\d{4}-\d{2}-\d{2}T/, '')
const endTime12 = fullSession?.endsAt
? DateTime.fromISO(fullSession.endsAt).toFormat('h:mm a').toLowerCase()
: undefined

const timeSlotEnd = endsAtTime?.replace(/:/g, '') ?? ''
const earliestEnd = !availableTimeSlots?.includes(timeSlotEnd)
? nextTimeSlotStart
: (timeSlotEnd ?? nextTimeSlotStart)

const earlierTimeSlots = schedule.timeSlots.filter((ts, index) => index < timeSlotIndex)
const laterTimeSlots = schedule.timeSlots.filter((ts, index) => index > timeSlotIndex)

// If this slot overlaps with another slot, we need to likely adjust the grid-column
const conflictingEarlierTimeslots =
timeSlot.rooms.length === 1 &&
earlierTimeSlots.filter((ts) => {
const slotEndTimes = ts.rooms.map((r) => r.session.endsAt.replace(/\d{4}-\d{2}-\d{2}T/, ''))
const maxEndTime = slotEndTimes.sort().at(-1)
if (maxEndTime && maxEndTime > timeSlot.slotStart) {
return true
}

return false
})
const conflictingLaterTimeslots =
timeSlot.rooms.length === 1 &&
laterTimeSlots.filter((ts) => {
if (!endsAtTime) {
return false
}

return ts.slotStart < endsAtTime
})

const hasConflictingEarlierSlots = conflictingEarlierTimeslots && conflictingEarlierTimeslots.length
const hasConflictingLaterSlots = conflictingLaterTimeslots && conflictingLaterTimeslots.length

const overrideRoomStart = hasConflictingEarlierSlots ? schedule.rooms.at(-1)?.id : undefined
const overrideRoomEnd = hasConflictingLaterSlots
? schedule.rooms.at(
Math.min(
...conflictingLaterTimeslots.map((cf) =>
schedule.rooms.findIndex((r) => cf.rooms.some((cfRoom) => cfRoom.id === r.id)),
),
) - 1,
)?.id
: undefined

const gridColumn =
timeSlot.rooms.length === 1 || (hasConflictingEarlierSlots && hasConflictingLaterSlots)
? `room-${overrideRoomStart ?? schedule.rooms.at(0)?.id} / room-${overrideRoomEnd ?? schedule.rooms.at(-1)?.id}`
: `room-${room.id}`

return (
<styled.div
key={room.id}
marginBottom="0"
xl={{ marginBottom: 1 }}
style={{
gridRow: `time-${timeSlotSimple} / time-${earliestEnd}`,
gridColumn: gridColumn,
}}
>
<Box
rounded="sm"
bgColor="#1F1F4E"
fontSize="sm"
height="full"
padding={2}
mt="2"
xl={{
mt: 0,
}}
>
<styled.h3
wordWrap="break-word"
color="white"
fontSize="md"
fontWeight="semibold"
lineHeight="tight"
mb={2}
>
{fullSession?.isServiceSession ? (
fullSession?.title
) : (
<AppLink
to={$path('/agenda/:year/talk/:sessionId', {
year,
sessionId: fullSession?.id ?? '#',
})}
>
{fullSession?.title}
</AppLink>
)}
</styled.h3>
<styled.span
display="none"
xl={{
display: 'flex',
}}
alignItems="center"
gap={2}
color="#C2C2FF"
textWrap="nowrap"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
style={{ width: '16px', height: '16px' }}
>
<path
fillRule="evenodd"
d="M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8Zm7.75-4.25a.75.75 0 0 0-1.5 0V8c0 .414.336.75.75.75h3.25a.75.75 0 0 0 0-1.5h-2.5v-3.5Z"
clipRule="evenodd"
/>
</svg>
{startTime12} - {endTime12}
</styled.span>
{fullSession?.isServiceSession ? null : (
<Flex alignItems="center" gap={2} color="#C2C2FF" textOverflow="ellipsis" textWrap="nowrap">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
style={{ width: '16px', height: '16px' }}
>
<path
fillRule="evenodd"
d="m7.539 14.841.003.003.002.002a.755.755 0 0 0 .912 0l.002-.002.003-.003.012-.009a5.57 5.57 0 0 0 .19-.153 15.588 15.588 0 0 0 2.046-2.082c1.101-1.362 2.291-3.342 2.291-5.597A5 5 0 0 0 3 7c0 2.255 1.19 4.235 2.292 5.597a15.591 15.591 0 0 0 2.046 2.082 8.916 8.916 0 0 0 .189.153l.012.01ZM8 8.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
clipRule="evenodd"
/>
</svg>
{room.name}
</Flex>
)}
{fullSession?.speakers?.length ? (
<Flex alignItems="center" gap={2} color="#C2C2FF">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
style={{ width: '16px', height: '16px' }}
>
<path
fillRule="evenodd"
d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
clipRule="evenodd"
/>
</svg>
{fullSession?.speakers.map((speaker) => speaker.name)?.join(', ')}
</Flex>
) : null}
</Box>
</styled.div>
)
}

function ConferenceBrowser({ conferences }: { conferences: { year: Year }[] }) {
return (
<styled.div padding="4" color="white">
Expand Down

0 comments on commit 6f0a188

Please sign in to comment.