@nexart/ui-renderer 0.2.1 → 0.4.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 +240 -174
- package/dist/compiler.d.ts +18 -2
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +25 -11
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -7
- package/dist/presets/backgrounds.d.ts +14 -0
- package/dist/presets/backgrounds.d.ts.map +1 -0
- package/dist/presets/backgrounds.js +222 -0
- package/dist/presets/index.d.ts +7 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +6 -0
- package/dist/presets/primitives.d.ts +16 -0
- package/dist/presets/primitives.d.ts.map +1 -0
- package/dist/presets/primitives.js +282 -0
- package/dist/presets/sketch-wrapper.d.ts +14 -0
- package/dist/presets/sketch-wrapper.d.ts.map +1 -0
- package/dist/presets/sketch-wrapper.js +70 -0
- package/dist/preview/code-renderer.d.ts +46 -0
- package/dist/preview/code-renderer.d.ts.map +1 -0
- package/dist/preview/code-renderer.js +759 -0
- package/dist/preview/renderer.d.ts +1 -1
- package/dist/preview/renderer.d.ts.map +1 -1
- package/dist/preview/renderer.js +23 -13
- package/dist/preview/unified-renderer.d.ts +16 -0
- package/dist/preview/unified-renderer.d.ts.map +1 -0
- package/dist/preview/unified-renderer.js +270 -0
- package/dist/system.d.ts +7 -3
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +204 -11
- package/dist/types.d.ts +127 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -3
- package/package.json +5 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/preview/renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/preview/renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoC,cAAc,EAAE,MAAM,UAAU,CAAC;AAyC/F,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,eAAe,CAmKjB"}
|
package/dist/preview/renderer.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.4.0 - Preview Renderer
|
|
3
3
|
*
|
|
4
4
|
* Renders visual approximations of NexArt systems.
|
|
5
5
|
* This is NOT canonical output - for preview/exploration only.
|
|
6
6
|
*/
|
|
7
|
+
import { isCodeModeSystem, isUnifiedModeSystem } from '../system';
|
|
8
|
+
import { renderCodeModeSystem } from './code-renderer';
|
|
9
|
+
import { renderUnifiedSystem } from './unified-renderer';
|
|
7
10
|
import { renderDots } from './primitives/dots';
|
|
8
11
|
import { renderLines } from './primitives/lines';
|
|
9
12
|
import { renderWaves } from './primitives/waves';
|
|
@@ -39,6 +42,13 @@ function parseColor(color) {
|
|
|
39
42
|
return colorMap[color.toLowerCase()] || color;
|
|
40
43
|
}
|
|
41
44
|
export function previewSystem(system, canvas, options = {}) {
|
|
45
|
+
if (isCodeModeSystem(system)) {
|
|
46
|
+
return renderCodeModeSystem(system, canvas, options);
|
|
47
|
+
}
|
|
48
|
+
if (isUnifiedModeSystem(system)) {
|
|
49
|
+
return renderUnifiedSystem(system, canvas, options);
|
|
50
|
+
}
|
|
51
|
+
const declarativeSystem = system;
|
|
42
52
|
const { mode = 'static', showBadge = true } = options;
|
|
43
53
|
const width = canvas.width || 800;
|
|
44
54
|
const height = canvas.height || 800;
|
|
@@ -47,11 +57,11 @@ export function previewSystem(system, canvas, options = {}) {
|
|
|
47
57
|
const ctx = canvas.getContext('2d');
|
|
48
58
|
let animationId = null;
|
|
49
59
|
const renderFrame = (t = 0) => {
|
|
50
|
-
const random = createPRNG(
|
|
51
|
-
ctx.fillStyle = parseColor(
|
|
60
|
+
const random = createPRNG(declarativeSystem.seed);
|
|
61
|
+
ctx.fillStyle = parseColor(declarativeSystem.background.color);
|
|
52
62
|
ctx.fillRect(0, 0, width, height);
|
|
53
|
-
if (
|
|
54
|
-
const g =
|
|
63
|
+
if (declarativeSystem.background.gradient) {
|
|
64
|
+
const g = declarativeSystem.background.gradient;
|
|
55
65
|
let gradient;
|
|
56
66
|
if (g.type === 'radial') {
|
|
57
67
|
gradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, Math.max(width, height) / 2);
|
|
@@ -68,10 +78,10 @@ export function previewSystem(system, canvas, options = {}) {
|
|
|
68
78
|
ctx.fillStyle = gradient;
|
|
69
79
|
ctx.fillRect(0, 0, width, height);
|
|
70
80
|
}
|
|
71
|
-
if (
|
|
81
|
+
if (declarativeSystem.background.texture === 'noise' || declarativeSystem.background.texture === 'grain') {
|
|
72
82
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
73
83
|
const data = imageData.data;
|
|
74
|
-
const intensity =
|
|
84
|
+
const intensity = declarativeSystem.background.texture === 'noise' ? 30 : 15;
|
|
75
85
|
for (let i = 0; i < data.length; i += 4) {
|
|
76
86
|
const noise = (random() - 0.5) * intensity;
|
|
77
87
|
data[i] += noise;
|
|
@@ -80,8 +90,8 @@ export function previewSystem(system, canvas, options = {}) {
|
|
|
80
90
|
}
|
|
81
91
|
ctx.putImageData(imageData, 0, 0);
|
|
82
92
|
}
|
|
83
|
-
for (const el of
|
|
84
|
-
const elRandom = createPRNG(
|
|
93
|
+
for (const el of declarativeSystem.elements) {
|
|
94
|
+
const elRandom = createPRNG(declarativeSystem.seed + declarativeSystem.elements.indexOf(el) * 1000);
|
|
85
95
|
switch (el.type) {
|
|
86
96
|
case 'dots':
|
|
87
97
|
renderDots(ctx, el, width, height, elRandom, t);
|
|
@@ -96,7 +106,7 @@ export function previewSystem(system, canvas, options = {}) {
|
|
|
96
106
|
renderGrid(ctx, el, width, height, elRandom, t);
|
|
97
107
|
break;
|
|
98
108
|
case 'flowField':
|
|
99
|
-
renderFlowField(ctx, el, width, height, elRandom,
|
|
109
|
+
renderFlowField(ctx, el, width, height, elRandom, declarativeSystem.seed, t);
|
|
100
110
|
break;
|
|
101
111
|
case 'orbits':
|
|
102
112
|
renderOrbits(ctx, el, width, height, elRandom, t);
|
|
@@ -132,14 +142,14 @@ export function previewSystem(system, canvas, options = {}) {
|
|
|
132
142
|
const start = () => {
|
|
133
143
|
stop();
|
|
134
144
|
const startTime = performance.now();
|
|
135
|
-
const speed =
|
|
145
|
+
const speed = declarativeSystem.motion?.speed ?? 1;
|
|
136
146
|
const loop = () => {
|
|
137
147
|
const elapsed = (performance.now() - startTime) / 1000;
|
|
138
|
-
const t =
|
|
148
|
+
const t = declarativeSystem.motion?.source === 'time' ? elapsed * speed : 0;
|
|
139
149
|
renderFrame(t);
|
|
140
150
|
animationId = requestAnimationFrame(loop);
|
|
141
151
|
};
|
|
142
|
-
if (mode === 'loop' &&
|
|
152
|
+
if (mode === 'loop' && declarativeSystem.motion?.source !== 'none') {
|
|
143
153
|
animationId = requestAnimationFrame(loop);
|
|
144
154
|
}
|
|
145
155
|
else {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Renderer - Handles background, primitive, and sketch elements
|
|
3
|
+
* Dispatches rendering per element type in order: background → primitive → sketch
|
|
4
|
+
* All elements render to the same canvas/context with shared timing
|
|
5
|
+
*/
|
|
6
|
+
import type { UnifiedSystem, PreviewOptions } from '../types';
|
|
7
|
+
export interface UnifiedRenderer {
|
|
8
|
+
render: () => void;
|
|
9
|
+
start: () => void;
|
|
10
|
+
stop: () => void;
|
|
11
|
+
destroy: () => void;
|
|
12
|
+
isCanonical: false;
|
|
13
|
+
isArchival: false;
|
|
14
|
+
}
|
|
15
|
+
export declare function renderUnifiedSystem(system: UnifiedSystem, canvas: HTMLCanvasElement, options?: PreviewOptions): UnifiedRenderer;
|
|
16
|
+
//# sourceMappingURL=unified-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unified-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/unified-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkB,cAAc,EAAoE,MAAM,UAAU,CAAC;AAgBhJ,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AA6ED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,eAAe,CAiOjB"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Renderer - Handles background, primitive, and sketch elements
|
|
3
|
+
* Dispatches rendering per element type in order: background → primitive → sketch
|
|
4
|
+
* All elements render to the same canvas/context with shared timing
|
|
5
|
+
*/
|
|
6
|
+
import { compileBackgroundPreset, getPaletteColors } from '../presets/backgrounds';
|
|
7
|
+
import { compilePrimitive } from '../presets/primitives';
|
|
8
|
+
import { wrapSketch, validateSketchSafety } from '../presets/sketch-wrapper';
|
|
9
|
+
import { createP5Runtime } from './code-renderer';
|
|
10
|
+
function validateSketchElement(element) {
|
|
11
|
+
const { safe, warnings } = validateSketchSafety(element.code);
|
|
12
|
+
if (!safe) {
|
|
13
|
+
throw new Error(`Unsafe sketch code: ${warnings.join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
if (warnings.length > 0) {
|
|
16
|
+
console.warn('[SDK] Sketch warnings:', warnings);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function inferPalette(elements) {
|
|
20
|
+
for (const el of elements) {
|
|
21
|
+
if (el.type === 'background' && el.palette) {
|
|
22
|
+
return el.palette;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return 'offwhite-dark';
|
|
26
|
+
}
|
|
27
|
+
function extractFunctionBody(code, funcName) {
|
|
28
|
+
const pattern = new RegExp(`function\\s+${funcName}\\s*\\(\\s*\\)\\s*\\{`);
|
|
29
|
+
const match = code.match(pattern);
|
|
30
|
+
if (!match || match.index === undefined)
|
|
31
|
+
return '';
|
|
32
|
+
const startIndex = match.index + match[0].length;
|
|
33
|
+
let braceCount = 1;
|
|
34
|
+
let i = startIndex;
|
|
35
|
+
while (i < code.length && braceCount > 0) {
|
|
36
|
+
if (code[i] === '{')
|
|
37
|
+
braceCount++;
|
|
38
|
+
if (code[i] === '}')
|
|
39
|
+
braceCount--;
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
return code.slice(startIndex, i - 1).trim();
|
|
43
|
+
}
|
|
44
|
+
function extractDrawCode(code) {
|
|
45
|
+
const body = extractFunctionBody(code, 'draw');
|
|
46
|
+
return body || code.trim();
|
|
47
|
+
}
|
|
48
|
+
function extractSetupCode(code) {
|
|
49
|
+
return extractFunctionBody(code, 'setup');
|
|
50
|
+
}
|
|
51
|
+
function compileBackgroundElement(element, palette) {
|
|
52
|
+
const code = compileBackgroundPreset(element.preset, element.palette || palette, element.loop || { duration: 120 });
|
|
53
|
+
return {
|
|
54
|
+
setup: extractSetupCode(code),
|
|
55
|
+
draw: extractDrawCode(code),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function compilePrimitiveElement(element, foreground) {
|
|
59
|
+
const primitiveCode = compilePrimitive({
|
|
60
|
+
name: element.name,
|
|
61
|
+
count: element.count,
|
|
62
|
+
strokeWeight: element.strokeWeight,
|
|
63
|
+
motion: element.motion,
|
|
64
|
+
color: element.color,
|
|
65
|
+
opacity: element.opacity,
|
|
66
|
+
}, foreground);
|
|
67
|
+
return { setup: '', draw: primitiveCode };
|
|
68
|
+
}
|
|
69
|
+
function compileSketchElement(element) {
|
|
70
|
+
validateSketchElement(element);
|
|
71
|
+
const wrapped = wrapSketch({
|
|
72
|
+
code: element.code,
|
|
73
|
+
normalize: element.normalize !== false,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
setup: extractSetupCode(wrapped),
|
|
77
|
+
draw: extractDrawCode(wrapped),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function renderUnifiedSystem(system, canvas, options = {}) {
|
|
81
|
+
const { showBadge = true, onPreview, onComplete, onError } = options;
|
|
82
|
+
canvas.width = system.width;
|
|
83
|
+
canvas.height = system.height;
|
|
84
|
+
const ctx = canvas.getContext('2d');
|
|
85
|
+
let animationId = null;
|
|
86
|
+
let isRunning = false;
|
|
87
|
+
let isDestroyed = false;
|
|
88
|
+
const palette = inferPalette(system.elements);
|
|
89
|
+
const colors = getPaletteColors(palette);
|
|
90
|
+
const totalFrames = system.loop?.duration ?? 120;
|
|
91
|
+
const sortedElements = [...system.elements].sort((a, b) => {
|
|
92
|
+
const order = { background: 0, primitive: 1, sketch: 2 };
|
|
93
|
+
return (order[a.type] ?? 3) - (order[b.type] ?? 3);
|
|
94
|
+
});
|
|
95
|
+
const backgroundElements = [];
|
|
96
|
+
const primitiveElements = [];
|
|
97
|
+
const sketchElements = [];
|
|
98
|
+
for (const element of sortedElements) {
|
|
99
|
+
switch (element.type) {
|
|
100
|
+
case 'background':
|
|
101
|
+
backgroundElements.push(compileBackgroundElement(element, palette));
|
|
102
|
+
break;
|
|
103
|
+
case 'primitive':
|
|
104
|
+
primitiveElements.push(compilePrimitiveElement(element, colors.foreground));
|
|
105
|
+
break;
|
|
106
|
+
case 'sketch':
|
|
107
|
+
sketchElements.push(compileSketchElement(element));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const hasElements = system.elements.length > 0;
|
|
112
|
+
const hasRenderers = backgroundElements.length > 0 || primitiveElements.length > 0 || sketchElements.length > 0;
|
|
113
|
+
console.log('[UIRenderer] Compiled elements:', {
|
|
114
|
+
background: backgroundElements.length,
|
|
115
|
+
primitive: primitiveElements.length,
|
|
116
|
+
sketch: sketchElements.length,
|
|
117
|
+
backgroundDrawEmpty: backgroundElements.map(e => !e.draw.trim()),
|
|
118
|
+
primitiveDrawEmpty: primitiveElements.map(e => !e.draw.trim()),
|
|
119
|
+
sketchDrawEmpty: sketchElements.map(e => !e.draw.trim()),
|
|
120
|
+
});
|
|
121
|
+
if (hasElements && !hasRenderers) {
|
|
122
|
+
throw new Error('[UIRenderer] No renderers executed despite having elements');
|
|
123
|
+
}
|
|
124
|
+
const drawBadge = () => {
|
|
125
|
+
if (!showBadge)
|
|
126
|
+
return;
|
|
127
|
+
const text = '⚠️ Preview Renderer (Non-Canonical)';
|
|
128
|
+
ctx.font = '12px -apple-system, sans-serif';
|
|
129
|
+
const metrics = ctx.measureText(text);
|
|
130
|
+
const padding = 8;
|
|
131
|
+
const badgeWidth = metrics.width + padding * 2;
|
|
132
|
+
const badgeHeight = 24;
|
|
133
|
+
const x = system.width - badgeWidth - 10;
|
|
134
|
+
const y = 10;
|
|
135
|
+
ctx.fillStyle = 'rgba(255, 100, 100, 0.15)';
|
|
136
|
+
ctx.strokeStyle = 'rgba(255, 100, 100, 0.4)';
|
|
137
|
+
ctx.lineWidth = 1;
|
|
138
|
+
ctx.beginPath();
|
|
139
|
+
ctx.roundRect(x, y, badgeWidth, badgeHeight, 4);
|
|
140
|
+
ctx.fill();
|
|
141
|
+
ctx.stroke();
|
|
142
|
+
ctx.fillStyle = '#ff9999';
|
|
143
|
+
ctx.fillText(text, x + padding, y + 16);
|
|
144
|
+
};
|
|
145
|
+
const executeCode = (p, code, frame, t) => {
|
|
146
|
+
if (!code.trim())
|
|
147
|
+
return;
|
|
148
|
+
try {
|
|
149
|
+
const wrappedCode = new Function('p', 'frameCount', 't', 'time', 'tGlobal', `with(p) { ${code} }`);
|
|
150
|
+
wrappedCode(p, frame, t, t * (totalFrames / 30), t);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
console.error('[UIRenderer] Code execution error:', err);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const runSetup = (p) => {
|
|
157
|
+
let setupRan = false;
|
|
158
|
+
for (const el of backgroundElements) {
|
|
159
|
+
if (el.setup) {
|
|
160
|
+
executeCode(p, el.setup, 0, 0);
|
|
161
|
+
setupRan = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const el of primitiveElements) {
|
|
165
|
+
if (el.setup) {
|
|
166
|
+
executeCode(p, el.setup, 0, 0);
|
|
167
|
+
setupRan = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const el of sketchElements) {
|
|
171
|
+
if (el.setup) {
|
|
172
|
+
executeCode(p, el.setup, 0, 0);
|
|
173
|
+
setupRan = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!setupRan) {
|
|
177
|
+
p.strokeWeight(1.5);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const runDraw = (p, frame, t) => {
|
|
181
|
+
let elementsRendered = 0;
|
|
182
|
+
const hasBackground = backgroundElements.length > 0;
|
|
183
|
+
if (!hasBackground) {
|
|
184
|
+
p.background(colors.background);
|
|
185
|
+
}
|
|
186
|
+
for (const el of backgroundElements) {
|
|
187
|
+
if (el.draw) {
|
|
188
|
+
console.log('[UIRenderer] background rendered');
|
|
189
|
+
executeCode(p, el.draw, frame, t);
|
|
190
|
+
elementsRendered++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const el of primitiveElements) {
|
|
194
|
+
if (el.draw) {
|
|
195
|
+
console.log('[UIRenderer] primitive rendered');
|
|
196
|
+
executeCode(p, el.draw, frame, t);
|
|
197
|
+
elementsRendered++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const el of sketchElements) {
|
|
201
|
+
if (el.draw) {
|
|
202
|
+
console.log('[UIRenderer] sketch rendered');
|
|
203
|
+
executeCode(p, el.draw, frame, t);
|
|
204
|
+
elementsRendered++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (hasElements && elementsRendered === 0) {
|
|
208
|
+
throw new Error('[UIRenderer] Zero renderers executed in frame despite having elements');
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const renderLoop = () => {
|
|
212
|
+
if (isDestroyed)
|
|
213
|
+
return;
|
|
214
|
+
try {
|
|
215
|
+
let frame = 0;
|
|
216
|
+
const p = createP5Runtime(canvas, system.width, system.height, system.seed);
|
|
217
|
+
runSetup(p);
|
|
218
|
+
const loop = () => {
|
|
219
|
+
if (!isRunning || isDestroyed)
|
|
220
|
+
return;
|
|
221
|
+
const t = frame / totalFrames;
|
|
222
|
+
p.randomSeed(system.seed);
|
|
223
|
+
p.noiseSeed(system.seed);
|
|
224
|
+
runDraw(p, frame, t);
|
|
225
|
+
drawBadge();
|
|
226
|
+
frame = (frame + 1) % totalFrames;
|
|
227
|
+
animationId = requestAnimationFrame(loop);
|
|
228
|
+
};
|
|
229
|
+
isRunning = true;
|
|
230
|
+
animationId = requestAnimationFrame(loop);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
234
|
+
onError?.(err);
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const render = () => {
|
|
239
|
+
if (isDestroyed)
|
|
240
|
+
return;
|
|
241
|
+
renderLoop();
|
|
242
|
+
};
|
|
243
|
+
const start = () => {
|
|
244
|
+
if (isDestroyed)
|
|
245
|
+
return;
|
|
246
|
+
stop();
|
|
247
|
+
renderLoop();
|
|
248
|
+
};
|
|
249
|
+
const stop = () => {
|
|
250
|
+
isRunning = false;
|
|
251
|
+
if (animationId !== null) {
|
|
252
|
+
cancelAnimationFrame(animationId);
|
|
253
|
+
animationId = null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const destroy = () => {
|
|
257
|
+
isDestroyed = true;
|
|
258
|
+
stop();
|
|
259
|
+
ctx.clearRect(0, 0, system.width, system.height);
|
|
260
|
+
};
|
|
261
|
+
const renderer = {
|
|
262
|
+
render,
|
|
263
|
+
start,
|
|
264
|
+
stop,
|
|
265
|
+
destroy,
|
|
266
|
+
isCanonical: false,
|
|
267
|
+
isArchival: false,
|
|
268
|
+
};
|
|
269
|
+
return renderer;
|
|
270
|
+
}
|
package/dist/system.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.4.0 - System Creation & Validation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Opinionated generative design system with aesthetic guardrails.
|
|
5
|
+
* Supports background, primitive, and sketch element types.
|
|
5
6
|
*/
|
|
6
|
-
import type { NexArtSystemInput, NexArtSystem, ValidationResult } from './types';
|
|
7
|
+
import type { NexArtSystemInput, NexArtSystem, DeclarativeSystem, NexArtCodeSystem, UnifiedSystem, ValidationResult } from './types';
|
|
7
8
|
export declare function validateSystem(input: NexArtSystemInput): ValidationResult;
|
|
8
9
|
export declare function createSystem(input: NexArtSystemInput): NexArtSystem;
|
|
10
|
+
export declare function isCodeModeSystem(system: NexArtSystem): system is NexArtCodeSystem;
|
|
11
|
+
export declare function isUnifiedModeSystem(system: NexArtSystem): system is UnifiedSystem;
|
|
12
|
+
export declare function isDeclarativeSystem(system: NexArtSystem): system is DeclarativeSystem;
|
|
9
13
|
//# sourceMappingURL=system.d.ts.map
|
package/dist/system.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../src/system.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../src/system.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EAEZ,iBAAiB,EAEjB,gBAAgB,EAEhB,aAAa,EAGb,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AAgUjB,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,gBAAgB,CAiBzE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CA8DnE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,gBAAgB,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,aAAa,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,iBAAiB,CAErF"}
|
package/dist/system.js
CHANGED
|
@@ -1,12 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.4.0 - System Creation & Validation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Opinionated generative design system with aesthetic guardrails.
|
|
5
|
+
* Supports background, primitive, and sketch element types.
|
|
5
6
|
*/
|
|
6
|
-
const CURRENT_VERSION = '0.
|
|
7
|
+
const CURRENT_VERSION = '0.4';
|
|
8
|
+
function isCodeSystem(input) {
|
|
9
|
+
return 'type' in input && input.type === 'code';
|
|
10
|
+
}
|
|
11
|
+
function isUnifiedSystem(input) {
|
|
12
|
+
if (!('elements' in input) || !Array.isArray(input.elements)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const elements = input.elements;
|
|
16
|
+
return elements.length > 0 && elements.some((el) => el.type === 'background' || el.type === 'primitive' || el.type === 'sketch');
|
|
17
|
+
}
|
|
18
|
+
function isDeclarativeInput(input) {
|
|
19
|
+
return 'elements' in input && 'background' in input && Array.isArray(input.elements);
|
|
20
|
+
}
|
|
7
21
|
const VALID_ELEMENT_TYPES = ['dots', 'lines', 'waves', 'grid', 'flowField', 'orbits'];
|
|
8
22
|
const VALID_MOTION_SOURCES = ['none', 'time', 'seed'];
|
|
9
23
|
const VALID_TEXTURES = ['none', 'noise', 'grain'];
|
|
24
|
+
const VALID_BACKGROUND_PRESETS = ['layered-waves', 'soft-noise-field', 'orbital-lines', 'flowing-stripes', 'minimal-grid'];
|
|
25
|
+
const VALID_PRIMITIVE_NAMES = ['waves', 'dots', 'lines', 'grid', 'flow', 'orbits', 'circles', 'stripes'];
|
|
26
|
+
const VALID_PALETTES = ['offwhite-dark', 'midnight', 'warm-neutral', 'ocean', 'sunset', 'forest'];
|
|
27
|
+
function validateUnifiedElement(el, index) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
const prefix = `Element[${index}]`;
|
|
30
|
+
if (!el.type) {
|
|
31
|
+
errors.push(`${prefix}: type is required`);
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
switch (el.type) {
|
|
35
|
+
case 'background':
|
|
36
|
+
if (!el.preset || !VALID_BACKGROUND_PRESETS.includes(el.preset)) {
|
|
37
|
+
errors.push(`${prefix}: preset must be one of: ${VALID_BACKGROUND_PRESETS.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
if (el.palette && !VALID_PALETTES.includes(el.palette)) {
|
|
40
|
+
errors.push(`${prefix}: palette must be one of: ${VALID_PALETTES.join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
if (el.loop) {
|
|
43
|
+
if (typeof el.loop.duration !== 'number' || el.loop.duration < 1 || el.loop.duration > 600) {
|
|
44
|
+
errors.push(`${prefix}: loop.duration must be between 1 and 600`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'primitive':
|
|
49
|
+
if (!el.name || !VALID_PRIMITIVE_NAMES.includes(el.name)) {
|
|
50
|
+
errors.push(`${prefix}: name must be one of: ${VALID_PRIMITIVE_NAMES.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
if (el.count !== undefined && (el.count < 1 || el.count > 100)) {
|
|
53
|
+
errors.push(`${prefix}: count must be between 1 and 100`);
|
|
54
|
+
}
|
|
55
|
+
if (el.opacity !== undefined && (el.opacity < 0 || el.opacity > 1)) {
|
|
56
|
+
errors.push(`${prefix}: opacity must be between 0 and 1`);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
case 'sketch':
|
|
60
|
+
if (!el.code || typeof el.code !== 'string' || el.code.trim().length === 0) {
|
|
61
|
+
errors.push(`${prefix}: code is required and must be a non-empty string`);
|
|
62
|
+
}
|
|
63
|
+
if (el.totalFrames !== undefined && (el.totalFrames < 1 || el.totalFrames > 600)) {
|
|
64
|
+
errors.push(`${prefix}: totalFrames must be between 1 and 600`);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
errors.push(`${prefix}: type must be "background", "primitive", or "sketch"`);
|
|
69
|
+
}
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
function validateUnifiedSystem(input) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
if (!Array.isArray(input.elements) || input.elements.length === 0) {
|
|
75
|
+
errors.push('elements array is required and must not be empty');
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
input.elements.forEach((el, i) => {
|
|
79
|
+
errors.push(...validateUnifiedElement(el, i));
|
|
80
|
+
});
|
|
81
|
+
if (input.width !== undefined && (input.width < 1 || input.width > 4096)) {
|
|
82
|
+
errors.push('width must be between 1 and 4096');
|
|
83
|
+
}
|
|
84
|
+
if (input.height !== undefined && (input.height < 1 || input.height > 4096)) {
|
|
85
|
+
errors.push('height must be between 1 and 4096');
|
|
86
|
+
}
|
|
87
|
+
if (!input.loop || typeof input.loop.duration !== 'number') {
|
|
88
|
+
errors.push('loop.duration is required for unified systems');
|
|
89
|
+
}
|
|
90
|
+
else if (input.loop.duration < 1 || input.loop.duration > 600) {
|
|
91
|
+
errors.push('loop.duration must be between 1 and 600');
|
|
92
|
+
}
|
|
93
|
+
return errors;
|
|
94
|
+
}
|
|
10
95
|
function validateBackground(bg) {
|
|
11
96
|
const errors = [];
|
|
12
97
|
if (!bg.color || typeof bg.color !== 'string') {
|
|
@@ -112,7 +197,56 @@ function validateMotion(motion) {
|
|
|
112
197
|
}
|
|
113
198
|
return errors;
|
|
114
199
|
}
|
|
115
|
-
|
|
200
|
+
function validateCodeSystem(input) {
|
|
201
|
+
const errors = [];
|
|
202
|
+
if (input.type !== 'code') {
|
|
203
|
+
errors.push('type must be "code"');
|
|
204
|
+
}
|
|
205
|
+
if (!input.source || typeof input.source !== 'string') {
|
|
206
|
+
errors.push('source is required and must be a string');
|
|
207
|
+
}
|
|
208
|
+
if (input.source && input.source.trim().length === 0) {
|
|
209
|
+
errors.push('source cannot be empty');
|
|
210
|
+
}
|
|
211
|
+
if (!['static', 'loop'].includes(input.mode)) {
|
|
212
|
+
errors.push('mode must be "static" or "loop"');
|
|
213
|
+
}
|
|
214
|
+
if (typeof input.width !== 'number' || input.width < 1 || input.width > 4096) {
|
|
215
|
+
errors.push('width is required and must be between 1 and 4096');
|
|
216
|
+
}
|
|
217
|
+
if (typeof input.height !== 'number' || input.height < 1 || input.height > 4096) {
|
|
218
|
+
errors.push('height is required and must be between 1 and 4096');
|
|
219
|
+
}
|
|
220
|
+
if (input.mode === 'loop') {
|
|
221
|
+
if (typeof input.totalFrames !== 'number' || input.totalFrames < 1 || input.totalFrames > 600) {
|
|
222
|
+
errors.push('totalFrames is required for loop mode and must be between 1 and 600');
|
|
223
|
+
}
|
|
224
|
+
if (input.seed === undefined) {
|
|
225
|
+
errors.push('seed is required for loop mode to ensure determinism');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (input.seed !== undefined && typeof input.seed !== 'number') {
|
|
229
|
+
errors.push('seed must be a number');
|
|
230
|
+
}
|
|
231
|
+
if (input.vars !== undefined) {
|
|
232
|
+
if (!Array.isArray(input.vars)) {
|
|
233
|
+
errors.push('vars must be an array');
|
|
234
|
+
}
|
|
235
|
+
else if (input.vars.length !== 10) {
|
|
236
|
+
errors.push('[Code Mode Protocol Error] VAR array must have exactly 10 elements');
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
for (let i = 0; i < input.vars.length; i++) {
|
|
240
|
+
const v = input.vars[i];
|
|
241
|
+
if (typeof v !== 'number') {
|
|
242
|
+
errors.push(`[Code Mode Protocol Error] VAR[${i}] must be a number`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return errors;
|
|
248
|
+
}
|
|
249
|
+
function validateDeclarativeSystem(input) {
|
|
116
250
|
const errors = [];
|
|
117
251
|
if (typeof input.seed !== 'number') {
|
|
118
252
|
errors.push('seed is required and must be a number');
|
|
@@ -134,6 +268,22 @@ export function validateSystem(input) {
|
|
|
134
268
|
if (input.motion) {
|
|
135
269
|
errors.push(...validateMotion(input.motion));
|
|
136
270
|
}
|
|
271
|
+
return errors;
|
|
272
|
+
}
|
|
273
|
+
export function validateSystem(input) {
|
|
274
|
+
const errors = [];
|
|
275
|
+
if (isCodeSystem(input)) {
|
|
276
|
+
errors.push(...validateCodeSystem(input));
|
|
277
|
+
}
|
|
278
|
+
else if (isUnifiedSystem(input)) {
|
|
279
|
+
errors.push(...validateUnifiedSystem(input));
|
|
280
|
+
}
|
|
281
|
+
else if (isDeclarativeInput(input)) {
|
|
282
|
+
errors.push(...validateDeclarativeSystem(input));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
errors.push('Invalid system input: must be a code system, unified system, or declarative system');
|
|
286
|
+
}
|
|
137
287
|
return {
|
|
138
288
|
valid: errors.length === 0,
|
|
139
289
|
errors,
|
|
@@ -144,22 +294,65 @@ export function createSystem(input) {
|
|
|
144
294
|
if (!validation.valid) {
|
|
145
295
|
throw new Error(`Invalid system: ${validation.errors.join('; ')}`);
|
|
146
296
|
}
|
|
297
|
+
if (isCodeSystem(input)) {
|
|
298
|
+
const codeSystem = {
|
|
299
|
+
protocol: 'nexart',
|
|
300
|
+
systemType: 'code',
|
|
301
|
+
systemVersion: CURRENT_VERSION,
|
|
302
|
+
source: input.source,
|
|
303
|
+
mode: input.mode,
|
|
304
|
+
width: input.width,
|
|
305
|
+
height: input.height,
|
|
306
|
+
seed: input.seed ?? Math.floor(Math.random() * 2147483647),
|
|
307
|
+
totalFrames: input.totalFrames,
|
|
308
|
+
vars: input.vars,
|
|
309
|
+
deterministic: true,
|
|
310
|
+
createdAt: new Date().toISOString(),
|
|
311
|
+
};
|
|
312
|
+
return codeSystem;
|
|
313
|
+
}
|
|
314
|
+
if (isUnifiedSystem(input)) {
|
|
315
|
+
const unifiedSystem = {
|
|
316
|
+
protocol: 'nexart',
|
|
317
|
+
systemType: 'unified',
|
|
318
|
+
systemVersion: CURRENT_VERSION,
|
|
319
|
+
seed: input.seed ?? Math.floor(Math.random() * 2147483647),
|
|
320
|
+
width: input.width ?? 800,
|
|
321
|
+
height: input.height ?? 800,
|
|
322
|
+
elements: input.elements,
|
|
323
|
+
loop: input.loop ?? { duration: 120 },
|
|
324
|
+
deterministic: true,
|
|
325
|
+
createdAt: new Date().toISOString(),
|
|
326
|
+
};
|
|
327
|
+
return unifiedSystem;
|
|
328
|
+
}
|
|
329
|
+
const declarativeInput = input;
|
|
147
330
|
const system = {
|
|
148
331
|
protocol: 'nexart',
|
|
149
|
-
|
|
150
|
-
|
|
332
|
+
systemType: 'declarative',
|
|
333
|
+
systemVersion: declarativeInput.version || CURRENT_VERSION,
|
|
334
|
+
seed: declarativeInput.seed,
|
|
151
335
|
background: {
|
|
152
|
-
color:
|
|
153
|
-
texture:
|
|
154
|
-
...(
|
|
336
|
+
color: declarativeInput.background.color,
|
|
337
|
+
texture: declarativeInput.background.texture || 'none',
|
|
338
|
+
...(declarativeInput.background.gradient && { gradient: declarativeInput.background.gradient }),
|
|
155
339
|
},
|
|
156
|
-
elements:
|
|
340
|
+
elements: declarativeInput.elements.map((el) => ({
|
|
157
341
|
...el,
|
|
158
342
|
opacity: el.opacity ?? 1,
|
|
159
343
|
})),
|
|
160
|
-
motion:
|
|
344
|
+
motion: declarativeInput.motion || { source: 'none' },
|
|
161
345
|
deterministic: true,
|
|
162
346
|
createdAt: new Date().toISOString(),
|
|
163
347
|
};
|
|
164
348
|
return system;
|
|
165
349
|
}
|
|
350
|
+
export function isCodeModeSystem(system) {
|
|
351
|
+
return 'systemType' in system && system.systemType === 'code';
|
|
352
|
+
}
|
|
353
|
+
export function isUnifiedModeSystem(system) {
|
|
354
|
+
return 'systemType' in system && system.systemType === 'unified';
|
|
355
|
+
}
|
|
356
|
+
export function isDeclarativeSystem(system) {
|
|
357
|
+
return !('systemType' in system) || system.systemType === 'declarative';
|
|
358
|
+
}
|