Skip to content

Instantly share code, notes, and snippets.

@0xlkda
Last active July 28, 2020 10:00
Show Gist options
  • Save 0xlkda/87b61b14fddc821611cb3e2bd44ab6e6 to your computer and use it in GitHub Desktop.
Save 0xlkda/87b61b14fddc821611cb3e2bd44ab6e6 to your computer and use it in GitHub Desktop.
import React, {useCallback, useEffect} from 'react'
import {createMachine, invoke, reduce, state, transition} from 'robot3'
import {useMachine} from 'react-robot'
import { curry, apply } from 'ramda'
// Behaviours
const loadFabricJS = () => import('fabric')
const addRect = (ctx, ev) => {
const {canvas} = ctx
canvas.add(ev.rect)
return ctx
}
const resize = (ctx) => {
var {canvas} = ctx
if (!canvas) {
canvas = new ctx.fabric.Canvas('kustom')
}
const ratio = canvas.getWidth() / canvas.getHeight()
const containerWidth = window.innerWidth
const scale = containerWidth / canvas.getWidth()
const zoom = canvas.getZoom() * scale
canvas.setWidth(containerWidth)
canvas.setHeight(containerWidth / ratio)
canvas.setDimensions({width: containerWidth, height: containerWidth / ratio})
canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0])
canvas.renderAll()
return canvas
}
// State Machine
const context = () => ({
fabric: undefined,
canvas: undefined,
})
const appMachine = createMachine(
'idle',
{ idle: state(transition('initVendor', 'initializing')),
initializing: invoke(
loadFabricJS,
transition( 'done', 'initialized',
reduce((ctx, ev) => {
const nextCtx = {...ctx, fabric: ev.data.default.fabric}
const canvas = resize(nextCtx)
return {...nextCtx, canvas}})),
transition( 'error', 'error',
reduce((ctx, ev) => ({...ctx, error: ev.error})))),
initialized: state(
transition( 'onResize', 'initialized',
reduce((ctx) => ({...ctx, ...resize(ctx)}))),
transition('addRect', 'initialized', reduce(addRect))),
error: state()
}, context)
// Utils
const debounce = curry((fn, timeMs, immediate = false) => {
let timeout;
return (...args) => {
const callNow = immediate && !timeout;
const later = () => {
timeout = null;
if (!immediate) apply(fn, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, timeMs);
if (callNow) apply(fn, args);
return timeout;
};
});
const debounced = (fn) => debounce(fn, 500)
// Hooks
const useEvent = (event, handler, passive = false) => {
useEffect(() => {
window.addEventListener(event, handler, passive)
return () => window.removeEventListener(event, handler)
})
}
// Components
const Button = (props) => <button {...props}>{props.text}</button>
const App = () => {
const [current, send] = useMachine(appMachine)
const handleResize = useCallback(() => send({type: 'onResize'}), [send])
const handleAddThing = useCallback((specs) => () =>
current.name === 'initialized' &&
send({type: 'addRect', rect: new current.context.fabric[specs.type](specs)}),
[current, send])
useEvent('resize', debounced(handleResize), [send])
useEffect(() => send('initVendor'), [send])
return (
<div className="App">
{current.name}
<canvas id="kustom" />
<Button
text="Add Circle"
onClick={handleAddThing({ type: 'Circle', radius: 50 })}
/>
<Button
text="Add Rect"
onClick={handleAddThing({ type: 'Rect', width: 30, height: 30, left: 100, top: 100, fill: 'red' })} />
</div>
)
}
export default App
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment