Skip to content

Commit 3470cbd

Browse files
authored
Merge pull request #317 from lf-lang/events
Addition of events tab with upcoming and past events. (e.g., ReCPS workshop, TCRS workshops, ESWEEK tutorial)
2 parents 4607876 + 6ec88bc commit 3470cbd

File tree

8 files changed

+1282
-0
lines changed

8 files changed

+1282
-0
lines changed

docusaurus.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const config: Config = {
8484
{to: '/blog', label: 'Blog', position: 'left'},
8585
{to: '/research', label: 'Research', position: 'left'},
8686
{to: '/community', label: 'Community', position: 'left'},
87+
{to: '/events', label: 'Events', position: 'left'},
8788
{
8889
type: 'docsVersionDropdown',
8990
position: 'right',

src/components/Events/events.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
export interface Event {
2+
title: string;
3+
date: string;
4+
endDate?: string;
5+
location: string;
6+
description: string;
7+
link?: string;
8+
type: "conference" | "workshop" | "meetup" | "webinar" | "hackathon" | "tutorial";
9+
isUpcoming: boolean;
10+
isExternal?: boolean; // true if link goes to external site
11+
}
12+
13+
// Add your events here
14+
// Events are automatically sorted by date
15+
export const events: Event[] = [
16+
// Upcoming Events
17+
{
18+
title: "ReCPS: Workshop on Reactive Cyber-Physical Systems",
19+
date: "2026-04-20",
20+
endDate: "2026-04-22",
21+
location: "Verona, Italy (DATE 2026 Conference)",
22+
description:
23+
"Workshop on Reactive Cyber-Physical Systems: Design, Simulation, and Coordination, co-located with the Design, Automation and Test in Europe (DATE) Conference 2026.",
24+
link: "/events/recps-2026",
25+
type: "workshop",
26+
isUpcoming: true,
27+
},
28+
// Past Events
29+
{
30+
title: "TCRS '25: Time-Centric Reactive Software",
31+
date: "2025-10-02",
32+
location: "Taipei, Taiwan (ESWEEK 2025)",
33+
description:
34+
"Third edition of the workshop on Time-Centric Reactive Software, co-located with Embedded Systems Week (ESWEEK) 2025 at the Taipei International Convention Center.",
35+
link: "https://www.tcrs.io/",
36+
type: "workshop",
37+
isUpcoming: false,
38+
isExternal: true,
39+
},
40+
{
41+
title: "TCRS '24: Time-Centric Reactive Software",
42+
date: "2024-10-03",
43+
location: "Raleigh, NC, USA (ESWEEK 2024)",
44+
description:
45+
"Second edition of the workshop on Time-Centric Reactive Software, co-located with Embedded Systems Week (ESWEEK) 2024.",
46+
link: "https://www.tcrs.io/2024/",
47+
type: "workshop",
48+
isUpcoming: false,
49+
isExternal: true,
50+
},
51+
{
52+
title: "TCRS '23: Time-Centric Reactive Software",
53+
date: "2023-05-09",
54+
location: "San Antonio, Texas (CPS-IoT Week 2023)",
55+
description:
56+
"First edition of the workshop on Time-Centric Reactive Software, co-located with ACM/IEEE CPS-IoT Week 2023.",
57+
link: "https://www.tcrs.io/2023/",
58+
type: "workshop",
59+
isUpcoming: false,
60+
isExternal: true,
61+
},
62+
{
63+
title: "Lingua Franca Tutorial at ESWEEK 2021",
64+
date: "2021-10-08",
65+
location: "Online (EMSOFT Conference)",
66+
description:
67+
"A comprehensive tutorial introducing Lingua Franca, a polyglot coordination language for concurrent and time-sensitive applications. Part of the Embedded Systems Week (ESWEEK) 2021.",
68+
link: "/events/esweek-2021-tutorial",
69+
type: "tutorial",
70+
isUpcoming: false,
71+
},
72+
];
73+
74+
// Helper to sort events by date
75+
export const sortEventsByDate = (eventList: Event[], ascending = true): Event[] => {
76+
return [...eventList].sort((a, b) => {
77+
const dateA = new Date(a.date).getTime();
78+
const dateB = new Date(b.date).getTime();
79+
return ascending ? dateA - dateB : dateB - dateA;
80+
});
81+
};
82+
83+
export const upcomingEvents = sortEventsByDate(
84+
events.filter((e) => e.isUpcoming),
85+
true
86+
);
87+
88+
export const pastEvents = sortEventsByDate(
89+
events.filter((e) => !e.isUpcoming),
90+
false
91+
);
92+

src/components/Events/index.tsx

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import clsx from "clsx";
2+
3+
import Translate from "@docusaurus/Translate";
4+
import Layout from "@theme/Layout";
5+
import Heading from "@theme/Heading";
6+
import Link from "@docusaurus/Link";
7+
8+
import { Event, upcomingEvents, pastEvents } from "./events";
9+
import styles from "./styles.module.css";
10+
11+
// Calendar icon
12+
const CalendarIcon = () => (
13+
<svg
14+
width="16"
15+
height="16"
16+
viewBox="0 0 24 24"
17+
fill="none"
18+
stroke="currentColor"
19+
strokeWidth="2"
20+
strokeLinecap="round"
21+
strokeLinejoin="round"
22+
>
23+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
24+
<line x1="16" y1="2" x2="16" y2="6" />
25+
<line x1="8" y1="2" x2="8" y2="6" />
26+
<line x1="3" y1="10" x2="21" y2="10" />
27+
</svg>
28+
);
29+
30+
// Location pin icon
31+
const LocationIcon = () => (
32+
<svg
33+
width="16"
34+
height="16"
35+
viewBox="0 0 24 24"
36+
fill="none"
37+
stroke="currentColor"
38+
strokeWidth="2"
39+
strokeLinecap="round"
40+
strokeLinejoin="round"
41+
>
42+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
43+
<circle cx="12" cy="10" r="3" />
44+
</svg>
45+
);
46+
47+
// Empty calendar icon
48+
const EmptyCalendarIcon = () => (
49+
<svg
50+
width="64"
51+
height="64"
52+
viewBox="0 0 24 24"
53+
fill="none"
54+
stroke="currentColor"
55+
strokeWidth="1.5"
56+
strokeLinecap="round"
57+
strokeLinejoin="round"
58+
>
59+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
60+
<line x1="16" y1="2" x2="16" y2="6" />
61+
<line x1="8" y1="2" x2="8" y2="6" />
62+
<line x1="3" y1="10" x2="21" y2="10" />
63+
</svg>
64+
);
65+
66+
const formatDate = (dateStr: string, endDateStr?: string): string => {
67+
const options: Intl.DateTimeFormatOptions = {
68+
year: "numeric",
69+
month: "long",
70+
day: "numeric",
71+
};
72+
const startDate = new Date(dateStr).toLocaleDateString("en-US", options);
73+
74+
if (endDateStr) {
75+
const endDate = new Date(endDateStr).toLocaleDateString("en-US", options);
76+
return `${startDate} - ${endDate}`;
77+
}
78+
79+
return startDate;
80+
};
81+
82+
// External link icon
83+
const ExternalLinkIcon = () => (
84+
<svg
85+
width="12"
86+
height="12"
87+
viewBox="0 0 24 24"
88+
fill="none"
89+
stroke="currentColor"
90+
strokeWidth="2"
91+
strokeLinecap="round"
92+
strokeLinejoin="round"
93+
style={{ marginLeft: "4px", verticalAlign: "middle" }}
94+
>
95+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
96+
<polyline points="15 3 21 3 21 9" />
97+
<line x1="10" y1="14" x2="21" y2="3" />
98+
</svg>
99+
);
100+
101+
const EventCard = ({ event }: { event: Event }) => {
102+
const typeClassName = styles[event.type] || "";
103+
const linkProps = event.isExternal
104+
? { target: "_blank", rel: "noopener noreferrer" }
105+
: {};
106+
107+
return (
108+
<div className={clsx("card", "margin-bottom--md", styles.eventCard)}>
109+
<div className="card__header">
110+
<div className="row">
111+
<div className="col">
112+
<span className={clsx(styles.eventType, typeClassName)}>
113+
{event.type}
114+
</span>
115+
<Heading as="h3" className="margin-top--sm margin-bottom--none">
116+
{event.link ? (
117+
<Link href={event.link} {...linkProps}>
118+
{event.title}
119+
{event.isExternal && <ExternalLinkIcon />}
120+
</Link>
121+
) : (
122+
event.title
123+
)}
124+
</Heading>
125+
</div>
126+
</div>
127+
</div>
128+
<div className="card__body">
129+
<p>{event.description}</p>
130+
<div className={styles.eventMeta}>
131+
<span>
132+
<CalendarIcon /> {formatDate(event.date, event.endDate)}
133+
</span>
134+
<span>
135+
<LocationIcon /> {event.location}
136+
</span>
137+
</div>
138+
</div>
139+
{event.link && (
140+
<div className="card__footer">
141+
<Link
142+
className="button button--primary button--sm"
143+
href={event.link}
144+
{...linkProps}
145+
>
146+
<Translate>{event.isExternal ? "Visit Website" : "Learn More"}</Translate>
147+
{event.isExternal && <ExternalLinkIcon />}
148+
</Link>
149+
</div>
150+
)}
151+
</div>
152+
);
153+
};
154+
155+
const EmptyState = ({ message }: { message: string }) => (
156+
<div className={styles.emptyState}>
157+
<EmptyCalendarIcon />
158+
<p>{message}</p>
159+
</div>
160+
);
161+
162+
export default function Events(): JSX.Element {
163+
return (
164+
<Layout
165+
title="Events"
166+
description="Lingua Franca events, workshops, and conferences"
167+
>
168+
{/* Hero Section */}
169+
<div className={styles.heroSection}>
170+
<div className="container">
171+
<Heading as="h1" className={styles.heroTitle}>
172+
<Translate>Events</Translate>
173+
</Heading>
174+
<p className={styles.heroSubtitle}>
175+
<Translate>
176+
Join us at conferences, workshops, and tutorials to learn more about
177+
Lingua Franca and connect with the community.
178+
</Translate>
179+
</p>
180+
</div>
181+
</div>
182+
183+
{/* Upcoming Events */}
184+
<div className="section">
185+
<div className="container">
186+
<Heading
187+
as="h2"
188+
className={clsx("margin-bottom--lg", "text--center")}
189+
>
190+
<Translate>Upcoming Events</Translate>
191+
</Heading>
192+
{upcomingEvents.length > 0 ? (
193+
<div className="row">
194+
<div className="col col--8 col--offset-2">
195+
{upcomingEvents.map((event, idx) => (
196+
<EventCard key={idx} event={event} />
197+
))}
198+
</div>
199+
</div>
200+
) : (
201+
<EmptyState message="No upcoming events scheduled. Check back soon or follow us on Zulip for announcements!" />
202+
)}
203+
</div>
204+
</div>
205+
206+
{/* Past Events */}
207+
<div className="section sectionAlt">
208+
<div className="container">
209+
<Heading
210+
as="h2"
211+
className={clsx("margin-bottom--lg", "text--center")}
212+
>
213+
<Translate>Past Events</Translate>
214+
</Heading>
215+
{pastEvents.length > 0 ? (
216+
<div className="row">
217+
<div className="col col--8 col--offset-2">
218+
{pastEvents.map((event, idx) => (
219+
<EventCard key={idx} event={event} />
220+
))}
221+
</div>
222+
</div>
223+
) : (
224+
<EmptyState message="No past events to display yet." />
225+
)}
226+
</div>
227+
</div>
228+
</Layout>
229+
);
230+
}
231+

0 commit comments

Comments
 (0)