-
{title}
{name && (
@@ -49,12 +66,12 @@ const Details = ({
)}
- {track === undefined || track === 'unified' ? null : (
+ {track === undefined || track === "unified" ? null : (
- {track} /{' '}
+ {track} /{" "}
)}
- {time.replace(' ', ' - ')}
+ {time.replace(" ", " - ")}
)}
-)
+);
Details.propTypes = {
title: React.PropTypes.string,
@@ -80,6 +97,7 @@ Details.propTypes = {
onNext: React.PropTypes.func,
onPrevious: React.PropTypes.func,
onSave: React.PropTypes.func,
-}
+ onCalendar: React.PropTypes.func
+};
-export default Details
+export default Details;
diff --git a/components/events/index.js b/components/events/index.js
index 16ed733..c451a09 100644
--- a/components/events/index.js
+++ b/components/events/index.js
@@ -1,114 +1,114 @@
-import React from 'react'
-import Event from './event'
-import Details from './details'
-import { below } from 'react-waypoint'
-import closestTo from 'date-fns/closest_to'
-import isWithinRange from 'date-fns/is_within_range'
-import format from 'date-fns/format'
-import Dropdown from '../drop-down'
-import './index.scss'
-
-const darkPattern = `url(${require('../../images/dark-pattern.png')})`
-const localStorageKey = 'jsconf-is-2018-schedule'
+import React from "react";
+import Event from "./event";
+import Details from "./details";
+import { below } from "react-waypoint";
+import closestTo from "date-fns/closest_to";
+import isWithinRange from "date-fns/is_within_range";
+import format from "date-fns/format";
+import Dropdown from "../drop-down";
+import "./index.scss";
+
+const darkPattern = `url(${require("../../images/dark-pattern.png")})`;
+const localStorageKey = "jsconf-is-2018-schedule";
function getWeekday(date) {
- return format(date, 'dddd').toLowerCase()
+ return format(date, "dddd").toLowerCase();
}
function isValidDay(type) {
return [
- 'sunday',
- 'monday',
- 'tuesday',
- 'wednesday',
- 'thursday',
- 'friday',
- 'saturday',
- ].includes(type)
+ "sunday",
+ "monday",
+ "tuesday",
+ "wednesday",
+ "thursday",
+ "friday",
+ "saturday"
+ ].includes(type);
}
const trackMap = {
- conference: 'Conference',
- so: 'Significant Others',
- lounge: 'Lounge',
-}
+ conference: "Conference",
+ so: "Significant Others",
+ lounge: "Lounge"
+};
function isValidType(type) {
- return Object.keys(trackMap).includes(type)
+ return Object.keys(trackMap).includes(type);
}
const getFromLocalStorage = () => {
try {
- const existing = window.localStorage.getItem(localStorageKey)
- return JSON.parse(existing) || {}
+ const existing = window.localStorage.getItem(localStorageKey);
+ return JSON.parse(existing) || {};
} catch (err) {
- return {}
+ return {};
}
-}
+};
function saveToLocalStorage(slugs) {
try {
- window.localStorage.setItem(localStorageKey, JSON.stringify(slugs))
+ window.localStorage.setItem(localStorageKey, JSON.stringify(slugs));
} catch (error) {
console.error(
"Couldn't save the talk. If you believe some of our code is not working correctly it'd be awesome if you could file an issue at https://git.io/vAw4I",
error
- )
+ );
}
}
function getCorrectDate(days, today = new Date().setHours(0, 0, 0, 0)) {
- const dates = days.map(({ date }) => date)
- let correctDay = dates[0]
+ const dates = days.map(({ date }) => date);
+ let correctDay = dates[0];
if (isWithinRange(today, dates[0], dates[days.length - 1])) {
- correctDay = closestTo(today, dates)
+ correctDay = closestTo(today, dates);
}
- return getWeekday(correctDay)
+ return getWeekday(correctDay);
}
function parseHash(state, { schedule }) {
- const hash = window.location.hash.split('/')
- const type = hash.length > 1 && isValidType(hash[1]) ? hash[1] : state.type
+ const hash = window.location.hash.split("/");
+ const type = hash.length > 1 && isValidType(hash[1]) ? hash[1] : state.type;
return {
type,
activeDate:
hash.length > 2 && isValidDay(hash[2])
? hash[2]
- : getCorrectDate(schedule[type] || []),
- }
+ : getCorrectDate(schedule[type] || [])
+ };
}
function setHash(type, day) {
- window.location.hash = `#!/${type}/${day}`
+ window.location.hash = `#!/${type}/${day}`;
}
class Events extends React.Component {
static propTypes = {
conference: React.PropTypes.arrayOf(React.PropTypes.object),
schedule: React.PropTypes.object.isRequired,
- footerPosition: React.PropTypes.string,
- }
+ footerPosition: React.PropTypes.string
+ };
constructor(props) {
- super(props)
+ super(props);
this.state = {
savedSlugs: getFromLocalStorage(),
activeDetails: null,
- type: 'conference',
+ type: "conference",
now: new Date(),
- activeDate: getCorrectDate(props.schedule.conference),
- }
+ activeDate: getCorrectDate(props.schedule.conference)
+ };
}
componentDidMount() {
- window.addEventListener('keyup', this.onKeyUp)
+ window.addEventListener("keyup", this.onKeyUp);
// eslint-disable-next-line react/no-did-mount-set-state
this.setState(parseHash, () => {
- const { type, activeDate } = this.state
- setHash(type, activeDate)
- })
- this.intervalTimer = setInterval(this.updateDate, 15 * 60000)
+ const { type, activeDate } = this.state;
+ setHash(type, activeDate);
+ });
+ this.intervalTimer = setInterval(this.updateDate, 15 * 60000);
}
componentWillReceiveProps(nextProps) {
@@ -116,160 +116,202 @@ class Events extends React.Component {
nextProps.footerPosition !== below &&
nextProps.footerPosition !== this.props.footerPosition
) {
- this.setState({ activeDetails: null })
+ this.setState({ activeDetails: null });
}
}
componentDidUpdate(prevProps, prevState) {
- const { type, activeDate } = this.state
+ const { type, activeDate } = this.state;
if (prevState.activeDate !== activeDate || prevState.type !== type) {
- setHash(type, activeDate)
+ setHash(type, activeDate);
}
}
componentWillUnmount() {
- window.removeEventListener('keyup', this.onKeyUp)
- clearInterval(this.intervalTimer)
+ window.removeEventListener("keyup", this.onKeyUp);
+ clearInterval(this.intervalTimer);
}
onKeyUp = ({ keyCode }) => {
switch (keyCode) {
case 37:
- this.onPrevious()
- break
+ this.onPrevious();
+ break;
case 39:
- this.onNext()
- break
+ this.onNext();
+ break;
default:
- break
+ break;
}
- }
+ };
onChangeDay = activeDate => {
this.setState({
activeDate,
- activeDetails: null,
- })
- }
+ activeDetails: null
+ });
+ };
onOpenTrackDetails = details => () => {
- this.setState({ activeDetails: details })
- }
+ this.setState({ activeDetails: details });
+ };
onPrevious = () => {
- this.navigate(-1)
- }
+ this.navigate(-1);
+ };
onNext = () => {
- this.navigate(1)
- }
+ this.navigate(1);
+ };
onSave = () => {
- const slot = this.getActiveSlot()
+ const slot = this.getActiveSlot();
if (!slot) {
- return
+ return;
}
const slugs = Object.assign({}, this.state.savedSlugs, {
- [slot.slug]: !this.state.savedSlugs[slot.slug],
- })
+ [slot.slug]: !this.state.savedSlugs[slot.slug]
+ });
this.setState({ savedSlugs: slugs }, () => {
- saveToLocalStorage(slugs)
- })
- }
+ saveToLocalStorage(slugs);
+ });
+ };
+
+ onCalendar = () => {
+ const slot = this.getActiveSlot();
+ const days = [
+ { year: "2018", month: "02", day: "28" },
+ { year: "2018", month: "03", day: "01" },
+ { year: "2018", month: "03", day: "02" },
+ { year: "2018", month: "03", day: "03" },
+ { year: "2018", month: "03", day: "04" },
+ { year: "2018", month: "03", day: "05" }
+ ];
+ const dateString = slot.time
+ .split(" ")
+ .map(time => {
+ const slotDay = days[parseInt(this.state.activeDetails.day, 10)];
+ return `${slotDay.year}${slotDay.month}${slotDay.day}T${time
+ .split(":")
+ .join("")}00Z`;
+ })
+ .join("/");
+ const calendarEvent = {
+ name: encodeURIComponent(`${slot.title} | ${slot.name}`),
+ details: encodeURIComponent(
+ `${slot.description}
For more details: https://2018.jsconf.is${
+ slot.link
+ }`
+ ),
+ location: encodeURIComponent(`JSConf.is`),
+ dates: encodeURIComponent(`${dateString}`)
+ };
+ const googleCalendarLink = `https://www.google.com/calendar/render?action=TEMPLATE&text=${
+ calendarEvent.name
+ }&dates=${calendarEvent.dates}&details=${calendarEvent.details}&location=${
+ calendarEvent.location
+ }&sf=true&output=xml`;
+ this.openInNewTab(googleCalendarLink);
+ };
onChangeTracks = track => {
this.setState((state, props) => ({
type: track,
- activeDate: getCorrectDate(this.getActiveSchedule(state, props)),
- }))
- }
+ activeDate: getCorrectDate(this.getActiveSchedule(state, props))
+ }));
+ };
getActiveSchedule(state = this.state, props = this.props) {
- const { type } = state
- const { schedule } = props
+ const { type } = state;
+ const { schedule } = props;
if (!schedule.hasOwnProperty(type)) {
- return []
+ return [];
}
- return schedule[type]
+ return schedule[type];
}
getActiveSlot() {
if (!this.state.activeDetails) {
- return null
+ return null;
}
- const { day, slot, track } = this.state.activeDetails
- const schedule = this.getActiveSchedule()
- return schedule[day].slots[slot].tracks[track]
+ const { day, slot, track } = this.state.activeDetails;
+ const schedule = this.getActiveSchedule();
+ return schedule[day].slots[slot].tracks[track];
}
+ openInNewTab = (URI, target = "_blank") => {
+ const win = window.open(URI, target);
+ win.focus();
+ };
+
updateDate = () => {
this.setState({
- now: new Date(),
- })
- }
+ now: new Date()
+ });
+ };
calculateNextTrack(direction, indicies) {
- const { activeDetails } = this.state
- const current = indicies || { ...activeDetails }
- const days = this.getActiveSchedule()
- const slots = days[current.day].slots
- const tracks = slots[current.slot].tracks
+ const { activeDetails } = this.state;
+ const current = indicies || { ...activeDetails };
+ const days = this.getActiveSchedule();
+ const slots = days[current.day].slots;
+ const tracks = slots[current.slot].tracks;
- current.track += direction
+ current.track += direction;
if (current.track >= tracks.length) {
- current.slot += direction
- current.track = -1
+ current.slot += direction;
+ current.track = -1;
if (!slots[current.slot]) {
- return activeDetails
+ return activeDetails;
}
- return this.calculateNextTrack(direction, current)
+ return this.calculateNextTrack(direction, current);
}
if (current.track < 0) {
- current.slot += direction
+ current.slot += direction;
if (!slots[current.slot]) {
- return activeDetails
+ return activeDetails;
}
- current.track = slots[current.slot].tracks.length
- return this.calculateNextTrack(direction, current)
+ current.track = slots[current.slot].tracks.length;
+ return this.calculateNextTrack(direction, current);
}
if (tracks[current.track].grayed) {
- return this.calculateNextTrack(direction, current)
+ return this.calculateNextTrack(direction, current);
}
- return current
+ return current;
}
navigate(direction) {
if (this.state.activeDetails) {
this.setState({
- activeDetails: this.calculateNextTrack(direction),
- })
+ activeDetails: this.calculateNextTrack(direction)
+ });
}
}
render() {
- const { type, activeDate, activeDetails, savedSlugs } = this.state
- const schedule = this.getActiveSchedule()
- const weekdays = schedule.map(({ date }) => getWeekday(date))
+ const { type, activeDate, activeDetails, savedSlugs } = this.state;
+ const schedule = this.getActiveSchedule();
+ const weekdays = schedule.map(({ date }) => getWeekday(date));
- const hasDetails = !!activeDetails
- let talk = {}
+ const hasDetails = !!activeDetails;
+ let talk = {};
if (hasDetails) {
- const { day, slot, track } = activeDetails
- talk = schedule[day].slots[slot].tracks[track]
- talk.time = schedule[day].slots[slot].time
+ const { day, slot, track } = activeDetails;
+ talk = schedule[day].slots[slot].tracks[track];
+ talk.time = schedule[day].slots[slot].time;
}
return (
@@ -278,17 +320,17 @@ class Events extends React.Component {
className="Events-pattern"
style={{
backgroundImage: darkPattern,
- transform: `rotate(${type === 'so' ? -67 : -37}deg)`,
+ transform: `rotate(${type === "so" ? -67 : -37}deg)`
}}
/>
-