@buerli.io/react
TypeScript icon, indicating that this package has built-in type declarations

0.11.0 • Public • Published

@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
}

/@buerli.io/react/

    Package Sidebar

    Install

    npm i @buerli.io/react

    Weekly Downloads

    1

    Version

    0.11.0

    License

    ISC

    Unpacked Size

    287 kB

    Total Files

    158

    Last publish

    Collaborators

    • dm385
    • drcmda
    • awv-build