blob: 0a0056640dcae8825182b5a5129bcc8df3711956 [file]
import {translate} from '@docusaurus/Translate';
import React, { JSX, KeyboardEvent, RefObject, useCallback, useEffect, useRef, useState } from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import { useLocation } from '@docusaurus/router';
import {
HACKATHON_EVENT,
HACKATHON_TASKS,
HACKATHON_TRACK_B,
hackathonTaskPath,
hackathonTrackBPath,
} from './hackathonEvent';
import './HackathonCourseTrack.scss';
// The permanent "Hackathon" track inside the /course "Your Doris path" card.
// `#hackathon` on the URL selects it, so messages can link straight to it.
export type CourseTrack = 'course' | 'hackathon';
const TRACK_HASH = '#hackathon';
const TRACKS: CourseTrack[] = ['course', 'hackathon'];
export function useCourseTrack(): [CourseTrack, (track: CourseTrack) => void] {
const location = useLocation();
// Server HTML always shows the course; the hash is applied after hydration.
const [track, setTrackState] = useState<CourseTrack>('course');
useEffect(() => {
if (location.hash === TRACK_HASH) {
setTrackState('hackathon');
}
}, [location.hash]);
const setTrack = useCallback((next: CourseTrack) => {
setTrackState(next);
// Not through the router: Docusaurus scrolls to the top whenever the hash is cleared.
const { pathname, search } = window.location;
window.history.replaceState(
window.history.state,
'',
pathname + search + (next === 'hackathon' ? TRACK_HASH : ''),
);
}, []);
return [track, setTrack];
}
/** On one-column layouts the card sits below the hero: bring it into view for `#hackathon` links. */
export function useRevealTrackCard(cardRef: RefObject<HTMLElement>): void {
useEffect(() => {
const card = cardRef.current;
if (!card || window.location.hash !== TRACK_HASH || !window.matchMedia('(max-width: 900px)').matches) {
return;
}
const navbarOffset = 72;
window.scrollTo({ top: card.getBoundingClientRect().top + window.scrollY - navbarOffset });
}, [cardRef]);
}
interface CourseTrackSwitchProps {
track: CourseTrack;
onSelect: (track: CourseTrack) => void;
}
export function CourseTrackSwitch({ track, onSelect }: CourseTrackSwitchProps): JSX.Element {
const tabRefs = useRef<Partial<Record<CourseTrack, HTMLButtonElement | null>>>({});
const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
return;
}
event.preventDefault();
let next = (index + (event.key === 'ArrowRight' ? 1 : -1) + TRACKS.length) % TRACKS.length;
if (event.key === 'Home') {
next = 0;
} else if (event.key === 'End') {
next = TRACKS.length - 1;
}
onSelect(TRACKS[next]);
tabRefs.current[TRACKS[next]]?.focus();
};
const tabProps = (value: CourseTrack, index: number) => ({
type: 'button' as const,
role: 'tab',
className: 'hk-switch__tab',
id: `hk-tab-${value}`,
'aria-selected': track === value,
'aria-controls': `hk-panel-${value}`,
tabIndex: track === value ? 0 : -1,
ref: (element: HTMLButtonElement | null) => {
tabRefs.current[value] = element;
},
onClick: () => onSelect(value),
onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => onKeyDown(event, index),
});
return (
<div className="hk-switch" role="tablist" aria-label={translate({id: "landing.HackathonCourseTrack.001", message: "Choose a track"})}>
<button {...tabProps('course', 0)}>
<strong>{translate({id: "landing.HackathonCourseTrack.002", message: "Doris 101"})}</strong>
<small>{translate({id: "landing.HackathonCourseTrack.003", message: "Free course"})}</small>
</button>
<button {...tabProps('hackathon', 1)}>
<strong>{translate({id: "landing.HackathonCourseTrack.004", message: "Hackathon"})}</strong>
<small>
{translate({id: "landing.HackathonCourseTrack.005", message: "Open tasks ·"})} <span className="hk-nw">{translate({id: "landing.HackathonCourseTrack.006", message: "start anytime"})}</span>
</small>
</button>
</div>
);
}
export function HackathonTrackPanel({ hidden }: { hidden: boolean }): JSX.Element {
const badgeUrl = useBaseUrl('/images/community-badges/badge-contributor.png');
return (
<section
className="hk-track"
id="hk-panel-hackathon"
role="tabpanel"
aria-labelledby="hk-tab-hackathon"
hidden={hidden}
>
<div className="hk-track__event">
<p className="hk-track__kicker">{translate({id: "landing.HackathonCourseTrack.007", message: "Apache Doris Hackathon"})}</p>
<h3 className="hk-track__headline">{translate({id: "landing.HackathonCourseTrack.008", message: "Build something real on Apache Doris — at your own pace."})}</h3>
<p className="hk-track__sub">
{translate({id: "landing.HackathonCourseTrack.009", message: "Start any time and work at your own pace: one sitting or a few evenings. No Doris experience needed."})}
</p>
</div>
<div className="hk-track__head">
<h4>{translate({id: "landing.HackathonCourseTrack.010", message: "Track A · Build on Doris"})}</h4>
<span>{HACKATHON_TASKS.length} {translate({id: "landing.HackathonCourseTrack.011", message: "tasks"})}</span>
</div>
<ol className="hk-track__pads">
{HACKATHON_TASKS.map(task => (
<li key={task.code}>
<Link className="hk-pad" to={hackathonTaskPath(task)}>
<span className="hk-pad__top">
<span className="hk-pad__code">{task.code}</span>
<em>{task.duration}</em>
</span>
<strong>{task.title}</strong>
</Link>
</li>
))}
</ol>
<Link className="hk-track__b" to={hackathonTrackBPath()}>
<span className="hk-track__bkey" aria-hidden="true">
B
</span>
<span>
{translate({id: "landing.HackathonCourseTrack.013", message: "Track B ·"})} {HACKATHON_TRACK_B.title}{translate({id: "landing.HackathonCourseTrack.014", message: ": add a step-by-step demo to a docs page, with a pull request."})}{' '}
<span className="hk-nw">{translate({id: "landing.HackathonCourseTrack.015", message: "Open the brief →"})}</span>
</span>
</Link>
<div className="hk-track__badge">
<img src={badgeUrl} alt={translate({id: "landing.HackathonCourseTrack.016", message: "Apache Doris Contributor badge"})} width="52" height="52" />
<p>
{translate({id: "landing.HackathonCourseTrack.017", message: "Take part in a task, share it in"})} <code>{HACKATHON_EVENT.slackChannel}</code> {translate({id: "landing.HackathonCourseTrack.018", message: "on Slack, collect your Contributor badge."})}
</p>
</div>
</section>
);
}