Skip to content

Instantly share code, notes, and snippets.

@quirinpa
Last active August 29, 2015 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save quirinpa/cc69ebf9e36d52840fcc to your computer and use it in GitHub Desktop.
Save quirinpa/cc69ebf9e36d52840fcc to your computer and use it in GitHub Desktop.
import React, { Component } from 'react';
import TimedMutationsContainer from './TimedMutationsContainer';
function toArray(elementsO) {
if (!elementsO)
return [];
if (Array.isArray(elementsO))
return elementsO;
return [elementsO];
}
import _ from 'lodash';
export default class SimpleTransitions extends Component {
titems = new TimedMutationsContainer(this);
constructor(props) {
super(props);
this.children = toArray(props.children);
this.childProps = {
enterS: props.enterS || true,
leaveS: props.leaveS || true
};
this.children.forEach(element =>
this.titems.set(element.key, { element }));
}
mapState(element, state) {
// console.log('mapping state', state, 'to', element.key);
return this.titems.mutateElement(element,
_.mapValues(state, (value, key) => this.childProps[key]));
}
_setItem(element, state = undefined) {
// console.log('SETITEM', element.key, state);
const setOrFail = (expect, set, also) => {
_.forOwn(expect, (value, key) => {
if (!state[key] || (state[key] !== value))
return false;
});
this.titems.set(element.key, set);
if (also) also(set);
return true;
};
if (!state) return false; // should be manual
const last = this.titems.get(element.key);
if (!last) return setOrFail({appeared: true}, {
state,
element: this.mapState(element, { enterS: true }),
original: element
// tick: (new Date()).getTime()
}, () => {
this.titems.timedMutate(
element.key, { element },
this.transitionTime);
});
const lstate = last.state;
if (!lstate) return setOrFail({disappearing: true}, {
state,
element: this.mapState(last.element, { leaveS: true })
// tick: (new Date()).getTime()
}, () => this.titems.timedDelete(element.key, this.transitionTime));
if (lstate.appeared) return false; // should be manual
if (lstate.appearing) return setOrFail(
lstate.reversed ?
{ disappearing: true } :
{ disappearing: true, reversed: true },
{
state,
element: this.mapState(element,
lstate.reversed ? { leaveS: true } : { enterS: true }),
tick: last.tick
}, () => this.titems.timedDelete(element.key, this.transitionTime));
if (lstate.disappearing) return setOrFail(
lstate.reversed ?
{ appearing: true } :
{ appearing: true, reversed: true },
{
state,
element: element,
tick: last.tick
}, () => this.titems.timedMutate(element.key, { element }, this.transitionTime));
return false; // shouldn't happen
}
setItem(element, state = undefined) {
const res = this._setItem(element, state);
if (!res) console.warn('invalid SETITEM:', element.key, state);
return res;
}
makeDisappear(element) {
const item = this.titems.get(element.key);
if (!item) return false;
const state = item.state;
if (!state) return this.setItem(element, { disappearing: true });
if (state.disappearing) return false;
if (state.appearing)
return this.setItem(element,
state.reversed ?
{ disappearing: true } :
{ disappearing: true, reversed: true });
return false;
}
makeAppear(element) {
// console.log('makeAppear', element.key);
const item = this.titems.get(element.key);
if (!item) return this.setItem(element, {
appeared: true
});
const state = item.state;
if (!state || state.appearing) return false;
if (state.disappearing)
return this.setItem(element,
state.reversed ?
{ appearing: true } :
{ appearing: true, reversed: true });
return false;
}
getElementArray() {
let arr = [];
// let keys = [];
this.titems.forEach(item => {
const {state, element} = item;
if (!state || !state.disappearing) {
// keys.push([element.key, state]);
arr.push(element);
} return true;
});
// console.log(JSON.stringify(keys));
return arr;
}
componentWillReceiveProps(props) {
const {children, transitionTime} = props;
this.transitionTime = transitionTime;
let update = false;
const newA = toArray(children);
let aux = this.getElementArray();
for (let i = 0; i < newA.length; i++) {
let maintained = false;
const n = newA[i];
let j = 0;
for (; j < aux.length; j++) {
const o = aux[j];
if (o.key === n.key) {
for (let x = 0; x < j; x++)
update |= this.makeDisappear(aux.shift());
maintained = true;
if (o.props !== n.props) {
const titem = this.titems.get(o.key);
this.titems.set(o.key, {
...titem,
element: this.titems.mutateElement(n, titem.state)
});
update = true;
}
aux.shift();
break;
}
}
if (!maintained && j === aux.length)
update |= this.makeAppear(n);
}
for (let i = 0; i < aux.length; i++)
update |= this.makeDisappear(aux[i]);
this.children = newA;
if (update) {
// console.log('prop update force');
this.forceUpdate();
this.appearTimer = setTimeout(() => {
let anyAppeared = false;
this.titems.map(item => {
const state = item.state;
if (!state || !state.appeared) return item;
anyAppeared = true;
return {
state: { appearing: true },
element: item.original,
tick: item.tick
};
});
if (anyAppeared) this.forceUpdate();
}, 50);
}
}
shouldComponentUpdate() { return false; }
componentWillUnmount() {
if (this.appearTimer !== undefined)
clearTimeout(this.appearTimer);
this.titems.cleanup();
}
render() {
// let present = [];
// this.titems.forEach(item => {
// present.push([item.element.key, item.state]);
// });
// console.log('render', JSON.stringify(present));
return (
<div {...this.props}>
{this.titems.render()}
</div>
);
}
}
import React from 'react';
import Immutable from 'immutable';
export default class TimedMutationsContainer {
items = Immutable.OrderedMap();
timers = Immutable.Map();
constructor(component) {
this.component = component;
}
set(key, newItem) {
// console.log('set', key, newItem);
this.items = this.items.set(key, newItem);
}
get(key) {
return this.items.get(key);
}
delete(key) {
// console.log('del', key);
this.items = this.items.delete(key);
}
clearTimeout(key) {
const timer = this.timers.get(key);
if (timer) {
// console.log('del timeout', key);
clearTimeout(timer.timeout);
this.timers = this.timers.delete(key);
return true;
}
return false;
}
setTimeout(key, callback, delay) {
this.clearTimeout(key);
let timer = {
timeout: setTimeout(() => {
this.timers = this.timers.delete(key);
callback();
// console.log('exec timeout', key);
}, delay),
callback,
delay
};
// console.log('set timeout', key);
this.timers = this.timers.set(key, timer);
return timer;
}
timedMutate(key, newItem, delay, andThenCallback) {
// console.log('timed mutate', key);
return this.setTimeout(key, () => {
this.set(key, newItem);
this.component.forceUpdate();
if (andThenCallback) andThenCallback();
}, delay);
}
timedDelete(key, delay) {
// console.log('timed delete', key);
return this.setTimeout(key, () => {
this.delete(key);
this.component.forceUpdate(); // may not be needed
}, delay);
}
mutateElement(element, props) {
const { children, ...rest } = element.props;
return React.cloneElement(
element, { ...rest, ...props }, children
);
}
cleanup() {
this.timers.forEach(timer => {
clearTimeout(timer.timeout);
});
}
render() {
return this.items.map(item => item.element).toArray();
}
forEach(cb) { this.items.forEach(cb); }
map(cb) {
this.items = this.items.map((item) => {
return cb(item);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment