diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6f4af97..2850486 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -3,6 +3,7 @@ name: CI on: pull_request: branches: [master] + merge_group: workflow_dispatch: concurrency: diff --git a/README.md b/README.md index 4ccac7f..422b418 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,10 @@ # @P5-wrapper/react A component to integrate [P5.js](https://p5js.org/) sketches into -[React](https://reactjs.org/) apps. +[React](https://react.dev/) apps. -> Note: Version 5 is still in development, currently the `5.0.0-rc.x` range has -> been released for internal development or experimental testing ONLY. It is -> recommended to continue utilising version `4.4.1` until version `5.0.0` is out -> of the `rc` versioning scheme. -> -> One other thing to note about the coming release of the version `5.x.x` range -> is that it will support the `p5` version `2.x.x` range which means -> [support for `async` and `await`](https://beta.p5js.org/reference/p5/async_await/) -> in your sketches and much more besides, you can read more on the upcoming -> version of the [P5 docs](https://beta.p5js.org/). - -## ⚠️ Migration Notes - -The main component for rendering p5 sketches in React depends on your version: - -- Version 4 and earlier: use `ReactP5Wrapper`. -- Version 5 and later: use `P5Canvas`. - -Both components work in the same way. +
Migrating from version 4? +

Breaking changes in v5: @@ -34,6 +17,17 @@ Breaking changes in v5: - `P5WrapperProps` → `P5CanvasProps` - `P5WrapperClassName` → `CanvasContainerClassName` +3. Peer dependencies + - `p5` >= 2.0.0 + - `react` >= 19.0.0 + - `react-dom` >= 19.0.0 + +If you are still using version 4, you can find the documentation +[here](https://github.com/P5-wrapper/react/tree/v4.4.1#readme). + +

+
+ ## Installation To install, use the following command in the format appropriate to your chosen @@ -48,17 +42,6 @@ package manager: Please note that `p5`, `react` and `react-dom` are peer dependencies. Make sure they are installed in your project before installing this package. -```js -"peerDependencies": { - "p5": ">= 1.4.1", - "react": ">= 18.2.0", - "react-dom": ">= 18.2.0" -}, -``` - -
Version 5 -

- ```js "peerDependencies": { "p5": ">= 2.0.0", @@ -67,9 +50,6 @@ they are installed in your project before installing this package. }, ``` -

-
- ### TypeScript If you would like to use Typescript, you should install `p5` types in the @@ -122,33 +102,6 @@ Then just open `http://localhost:3001` in a browser. ### Javascript -```jsx -import * as React from "react"; -import { ReactP5Wrapper } from "@p5-wrapper/react"; - -function sketch(p5) { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -export function App() { - return ; -} -``` - -
Version 5 -

- ```jsx import * as React from "react"; import { P5Canvas } from "@p5-wrapper/react"; @@ -173,9 +126,6 @@ export function App() { } ``` -

-
- ### TypeScript TypeScript sketches can be declared in two different ways, below you will find @@ -187,33 +137,6 @@ only argument. #### Option 1: Declaring a sketch using the `P5CanvasInstance` type -```tsx -import * as React from "react"; -import { P5CanvasInstance, ReactP5Wrapper } from "@p5-wrapper/react"; - -function sketch(p5: P5CanvasInstance) { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -export function App() { - return ; -} -``` - -
Version 5 -

- ```tsx import * as React from "react"; import { P5Canvas, P5CanvasInstance } from "@p5-wrapper/react"; @@ -238,9 +161,6 @@ export function App() { } ``` -

-
- #### Option 2: Declaring a sketch using the `Sketch` type Using the `Sketch` type has one nice benefit over using `P5CanvasInstance` and @@ -253,33 +173,6 @@ that is that the `p5` argument passed to the sketch function is auto-typed as a > sketches and there is nothing wrong with using the `P5CanvasInstance` manually > in a regular `function` declaration. -```tsx -import * as React from "react"; -import { ReactP5Wrapper, Sketch } from "@p5-wrapper/react"; - -const sketch: Sketch = p5 => { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -}; - -export function App() { - return ; -} -``` - -
Version 5 -

- ```tsx import * as React from "react"; import { P5Canvas, Sketch } from "@p5-wrapper/react"; @@ -304,9 +197,6 @@ export function App() { } ``` -

-
- #### TypeScript Generics We also support the use of Generics to add type definitions for your props. If @@ -335,61 +225,6 @@ correctly typed as a `number`. ##### Usage with the `P5CanvasInstance` type -```tsx -import { - P5CanvasInstance, - ReactP5Wrapper, - SketchProps -} from "@p5-wrapper/react"; -import React, { useEffect, useState } from "react"; - -type MySketchProps = SketchProps & { - rotation: number; -}; - -function sketch(p5: P5CanvasInstance) { - let rotation = 0; - - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.updateWithProps = props => { - if (props.rotation) { - rotation = (props.rotation * Math.PI) / 180; - } - }; - - p5.draw = () => { - p5.background(100); - p5.normalMaterial(); - p5.noStroke(); - p5.push(); - p5.rotateY(rotation); - p5.box(100); - p5.pop(); - }; -} - -export function App() { - const [rotation, setRotation] = useState(0); - - useEffect(() => { - const interval = setInterval( - () => setRotation(rotation => rotation + 100), - 100 - ); - - return () => { - clearInterval(interval); - }; - }, []); - - return ; -} -``` - -
Version 5 -

- ```tsx import { P5Canvas, P5CanvasInstance, SketchProps } from "@p5-wrapper/react"; import React, { useEffect, useState } from "react"; @@ -438,62 +273,8 @@ export function App() { } ``` -

-
- ##### Usage with the `Sketch` type -```tsx -import { ReactP5Wrapper, Sketch, SketchProps } from "@p5-wrapper/react"; -import React, { useEffect, useState } from "react"; - -type MySketchProps = SketchProps & { - rotation: number; -}; - -const sketch: Sketch = p5 => { - let rotation = 0; - - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.updateWithProps = props => { - if (props.rotation) { - rotation = (props.rotation * Math.PI) / 180; - } - }; - - p5.draw = () => { - p5.background(100); - p5.normalMaterial(); - p5.noStroke(); - p5.push(); - p5.rotateY(rotation); - p5.box(100); - p5.pop(); - }; -}; - -export function App() { - const [rotation, setRotation] = useState(0); - - useEffect(() => { - const interval = setInterval( - () => setRotation(rotation => rotation + 100), - 100 - ); - - return () => { - clearInterval(interval); - }; - }, []); - - return ; -} -``` - -
Version 5 -

- ```tsx import { P5Canvas, Sketch, SketchProps } from "@p5-wrapper/react"; import React, { useEffect, useState } from "react"; @@ -542,47 +323,8 @@ export function App() { } ``` -

-
- ### Using abstracted setup and draw functions -```jsx -import * as React from "react"; -import { ReactP5Wrapper } from "@p5-wrapper/react"; - -function setup(p5) { - return () => { - p5.createCanvas(600, 400, p5.WEBGL); - }; -} - -function draw(p5) { - return () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -function sketch(p5) { - p5.setup = setup(p5); - p5.draw = draw(p5); -} - -export function App() { - return ; -} -``` - -
Version 5 -

- ```jsx import * as React from "react"; import { P5Canvas } from "@p5-wrapper/react"; @@ -616,9 +358,6 @@ export function App() { } ``` -

-
- ### Props The only required property is the sketch prop. The sketch prop is a function @@ -632,12 +371,12 @@ updateWithProps method if you have defined it within your sketch. In the below example you see the `updateWithProps` method being used. This is called when the component initially renders and when the props passed to the -wrapper are changed, if it is set within your sketch. This way we can render our -component (`ReactP5Wrapper` in v4, or `P5Canvas` in v5) and react to component -prop changes directly within our sketches! +`P5Canvas` component are changed, if it is set within your sketch. This way we +can render our component and react to component prop changes directly within our +sketches! ```jsx -import { ReactP5Wrapper } from "@p5-wrapper/react"; +import { P5Canvas } from "@p5-wrapper/react"; import React, { useEffect, useState } from "react"; function sketch(p5) { @@ -676,125 +415,23 @@ export function App() { }; }, []); - return ; + return ; } ``` -
Version 5 -

+### Children -```jsx -import { P5Canvas } from "@p5-wrapper/react"; -import React, { useEffect, useState } from "react"; +To render a component on top of the sketch, you can add it as a child of the +`P5Canvas` component and then use the exported constant +`CanvasContainerClassName` in your css-in-js library of choice to style one +element above the other via css. -function sketch(p5) { - let rotation = 0; +For instance, using [styled components](https://styled-components.com), we could +center some text on top of our sketch like so: - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.updateWithProps = props => { - if (props.rotation) { - rotation = (props.rotation * Math.PI) / 180; - } - }; - - p5.draw = () => { - p5.background(100); - p5.normalMaterial(); - p5.noStroke(); - p5.push(); - p5.rotateY(rotation); - p5.box(100); - p5.pop(); - }; -} - -export function App() { - const [rotation, setRotation] = useState(0); - - useEffect(() => { - const interval = setInterval( - () => setRotation(rotation => rotation + 100), - 100 - ); - - return () => { - clearInterval(interval); - }; - }, []); - - return ; -} -``` - -

-
- -### Children - -To render a component on top of the sketch, you can add it as a child of the -component (`ReactP5Wrapper` in v4, or `P5Canvas` in v5) and then use the -exported constant (`P5WrapperClassName` in v4, or `CanvasContainerClassName` in -v5) in your css-in-js library of choice to style one element above the other via -css. - -For instance, using [styled components](https://styled-components.com), we could -center some text on top of our sketch like so: - -```jsx -import { P5WrapperClassName, ReactP5Wrapper } from "@p5-wrapper/react"; -import styled, { createGlobalStyle } from "styled-components"; - -const GlobalWrapperStyles = createGlobalStyle` - .${P5WrapperClassName} { - position: relative; - } -`; - -const StyledCentredText = styled.span` - .${P5WrapperClassName} & { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: white; - font-size: 2rem; - margin: 0; - text-align: center; - } -`; - -export function App() { - const [rotation, setRotation] = useState(0); - - useEffect(() => { - const interval = setInterval( - () => setRotation(rotation => rotation + 100), - 100 - ); - - return () => { - clearInterval(interval); - }; - }, []); - - return ( - - - - Hello world! - - - ); -} -``` - -
Version 5 -

- -```jsx -import { CanvasContainerClassName, P5Canvas } from "@p5-wrapper/react"; -import styled, { createGlobalStyle } from "styled-components"; +```jsx +import { CanvasContainerClassName, P5Canvas } from "@p5-wrapper/react"; +import styled, { createGlobalStyle } from "styled-components"; const GlobalWrapperStyles = createGlobalStyle` .${CanvasContainerClassName} { @@ -840,10 +477,6 @@ export function App() { } ``` -

-
-
- Of course, you can also use any other css-in-js library or by just using simple css to achieve almost anything you can imagine just by using the wrapper class as your root selector. @@ -855,71 +488,6 @@ sync or is undefined for some reason. If this is a use case for you then you call use the `fallback` prop to provide the necessary UI to show in the case that the `sketch` becomes undefined. An example could be as follows: -```jsx -import * as React from "react"; -import { ReactP5Wrapper } from "@p5-wrapper/react"; - -function sketchOne(p5) { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -function sketchTwo(p5) { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(500); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -export function App() { - const [sketch, setSketch] = React.useState(undefined); - const chooseNothing = () => setSketch(undefined); - const chooseSketchOne = () => setSketch(sketchOne); - const chooseSketchTwo = () => setSketch(sketchTwo); - - return ( - <> -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- No sketch selected yet.} - sketch={sketch} - /> - - ); -} -``` - -
Version 5 -

- ```jsx import * as React from "react"; import { P5Canvas } from "@p5-wrapper/react"; @@ -979,10 +547,6 @@ export function App() { } ``` -

-
-
- In this case, by default the fallback UI containing `

No sketch selected yet.

` will be rendered, then if you select a sketch, it will be rendered until you choose to once again "show nothing" which @@ -990,11 +554,8 @@ falls back to the fallback UI. ### Error and Loading UIs -Since version 4.4.0, it was possible to add a `fallback` prop, see the section -on fallbacks. - -Since version 5 it is now possible to pass an `error` and `loading` prop to the -wrapper which allow the user to pass different UIs for error and loading states. +You can pass `error` and `loading` props to the `P5Canvas` component to provide +custom UIs for error and loading states. - The `error` state will trigger if the sketch or the wrapper encounter an issue, otherwise a default error view will be shown. @@ -1003,53 +564,8 @@ wrapper which allow the user to pass different UIs for error and loading states. #### Error UIs -To show a custom UI when an error occurs within the sketch or the wrapper, you -can pass a lazy function to the `error` prop. - -```tsx -import * as React from "react"; -import { P5CanvasInstance, ReactP5Wrapper } from "@p5-wrapper/react"; - -// This child will throw an error, oh no! -function ErrorChild() { - throw new Error("oops"); -} - -// This view will catch the thrown error and give you access to what exactly was thrown. -function ErrorUI(error: any) { - if (error instanceof Error) { - return

An error occured: {error.message}

; - } - - return

