Skip to content

Instantly share code, notes, and snippets.

@layflags
Last active June 3, 2018 18:55
Show Gist options
  • Select an option

  • Save layflags/b33fe55bbe1099babb1554ae79c49d5e to your computer and use it in GitHub Desktop.

Select an option

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

layflags commented Jun 3, 2018

Copy link
Copy Markdown
Author

Still WIP

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