Skip to content

Commit 9dd6946

Browse files
authored
feat: add assets and recording (graphql#1575)
1 parent 8742629 commit 9dd6946

File tree

8 files changed

+340
-11
lines changed

8 files changed

+340
-11
lines changed

gatsby-node.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export const createPages: GatsbyNode["createPages"] = async ({
158158
const schedAccessToken = process.env.SCHED_ACCESS_TOKEN
159159

160160
const schedule: ScheduleSession[] = await fetchData(
161-
`https://graphqlconf23.sched.com/api/session/list?api_key=${schedAccessToken}&format=json`
161+
`https://graphqlconf23.sched.com/api/session/export?api_key=${schedAccessToken}&format=json`
162162
)
163163

164164
const usernames: { username: string }[] = await fetchData(
@@ -187,7 +187,7 @@ export const createPages: GatsbyNode["createPages"] = async ({
187187
// Create schedule events' pages
188188
schedule.forEach(event => {
189189
const eventSpeakers = speakers.filter(e =>
190-
event.speakers?.includes(e.name)
190+
event.speakers?.find(({ username }) => username === e.username)
191191
)
192192

193193
createPage({
@@ -232,8 +232,9 @@ export const createPages: GatsbyNode["createPages"] = async ({
232232
// Create a page for each speaker
233233
speakers.forEach(speaker => {
234234
const speakerSessions: ScheduleSession[] =
235-
schedule.filter(session => session.speakers?.includes(speaker.name)) ||
236-
[]
235+
schedule.filter(session => {
236+
return session.speakers?.find(e => e.username === speaker.username)
237+
}) || []
237238

238239
createPage({
239240
path: `/conf/speakers/${speaker.username}`,

generate-videos-mappings.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import os
2+
import googleapiclient.discovery
3+
import googleapiclient.errors
4+
from dotenv import load_dotenv
5+
6+
7+
load_dotenv(dotenv_path='.env.development')
8+
9+
api_service_name = "youtube"
10+
api_version = "v3"
11+
api_key = os.getenv("YOUTUBE_ACCESS_TOKEN")
12+
13+
youtube = googleapiclient.discovery.build(
14+
api_service_name, api_version, developerKey=api_key)
15+
16+
def get_videos(channel_id, max_results=100):
17+
request = youtube.search().list(
18+
part="snippet",
19+
channelId=channel_id,
20+
maxResults=max_results,
21+
order="date",
22+
type="video"
23+
)
24+
response = request.execute()
25+
26+
videos = []
27+
for item in response.get("items", []):
28+
video_id = item["id"]["videoId"]
29+
title = item["snippet"]["title"]
30+
videos.append({'id': video_id, 'title': title})
31+
32+
return videos
33+
34+
def get_channel_id(channel_name):
35+
request = youtube.search().list(
36+
part="snippet",
37+
q=channel_name,
38+
type="channel",
39+
maxResults=1
40+
)
41+
response = request.execute()
42+
items = response.get("items", [])
43+
if items:
44+
return items[0]["snippet"]["channelId"]
45+
else:
46+
return None
47+
48+
channel_name = "GraphQLFoundation"
49+
channel_id = get_channel_id(channel_name)
50+
51+
if channel_id:
52+
videos = get_videos(channel_id)
53+
54+
55+
with open('src/templates/videos.ts', 'w') as f:
56+
f.write('export const videos = [\n')
57+
for video in videos:
58+
f.write(f" {{ id: '{video['id']}', title: `{video['title']}` }},\n")
59+
f.write('];\n')
60+
print("JS file has been written with video information!")
61+
else:
62+
print(f"No channel found with name {channel_name}")

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"react-markdown": "^8.0.7",
5252
"react-tooltip": "^5.18.1",
5353
"rehype-raw": "^6.1.1",
54+
"string-similarity": "^4.0.4",
5455
"timeago.js": "4.0.2"
5556
},
5657
"devDependencies": {
@@ -59,6 +60,7 @@
5960
"@types/codemirror": "5.60.7",
6061
"@types/prismjs": "1.26.0",
6162
"@types/react-tooltip": "^4.2.4",
63+
"@types/string-similarity": "^4.0.0",
6264
"@typescript-eslint/parser": "5.59.7",
6365
"autoprefixer": "10.4.14",
6466
"eslint": "8.42.0",

src/components/Conf/Schedule/ScheduleList.tsx

+17-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import React, { FC, useEffect, useState } from "react"
33
import { eventsColors } from "../../../utils/eventsColors"
44
import { getEventTitle } from "../../../utils/eventTitle"
55
import Filters from "./Filters"
6+
import { SchedSpeaker } from "../Speakers/Speaker"
7+
8+
function isString(x: any) {
9+
return Object.prototype.toString.call(x) === "[object String]"
10+
}
611

712
export interface ScheduleSession {
813
id: string
@@ -14,7 +19,8 @@ export interface ScheduleSession {
1419
event_type: string
1520
name: string
1621
venue: string
17-
speakers?: string
22+
speakers?: SchedSpeaker[] | string
23+
files?: { name: string; path: string }[]
1824
}
1925

2026
export interface ConcurrentSessions {
@@ -214,8 +220,14 @@ const ScheduleList: FC<Props> = ({
214220
? session.event_type.slice(0, -1)
215221
: session.event_type
216222

217-
const speakers = session.speakers?.split(",") || []
218-
const eventTitle = getEventTitle(session, speakers)
223+
const speakers = session.speakers
224+
const formattedSpeakers = isString(speakers || [])
225+
? (speakers as string)?.split(",")
226+
: (speakers as SchedSpeaker[])?.map(e => e.name)
227+
const eventTitle = getEventTitle(
228+
session,
229+
formattedSpeakers
230+
)
219231

220232
const borderColor = eventsColors[session.event_type]
221233

@@ -260,9 +272,9 @@ const ScheduleList: FC<Props> = ({
260272
{showEventType ? eventType + " / " : ""}
261273
{eventTitle}
262274
<div className="flex flex-col">
263-
{speakers.length > 0 && (
275+
{(speakers?.length || 0) > 0 && (
264276
<span className="font-light">
265-
{speakers.join(", ")}
277+
{formattedSpeakers.join(", ")}
266278
</span>
267279
)}
268280
<span className="font-bold mt-2 flex items-center text-gray-700">

src/templates/event.tsx

+50-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
} from "../components/Conf/Speakers/SocialMedia"
1919
import { BackLink } from "../components/Conf/Schedule/BackLink"
2020
import { getEventTitle } from "../utils/eventTitle"
21+
import { videos } from "./videos"
22+
import { findBestMatch } from "string-similarity"
2123

2224
const Tag = ({
2325
text,
@@ -52,6 +54,11 @@ export const EventComponent: FC<{
5254
speakers.map(s => s.name)
5355
)
5456

57+
const recordingTitle = findBestMatch(
58+
`${eventTitle} ${speakers.map(e => e.name).join(" ")}`,
59+
videos.map(e => e.title)
60+
).bestMatch
61+
5562
return (
5663
<div className={`bg-white ${!hideBackButton ? "py-10" : ""}`}>
5764
<section className="text-[#333333] min-h-[80vh] flex-col mx-auto px-2 xs:px-0 lg:justify-between justify-center md:container">
@@ -124,7 +131,6 @@ export const EventComponent: FC<{
124131
remarkPlugins={[remarkGfm]}
125132
rehypePlugins={[rehypeRaw]}
126133
/>
127-
128134
<div className="flex lg:flex-row flex-col sm:gap-5">
129135
{speakers?.map(speaker => (
130136
<div className="flex items-center gap-3">
@@ -173,6 +179,49 @@ export const EventComponent: FC<{
173179
</div>
174180
))}
175181
</div>
182+
183+
{recordingTitle.rating > 0.5 && (
184+
<div>
185+
<span className="block text-3xl font-bold text-[#111827] mt-5 mb-4">
186+
Recording
187+
</span>
188+
189+
<iframe
190+
height="408px"
191+
width="100%"
192+
src={`https://www.youtube.com/embed/${
193+
videos.find(e => e.title === recordingTitle.target)?.id
194+
}`}
195+
title={recordingTitle.target}
196+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
197+
allowFullScreen
198+
/>
199+
</div>
200+
)}
201+
202+
{event.files && (
203+
<div>
204+
<span className="block text-3xl font-bold text-[#111827] mt-5 mb-4">
205+
Assets
206+
</span>
207+
{event.files?.map(({ name, path }) => (
208+
<>
209+
<a href={path} target="_blank">
210+
{name}
211+
</a>
212+
<iframe
213+
style={{
214+
height: "500px",
215+
width: "100%",
216+
resize: "both",
217+
overflow: "auto",
218+
}}
219+
src={path}
220+
></iframe>
221+
</>
222+
))}
223+
</div>
224+
)}
176225
</div>
177226
</div>
178227
</section>

0 commit comments

Comments
 (0)