An unknown error occured: {error.toString()}

; -} - -function sketch(p5: P5CanvasInstance) { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -export function App() { - return ( - - - - ); -} -``` - -
Version 5 -

+To show a custom UI when an error occurs within the sketch or the component, you +can pass a function to the `error` prop. ```tsx import * as React from "react"; @@ -1093,10 +609,6 @@ export function App() { } ``` -

-

-
- Instead of the sketch, this will render `

An error occured: oops

`. Note that in truth, the `ErrorView` will **always** receive `any` values since JS / TS allow you to `throw` whatever values you want to, this is why we have to add @@ -1104,44 +616,10 @@ the `error instanceof Error` check to be sure the value we got was actually an `Error` instance and not some other value like a `number`, `string`, `object` or anything else that could be thrown by JS / TS. -As mentioned above, the `error` state will trigger if the sketch or the wrapper -encounter an issue, otherwise a default error view will be shown. - #### Loading UIs To show a custom UI while the sketch UI is being lazy loaded, you can pass a -lazy function to the `loading` prop. - -```tsx -import * as React from "react"; -import { P5CanvasInstance, ReactP5Wrapper } from "@p5-wrapper/react"; - -function LoadingUI() { - return

The sketch is being loaded.

; -} - -function sketch(p5: P5CanvasInstance) { - p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -} - -export function App() { - return ; -} -``` - -
Version 5 -

+function to the `loading` prop. ```tsx import * as React from "react"; @@ -1171,16 +649,9 @@ export function App() { } ``` -

-
-
- In the initial period between the sketch render starting and it's lazy loading ending, the `LoadingUI` will be shown! -As mentioned above, the `loading` state will trigger while the wrapper is being -lazy loaded, otherwise a default loading view will be shown. - ## P5 plugins and constructors As discussed in multiple issues such as @@ -1202,70 +673,6 @@ Let's say we want to use the [P5 sound plugin](https://p5js.org/reference/#/libraries/p5.sound) in our component, we could do the following: -```tsx -import * as p5 from "p5"; -import { ReactP5Wrapper, Sketch } from "@p5-wrapper/react"; -import React, { useEffect, useState } from "react"; - -(window as any).p5 = p5; - -await import("p5/lib/addons/p5.sound"); - -const sketch: Sketch = p5 => { - let song: p5.SoundFile; - let button: p5.Element; - - p5.setup = () => { - p5.createCanvas(600, 400, p5.WEBGL); - p5.background(255, 0, 0); - button = p5.createButton("Toggle audio"); - - button.mousePressed(() => { - if (!song) { - const songPath = "/piano.mp3"; - song = p5.loadSound( - songPath, - () => { - song.play(); - }, - () => { - console.error( - `Could not load the requested sound file ${songPath}` - ); - } - ); - return; - } - - if (!song.isPlaying()) { - song.play(); - return; - } - - song.pause(); - }); - }; - - p5.draw = () => { - p5.background(250); - p5.normalMaterial(); - p5.push(); - p5.rotateZ(p5.frameCount * 0.01); - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - p5.plane(100); - p5.pop(); - }; -}; - -export default function App() { - return ; -} -``` - -
Version 5 -

- ```tsx import * as p5 from "p5"; import { P5Canvas, Sketch } from "@p5-wrapper/react"; @@ -1327,10 +734,6 @@ export default function App() { } ``` -

-
-
- In this Typescript + React example, we can see a few key things. - Firstly we need to set `p5` on the `window` object manually. This is because