Skip to content

Instantly share code, notes, and snippets.

@pomber
Created May 11, 2019 15:08
Show Gist options
  • Save pomber/31ef27189c428a4016586e7d34465000 to your computer and use it in GitHub Desktop.
Save pomber/31ef27189c428a4016586e7d34465000 to your computer and use it in GitHub Desktop.
mdx-deck teleprompter
import React from "react"
import { globalHistory } from "@reach/router"
import Zoom from "./Zoom"
import Slide from "./Slide"
import Pre from "./Pre"
import Clock from "./Clock"
// based on https://github.com/streamich/react-use/blob/master/src/useSpring.ts
import { SpringSystem } from "rebound"
import { useState, useEffect } from "react"
function useSpring({
target = 0,
current = null,
tension = 0,
friction = 20,
round = x => x
}) {
const [spring, setSpring] = useState(null)
const [value, setValue] = useState(target)
useEffect(() => {
const listener = {
onSpringUpdate: spring => {
const value = spring.getCurrentValue()
setValue(round(value))
}
}
if (!spring) {
const newSpring = new SpringSystem().createSpring(tension, friction)
newSpring.setCurrentValue(target)
setSpring(newSpring)
newSpring.addListener(listener)
return
}
return () => {
spring.removeListener(listener)
setSpring(null)
}
}, [tension, friction])
useEffect(() => {
if (spring) {
spring.setEndValue(target)
if (current != null) {
spring.setCurrentValue(current)
}
}
}, [target, current])
return value
}
const Teleprompter = ({ index, children, style }) => {
const ref = React.useRef()
const [target, setTarget] = React.useState(0)
const scrollTop = useSpring({
target,
friction: 25
})
React.useEffect(() => {
const self = ref.current
const child = self.children[index + 1]
const childTop = child.offsetTop - self.offsetTop
const childHeight = child.getBoundingClientRect().height
const selfHeight = self.getBoundingClientRect().height
setTarget(childTop - selfHeight / 2 + (3 * childHeight) / 4)
}, [index])
React.useLayoutEffect(() => {
ref.current.scrollTop = scrollTop
}, [scrollTop])
return (
<div style={{ ...style }} ref={ref}>
<div style={{ height: "50%" }} />
{children}
<div style={{ height: "50%" }} />
</div>
)
}
const separator = "[*]"
export const Presenter = props => {
const { slides, index, step, next } = props
const allNotes = React.useMemo(
() =>
slides.flatMap((slide, slideIndex) => {
const slideNotes = (slide.meta && slide.meta.notes) || ""
return slideNotes.split(separator).map((stepNotes, stepIndex) => ({
notes: stepNotes,
slideIndex,
stepIndex
}))
}),
[slides]
)
const noteIndex = allNotes.findIndex(
stepNotes => stepNotes.slideIndex === index && stepNotes.stepIndex === step
)
return (
<div
style={{
color: "white",
backgroundColor: "black",
display: "flex",
flexDirection: "column",
height: "100vh"
}}
>
<div
style={{
marginTop: "auto",
marginBottom: "auto",
display: "flex",
overflow: "hidden"
}}
>
<div
style={{
width: 500 / 8 + "%",
minWidth: 0,
marginLeft: "auto",
marginRight: "auto",
display: "flex",
alignItems: "center"
}}
>
<Zoom zoom={5 / 8}>{props.children}</Zoom>
</div>
<div
style={{
width: 100 / 4 + "%",
minWidth: 0,
marginLeft: "auto",
marginRight: "auto",
padding: "5% 0"
}}
onClick={next}
>
<Teleprompter
index={noteIndex}
style={{
color: "#FFFF33",
whiteSpace: "pre-wrap",
overflow: "hidden",
height: "100%"
}}
>
{allNotes.map((note, i) => (
<span style={{ opacity: noteIndex === i ? 1 : 0.5 }} key={i}>
{note.notes}
</span>
))}
</Teleprompter>
</div>
</div>
<div
style={{
display: "flex",
alignItems: "center",
padding: 16
}}
>
<Pre>
{index} of {slides.length - 1}
</Pre>
<div style={{ margin: "auto" }} />
<a
target="_blank"
rel="noopener noreferrer"
href={globalHistory.location.origin + globalHistory.location.pathname}
style={{
color: "inherit"
}}
>
Open New Window
</a>
<div style={{ margin: "auto" }} />
<Clock />
</div>
</div>
)
}
export default Presenter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment