diff --git a/src/airframe/airframe.js b/src/airframe/airframe.js new file mode 100644 index 0000000..bcddabf --- /dev/null +++ b/src/airframe/airframe.js @@ -0,0 +1,119 @@ +import easing from "./easing"; +/* eslint-disable */ +function mergeResults(results) { + const firstResult = results[0]; + if (results.length < 2) { + return firstResult; + } + if (Array.isArray(firstResult)) { + // console.log("merge", results); + return firstResult.map((_, i) => { + return mergeResults(results.map(result => result[i])); + }); + } else { + return Object.assign({}, ...results); + } +} + +const airframe = { + parallel: ({ children: fns }) => { + return (t, ...args) => { + const styles = fns.map(fn => fn(t, ...args)); + const result = mergeResults(styles); + return result; + }; + }, + chain: ({ children: fns, durations }) => { + return (t, ...args) => { + let style = fns[0](0, ...args); + let lowerDuration = 0; + for (let i = 0; i < fns.length; i++) { + const fn = fns[i]; + const thisDuration = durations[i]; + const upperDuration = lowerDuration + thisDuration; + if (lowerDuration <= t && t <= upperDuration) { + const innerT = (t - lowerDuration) / thisDuration; + style = mergeResults([style, fn(innerT, ...args)]); + } else if (upperDuration < t) { + // merge the end of previous animation + style = mergeResults([style, fn(1, ...args)]); + } else if (t < lowerDuration) { + // merge the start of future animation + style = mergeResults([fn(0, ...args), style]); + } + lowerDuration = upperDuration; + } + return style; + }; + }, + delay: () => () => ({}), + tween: ({ from, to, ease = easing.linear }) => (t, targets) => { + const style = {}; + Object.keys(from).forEach(key => { + const value = from[key] + (to[key] - from[key]) * ease(t); + if (key === "x") { + style["transform"] = `translateX(${value}px)`; + } else { + style[key] = value; + } + }); + return style; + } +}; + +/* @jsx createAnimation */ +export const Stagger = props => (t, targets) => { + const filter = target => !props.filter || props.filter(target); + const interval = + targets.filter(filter).length < 2 + ? 0 + : props.interval / (targets.filter(filter).length - 1); + let i = 0; + return targets.map(target => { + // console.log(target, props.filter(target)); + if (!filter(target)) { + return {}; + } + const animation = ( + + + + {props.children[0]} + + + ); + i++; + const result = animation(t, target); + // console.log("Stagger Result", t, result); + return result; + }); +}; + +export function createAnimation(type, props, ...children) { + const allProps = Object.assign({ children }, props); + if (typeof type === "string") { + if (window.LOG === "verbose") { + return (t, ...args) => { + console.groupCollapsed(type, t); + const result = airframe[type](allProps)(t, ...args); + console.log(result); + console.groupEnd(); + return result; + }; + } else { + return airframe[type](allProps); + } + } else { + if (window.LOG === "verbose") { + return (t, ...args) => { + console.groupCollapsed(type.name, t); + const result = type(allProps)(t, ...args); + console.log(result); + console.groupEnd(); + return result; + }; + } else { + return type(allProps); + } + } +} diff --git a/src/airframe/easing.js b/src/airframe/easing.js new file mode 100644 index 0000000..fafdca9 --- /dev/null +++ b/src/airframe/easing.js @@ -0,0 +1,54 @@ +export default { + // no easing, no acceleration + linear: function(t) { + return t; + }, + // accelerating from zero velocity + easeInQuad: function(t) { + return t * t; + }, + // decelerating to zero velocity + easeOutQuad: function(t) { + return t * (2 - t); + }, + // acceleration until halfway, then deceleration + easeInOutQuad: function(t) { + return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; + }, + // accelerating from zero velocity + easeInCubic: function(t) { + return t * t * t; + }, + // decelerating to zero velocity + easeOutCubic: function(t) { + return --t * t * t + 1; + }, + // acceleration until halfway, then deceleration + easeInOutCubic: function(t) { + return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; + }, + // accelerating from zero velocity + easeInQuart: function(t) { + return t * t * t * t; + }, + // decelerating to zero velocity + easeOutQuart: function(t) { + return 1 - --t * t * t * t; + }, + // acceleration until halfway, then deceleration + easeInOutQuart: function(t) { + return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; + }, + // accelerating from zero velocity + easeInQuint: function(t) { + return t * t * t * t * t; + }, + // decelerating to zero velocity + easeOutQuint: function(t) { + return 1 + --t * t * t * t * t; + }, + // acceleration until halfway, then deceleration + easeInOutQuint: function(t) { + return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; + } +}; diff --git a/src/animation.js b/src/animation.js new file mode 100644 index 0000000..f173867 --- /dev/null +++ b/src/animation.js @@ -0,0 +1,78 @@ +import { createAnimation, Stagger } from "./airframe/airframe"; +import easing from "./airframe/easing"; + +/* eslint-disable */ + +const dx = 250; + +/* @jsx createAnimation */ + +// window.LOG = "verbose"; + +const SlideToLeft = () => ( + +); + +function ShrinkHeight() { + return ( + + ); +} + +const SlideFromRight = () => ( + +); +function GrowHeight() { + return ( + + ); +} + +function SwitchLines({ filterExit, filterEnter }) { + return ( + + + + + + + + + + + + + + + + ); +} + +export default ( + + line.left && !line.middle} + filterEnter={line => !line.left && line.middle} + /> + line.middle && !line.right} + filterEnter={line => !line.middle && line.right} + /> + +); diff --git a/src/differ.js b/src/differ.js index fb74815..ff6e6a0 100644 --- a/src/differ.js +++ b/src/differ.js @@ -83,61 +83,3 @@ export function getSlides(codes) { .filter(line => line.middle || line.left || line.right); }); } - -// - -function getLines(change) { - return change.value - .trimRight(newlineRe) - .split(newlineRe) - .map(content => ({ - content - })); -} - -export function tripleDiff(left, middle, right) { - const leftDiff = diff.diffLines(left, middle); - const rightDiff = diff.diffLines(middle, right); - - let x = leftDiff - .map(change => - change.value - .trimRight(newlineRe) - .split(newlineRe) - .map(content => ({ - content, - left: !change.added, - middle: !change.removed - })) - ) - .flat(); - // console.log(JSON.stringify(leftDiff, null, 2)); - // console.log(JSON.stringify(x, null, 2)); - - let i = 0; - // console.log(rightDiff); - rightDiff.forEach(change => { - let mx = x.filter(l => l.middle || l.right); - if (change.added) { - const lines = change.value - .trimRight(newlineRe) - .split(newlineRe) - .map(content => ({ - content, - left: false, - middle: false, - right: true - })); - insert(x, i, lines); - } else if (!change.removed) { - // console.log(change); - for (let j = 0; j < change.count; j++) { - mx[i + j].right = true; - } - } - i += change.count; - }); - // console.log(JSON.stringify(rightDiff, null, 2)); - // console.log(JSON.stringify(x, null, 2)); - return x; -} diff --git a/src/history.js b/src/history.js index 803ed1e..92d01d7 100644 --- a/src/history.js +++ b/src/history.js @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import { getSlides } from "./differ"; import { useSpring } from "react-use"; +import Slide from "./slide"; export default function History({ commits, language }) { const codes = commits.map(commit => commit.content); @@ -22,7 +23,11 @@ export default function History({ commits, language }) { } }; }); - return
{codes[index]}
; + return ( + + + + ); } function useSliderSpring(initial) { const [target, setTarget] = useState(initial); diff --git a/src/index.css b/src/index.css index cee5f34..c67b788 100755 --- a/src/index.css +++ b/src/index.css @@ -6,6 +6,8 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background-color: rgb(1, 22, 39); + color: rgb(214, 222, 235); } code { diff --git a/src/nightOwl.js b/src/nightOwl.js new file mode 100644 index 0000000..4d61fe7 --- /dev/null +++ b/src/nightOwl.js @@ -0,0 +1,107 @@ +const theme = { + plain: { + color: "#d6deeb", + backgroundColor: "#011627" + }, + styles: [ + { + types: ["changed"], + style: { + color: "rgb(162, 191, 252)", + fontStyle: "italic" + } + }, + { + types: ["deleted"], + style: { + color: "rgba(239, 83, 80, 0.56)", + fontStyle: "italic" + } + }, + { + types: ["inserted", "attr-name"], + style: { + color: "rgb(173, 219, 103)", + fontStyle: "italic" + } + }, + { + types: ["comment"], + style: { + color: "rgb(99, 119, 119)", + fontStyle: "italic" + } + }, + { + types: ["string", "url"], + style: { + color: "rgb(173, 219, 103)" + } + }, + { + types: ["variable"], + style: { + color: "rgb(214, 222, 235)" + } + }, + { + types: ["number"], + style: { + color: "rgb(247, 140, 108)" + } + }, + { + types: ["builtin", "char", "constant", "function"], + style: { + color: "rgb(130, 170, 255)" + } + }, + { + // This was manually added after the auto-generation + // so that punctuations are not italicised + types: ["punctuation"], + style: { + color: "rgb(199, 146, 234)" + } + }, + { + types: ["selector", "doctype"], + style: { + color: "rgb(199, 146, 234)", + fontStyle: "italic" + } + }, + { + types: ["class-name"], + style: { + color: "rgb(255, 203, 139)" + } + }, + { + types: ["tag", "operator", "keyword"], + style: { + color: "rgb(127, 219, 202)" + } + }, + { + types: ["boolean"], + style: { + color: "rgb(255, 88, 116)" + } + }, + { + types: ["property"], + style: { + color: "rgb(128, 203, 196)" + } + }, + { + types: ["namespace"], + style: { + color: "rgb(178, 204, 214)" + } + } + ] +}; + +module.exports = theme; diff --git a/src/slide.js b/src/slide.js new file mode 100644 index 0000000..4863d5c --- /dev/null +++ b/src/slide.js @@ -0,0 +1,34 @@ +import React from "react"; +import animation from "./animation"; +import theme from "./nightOwl"; + +export default function Slide({ time, lines }) { + const styles = animation((time + 1) / 2, lines); + return ( +
+      {lines.map((line, i) => (
+        
+ {line.tokens.map(token => { + const props = theme.styles.find(s => s.types.includes(token.type)); + return {token.content}; + })} +
+ ))} +
+ ); +}