react-shadertoy 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -6
- package/dist/index.d.mts +79 -3
- package/dist/index.d.ts +79 -3
- package/dist/index.js +177 -8
- package/dist/index.mjs +176 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
Run [Shadertoy](https://www.shadertoy.com/) GLSL shaders in React. Copy-paste and it works.
|
|
4
4
|
|
|
5
5
|
- Zero dependencies (just React)
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
6
|
+
- WebGL2 (GLSL ES 3.0) — full Shadertoy compatibility
|
|
7
|
+
- All uniforms: `iTime`, `iResolution`, `iMouse`, `iDate`, `iFrame`, etc.
|
|
8
|
+
- iChannel0-3 textures (image URL, video, canvas, with wrap/filter/vflip)
|
|
9
|
+
- Multipass rendering (Buffer A-D with ping-pong FBO)
|
|
10
|
+
- Shadertoy API integration (`<Shadertoy id="MdX3zr" />`)
|
|
9
11
|
- Mouse & touch interaction built-in
|
|
10
12
|
- TypeScript-first
|
|
11
13
|
|
|
@@ -15,7 +17,7 @@ Run [Shadertoy](https://www.shadertoy.com/) GLSL shaders in React. Copy-paste an
|
|
|
15
17
|
npm install react-shadertoy
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
##
|
|
20
|
+
## Quick Start
|
|
19
21
|
|
|
20
22
|
```tsx
|
|
21
23
|
import { Shadertoy } from 'react-shadertoy'
|
|
@@ -37,13 +39,99 @@ function App() {
|
|
|
37
39
|
|
|
38
40
|
Find a shader on [Shadertoy](https://www.shadertoy.com/), copy the GLSL code, paste it into `fragmentShader`. Done.
|
|
39
41
|
|
|
42
|
+
## Textures
|
|
43
|
+
|
|
44
|
+
Pass image URLs, video elements, or canvas elements as textures:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Shadertoy
|
|
48
|
+
fragmentShader={code}
|
|
49
|
+
textures={{
|
|
50
|
+
iChannel0: '/noise.png',
|
|
51
|
+
iChannel1: videoRef.current,
|
|
52
|
+
iChannel2: canvasRef.current,
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Texture Options
|
|
58
|
+
|
|
59
|
+
Control wrap mode, filtering, and vertical flip:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<Shadertoy
|
|
63
|
+
fragmentShader={code}
|
|
64
|
+
textures={{
|
|
65
|
+
iChannel0: {
|
|
66
|
+
src: '/noise.png',
|
|
67
|
+
wrap: 'repeat', // 'clamp' | 'repeat' (default: 'clamp')
|
|
68
|
+
filter: 'mipmap', // 'nearest' | 'linear' | 'mipmap' (default: 'mipmap')
|
|
69
|
+
vflip: true, // vertical flip (default: true)
|
|
70
|
+
},
|
|
71
|
+
iChannel1: '/simple.png', // shorthand = default options
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Multipass
|
|
77
|
+
|
|
78
|
+
Buffer A-D with self-referencing feedback loops:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<Shadertoy
|
|
82
|
+
passes={{
|
|
83
|
+
BufferA: {
|
|
84
|
+
code: bufferACode,
|
|
85
|
+
iChannel0: 'BufferA', // self-reference (previous frame)
|
|
86
|
+
iChannel1: '/noise.png', // external texture
|
|
87
|
+
},
|
|
88
|
+
BufferB: {
|
|
89
|
+
code: bufferBCode,
|
|
90
|
+
iChannel0: 'BufferA', // read Buffer A output
|
|
91
|
+
},
|
|
92
|
+
Image: {
|
|
93
|
+
code: imageCode,
|
|
94
|
+
iChannel0: 'BufferA',
|
|
95
|
+
iChannel1: 'BufferB',
|
|
96
|
+
},
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Shadertoy API
|
|
102
|
+
|
|
103
|
+
Load shaders directly from Shadertoy by ID:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<Shadertoy
|
|
107
|
+
id="MdX3zr"
|
|
108
|
+
apiKey="your-api-key"
|
|
109
|
+
/>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Shows an author/name overlay by default. Disable with `showLicense={false}`.
|
|
113
|
+
|
|
114
|
+
API key from [shadertoy.com/myapps](https://www.shadertoy.com/myapps).
|
|
115
|
+
|
|
116
|
+
### Build-Time Fetch
|
|
117
|
+
|
|
118
|
+
For production, fetch at build time to avoid runtime API calls:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { fetchShader, apiToConfig } from 'react-shadertoy'
|
|
122
|
+
|
|
123
|
+
const shader = await fetchShader('MdX3zr', process.env.SHADERTOY_API_KEY)
|
|
124
|
+
const config = apiToConfig(shader)
|
|
125
|
+
// Save config to JSON, use passes prop at runtime
|
|
126
|
+
```
|
|
127
|
+
|
|
40
128
|
## Hooks API
|
|
41
129
|
|
|
42
130
|
```tsx
|
|
43
131
|
import { useShadertoy } from 'react-shadertoy'
|
|
44
132
|
|
|
45
133
|
function MyShader() {
|
|
46
|
-
const { canvasRef, isReady, error, pause, resume } = useShadertoy({
|
|
134
|
+
const { canvasRef, isReady, error, pause, resume, meta } = useShadertoy({
|
|
47
135
|
fragmentShader: `
|
|
48
136
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
49
137
|
vec2 uv = fragCoord / iResolution.xy;
|
|
@@ -60,7 +148,12 @@ function MyShader() {
|
|
|
60
148
|
|
|
61
149
|
| Prop | Type | Default | Description |
|
|
62
150
|
|------|------|---------|-------------|
|
|
63
|
-
| `fragmentShader` | `string` |
|
|
151
|
+
| `fragmentShader` | `string` | — | Shadertoy GLSL code |
|
|
152
|
+
| `textures` | `TextureInputs` | — | iChannel0-3 texture sources |
|
|
153
|
+
| `passes` | `MultipassConfig` | — | Multipass Buffer A-D + Image |
|
|
154
|
+
| `id` | `string` | — | Shadertoy shader ID (API mode) |
|
|
155
|
+
| `apiKey` | `string` | — | Shadertoy API key |
|
|
156
|
+
| `showLicense` | `boolean` | `true` (API) | Show author overlay |
|
|
64
157
|
| `style` | `CSSProperties` | — | Container style |
|
|
65
158
|
| `className` | `string` | — | Container className |
|
|
66
159
|
| `paused` | `boolean` | `false` | Pause rendering |
|
|
@@ -80,6 +173,8 @@ function MyShader() {
|
|
|
80
173
|
| `iFrame` | `int` | Frame counter |
|
|
81
174
|
| `iMouse` | `vec4` | Mouse position & click state |
|
|
82
175
|
| `iDate` | `vec4` | Year, month, day, seconds |
|
|
176
|
+
| `iChannel0-3` | `sampler2D` | Texture inputs |
|
|
177
|
+
| `iChannelResolution` | `vec3[4]` | Texture dimensions |
|
|
83
178
|
|
|
84
179
|
## License
|
|
85
180
|
|
package/dist/index.d.mts
CHANGED
|
@@ -36,6 +36,58 @@ interface PassConfig {
|
|
|
36
36
|
type MultipassConfig = {
|
|
37
37
|
[K in PassName]?: PassConfig;
|
|
38
38
|
};
|
|
39
|
+
interface ShadertoyApiSampler {
|
|
40
|
+
filter: string;
|
|
41
|
+
wrap: string;
|
|
42
|
+
vflip: string;
|
|
43
|
+
srgb: string;
|
|
44
|
+
internal: string;
|
|
45
|
+
}
|
|
46
|
+
interface ShadertoyApiInput {
|
|
47
|
+
id: number;
|
|
48
|
+
src: string;
|
|
49
|
+
ctype: string;
|
|
50
|
+
channel: number;
|
|
51
|
+
sampler: ShadertoyApiSampler;
|
|
52
|
+
published: number;
|
|
53
|
+
}
|
|
54
|
+
interface ShadertoyApiOutput {
|
|
55
|
+
id: number;
|
|
56
|
+
channel: number;
|
|
57
|
+
}
|
|
58
|
+
interface ShadertoyApiRenderPass {
|
|
59
|
+
inputs: ShadertoyApiInput[];
|
|
60
|
+
outputs: ShadertoyApiOutput[];
|
|
61
|
+
code: string;
|
|
62
|
+
name: string;
|
|
63
|
+
description: string;
|
|
64
|
+
type: string;
|
|
65
|
+
}
|
|
66
|
+
interface ShadertoyApiInfo {
|
|
67
|
+
id: string;
|
|
68
|
+
date: string;
|
|
69
|
+
viewed: number;
|
|
70
|
+
name: string;
|
|
71
|
+
username: string;
|
|
72
|
+
description: string;
|
|
73
|
+
likes: number;
|
|
74
|
+
published: number;
|
|
75
|
+
flags: number;
|
|
76
|
+
tags: string[];
|
|
77
|
+
hasliked: number;
|
|
78
|
+
}
|
|
79
|
+
interface ShadertoyApiShader {
|
|
80
|
+
ver: string;
|
|
81
|
+
info: ShadertoyApiInfo;
|
|
82
|
+
renderpass: ShadertoyApiRenderPass[];
|
|
83
|
+
}
|
|
84
|
+
interface ShaderMeta {
|
|
85
|
+
name: string;
|
|
86
|
+
author: string;
|
|
87
|
+
description: string;
|
|
88
|
+
tags: string[];
|
|
89
|
+
license?: string;
|
|
90
|
+
}
|
|
39
91
|
interface ShadertoyProps {
|
|
40
92
|
/** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
|
|
41
93
|
fragmentShader?: string;
|
|
@@ -43,6 +95,12 @@ interface ShadertoyProps {
|
|
|
43
95
|
passes?: MultipassConfig;
|
|
44
96
|
/** Texture inputs for iChannel0-3 (single-pass mode) */
|
|
45
97
|
textures?: TextureInputs;
|
|
98
|
+
/** Shadertoy shader ID — fetches shader from API */
|
|
99
|
+
id?: string;
|
|
100
|
+
/** Shadertoy API key (required when using id) */
|
|
101
|
+
apiKey?: string;
|
|
102
|
+
/** Show license/author overlay (default: true when using id) */
|
|
103
|
+
showLicense?: boolean;
|
|
46
104
|
/** Container style */
|
|
47
105
|
style?: CSSProperties;
|
|
48
106
|
/** Container className */
|
|
@@ -64,6 +122,8 @@ interface UseShadertoyOptions {
|
|
|
64
122
|
fragmentShader?: string;
|
|
65
123
|
passes?: MultipassConfig;
|
|
66
124
|
textures?: TextureInputs;
|
|
125
|
+
id?: string;
|
|
126
|
+
apiKey?: string;
|
|
67
127
|
paused?: boolean;
|
|
68
128
|
speed?: number;
|
|
69
129
|
pixelRatio?: number;
|
|
@@ -77,10 +137,26 @@ interface UseShadertoyReturn {
|
|
|
77
137
|
error: string | null;
|
|
78
138
|
pause: () => void;
|
|
79
139
|
resume: () => void;
|
|
140
|
+
meta: ShaderMeta | null;
|
|
80
141
|
}
|
|
81
142
|
|
|
82
|
-
declare function Shadertoy({ fragmentShader, passes, textures, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
|
|
143
|
+
declare function Shadertoy({ fragmentShader, passes, textures, id, apiKey, showLicense, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
|
|
83
144
|
|
|
84
|
-
declare function useShadertoy({ fragmentShader, passes: passesProp, textures: texturesProp, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn
|
|
145
|
+
declare function useShadertoy({ fragmentShader, passes: passesProp, textures: texturesProp, id, apiKey, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn & {
|
|
146
|
+
meta: ShaderMeta | null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fetch a shader from the Shadertoy API.
|
|
151
|
+
*/
|
|
152
|
+
declare function fetchShader(id: string, apiKey: string): Promise<ShadertoyApiShader>;
|
|
153
|
+
/**
|
|
154
|
+
* Convert a Shadertoy API shader to our MultipassConfig + TextureInputs + meta.
|
|
155
|
+
*/
|
|
156
|
+
declare function apiToConfig(shader: ShadertoyApiShader): {
|
|
157
|
+
passes: MultipassConfig;
|
|
158
|
+
textures: TextureInputs;
|
|
159
|
+
meta: ShaderMeta;
|
|
160
|
+
};
|
|
85
161
|
|
|
86
|
-
export { type MultipassConfig, type PassConfig, type PassName, Shadertoy, type ShadertoyProps, type TextureFilter, type TextureInput, type TextureInputs, type TextureOptions, type TextureWrap, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
|
|
162
|
+
export { type MultipassConfig, type PassConfig, type PassName, type ShaderMeta, Shadertoy, type ShadertoyApiShader, type ShadertoyProps, type TextureFilter, type TextureInput, type TextureInputs, type TextureOptions, type TextureWrap, type UseShadertoyOptions, type UseShadertoyReturn, apiToConfig, fetchShader, useShadertoy };
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,58 @@ interface PassConfig {
|
|
|
36
36
|
type MultipassConfig = {
|
|
37
37
|
[K in PassName]?: PassConfig;
|
|
38
38
|
};
|
|
39
|
+
interface ShadertoyApiSampler {
|
|
40
|
+
filter: string;
|
|
41
|
+
wrap: string;
|
|
42
|
+
vflip: string;
|
|
43
|
+
srgb: string;
|
|
44
|
+
internal: string;
|
|
45
|
+
}
|
|
46
|
+
interface ShadertoyApiInput {
|
|
47
|
+
id: number;
|
|
48
|
+
src: string;
|
|
49
|
+
ctype: string;
|
|
50
|
+
channel: number;
|
|
51
|
+
sampler: ShadertoyApiSampler;
|
|
52
|
+
published: number;
|
|
53
|
+
}
|
|
54
|
+
interface ShadertoyApiOutput {
|
|
55
|
+
id: number;
|
|
56
|
+
channel: number;
|
|
57
|
+
}
|
|
58
|
+
interface ShadertoyApiRenderPass {
|
|
59
|
+
inputs: ShadertoyApiInput[];
|
|
60
|
+
outputs: ShadertoyApiOutput[];
|
|
61
|
+
code: string;
|
|
62
|
+
name: string;
|
|
63
|
+
description: string;
|
|
64
|
+
type: string;
|
|
65
|
+
}
|
|
66
|
+
interface ShadertoyApiInfo {
|
|
67
|
+
id: string;
|
|
68
|
+
date: string;
|
|
69
|
+
viewed: number;
|
|
70
|
+
name: string;
|
|
71
|
+
username: string;
|
|
72
|
+
description: string;
|
|
73
|
+
likes: number;
|
|
74
|
+
published: number;
|
|
75
|
+
flags: number;
|
|
76
|
+
tags: string[];
|
|
77
|
+
hasliked: number;
|
|
78
|
+
}
|
|
79
|
+
interface ShadertoyApiShader {
|
|
80
|
+
ver: string;
|
|
81
|
+
info: ShadertoyApiInfo;
|
|
82
|
+
renderpass: ShadertoyApiRenderPass[];
|
|
83
|
+
}
|
|
84
|
+
interface ShaderMeta {
|
|
85
|
+
name: string;
|
|
86
|
+
author: string;
|
|
87
|
+
description: string;
|
|
88
|
+
tags: string[];
|
|
89
|
+
license?: string;
|
|
90
|
+
}
|
|
39
91
|
interface ShadertoyProps {
|
|
40
92
|
/** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
|
|
41
93
|
fragmentShader?: string;
|
|
@@ -43,6 +95,12 @@ interface ShadertoyProps {
|
|
|
43
95
|
passes?: MultipassConfig;
|
|
44
96
|
/** Texture inputs for iChannel0-3 (single-pass mode) */
|
|
45
97
|
textures?: TextureInputs;
|
|
98
|
+
/** Shadertoy shader ID — fetches shader from API */
|
|
99
|
+
id?: string;
|
|
100
|
+
/** Shadertoy API key (required when using id) */
|
|
101
|
+
apiKey?: string;
|
|
102
|
+
/** Show license/author overlay (default: true when using id) */
|
|
103
|
+
showLicense?: boolean;
|
|
46
104
|
/** Container style */
|
|
47
105
|
style?: CSSProperties;
|
|
48
106
|
/** Container className */
|
|
@@ -64,6 +122,8 @@ interface UseShadertoyOptions {
|
|
|
64
122
|
fragmentShader?: string;
|
|
65
123
|
passes?: MultipassConfig;
|
|
66
124
|
textures?: TextureInputs;
|
|
125
|
+
id?: string;
|
|
126
|
+
apiKey?: string;
|
|
67
127
|
paused?: boolean;
|
|
68
128
|
speed?: number;
|
|
69
129
|
pixelRatio?: number;
|
|
@@ -77,10 +137,26 @@ interface UseShadertoyReturn {
|
|
|
77
137
|
error: string | null;
|
|
78
138
|
pause: () => void;
|
|
79
139
|
resume: () => void;
|
|
140
|
+
meta: ShaderMeta | null;
|
|
80
141
|
}
|
|
81
142
|
|
|
82
|
-
declare function Shadertoy({ fragmentShader, passes, textures, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
|
|
143
|
+
declare function Shadertoy({ fragmentShader, passes, textures, id, apiKey, showLicense, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
|
|
83
144
|
|
|
84
|
-
declare function useShadertoy({ fragmentShader, passes: passesProp, textures: texturesProp, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn
|
|
145
|
+
declare function useShadertoy({ fragmentShader, passes: passesProp, textures: texturesProp, id, apiKey, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn & {
|
|
146
|
+
meta: ShaderMeta | null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fetch a shader from the Shadertoy API.
|
|
151
|
+
*/
|
|
152
|
+
declare function fetchShader(id: string, apiKey: string): Promise<ShadertoyApiShader>;
|
|
153
|
+
/**
|
|
154
|
+
* Convert a Shadertoy API shader to our MultipassConfig + TextureInputs + meta.
|
|
155
|
+
*/
|
|
156
|
+
declare function apiToConfig(shader: ShadertoyApiShader): {
|
|
157
|
+
passes: MultipassConfig;
|
|
158
|
+
textures: TextureInputs;
|
|
159
|
+
meta: ShaderMeta;
|
|
160
|
+
};
|
|
85
161
|
|
|
86
|
-
export { type MultipassConfig, type PassConfig, type PassName, Shadertoy, type ShadertoyProps, type TextureFilter, type TextureInput, type TextureInputs, type TextureOptions, type TextureWrap, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
|
|
162
|
+
export { type MultipassConfig, type PassConfig, type PassName, type ShaderMeta, Shadertoy, type ShadertoyApiShader, type ShadertoyProps, type TextureFilter, type TextureInput, type TextureInputs, type TextureOptions, type TextureWrap, type UseShadertoyOptions, type UseShadertoyReturn, apiToConfig, fetchShader, useShadertoy };
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Shadertoy: () => Shadertoy,
|
|
24
|
+
apiToConfig: () => apiToConfig,
|
|
25
|
+
fetchShader: () => fetchShader,
|
|
24
26
|
useShadertoy: () => useShadertoy
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -28,6 +30,98 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
30
|
// src/useShadertoy.ts
|
|
29
31
|
var import_react = require("react");
|
|
30
32
|
|
|
33
|
+
// src/api.ts
|
|
34
|
+
var SHADERTOY_BASE = "https://www.shadertoy.com";
|
|
35
|
+
var API_URL = `${SHADERTOY_BASE}/api/v1/shaders`;
|
|
36
|
+
var OUTPUT_ID_TO_PASS = {
|
|
37
|
+
257: "BufferA",
|
|
38
|
+
258: "BufferB",
|
|
39
|
+
259: "BufferC",
|
|
40
|
+
260: "BufferD"
|
|
41
|
+
};
|
|
42
|
+
var cache = /* @__PURE__ */ new Map();
|
|
43
|
+
async function fetchShader(id, apiKey) {
|
|
44
|
+
const cached = cache.get(id);
|
|
45
|
+
if (cached) return cached;
|
|
46
|
+
const res = await fetch(`${API_URL}/${id}?key=${apiKey}`);
|
|
47
|
+
if (!res.ok) throw new Error(`Shadertoy API error: ${res.status}`);
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
if (data.Error) throw new Error(`Shadertoy API: ${data.Error}`);
|
|
50
|
+
cache.set(id, data.Shader);
|
|
51
|
+
return data.Shader;
|
|
52
|
+
}
|
|
53
|
+
function mapWrap(wrap) {
|
|
54
|
+
if (wrap === "repeat") return "repeat";
|
|
55
|
+
return "clamp";
|
|
56
|
+
}
|
|
57
|
+
function mapFilter(filter) {
|
|
58
|
+
if (filter === "nearest") return "nearest";
|
|
59
|
+
if (filter === "linear") return "linear";
|
|
60
|
+
return "mipmap";
|
|
61
|
+
}
|
|
62
|
+
function resolveTextureSrc(src) {
|
|
63
|
+
if (src.startsWith("http")) return src;
|
|
64
|
+
return SHADERTOY_BASE + src;
|
|
65
|
+
}
|
|
66
|
+
function apiToConfig(shader) {
|
|
67
|
+
const passes = {};
|
|
68
|
+
const textures = {};
|
|
69
|
+
const commonPass = shader.renderpass.find((p) => p.type === "common");
|
|
70
|
+
const commonCode = commonPass ? commonPass.code + "\n" : "";
|
|
71
|
+
for (const rp of shader.renderpass) {
|
|
72
|
+
if (rp.type === "common" || rp.type === "sound") continue;
|
|
73
|
+
const passName = getPassName(rp);
|
|
74
|
+
if (!passName) continue;
|
|
75
|
+
const passConfig = {
|
|
76
|
+
code: commonCode + rp.code
|
|
77
|
+
};
|
|
78
|
+
for (const input of rp.inputs) {
|
|
79
|
+
const channelKey = `iChannel${input.channel}`;
|
|
80
|
+
if (channelKey === "code") continue;
|
|
81
|
+
if (input.ctype === "buffer") {
|
|
82
|
+
const refPass = OUTPUT_ID_TO_PASS[input.id];
|
|
83
|
+
if (refPass) {
|
|
84
|
+
;
|
|
85
|
+
passConfig[channelKey] = refPass;
|
|
86
|
+
}
|
|
87
|
+
} else if (input.ctype === "texture" || input.ctype === "cubemap") {
|
|
88
|
+
const texOpts = {
|
|
89
|
+
src: resolveTextureSrc(input.src),
|
|
90
|
+
wrap: mapWrap(input.sampler.wrap),
|
|
91
|
+
filter: mapFilter(input.sampler.filter),
|
|
92
|
+
vflip: input.sampler.vflip === "true"
|
|
93
|
+
};
|
|
94
|
+
passConfig[channelKey] = texOpts;
|
|
95
|
+
const texKey = `iChannel${input.channel}`;
|
|
96
|
+
textures[texKey] = texOpts;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
passes[passName] = passConfig;
|
|
100
|
+
}
|
|
101
|
+
const meta = {
|
|
102
|
+
name: shader.info.name,
|
|
103
|
+
author: shader.info.username,
|
|
104
|
+
description: shader.info.description,
|
|
105
|
+
tags: shader.info.tags
|
|
106
|
+
};
|
|
107
|
+
return { passes, textures, meta };
|
|
108
|
+
}
|
|
109
|
+
function getPassName(rp) {
|
|
110
|
+
if (rp.type === "image") return "Image";
|
|
111
|
+
if (rp.type === "buffer") {
|
|
112
|
+
for (const out of rp.outputs) {
|
|
113
|
+
const name = OUTPUT_ID_TO_PASS[out.id];
|
|
114
|
+
if (name) return name;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function isSinglePass(passes) {
|
|
121
|
+
const keys = Object.keys(passes);
|
|
122
|
+
return keys.length === 1 && keys[0] === "Image";
|
|
123
|
+
}
|
|
124
|
+
|
|
31
125
|
// src/renderer.ts
|
|
32
126
|
var QUAD_VERTICES = new Float32Array([
|
|
33
127
|
-1,
|
|
@@ -578,6 +672,8 @@ function useShadertoy({
|
|
|
578
672
|
fragmentShader,
|
|
579
673
|
passes: passesProp,
|
|
580
674
|
textures: texturesProp,
|
|
675
|
+
id,
|
|
676
|
+
apiKey,
|
|
581
677
|
paused = false,
|
|
582
678
|
speed = 1,
|
|
583
679
|
pixelRatio,
|
|
@@ -593,6 +689,8 @@ function useShadertoy({
|
|
|
593
689
|
const speedRef = (0, import_react.useRef)(speed);
|
|
594
690
|
const [isReady, setIsReady] = (0, import_react.useState)(false);
|
|
595
691
|
const [error, setError] = (0, import_react.useState)(null);
|
|
692
|
+
const [meta, setMeta] = (0, import_react.useState)(null);
|
|
693
|
+
const [resolved, setResolved] = (0, import_react.useState)(id ? null : { passes: passesProp, textures: texturesProp, fragmentShader });
|
|
596
694
|
const mouseState = (0, import_react.useRef)({
|
|
597
695
|
x: 0,
|
|
598
696
|
y: 0,
|
|
@@ -603,8 +701,43 @@ function useShadertoy({
|
|
|
603
701
|
const sharedState = (0, import_react.useRef)({ time: 0, frame: 0 });
|
|
604
702
|
pausedRef.current = paused;
|
|
605
703
|
speedRef.current = speed;
|
|
606
|
-
const isMultipass = !!passesProp;
|
|
607
704
|
(0, import_react.useEffect)(() => {
|
|
705
|
+
if (!id) return;
|
|
706
|
+
if (!apiKey) {
|
|
707
|
+
setError("apiKey is required when using id");
|
|
708
|
+
onError?.("apiKey is required when using id");
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
let cancelled = false;
|
|
712
|
+
fetchShader(id, apiKey).then((shader) => {
|
|
713
|
+
if (cancelled) return;
|
|
714
|
+
const config = apiToConfig(shader);
|
|
715
|
+
setMeta(config.meta);
|
|
716
|
+
if (isSinglePass(config.passes)) {
|
|
717
|
+
const imagePass = config.passes.Image;
|
|
718
|
+
setResolved({
|
|
719
|
+
fragmentShader: imagePass.code,
|
|
720
|
+
textures: config.textures
|
|
721
|
+
});
|
|
722
|
+
} else {
|
|
723
|
+
setResolved({ passes: config.passes });
|
|
724
|
+
}
|
|
725
|
+
}).catch((err) => {
|
|
726
|
+
if (cancelled) return;
|
|
727
|
+
const msg = err instanceof Error ? err.message : "Failed to fetch shader";
|
|
728
|
+
setError(msg);
|
|
729
|
+
onError?.(msg);
|
|
730
|
+
});
|
|
731
|
+
return () => {
|
|
732
|
+
cancelled = true;
|
|
733
|
+
};
|
|
734
|
+
}, [id, apiKey]);
|
|
735
|
+
const effectivePasses = resolved?.passes;
|
|
736
|
+
const effectiveTextures = resolved?.textures ?? texturesProp;
|
|
737
|
+
const effectiveShader = resolved?.fragmentShader ?? fragmentShader;
|
|
738
|
+
const isMultipass = !!effectivePasses;
|
|
739
|
+
(0, import_react.useEffect)(() => {
|
|
740
|
+
if (id && !resolved) return;
|
|
608
741
|
const canvas = canvasRef.current;
|
|
609
742
|
if (!canvas) return;
|
|
610
743
|
sharedState.current = { time: 0, frame: 0 };
|
|
@@ -621,9 +754,9 @@ function useShadertoy({
|
|
|
621
754
|
}
|
|
622
755
|
const externalTextures = [null, null, null, null];
|
|
623
756
|
const texturePromises = [];
|
|
624
|
-
if (
|
|
757
|
+
if (effectiveTextures) {
|
|
625
758
|
for (let i = 0; i < 4; i++) {
|
|
626
|
-
const src =
|
|
759
|
+
const src = effectiveTextures[CHANNEL_KEYS2[i]];
|
|
627
760
|
if (src != null) {
|
|
628
761
|
const { state, promise } = createTexture(gl, src, i);
|
|
629
762
|
externalTextures[i] = state;
|
|
@@ -641,7 +774,7 @@ function useShadertoy({
|
|
|
641
774
|
onError?.(msg);
|
|
642
775
|
};
|
|
643
776
|
if (isMultipass) {
|
|
644
|
-
const passResult = createMultipassRenderer(gl,
|
|
777
|
+
const passResult = createMultipassRenderer(gl, effectivePasses, externalTextures);
|
|
645
778
|
if (typeof passResult === "string") {
|
|
646
779
|
handleError(passResult);
|
|
647
780
|
return;
|
|
@@ -677,7 +810,7 @@ function useShadertoy({
|
|
|
677
810
|
setIsReady(false);
|
|
678
811
|
};
|
|
679
812
|
} else {
|
|
680
|
-
const shaderCode =
|
|
813
|
+
const shaderCode = effectiveShader || "void mainImage(out vec4 c, in vec2 f){ c = vec4(0); }";
|
|
681
814
|
const result = createRenderer(canvas, shaderCode);
|
|
682
815
|
if (typeof result === "string") {
|
|
683
816
|
handleError(result);
|
|
@@ -717,7 +850,7 @@ function useShadertoy({
|
|
|
717
850
|
setIsReady(false);
|
|
718
851
|
};
|
|
719
852
|
}
|
|
720
|
-
}, [
|
|
853
|
+
}, [effectiveShader, effectivePasses, effectiveTextures, resolved, onError, onLoad]);
|
|
721
854
|
(0, import_react.useEffect)(() => {
|
|
722
855
|
const canvas = canvasRef.current;
|
|
723
856
|
if (!canvas) return;
|
|
@@ -799,7 +932,7 @@ function useShadertoy({
|
|
|
799
932
|
const resume = (0, import_react.useCallback)(() => {
|
|
800
933
|
pausedRef.current = false;
|
|
801
934
|
}, []);
|
|
802
|
-
return { canvasRef, isReady, error, pause, resume };
|
|
935
|
+
return { canvasRef, isReady, error, pause, resume, meta };
|
|
803
936
|
}
|
|
804
937
|
|
|
805
938
|
// src/Shadertoy.tsx
|
|
@@ -808,6 +941,9 @@ function Shadertoy({
|
|
|
808
941
|
fragmentShader,
|
|
809
942
|
passes,
|
|
810
943
|
textures,
|
|
944
|
+
id,
|
|
945
|
+
apiKey,
|
|
946
|
+
showLicense,
|
|
811
947
|
style,
|
|
812
948
|
className,
|
|
813
949
|
paused,
|
|
@@ -817,10 +953,12 @@ function Shadertoy({
|
|
|
817
953
|
onError,
|
|
818
954
|
onLoad
|
|
819
955
|
}) {
|
|
820
|
-
const { canvasRef } = useShadertoy({
|
|
956
|
+
const { canvasRef, meta } = useShadertoy({
|
|
821
957
|
fragmentShader,
|
|
822
958
|
passes,
|
|
823
959
|
textures,
|
|
960
|
+
id,
|
|
961
|
+
apiKey,
|
|
824
962
|
paused,
|
|
825
963
|
speed,
|
|
826
964
|
pixelRatio,
|
|
@@ -828,6 +966,35 @@ function Shadertoy({
|
|
|
828
966
|
onError,
|
|
829
967
|
onLoad
|
|
830
968
|
});
|
|
969
|
+
const shouldShowLicense = showLicense ?? !!id;
|
|
970
|
+
const hasMeta = shouldShowLicense && meta;
|
|
971
|
+
if (hasMeta) {
|
|
972
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative", ...style }, className, children: [
|
|
973
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
974
|
+
"canvas",
|
|
975
|
+
{
|
|
976
|
+
ref: canvasRef,
|
|
977
|
+
style: { width: "100%", height: "100%", display: "block" }
|
|
978
|
+
}
|
|
979
|
+
),
|
|
980
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
|
|
981
|
+
position: "absolute",
|
|
982
|
+
bottom: 8,
|
|
983
|
+
right: 8,
|
|
984
|
+
background: "rgba(0,0,0,0.6)",
|
|
985
|
+
color: "#fff",
|
|
986
|
+
padding: "4px 10px",
|
|
987
|
+
borderRadius: 4,
|
|
988
|
+
fontSize: 12,
|
|
989
|
+
fontFamily: "system-ui, sans-serif",
|
|
990
|
+
pointerEvents: "none"
|
|
991
|
+
}, children: [
|
|
992
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: meta.name }),
|
|
993
|
+
" by ",
|
|
994
|
+
meta.author
|
|
995
|
+
] })
|
|
996
|
+
] });
|
|
997
|
+
}
|
|
831
998
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
832
999
|
"canvas",
|
|
833
1000
|
{
|
|
@@ -840,5 +1007,7 @@ function Shadertoy({
|
|
|
840
1007
|
// Annotate the CommonJS export names for ESM import in node:
|
|
841
1008
|
0 && (module.exports = {
|
|
842
1009
|
Shadertoy,
|
|
1010
|
+
apiToConfig,
|
|
1011
|
+
fetchShader,
|
|
843
1012
|
useShadertoy
|
|
844
1013
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,98 @@
|
|
|
1
1
|
// src/useShadertoy.ts
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
|
|
4
|
+
// src/api.ts
|
|
5
|
+
var SHADERTOY_BASE = "https://www.shadertoy.com";
|
|
6
|
+
var API_URL = `${SHADERTOY_BASE}/api/v1/shaders`;
|
|
7
|
+
var OUTPUT_ID_TO_PASS = {
|
|
8
|
+
257: "BufferA",
|
|
9
|
+
258: "BufferB",
|
|
10
|
+
259: "BufferC",
|
|
11
|
+
260: "BufferD"
|
|
12
|
+
};
|
|
13
|
+
var cache = /* @__PURE__ */ new Map();
|
|
14
|
+
async function fetchShader(id, apiKey) {
|
|
15
|
+
const cached = cache.get(id);
|
|
16
|
+
if (cached) return cached;
|
|
17
|
+
const res = await fetch(`${API_URL}/${id}?key=${apiKey}`);
|
|
18
|
+
if (!res.ok) throw new Error(`Shadertoy API error: ${res.status}`);
|
|
19
|
+
const data = await res.json();
|
|
20
|
+
if (data.Error) throw new Error(`Shadertoy API: ${data.Error}`);
|
|
21
|
+
cache.set(id, data.Shader);
|
|
22
|
+
return data.Shader;
|
|
23
|
+
}
|
|
24
|
+
function mapWrap(wrap) {
|
|
25
|
+
if (wrap === "repeat") return "repeat";
|
|
26
|
+
return "clamp";
|
|
27
|
+
}
|
|
28
|
+
function mapFilter(filter) {
|
|
29
|
+
if (filter === "nearest") return "nearest";
|
|
30
|
+
if (filter === "linear") return "linear";
|
|
31
|
+
return "mipmap";
|
|
32
|
+
}
|
|
33
|
+
function resolveTextureSrc(src) {
|
|
34
|
+
if (src.startsWith("http")) return src;
|
|
35
|
+
return SHADERTOY_BASE + src;
|
|
36
|
+
}
|
|
37
|
+
function apiToConfig(shader) {
|
|
38
|
+
const passes = {};
|
|
39
|
+
const textures = {};
|
|
40
|
+
const commonPass = shader.renderpass.find((p) => p.type === "common");
|
|
41
|
+
const commonCode = commonPass ? commonPass.code + "\n" : "";
|
|
42
|
+
for (const rp of shader.renderpass) {
|
|
43
|
+
if (rp.type === "common" || rp.type === "sound") continue;
|
|
44
|
+
const passName = getPassName(rp);
|
|
45
|
+
if (!passName) continue;
|
|
46
|
+
const passConfig = {
|
|
47
|
+
code: commonCode + rp.code
|
|
48
|
+
};
|
|
49
|
+
for (const input of rp.inputs) {
|
|
50
|
+
const channelKey = `iChannel${input.channel}`;
|
|
51
|
+
if (channelKey === "code") continue;
|
|
52
|
+
if (input.ctype === "buffer") {
|
|
53
|
+
const refPass = OUTPUT_ID_TO_PASS[input.id];
|
|
54
|
+
if (refPass) {
|
|
55
|
+
;
|
|
56
|
+
passConfig[channelKey] = refPass;
|
|
57
|
+
}
|
|
58
|
+
} else if (input.ctype === "texture" || input.ctype === "cubemap") {
|
|
59
|
+
const texOpts = {
|
|
60
|
+
src: resolveTextureSrc(input.src),
|
|
61
|
+
wrap: mapWrap(input.sampler.wrap),
|
|
62
|
+
filter: mapFilter(input.sampler.filter),
|
|
63
|
+
vflip: input.sampler.vflip === "true"
|
|
64
|
+
};
|
|
65
|
+
passConfig[channelKey] = texOpts;
|
|
66
|
+
const texKey = `iChannel${input.channel}`;
|
|
67
|
+
textures[texKey] = texOpts;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
passes[passName] = passConfig;
|
|
71
|
+
}
|
|
72
|
+
const meta = {
|
|
73
|
+
name: shader.info.name,
|
|
74
|
+
author: shader.info.username,
|
|
75
|
+
description: shader.info.description,
|
|
76
|
+
tags: shader.info.tags
|
|
77
|
+
};
|
|
78
|
+
return { passes, textures, meta };
|
|
79
|
+
}
|
|
80
|
+
function getPassName(rp) {
|
|
81
|
+
if (rp.type === "image") return "Image";
|
|
82
|
+
if (rp.type === "buffer") {
|
|
83
|
+
for (const out of rp.outputs) {
|
|
84
|
+
const name = OUTPUT_ID_TO_PASS[out.id];
|
|
85
|
+
if (name) return name;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function isSinglePass(passes) {
|
|
92
|
+
const keys = Object.keys(passes);
|
|
93
|
+
return keys.length === 1 && keys[0] === "Image";
|
|
94
|
+
}
|
|
95
|
+
|
|
4
96
|
// src/renderer.ts
|
|
5
97
|
var QUAD_VERTICES = new Float32Array([
|
|
6
98
|
-1,
|
|
@@ -551,6 +643,8 @@ function useShadertoy({
|
|
|
551
643
|
fragmentShader,
|
|
552
644
|
passes: passesProp,
|
|
553
645
|
textures: texturesProp,
|
|
646
|
+
id,
|
|
647
|
+
apiKey,
|
|
554
648
|
paused = false,
|
|
555
649
|
speed = 1,
|
|
556
650
|
pixelRatio,
|
|
@@ -566,6 +660,8 @@ function useShadertoy({
|
|
|
566
660
|
const speedRef = useRef(speed);
|
|
567
661
|
const [isReady, setIsReady] = useState(false);
|
|
568
662
|
const [error, setError] = useState(null);
|
|
663
|
+
const [meta, setMeta] = useState(null);
|
|
664
|
+
const [resolved, setResolved] = useState(id ? null : { passes: passesProp, textures: texturesProp, fragmentShader });
|
|
569
665
|
const mouseState = useRef({
|
|
570
666
|
x: 0,
|
|
571
667
|
y: 0,
|
|
@@ -576,8 +672,43 @@ function useShadertoy({
|
|
|
576
672
|
const sharedState = useRef({ time: 0, frame: 0 });
|
|
577
673
|
pausedRef.current = paused;
|
|
578
674
|
speedRef.current = speed;
|
|
579
|
-
const isMultipass = !!passesProp;
|
|
580
675
|
useEffect(() => {
|
|
676
|
+
if (!id) return;
|
|
677
|
+
if (!apiKey) {
|
|
678
|
+
setError("apiKey is required when using id");
|
|
679
|
+
onError?.("apiKey is required when using id");
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
let cancelled = false;
|
|
683
|
+
fetchShader(id, apiKey).then((shader) => {
|
|
684
|
+
if (cancelled) return;
|
|
685
|
+
const config = apiToConfig(shader);
|
|
686
|
+
setMeta(config.meta);
|
|
687
|
+
if (isSinglePass(config.passes)) {
|
|
688
|
+
const imagePass = config.passes.Image;
|
|
689
|
+
setResolved({
|
|
690
|
+
fragmentShader: imagePass.code,
|
|
691
|
+
textures: config.textures
|
|
692
|
+
});
|
|
693
|
+
} else {
|
|
694
|
+
setResolved({ passes: config.passes });
|
|
695
|
+
}
|
|
696
|
+
}).catch((err) => {
|
|
697
|
+
if (cancelled) return;
|
|
698
|
+
const msg = err instanceof Error ? err.message : "Failed to fetch shader";
|
|
699
|
+
setError(msg);
|
|
700
|
+
onError?.(msg);
|
|
701
|
+
});
|
|
702
|
+
return () => {
|
|
703
|
+
cancelled = true;
|
|
704
|
+
};
|
|
705
|
+
}, [id, apiKey]);
|
|
706
|
+
const effectivePasses = resolved?.passes;
|
|
707
|
+
const effectiveTextures = resolved?.textures ?? texturesProp;
|
|
708
|
+
const effectiveShader = resolved?.fragmentShader ?? fragmentShader;
|
|
709
|
+
const isMultipass = !!effectivePasses;
|
|
710
|
+
useEffect(() => {
|
|
711
|
+
if (id && !resolved) return;
|
|
581
712
|
const canvas = canvasRef.current;
|
|
582
713
|
if (!canvas) return;
|
|
583
714
|
sharedState.current = { time: 0, frame: 0 };
|
|
@@ -594,9 +725,9 @@ function useShadertoy({
|
|
|
594
725
|
}
|
|
595
726
|
const externalTextures = [null, null, null, null];
|
|
596
727
|
const texturePromises = [];
|
|
597
|
-
if (
|
|
728
|
+
if (effectiveTextures) {
|
|
598
729
|
for (let i = 0; i < 4; i++) {
|
|
599
|
-
const src =
|
|
730
|
+
const src = effectiveTextures[CHANNEL_KEYS2[i]];
|
|
600
731
|
if (src != null) {
|
|
601
732
|
const { state, promise } = createTexture(gl, src, i);
|
|
602
733
|
externalTextures[i] = state;
|
|
@@ -614,7 +745,7 @@ function useShadertoy({
|
|
|
614
745
|
onError?.(msg);
|
|
615
746
|
};
|
|
616
747
|
if (isMultipass) {
|
|
617
|
-
const passResult = createMultipassRenderer(gl,
|
|
748
|
+
const passResult = createMultipassRenderer(gl, effectivePasses, externalTextures);
|
|
618
749
|
if (typeof passResult === "string") {
|
|
619
750
|
handleError(passResult);
|
|
620
751
|
return;
|
|
@@ -650,7 +781,7 @@ function useShadertoy({
|
|
|
650
781
|
setIsReady(false);
|
|
651
782
|
};
|
|
652
783
|
} else {
|
|
653
|
-
const shaderCode =
|
|
784
|
+
const shaderCode = effectiveShader || "void mainImage(out vec4 c, in vec2 f){ c = vec4(0); }";
|
|
654
785
|
const result = createRenderer(canvas, shaderCode);
|
|
655
786
|
if (typeof result === "string") {
|
|
656
787
|
handleError(result);
|
|
@@ -690,7 +821,7 @@ function useShadertoy({
|
|
|
690
821
|
setIsReady(false);
|
|
691
822
|
};
|
|
692
823
|
}
|
|
693
|
-
}, [
|
|
824
|
+
}, [effectiveShader, effectivePasses, effectiveTextures, resolved, onError, onLoad]);
|
|
694
825
|
useEffect(() => {
|
|
695
826
|
const canvas = canvasRef.current;
|
|
696
827
|
if (!canvas) return;
|
|
@@ -772,15 +903,18 @@ function useShadertoy({
|
|
|
772
903
|
const resume = useCallback(() => {
|
|
773
904
|
pausedRef.current = false;
|
|
774
905
|
}, []);
|
|
775
|
-
return { canvasRef, isReady, error, pause, resume };
|
|
906
|
+
return { canvasRef, isReady, error, pause, resume, meta };
|
|
776
907
|
}
|
|
777
908
|
|
|
778
909
|
// src/Shadertoy.tsx
|
|
779
|
-
import { jsx } from "react/jsx-runtime";
|
|
910
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
780
911
|
function Shadertoy({
|
|
781
912
|
fragmentShader,
|
|
782
913
|
passes,
|
|
783
914
|
textures,
|
|
915
|
+
id,
|
|
916
|
+
apiKey,
|
|
917
|
+
showLicense,
|
|
784
918
|
style,
|
|
785
919
|
className,
|
|
786
920
|
paused,
|
|
@@ -790,10 +924,12 @@ function Shadertoy({
|
|
|
790
924
|
onError,
|
|
791
925
|
onLoad
|
|
792
926
|
}) {
|
|
793
|
-
const { canvasRef } = useShadertoy({
|
|
927
|
+
const { canvasRef, meta } = useShadertoy({
|
|
794
928
|
fragmentShader,
|
|
795
929
|
passes,
|
|
796
930
|
textures,
|
|
931
|
+
id,
|
|
932
|
+
apiKey,
|
|
797
933
|
paused,
|
|
798
934
|
speed,
|
|
799
935
|
pixelRatio,
|
|
@@ -801,6 +937,35 @@ function Shadertoy({
|
|
|
801
937
|
onError,
|
|
802
938
|
onLoad
|
|
803
939
|
});
|
|
940
|
+
const shouldShowLicense = showLicense ?? !!id;
|
|
941
|
+
const hasMeta = shouldShowLicense && meta;
|
|
942
|
+
if (hasMeta) {
|
|
943
|
+
return /* @__PURE__ */ jsxs("div", { style: { position: "relative", ...style }, className, children: [
|
|
944
|
+
/* @__PURE__ */ jsx(
|
|
945
|
+
"canvas",
|
|
946
|
+
{
|
|
947
|
+
ref: canvasRef,
|
|
948
|
+
style: { width: "100%", height: "100%", display: "block" }
|
|
949
|
+
}
|
|
950
|
+
),
|
|
951
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
952
|
+
position: "absolute",
|
|
953
|
+
bottom: 8,
|
|
954
|
+
right: 8,
|
|
955
|
+
background: "rgba(0,0,0,0.6)",
|
|
956
|
+
color: "#fff",
|
|
957
|
+
padding: "4px 10px",
|
|
958
|
+
borderRadius: 4,
|
|
959
|
+
fontSize: 12,
|
|
960
|
+
fontFamily: "system-ui, sans-serif",
|
|
961
|
+
pointerEvents: "none"
|
|
962
|
+
}, children: [
|
|
963
|
+
/* @__PURE__ */ jsx("strong", { children: meta.name }),
|
|
964
|
+
" by ",
|
|
965
|
+
meta.author
|
|
966
|
+
] })
|
|
967
|
+
] });
|
|
968
|
+
}
|
|
804
969
|
return /* @__PURE__ */ jsx(
|
|
805
970
|
"canvas",
|
|
806
971
|
{
|
|
@@ -812,5 +977,7 @@ function Shadertoy({
|
|
|
812
977
|
}
|
|
813
978
|
export {
|
|
814
979
|
Shadertoy,
|
|
980
|
+
apiToConfig,
|
|
981
|
+
fetchShader,
|
|
815
982
|
useShadertoy
|
|
816
983
|
};
|