@buerli.io/react
This is a wrapper for buerli.io that allows you to use ClassCAD headless in React. Usage information and reference details can be found in the buerli documentation
Install
npm install @buerli.io/react
yarn add @buerli.io/react
Types
/**
* Creates a ClassCAD connection and returns a set of functions to interact with it.
* @param {solid|history} impl The desired headless instance, for example: solid or history
* @param {string} url The url of the ClassCAD server
* @param {HeadlessConfig} config Optional object of options
* - socket: Buerli socket adapter, default: SocketIOClient
- lifespan: Suspense entry invalidation in ms, default: 0 (keep-alive forever)
- equal: Suspense cache equality function, default: (a, b) => a === b (reference equality)
- store: An object that can persist values for the lenght of the session, default: {}
* @returns {HeadlessReturn} A set of functions to interact with the headless instance
* @example
* const { run } = headless(solid, 'http://localhost:9091')
* function Box() {
* useEffect(() => {
* run(async (api) => {
* const id = await api.createBox(100, 100, 100)
* })
* }, [])
* return <BuerliGeometry />
* }
*/
export const headless = (impl: Impl, url: string, config: HeadlessConfig = {}): HeadlessReturn
type HeadlessReturn<Impl, Store> = {
/** A user-defined store where variables can be shared */
store: Store
/** The headless instance, for example: new solid() */
instance: InstanceType<Impl>
/** The headless instance API, for example: new solid().init(api => ...) */
api: Promise<ApiType<Impl>>
/**
* A hook that suspends the component until the drawing ID is available
* @returns {DrawingID} The drawing ID
*/
useDrawingId: () => DrawingID
/**
* Executes ClassCAD commands
* @param {function} callback A function that receives the headless API as the first parameter,
* and the user-defined store as the second
* @example
* function Box() {
* useEffect(() => {
* run(async (api) => {
* const id = await api.createBox(100, 100, 100)
* })
* }, [])
* return <BuerliGeometry />
* }
*/
run: <Fn extends (api: ApiType<Impl>, store: Store) => Promise<unknown>>(
callback: Fn,
) => Promise<Await<ReturnType<Fn>>>
/**
* Executes ClassCAD commands and returns a memoized result (using React suspense)
* @param {function} callback A function that receives the headless API as the first parameter,
* the user-defined store as the second, and the dependencies as the rest
* @param {Array} dependencies An array of dependencies that will trigger a re-execution of the callback
* @returns {any} The result of the callback
* @example
* function Box({ size }) {
* const geo = cache(async (api) => {
* const id = await api.createBox(size, size, size)
* return await api.createBufferGeometry(id)
* }, [size])
* return (
* <mesh geometry={geo}>
* <meshStandardMaterial />
* </mesh>
* )
* }
*/
cache: <
Keys extends Tuple<unknown>,
Fn extends (api: ApiType<Impl>, store: Store, ...keys: Keys) => Promise<unknown>,
>(
callback: Fn,
dependencies: Keys,
) => Await<ReturnType<Fn>>
/**
* Looks up a previously memoized result (that was executed by the cache function above)
* @param {Array} dependencies An array of dependencies
* @returns {any} The result of the callback, if present
*/
peek: <Keys extends Tuple<unknown>>(dependencies: Keys) => unknown
/**
* Executes ClassCAD commands and preloads the memoized result
* This function can be executed in global space to preload data before the app starts.
* By the time cache(fn, deps) is called with the same dependencies, the data will be available immediately.
* @param {function} callback A function that receives the headless API as the first parameter,
* the user-defined store as the second, and the dependencies as the rest
* @param {Array} dependencies An array of dependencies
*/
preload: <Keys extends Tuple<unknown>, Fn extends (api: ApiType<Impl>, ...keys: Keys) => Promise<unknown>>(
callback: Fn,
dependencies: Keys,
) => unknown
}
#### Run
Either use run
which merely executes and <BuerliGeometry>
to display the results asynchroneously (although the suspend
option would allow you to orchestrate inside useEffect and useLayoutEffect).
import { BuerliGeometry, headless } from '@buerli.io/react'
import { history } from '@buerli.io/headless'
const { run } = headless(history, 'ws://localhost:9091')
function Scene({ width = 100 }) {
useEffect(() => {
run(async (api, store) => {
const part = await api.createPart('Part')
const wcsy = await api.createWorkCoordSystem(part, 8, [], [], [0, width / 3, 0], [Math.PI / 3, 0, 0])
const wcsx = await api.createWorkCoordSystem(part, 8, [], [], [0, -width / 5, -width / 8], [0, 0, 0])
const a = await api.cylinder(part, [wcsx], 10, width)
const b = await api.cylinder(part, [wcsy], 10, width)
await api.boolean(part, 0, [a, b])
// You can put anything into the store and use it in consecutive run calls
store.part = part
})
}, [])
return <BuerliGeometry suspend />
}
Cache
Or use cache
which suspends and returns the result of the function as a cached and memoized value. The results can be displayed in whichever way you like, for instance returning a geometry and adding it to a mesh right away.
The dependecies are cache keys. Similar to a useMemo the inner function is called when the cache keys change. The dependencies are also passed to the inner function, so you could hoist it.
import { BuerliGeometry, headless } from '@buerli.io/react'
import { history } from '@buerli.io/headless'
const { cache } = headless(history, 'ws://localhost:9091')
function Scene({ width = 100 }) {
const geo = cache(
async (api, store, ...args) => {
const part = await api.createPart('Part')
const wcsy = await api.createWorkCoordSystem(part, 8, [], [], [0, width / 3, 0], [Math.PI / 3, 0, 0])
const wcsx = await api.createWorkCoordSystem(part, 8, [], [], [0, -width / 5, -width / 8], [0, 0, 0])
const a = await api.cylinder(part, [wcsx], 10, width)
const b = await api.cylinder(part, [wcsy], 10, width)
const solid = await api.boolean(part, 0, [a, b])
return await api.createBufferGeometry(solid)
},
[width],
)
return (
<mesh geometry={geo}>
<meshStandardMaterial />
</mesh>
)
}
### Typescript
All input and output types are correctly inferred.
const { cache, instance, api } = headless(history, 'ws://localhost:9091', { store: { foo: 'bar' } })
// typeof instance === typeof history
// typeof api === Promise<typeof ApiHistory>
function Foo() {
const baz = cache(
async (api, store, dep1, dep2, ...rest) => {
// typeof api === typeof ApiHistory
// typeof store === { foo: string }
// typeof dep1 === string
// typeof dep2 === number
// typeof rest === [boolean, { hello: string }]
const part = api.createPart('foo')
// typeof part === typeof ID
return store.foo
},
['hi', 1, true, { hello: 'world' }],
)
// typeof baz === string
}