Created
May 11, 2019 15:08
-
-
Save pomber/31ef27189c428a4016586e7d34465000 to your computer and use it in GitHub Desktop.
mdx-deck teleprompter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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