this repo has no description atmosphereconf-vods.wisp.place/
4
fork

Configure Feed

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

mobile

+365 -145
+365 -145
src/routes/schedule.tsx
··· 6 6 import { Text } from "#/components/typography/text"; 7 7 import { formatLocalTime, getScheduleByDay } from "#/lib/conference"; 8 8 import { uiColor, warningColor } from "../components/theme/color.stylex"; 9 + import { breakpoints } from "../components/theme/media-queries.stylex"; 9 10 import { 10 11 gap, 11 12 horizontalSpace, ··· 17 18 const CALENDAR_PIXELS_PER_MINUTE = 5; 18 19 const CALENDAR_SLOT_MINUTES = 30; 19 20 const CALENDAR_TIME_COLUMN_REM = 5.5; 21 + const CALENDAR_TIME_COLUMN_MOBILE_REM = 4.25; 20 22 const CALENDAR_ROOM_COLUMN_REM = 16; 21 23 22 24 const styles = stylex.create({ ··· 57 59 color: "inherit", 58 60 textDecoration: "none", 59 61 }, 62 + desktopSchedule: { 63 + display: { 64 + default: "none", 65 + [breakpoints.md]: "block", 66 + }, 67 + }, 68 + mobileSchedule: { 69 + display: { 70 + default: "flex", 71 + [breakpoints.md]: "none", 72 + }, 73 + flexDirection: "column", 74 + gap: gap["6xl"], 75 + }, 76 + mobileRoomSection: { 77 + display: "flex", 78 + flexDirection: "column", 79 + gap: gap["3xl"], 80 + }, 81 + mobileTimeline: { 82 + display: "grid", 83 + gap: gap["lg"], 84 + alignItems: "start", 85 + gridTemplateColumns: `${CALENDAR_TIME_COLUMN_MOBILE_REM}rem minmax(0, 1fr)`, 86 + }, 60 87 scheduleScroller: { 61 88 overflowX: "auto", 62 89 paddingBottom: verticalSpace["md"], ··· 78 105 display: "flex", 79 106 flexDirection: "column", 80 107 gap: gap["sm"], 81 - minWidth: `${CALENDAR_ROOM_COLUMN_REM}rem`, 108 + minWidth: { 109 + default: "auto", 110 + [breakpoints.md]: `${CALENDAR_ROOM_COLUMN_REM}rem`, 111 + }, 82 112 paddingBottom: verticalSpace["lg"], 83 113 }, 84 114 roomLaneHeaderMeta: { ··· 107 137 }, 108 138 roomLane: { 109 139 position: "relative", 110 - minWidth: `${CALENDAR_ROOM_COLUMN_REM}rem`, 140 + minWidth: { 141 + default: 0, 142 + [breakpoints.md]: `${CALENDAR_ROOM_COLUMN_REM}rem`, 143 + }, 111 144 borderLeftColor: uiColor.border2, 112 145 borderLeftStyle: "solid", 113 146 borderLeftWidth: 1, ··· 267 300 </Flex> 268 301 269 302 <div {...stylex.props(styles.scheduleScroller)}> 270 - <div 271 - {...stylex.props(styles.scheduleBoard)} 272 - style={{ 273 - gridTemplateColumns: `${CALENDAR_TIME_COLUMN_REM}rem repeat(${rooms.length}, minmax(${CALENDAR_ROOM_COLUMN_REM}rem, 1fr))`, 274 - }} 275 - > 276 - <div {...stylex.props(styles.timeRailHeader)}> 277 - <Text size="xs" variant="secondary"> 278 - PDT 279 - </Text> 280 - </div> 281 - 282 - {rooms.map(({ track, sessions }) => ( 283 - <div 284 - key={`${day.id}-${track.slug}-header`} 285 - {...stylex.props(styles.roomLaneHeader)} 286 - > 287 - <div {...stylex.props(styles.roomLaneHeaderMeta)}> 288 - <Text 289 - size="xs" 290 - style={[ 291 - styles.roomChipBase, 292 - styles.roomChipSurface, 293 - ]} 294 - > 295 - {track.stageLabel} 296 - </Text> 297 - <Text 298 - size="xs" 299 - style={[ 300 - styles.roomChipBase, 301 - styles.roomChipSurface, 302 - ]} 303 - > 304 - {sessions.length} session 305 - {sessions.length === 1 ? "" : "s"} 306 - </Text> 307 - </div> 308 - <Link 309 - to="/tracks/$trackSlug" 310 - params={{ trackSlug: track.slug }} 311 - {...stylex.props(styles.roomTitleLink)} 312 - > 313 - <Text font="title" size="lg" weight="bold"> 314 - {track.name} 315 - </Text> 316 - </Link> 317 - </div> 318 - ))} 319 - 303 + <div {...stylex.props(styles.desktopSchedule)}> 320 304 <div 321 - {...stylex.props(styles.timeRail)} 322 - style={{ height: `${timelineHeight}px` }} 305 + {...stylex.props(styles.scheduleBoard)} 306 + style={{ 307 + gridTemplateColumns: `${CALENDAR_TIME_COLUMN_REM}rem repeat(${rooms.length}, minmax(${CALENDAR_ROOM_COLUMN_REM}rem, 1fr))`, 308 + }} 323 309 > 324 - {timeSlots.map((slot) => { 325 - const top = 326 - (slot - dayStartMinutes) * CALENDAR_PIXELS_PER_MINUTE; 310 + <div {...stylex.props(styles.timeRailHeader)}> 311 + <Text size="xs" variant="secondary"> 312 + PDT 313 + </Text> 314 + </div> 327 315 328 - return ( 329 - <div key={`${day.id}-time-${slot}`}> 330 - <div 331 - {...stylex.props(styles.timeRailTick)} 332 - style={{ top: `${top}px` }} 333 - /> 334 - <div 335 - {...stylex.props(styles.timeLabel)} 336 - style={{ top: `${top}px` }} 316 + {rooms.map(({ track, sessions }) => ( 317 + <div 318 + key={`${day.id}-${track.slug}-header`} 319 + {...stylex.props(styles.roomLaneHeader)} 320 + > 321 + <div {...stylex.props(styles.roomLaneHeaderMeta)}> 322 + <Text 323 + size="xs" 324 + style={[ 325 + styles.roomChipBase, 326 + styles.roomChipSurface, 327 + ]} 328 + > 329 + {track.stageLabel} 330 + </Text> 331 + <Text 332 + size="xs" 333 + style={[ 334 + styles.roomChipBase, 335 + styles.roomChipSurface, 336 + ]} 337 337 > 338 - <Text size="xs" variant="secondary"> 339 - {formatScheduleAxisTime(slot)} 340 - </Text> 341 - </div> 338 + {sessions.length} session 339 + {sessions.length === 1 ? "" : "s"} 340 + </Text> 342 341 </div> 343 - ); 344 - })} 345 - </div> 342 + <Link 343 + to="/tracks/$trackSlug" 344 + params={{ trackSlug: track.slug }} 345 + {...stylex.props(styles.roomTitleLink)} 346 + > 347 + <Text font="title" size="lg" weight="bold"> 348 + {track.name} 349 + </Text> 350 + </Link> 351 + </div> 352 + ))} 346 353 347 - {rooms.map(({ track, sessions }) => ( 348 354 <div 349 - key={`${day.id}-${track.slug}-lane`} 350 - {...stylex.props(styles.roomLane)} 355 + {...stylex.props(styles.timeRail)} 351 356 style={{ height: `${timelineHeight}px` }} 352 357 > 353 358 {timeSlots.map((slot) => { ··· 356 361 CALENDAR_PIXELS_PER_MINUTE; 357 362 358 363 return ( 359 - <div 360 - key={`${day.id}-${track.slug}-${slot}`} 361 - {...stylex.props(styles.roomLaneTick)} 362 - style={{ top: `${top}px` }} 363 - /> 364 + <div key={`${day.id}-time-${slot}`}> 365 + <div 366 + {...stylex.props(styles.timeRailTick)} 367 + style={{ top: `${top}px` }} 368 + /> 369 + <div 370 + {...stylex.props(styles.timeLabel)} 371 + style={{ top: `${top}px` }} 372 + > 373 + <Text size="xs" variant="secondary"> 374 + {formatScheduleAxisTime(slot)} 375 + </Text> 376 + </div> 377 + </div> 364 378 ); 365 379 })} 380 + </div> 366 381 367 - {sessions.map((session) => { 368 - const startMinutes = getMinutesInDay( 369 - session.startsAt, 370 - ); 371 - const endMinutes = getMinutesInDay(session.endsAt); 372 - const durationMinutes = endMinutes - startMinutes; 373 - const top = 374 - (startMinutes - dayStartMinutes) * 375 - CALENDAR_PIXELS_PER_MINUTE; 376 - const height = Math.max( 377 - durationMinutes * CALENDAR_PIXELS_PER_MINUTE, 378 - 64, 379 - ); 380 - const isCompact = durationMinutes <= 10; 381 - const showSpeakers = durationMinutes >= 35; 382 - const showStatus = durationMinutes >= 45; 382 + {rooms.map(({ track, sessions }) => ( 383 + <div 384 + key={`${day.id}-${track.slug}-lane`} 385 + {...stylex.props(styles.roomLane)} 386 + style={{ height: `${timelineHeight}px` }} 387 + > 388 + {timeSlots.map((slot) => { 389 + const top = 390 + (slot - dayStartMinutes) * 391 + CALENDAR_PIXELS_PER_MINUTE; 392 + 393 + return ( 394 + <div 395 + key={`${day.id}-${track.slug}-${slot}`} 396 + {...stylex.props(styles.roomLaneTick)} 397 + style={{ top: `${top}px` }} 398 + /> 399 + ); 400 + })} 401 + 402 + {sessions.map((session) => { 403 + const startMinutes = getMinutesInDay( 404 + session.startsAt, 405 + ); 406 + const endMinutes = getMinutesInDay(session.endsAt); 407 + const durationMinutes = endMinutes - startMinutes; 408 + const top = 409 + (startMinutes - dayStartMinutes) * 410 + CALENDAR_PIXELS_PER_MINUTE; 411 + const height = Math.max( 412 + durationMinutes * CALENDAR_PIXELS_PER_MINUTE, 413 + 64, 414 + ); 415 + const isCompact = durationMinutes <= 10; 416 + const showSpeakers = durationMinutes >= 35; 417 + const showStatus = durationMinutes >= 45; 383 418 384 - return ( 385 - <div 386 - key={session.id} 387 - {...stylex.props(styles.calendarEventShell)} 388 - style={{ 389 - top: `${top}px`, 390 - height: `${height}px`, 391 - }} 392 - > 393 - <Link 394 - to="/videos/$videoSlug" 395 - params={{ videoSlug: session.slug }} 396 - search={{ autoplay: false }} 397 - title={`${session.title} · ${formatLocalTime(session.startsAt)} - ${formatLocalTime(session.endsAt)}`} 398 - {...stylex.props(styles.calendarEventLink)} 419 + return ( 420 + <div 421 + key={session.id} 422 + {...stylex.props(styles.calendarEventShell)} 423 + style={{ 424 + top: `${top}px`, 425 + height: `${height}px`, 426 + }} 399 427 > 400 - <div {...stylex.props(styles.calendarEvent)}> 401 - <div 402 - {...stylex.props(styles.calendarEventMeta)} 403 - > 404 - <Text size="xs" variant="secondary"> 405 - {formatLocalTime(session.startsAt)} 428 + <Link 429 + to="/videos/$videoSlug" 430 + params={{ videoSlug: session.slug }} 431 + search={{ autoplay: false }} 432 + title={`${session.title} · ${formatLocalTime(session.startsAt)} - ${formatLocalTime(session.endsAt)}`} 433 + {...stylex.props(styles.calendarEventLink)} 434 + > 435 + <div {...stylex.props(styles.calendarEvent)}> 436 + <div 437 + {...stylex.props( 438 + styles.calendarEventMeta, 439 + )} 440 + > 441 + <Text size="xs" variant="secondary"> 442 + {formatLocalTime(session.startsAt)} 443 + </Text> 444 + {!session.recordUri && showStatus ? ( 445 + <Text 446 + size="xs" 447 + variant="secondary" 448 + style={styles.calendarEventStatus} 449 + > 450 + No recording 451 + </Text> 452 + ) : null} 453 + </div> 454 + <Text 455 + size={isCompact ? "xs" : "sm"} 456 + weight="medium" 457 + style={styles.calendarEventTitle} 458 + > 459 + {session.title} 406 460 </Text> 407 - {!session.recordUri && showStatus ? ( 408 - <Text 409 - size="xs" 410 - variant="secondary" 411 - style={styles.calendarEventStatus} 412 - > 413 - No recording 461 + {showSpeakers && 462 + session.speakerProfiles.length > 0 ? ( 463 + <Text size="xs" variant="secondary"> 464 + {session.speakerProfiles 465 + .map( 466 + (speaker) => 467 + speaker.displayName ?? 468 + speaker.name, 469 + ) 470 + .join(", ")} 414 471 </Text> 415 472 ) : null} 416 473 </div> 417 - <Text 418 - size={isCompact ? "xs" : "sm"} 419 - weight="medium" 420 - style={styles.calendarEventTitle} 474 + </Link> 475 + </div> 476 + ); 477 + })} 478 + </div> 479 + ))} 480 + </div> 481 + </div> 482 + 483 + <div {...stylex.props(styles.mobileSchedule)}> 484 + {rooms.map(({ track, sessions }) => ( 485 + <section 486 + key={`${day.id}-${track.slug}-mobile`} 487 + {...stylex.props(styles.mobileRoomSection)} 488 + > 489 + <div {...stylex.props(styles.roomLaneHeader)}> 490 + <div {...stylex.props(styles.roomLaneHeaderMeta)}> 491 + <Text 492 + size="xs" 493 + style={[ 494 + styles.roomChipBase, 495 + styles.roomChipSurface, 496 + ]} 497 + > 498 + {track.stageLabel} 499 + </Text> 500 + <Text 501 + size="xs" 502 + style={[ 503 + styles.roomChipBase, 504 + styles.roomChipSurface, 505 + ]} 506 + > 507 + {sessions.length} session 508 + {sessions.length === 1 ? "" : "s"} 509 + </Text> 510 + </div> 511 + <Link 512 + to="/tracks/$trackSlug" 513 + params={{ trackSlug: track.slug }} 514 + {...stylex.props(styles.roomTitleLink)} 515 + > 516 + <Text font="title" size="lg" weight="bold"> 517 + {track.name} 518 + </Text> 519 + </Link> 520 + </div> 521 + 522 + <div {...stylex.props(styles.mobileTimeline)}> 523 + <div {...stylex.props(styles.timeRailHeader)}> 524 + <Text size="xs" variant="secondary"> 525 + PDT 526 + </Text> 527 + </div> 528 + <div /> 529 + 530 + <div 531 + {...stylex.props(styles.timeRail)} 532 + style={{ height: `${timelineHeight}px` }} 533 + > 534 + {timeSlots.map((slot) => { 535 + const top = 536 + (slot - dayStartMinutes) * 537 + CALENDAR_PIXELS_PER_MINUTE; 538 + 539 + return ( 540 + <div 541 + key={`${day.id}-${track.slug}-time-${slot}`} 542 + > 543 + <div 544 + {...stylex.props(styles.timeRailTick)} 545 + style={{ top: `${top}px` }} 546 + /> 547 + <div 548 + {...stylex.props(styles.timeLabel)} 549 + style={{ top: `${top}px` }} 421 550 > 422 - {session.title} 423 - </Text> 424 - {showSpeakers && 425 - session.speakerProfiles.length > 0 ? ( 426 551 <Text size="xs" variant="secondary"> 427 - {session.speakerProfiles 428 - .map( 429 - (speaker) => 430 - speaker.displayName ?? speaker.name, 431 - ) 432 - .join(", ")} 552 + {formatScheduleAxisTime(slot)} 433 553 </Text> 434 - ) : null} 554 + </div> 555 + </div> 556 + ); 557 + })} 558 + </div> 559 + 560 + <div 561 + {...stylex.props(styles.roomLane)} 562 + style={{ height: `${timelineHeight}px` }} 563 + > 564 + {timeSlots.map((slot) => { 565 + const top = 566 + (slot - dayStartMinutes) * 567 + CALENDAR_PIXELS_PER_MINUTE; 568 + 569 + return ( 570 + <div 571 + key={`${day.id}-${track.slug}-mobile-${slot}`} 572 + {...stylex.props(styles.roomLaneTick)} 573 + style={{ top: `${top}px` }} 574 + /> 575 + ); 576 + })} 577 + 578 + {sessions.map((session) => { 579 + const startMinutes = getMinutesInDay( 580 + session.startsAt, 581 + ); 582 + const endMinutes = getMinutesInDay( 583 + session.endsAt, 584 + ); 585 + const durationMinutes = endMinutes - startMinutes; 586 + const top = 587 + (startMinutes - dayStartMinutes) * 588 + CALENDAR_PIXELS_PER_MINUTE; 589 + const height = Math.max( 590 + durationMinutes * CALENDAR_PIXELS_PER_MINUTE, 591 + 64, 592 + ); 593 + const isCompact = durationMinutes <= 10; 594 + const showSpeakers = durationMinutes >= 35; 595 + const showStatus = durationMinutes >= 45; 596 + 597 + return ( 598 + <div 599 + key={`${session.id}-mobile`} 600 + {...stylex.props(styles.calendarEventShell)} 601 + style={{ 602 + top: `${top}px`, 603 + height: `${height}px`, 604 + }} 605 + > 606 + <Link 607 + to="/videos/$videoSlug" 608 + params={{ videoSlug: session.slug }} 609 + search={{ autoplay: false }} 610 + title={`${session.title} · ${formatLocalTime(session.startsAt)} - ${formatLocalTime(session.endsAt)}`} 611 + {...stylex.props(styles.calendarEventLink)} 612 + > 613 + <div 614 + {...stylex.props(styles.calendarEvent)} 615 + > 616 + <div 617 + {...stylex.props( 618 + styles.calendarEventMeta, 619 + )} 620 + > 621 + <Text size="xs" variant="secondary"> 622 + {formatLocalTime(session.startsAt)} 623 + </Text> 624 + {!session.recordUri && showStatus ? ( 625 + <Text 626 + size="xs" 627 + variant="secondary" 628 + style={styles.calendarEventStatus} 629 + > 630 + No recording 631 + </Text> 632 + ) : null} 633 + </div> 634 + <Text 635 + size={isCompact ? "xs" : "sm"} 636 + weight="medium" 637 + style={styles.calendarEventTitle} 638 + > 639 + {session.title} 640 + </Text> 641 + {showSpeakers && 642 + session.speakerProfiles.length > 0 ? ( 643 + <Text size="xs" variant="secondary"> 644 + {session.speakerProfiles 645 + .map( 646 + (speaker) => 647 + speaker.displayName ?? 648 + speaker.name, 649 + ) 650 + .join(", ")} 651 + </Text> 652 + ) : null} 653 + </div> 654 + </Link> 435 655 </div> 436 - </Link> 437 - </div> 438 - ); 439 - })} 440 - </div> 656 + ); 657 + })} 658 + </div> 659 + </div> 660 + </section> 441 661 ))} 442 662 </div> 443 663 </div>