From a9b04d0108502b85fba51b396d13f83175b300e1 Mon Sep 17 00:00:00 2001 From: Jason Wilson Date: Wed, 28 Feb 2018 23:02:42 -0500 Subject: [PATCH 1/3] Added PNG for Calendar button --- images/calendar.png | Bin 0 -> 751 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/calendar.png diff --git a/images/calendar.png b/images/calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..1a7fa5559e6ae4852a228bbd7f4318700bb80f41 GIT binary patch literal 751 zcmVQy0EMLyKX`0|N`u_fA(=?6a_->M4m1X(w-3%N0 zFs13kl%@|;ny$#iXjGPES(fkO7{`%H_Z9v0VM@~#nb@%j0021Wj4{r+5CUT?&-0=v zilX?--aZ5XS(X(=A%tKoX}ct&VHmX5Gz}r(enN<>-y36S>b>{g8)NFa&a&)q zyW`kdYY8aJGNlx`#~9Q1ePpk-CZM7yq?DX<0GQ`_ZM>;tKM_z>RYC|Hd+*z}4Ivm~ zgb>TOM|;Lt(=_1^Za(ZZ7!idBGKNFKbF2c)a&($5_#zZ&Uv2a zxJrrWz3;jX$A`%T>Gdg+wH9BUa~^G@l*07K3c@+Zj0AwHs#1dZil4mqbgI|&2`eOy zLkI*!E zl(s?$tu;0?ZQEkcWUXCx3MViP!>|m*{w;y+0KS8BE;1HEq%a-CZrKba3adaAU(UHw zYO?`c>%dP^N-5=2OoTPhGv*K-xD7Y?!6ydm*1ZMW4g#k h=_|(W>w@3xe*n-tgL`OQI-~#q002ovPDHLkV1j%-Tj~G+ literal 0 HcmV?d00001 From 974a3b909478e36ad2f491bf4256ce9158a7f86a Mon Sep 17 00:00:00 2001 From: Jason Wilson Date: Wed, 28 Feb 2018 23:04:14 -0500 Subject: [PATCH 2/3] Added button and styles for add to calendar functions --- components/events/details.js | 48 +++++++++++++++++++++++++----------- components/events/index.scss | 11 ++++++++- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/components/events/details.js b/components/events/details.js index a51ea3a..bf74d3e 100644 --- a/components/events/details.js +++ b/components/events/details.js @@ -1,9 +1,10 @@ -import React from 'react' -import './details.scss' -import { Link } from 'react-router' -import { prefixLink } from 'gatsby-helpers' +import React from "react"; +import "./details.scss"; +import { Link } from "react-router"; +import { prefixLink } from "gatsby-helpers"; -const hearts = `url(${require('../../images/hearts.png')})` +const hearts = `url(${require("../../images/hearts.png")})`; +const calendar = `url(${require("../../images/calendar.png")})`; const Details = ({ title, @@ -19,12 +20,17 @@ const Details = ({ onSave, savedSlugs, onClose, + onCalendar }) => ( -
+
{!isActive ? null : (
- - +

{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.scss b/components/events/index.scss index 269601b..23e7c83 100644 --- a/components/events/index.scss +++ b/components/events/index.scss @@ -38,7 +38,7 @@ } &::before { - content: ''; + content: ""; display: block; padding-bottom: 115.53%; width: 100%; @@ -97,6 +97,15 @@ } } +.Events-calendar { + background: no-repeat left center; + background-size: 25px 21px; + height: 21px; + display: inline-block; + vertical-align: middle; + width: 25px; +} + .Events-tabs { flex-basis: 100%; margin: -20px 0 20px; From 3785298380b44115c5da0c7eb8d7cab899ddbae6 Mon Sep 17 00:00:00 2001 From: Jason Wilson Date: Wed, 28 Feb 2018 23:05:14 -0500 Subject: [PATCH 3/3] Added event handlers and helper methods to construct google calendar link --- components/events/index.js | 295 +++++++++++++++++++++---------------- 1 file changed, 169 insertions(+), 126 deletions(-) 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)` }} />
-
-
+
{schedule.map((day, index) => (
- ) + ); } } -export default Events +export default Events;