Skip to content

Instantly share code, notes, and snippets.

@layflags
Last active June 3, 2018 18:55
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 layflags/b33fe55bbe1099babb1554ae79c49d5e to your computer and use it in GitHub Desktop.
Save layflags/b33fe55bbe1099babb1554ae79c49d5e to your computer and use it in GitHub Desktop.
Port of the reactor bundle (part of HenrikJoreteg/redux-bundler) for HyperApp
import createDebug from 'debug';
/* eslint-disable no-restricted-globals */
const HAS_WINDOW = typeof window !== 'undefined';
const IS_BROWSER = HAS_WINDOW || typeof self !== 'undefined';
const ricOptions = { timeout: 500 };
// helpers
const debug = createDebug('hyperapp-reactor');
const fallback = func => {
setTimeout(func, 0);
};
const raf =
IS_BROWSER && self.requestAnimationFrame
? self.requestAnimationFrame
: fallback;
const ric =
IS_BROWSER && self.requestIdleCallback ? self.requestIdleCallback : fallback;
const isArray =
Array.isArray || (arr => ({}.toString.call(arr) === '[object Array]'));
const values = obj => Object.keys(obj).map(key => obj[key]);
const debounce = (fn, wait) => {
let timeout;
function debounced(...args) {
const ctx = this;
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(ctx, args);
}, wait);
}
debounced.cancel = () => {
clearTimeout(timeout);
};
return debounced;
};
const getIdleDispatcher = (stopWhenInactive, timeout, fn) =>
debounce(() => {
// the requestAnimationFrame ensures it doesn't run when tab isn't active
if (stopWhenInactive) {
raf(() => ric(fn, ricOptions));
} else {
ric(fn, ricOptions);
}
}, timeout);
// HOA
const withReactions = (
reactorListOrMap,
{
idleTimeout = 30000,
idleCallback = Function.prototype,
doneCallback = Function.prototype,
stopWhenTabInactive = true,
} = {}
) => {
if (!reactorListOrMap) throw Error('reactors missing');
// internal idle action increments `idleCount`
// and calls `idleCallback` if defined
const idle = () => _state => {
const idleCount = _state.idleCount + 1;
debug(`app idled (${idleCount})`);
idleCallback(_state);
return { idleCount };
};
return app => (state, actions, view, container) => {
let executeNextReaction;
let idleDispatcher;
let cancelIfDone;
let nextReaction;
let activeReactor;
// create the app
const boundActions = app(
{ idleCount: 0, ...state },
{
idle,
...actions,
},
(_state, _actions) => {
executeNextReaction(_state);
if (idleDispatcher) {
idleDispatcher();
cancelIfDone();
}
return view(_state, _actions);
},
container
);
// create idle dispatcher
if (idleTimeout) {
idleDispatcher = getIdleDispatcher(stopWhenTabInactive, idleTimeout, () =>
boundActions.idle()
);
}
cancelIfDone = () => {
if (!IS_BROWSER && !nextReaction) {
if (idleDispatcher) idleDispatcher.cancel();
debug('idle dispatcher canceled');
doneCallback();
}
};
const reactors = isArray(reactorListOrMap)
? reactorListOrMap
: values(reactorListOrMap);
const boundReactors = reactors.map(reactor => {
const boundReactor = reactor(boundActions);
boundReactor.displayName = reactor.name || 'anonymous';
return boundReactor;
});
executeNextReaction = _state => {
// one at a time
if (nextReaction) {
debug(`reactor '${activeReactor}' skipped`);
return;
}
// look for the next one
boundReactors.some(reactor => {
const result = reactor(_state);
if (result) {
activeReactor = reactor.displayName;
nextReaction = result;
return true;
}
return false;
});
if (nextReaction) {
// let browser chill
ric(() => {
nextReaction();
debug(`reactor '${activeReactor}' called`);
nextReaction = null;
activeReactor = null;
}, ricOptions);
}
};
return boundActions;
};
};
export default withReactions;
@layflags
Copy link
Author

layflags commented Jun 3, 2018

Still WIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment