Animate code

This commit is contained in:
Rodrigo Pombo 2019-02-04 20:39:25 -03:00
parent 269542d60e
commit 2b7a3072a6
8 changed files with 400 additions and 59 deletions

119
src/airframe/airframe.js Normal file
View File

@ -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 = (
<parallel>
<chain durations={[i * interval, 1 - props.interval]}>
<delay />
{props.children[0]}
</chain>
</parallel>
);
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);
}
}
}

54
src/airframe/easing.js Normal file
View File

@ -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;
}
};

78
src/animation.js Normal file
View File

@ -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 = () => (
<tween
from={{ x: 0, opacity: 1 }}
to={{ x: -dx, opacity: 0 }}
ease={easing.easeInQuad}
/>
);
function ShrinkHeight() {
return (
<tween
from={{ height: 15 }}
to={{ height: 0 }}
ease={easing.easeInOutQuad}
/>
);
}
const SlideFromRight = () => (
<tween
from={{ x: dx, opacity: 0 }}
to={{ x: 0, opacity: 1 }}
ease={easing.easeOutQuad}
/>
);
function GrowHeight() {
return (
<tween
from={{ height: 0 }}
to={{ height: 15 }}
ease={easing.easeInOutQuad}
/>
);
}
function SwitchLines({ filterExit, filterEnter }) {
return (
<parallel>
<Stagger interval={0.2} filter={filterExit}>
<chain durations={[0.35, 0.3, 0.35]}>
<SlideToLeft />
<ShrinkHeight />
</chain>
</Stagger>
<Stagger interval={0.2} filter={filterEnter}>
<chain durations={[0.35, 0.3, 0.35]}>
<delay />
<GrowHeight />
<SlideFromRight />
</chain>
</Stagger>
</parallel>
);
}
export default (
<chain durations={[0.5, 0.5]}>
<SwitchLines
filterExit={line => line.left && !line.middle}
filterEnter={line => !line.left && line.middle}
/>
<SwitchLines
filterExit={line => line.middle && !line.right}
filterEnter={line => !line.middle && line.right}
/>
</chain>
);

View File

@ -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;
}

View File

@ -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 <pre>{codes[index]}</pre>;
return (
<React.Fragment>
<Slide time={current - index} lines={slideLines[index]} />
</React.Fragment>
);
}
function useSliderSpring(initial) {
const [target, setTarget] = useState(initial);

View File

@ -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 {

107
src/nightOwl.js Normal file
View File

@ -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;

34
src/slide.js Normal file
View File

@ -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 (
<pre
style={{
backgroundColor: theme.plain.backgroundColor,
color: theme.plain.color,
overflow: "hidden",
maxWidth: "978px",
margin: "auto",
padding: "10px"
}}
>
{lines.map((line, i) => (
<div
style={Object.assign(
{ overflow: "hidden", height: "15px" },
styles[i]
)}
key={line.key}
>
{line.tokens.map(token => {
const props = theme.styles.find(s => s.types.includes(token.type));
return <span {...props}>{token.content}</span>;
})}
</div>
))}
</pre>
);
}