loopwind 0.18.1 → 0.20.1
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 +83 -0
- package/dist/commands/preview.d.ts.map +1 -1
- package/dist/commands/preview.js +2 -1
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +2 -0
- package/dist/commands/render.js.map +1 -1
- package/dist/default-templates/AGENTS.md +54 -0
- package/dist/lib/renderer.d.ts.map +1 -1
- package/dist/lib/renderer.js +3 -0
- package/dist/lib/renderer.js.map +1 -1
- package/dist/lib/tailwind-browser.d.ts.map +1 -1
- package/dist/lib/tailwind-browser.js +169 -6
- package/dist/lib/tailwind-browser.js.map +1 -1
- package/dist/lib/tailwind.d.ts.map +1 -1
- package/dist/lib/tailwind.js +178 -7
- package/dist/lib/tailwind.js.map +1 -1
- package/dist/lib/video-preview.d.ts +1 -1
- package/dist/lib/video-preview.d.ts.map +1 -1
- package/dist/lib/video-preview.js +266 -249
- package/dist/lib/video-preview.js.map +1 -1
- package/dist/lib/video-renderer.d.ts +2 -0
- package/dist/lib/video-renderer.d.ts.map +1 -1
- package/dist/lib/video-renderer.js +4 -4
- package/dist/lib/video-renderer.js.map +1 -1
- package/dist/sdk/compiler.d.ts +94 -0
- package/dist/sdk/compiler.d.ts.map +1 -0
- package/dist/sdk/compiler.js +122 -0
- package/dist/sdk/compiler.js.map +1 -0
- package/dist/sdk/index.d.ts +3 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +2 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/preview.d.ts +65 -0
- package/dist/sdk/preview.d.ts.map +1 -0
- package/dist/sdk/preview.js +262 -0
- package/dist/sdk/preview.js.map +1 -0
- package/dist/sdk/template.d.ts +47 -24
- package/dist/sdk/template.d.ts.map +1 -1
- package/dist/sdk/template.js +53 -93
- package/dist/sdk/template.js.map +1 -1
- package/examples/nextjs-template-import.ts +2 -2
- package/examples/sdk-video-preview.tsx +120 -0
- package/examples/template-compiler-workflow.ts +251 -0
- package/package.json +6 -2
- package/render-examples-600x400.mjs +161 -0
- package/render-spring-variants-fixed.mjs +60 -0
- package/render-staggered-text.mjs +56 -0
- package/test-jsx-support.mjs +32 -6
- package/test-sdk-config.mjs +138 -81
- package/test-sdk-source-config.mjs +427 -0
- package/test-static-debug.tsx +19 -0
- package/test-templates/config-test.mjs +17 -0
- package/test-templates/test-sdk.mjs +46 -22
- package/test-video-props.json +3 -0
- package/website/DEPLOYMENT.md +1 -0
- package/website/OG_IMAGES.md +1 -0
- package/website/astro.config.mjs +18 -2
- package/website/dist/.gitkeep +1 -0
- package/website/dist/_worker.js/index.js +1 -1
- package/website/dist/_worker.js/{manifest_BAAoOzaU.mjs → manifest_CT_D-YDe.mjs} +1 -1
- package/website/dist/llm.txt +1 -1
- package/website/dist/sdk/index.html +405 -102
- package/website/dist/sitemap.xml +12 -12
- package/website/package-lock.json +2866 -7080
- package/website/package.json +1 -2
- package/website/public/.gitkeep +1 -0
- package/website/templates/og-image.tsx +20 -21
- package/website/test-playground.mjs +45 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { renderToSVG } from '../lib/renderer.js';
|
|
3
|
+
import { templateToMeta } from './template.js';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
/**
|
|
8
|
+
* Pre-render all frames of a video template for preview
|
|
9
|
+
* Call this on the server or at build time
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const template = defineTemplate({
|
|
14
|
+
* name: 'intro',
|
|
15
|
+
* type: 'video',
|
|
16
|
+
* size: { width: 1920, height: 1080 },
|
|
17
|
+
* video: { fps: 30, duration: 3 },
|
|
18
|
+
* render: ({ tw, progress }) => <div style={tw('flex')}>Frame</div>
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* const frames = await previewVideo(template, { title: 'Hello' });
|
|
22
|
+
* // Pass to <VideoPreview frames={frames} />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export async function previewVideo(template, props, options = {}) {
|
|
26
|
+
const { config, format = 'svg' } = options;
|
|
27
|
+
const meta = templateToMeta(template);
|
|
28
|
+
if (meta.type !== 'video') {
|
|
29
|
+
throw new Error('Cannot preview image template. Only video templates are supported.');
|
|
30
|
+
}
|
|
31
|
+
if (!meta.video) {
|
|
32
|
+
throw new Error('Template is missing video metadata (fps, duration)');
|
|
33
|
+
}
|
|
34
|
+
const { fps, duration } = meta.video;
|
|
35
|
+
const totalFrames = Math.floor(fps * duration);
|
|
36
|
+
const frames = [];
|
|
37
|
+
// Save template to temp file so we can use the existing renderer
|
|
38
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'loopwind-preview-'));
|
|
39
|
+
const templatePath = path.join(tempDir, 'template.tsx');
|
|
40
|
+
try {
|
|
41
|
+
// Write template component with embedded meta
|
|
42
|
+
const templateContent = `import React from 'react';
|
|
43
|
+
|
|
44
|
+
const h = React.createElement;
|
|
45
|
+
|
|
46
|
+
export const meta = ${JSON.stringify(meta, null, 2)};
|
|
47
|
+
|
|
48
|
+
export default ${template.render.toString()};`;
|
|
49
|
+
await fs.writeFile(templatePath, templateContent);
|
|
50
|
+
// Merge configs
|
|
51
|
+
const mergedConfig = {
|
|
52
|
+
...template.config,
|
|
53
|
+
...config,
|
|
54
|
+
colors: { ...template.config?.colors, ...config?.colors },
|
|
55
|
+
fonts: { ...template.config?.fonts, ...config?.fonts },
|
|
56
|
+
};
|
|
57
|
+
// Render each frame
|
|
58
|
+
for (let frame = 0; frame < totalFrames; frame++) {
|
|
59
|
+
const progress = frame / totalFrames;
|
|
60
|
+
const svg = await renderToSVG(templatePath, { ...props, frame, progress }, { config: mergedConfig });
|
|
61
|
+
if (format === 'data-url') {
|
|
62
|
+
// Convert SVG to data URL for easier browser usage
|
|
63
|
+
const dataUrl = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
|
|
64
|
+
frames.push(dataUrl);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
frames.push(svg);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
frames,
|
|
72
|
+
meta: {
|
|
73
|
+
name: meta.name,
|
|
74
|
+
width: meta.size.width,
|
|
75
|
+
height: meta.size.height,
|
|
76
|
+
fps,
|
|
77
|
+
duration,
|
|
78
|
+
totalFrames,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
// Cleanup temp files
|
|
84
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Video preview component that displays pre-rendered frames
|
|
89
|
+
* Works in browser - no Node.js dependencies
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* // Server-side (Next.js, etc.)
|
|
94
|
+
* const frames = await previewVideo(template, props);
|
|
95
|
+
*
|
|
96
|
+
* // Client-side
|
|
97
|
+
* <VideoPreview frames={frames} autoPlay loop />
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function VideoPreview({ frames: previewFrames, autoPlay = true, loop = true, controls = true, className, style, onFrameChange, }) {
|
|
101
|
+
const [frame, setFrame] = useState(0);
|
|
102
|
+
const [isPlaying, setIsPlaying] = useState(autoPlay);
|
|
103
|
+
const [isLooping, setIsLooping] = useState(loop);
|
|
104
|
+
const animationRef = useRef(null);
|
|
105
|
+
const lastTimeRef = useRef(0);
|
|
106
|
+
const { frames, meta } = previewFrames;
|
|
107
|
+
const { fps, duration, totalFrames, width, height } = meta;
|
|
108
|
+
const durationMs = duration * 1000;
|
|
109
|
+
// Notify parent of frame changes
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (onFrameChange) {
|
|
112
|
+
onFrameChange(Math.floor(frame));
|
|
113
|
+
}
|
|
114
|
+
}, [frame, onFrameChange]);
|
|
115
|
+
// Playback loop
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!isPlaying) {
|
|
118
|
+
if (animationRef.current) {
|
|
119
|
+
cancelAnimationFrame(animationRef.current);
|
|
120
|
+
animationRef.current = null;
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
lastTimeRef.current = performance.now();
|
|
125
|
+
const animate = (time) => {
|
|
126
|
+
const delta = time - lastTimeRef.current;
|
|
127
|
+
lastTimeRef.current = time;
|
|
128
|
+
setFrame((prev) => {
|
|
129
|
+
const increment = (delta / 1000) * fps;
|
|
130
|
+
const next = prev + increment;
|
|
131
|
+
if (next >= totalFrames) {
|
|
132
|
+
if (isLooping) {
|
|
133
|
+
return next % totalFrames;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
setIsPlaying(false);
|
|
137
|
+
return totalFrames - 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return next;
|
|
141
|
+
});
|
|
142
|
+
animationRef.current = requestAnimationFrame(animate);
|
|
143
|
+
};
|
|
144
|
+
animationRef.current = requestAnimationFrame(animate);
|
|
145
|
+
return () => {
|
|
146
|
+
if (animationRef.current) {
|
|
147
|
+
cancelAnimationFrame(animationRef.current);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}, [isPlaying, fps, totalFrames, isLooping]);
|
|
151
|
+
const currentMs = (frame / totalFrames) * durationMs;
|
|
152
|
+
const progress = (frame / totalFrames) * 100;
|
|
153
|
+
const frameNum = Math.floor(frame);
|
|
154
|
+
const handleScrub = (e) => {
|
|
155
|
+
const value = parseFloat(e.target.value);
|
|
156
|
+
setFrame((value / 100) * totalFrames);
|
|
157
|
+
};
|
|
158
|
+
const handlePlayPause = () => {
|
|
159
|
+
if (!isPlaying && frame >= totalFrames - 1) {
|
|
160
|
+
setFrame(0);
|
|
161
|
+
}
|
|
162
|
+
setIsPlaying(!isPlaying);
|
|
163
|
+
};
|
|
164
|
+
const handleRestart = () => {
|
|
165
|
+
setFrame(0);
|
|
166
|
+
setIsPlaying(true);
|
|
167
|
+
};
|
|
168
|
+
const handleStepBackward = () => {
|
|
169
|
+
setIsPlaying(false);
|
|
170
|
+
setFrame((prev) => Math.max(0, prev - 1));
|
|
171
|
+
};
|
|
172
|
+
const handleStepForward = () => {
|
|
173
|
+
setIsPlaying(false);
|
|
174
|
+
setFrame((prev) => Math.min(totalFrames - 1, prev + 1));
|
|
175
|
+
};
|
|
176
|
+
return (React.createElement("div", { className: className, style: {
|
|
177
|
+
display: 'inline-flex',
|
|
178
|
+
flexDirection: 'column',
|
|
179
|
+
gap: '0.75rem',
|
|
180
|
+
...style,
|
|
181
|
+
} },
|
|
182
|
+
React.createElement("div", { style: {
|
|
183
|
+
position: 'relative',
|
|
184
|
+
width,
|
|
185
|
+
height,
|
|
186
|
+
overflow: 'hidden',
|
|
187
|
+
borderRadius: '0.5rem',
|
|
188
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
189
|
+
} }, frames[frameNum]?.startsWith('data:') ? (React.createElement("img", { src: frames[frameNum], alt: `Frame ${frameNum}`, style: { display: 'block', width: '100%', height: '100%' } })) : (React.createElement("div", { dangerouslySetInnerHTML: { __html: frames[frameNum] || '' }, style: { width: '100%', height: '100%' } }))),
|
|
190
|
+
controls && (React.createElement("div", { style: {
|
|
191
|
+
display: 'flex',
|
|
192
|
+
flexDirection: 'column',
|
|
193
|
+
gap: '0.5rem',
|
|
194
|
+
width,
|
|
195
|
+
} },
|
|
196
|
+
React.createElement("input", { type: "range", min: "0", max: "100", step: "0.1", value: progress, onChange: handleScrub, style: {
|
|
197
|
+
width: '100%',
|
|
198
|
+
height: '6px',
|
|
199
|
+
background: `linear-gradient(to right, #7c3aed ${progress}%, #d1d5db ${progress}%)`,
|
|
200
|
+
borderRadius: '3px',
|
|
201
|
+
cursor: 'pointer',
|
|
202
|
+
WebkitAppearance: 'none',
|
|
203
|
+
appearance: 'none',
|
|
204
|
+
} }),
|
|
205
|
+
React.createElement("div", { style: {
|
|
206
|
+
display: 'flex',
|
|
207
|
+
alignItems: 'center',
|
|
208
|
+
justifyContent: 'space-between',
|
|
209
|
+
fontSize: '0.875rem',
|
|
210
|
+
} },
|
|
211
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem' } },
|
|
212
|
+
React.createElement("button", { onClick: handleRestart, title: "Restart", style: {
|
|
213
|
+
background: 'transparent',
|
|
214
|
+
border: 'none',
|
|
215
|
+
cursor: 'pointer',
|
|
216
|
+
padding: '0.25rem',
|
|
217
|
+
fontSize: '1.25rem',
|
|
218
|
+
} }, "\u23EE"),
|
|
219
|
+
React.createElement("button", { onClick: handleStepBackward, title: "Previous frame", style: {
|
|
220
|
+
background: 'transparent',
|
|
221
|
+
border: 'none',
|
|
222
|
+
cursor: 'pointer',
|
|
223
|
+
padding: '0.25rem',
|
|
224
|
+
fontSize: '1.25rem',
|
|
225
|
+
} }, "\u23EA"),
|
|
226
|
+
React.createElement("button", { onClick: handlePlayPause, style: {
|
|
227
|
+
background: '#7c3aed',
|
|
228
|
+
border: 'none',
|
|
229
|
+
color: 'white',
|
|
230
|
+
cursor: 'pointer',
|
|
231
|
+
padding: '0.5rem 0.75rem',
|
|
232
|
+
borderRadius: '0.375rem',
|
|
233
|
+
fontSize: '1rem',
|
|
234
|
+
minWidth: '3rem',
|
|
235
|
+
} }, isPlaying ? '⏸' : '▶'),
|
|
236
|
+
React.createElement("button", { onClick: handleStepForward, title: "Next frame", style: {
|
|
237
|
+
background: 'transparent',
|
|
238
|
+
border: 'none',
|
|
239
|
+
cursor: 'pointer',
|
|
240
|
+
padding: '0.25rem',
|
|
241
|
+
fontSize: '1.25rem',
|
|
242
|
+
} }, "\u23E9"),
|
|
243
|
+
React.createElement("button", { onClick: () => setIsLooping(!isLooping), title: isLooping ? 'Loop enabled' : 'Loop disabled', style: {
|
|
244
|
+
background: isLooping ? '#e5e7eb' : 'transparent',
|
|
245
|
+
border: '1px solid #d1d5db',
|
|
246
|
+
cursor: 'pointer',
|
|
247
|
+
padding: '0.25rem 0.5rem',
|
|
248
|
+
borderRadius: '0.375rem',
|
|
249
|
+
fontSize: '0.875rem',
|
|
250
|
+
} }, "\uD83D\uDD01")),
|
|
251
|
+
React.createElement("div", { style: { fontFamily: 'monospace', color: '#6b7280' } },
|
|
252
|
+
Math.floor(currentMs),
|
|
253
|
+
"ms / ",
|
|
254
|
+
Math.floor(durationMs),
|
|
255
|
+
"ms",
|
|
256
|
+
React.createElement("span", { style: { marginLeft: '0.5rem' } },
|
|
257
|
+
"Frame ",
|
|
258
|
+
frameNum,
|
|
259
|
+
" / ",
|
|
260
|
+
totalFrames)))))));
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/sdk/preview.tsx"],"names":[],"mappings":"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"}
|
package/dist/sdk/template.d.ts
CHANGED
|
@@ -15,25 +15,6 @@ export interface TemplateDefinition<TProps = any> {
|
|
|
15
15
|
config?: StyleConfig;
|
|
16
16
|
render: (props: TProps & TemplateProps) => JSX.Element;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Define a template programmatically for use in serverless environments
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```typescript
|
|
23
|
-
* const ogImage = defineTemplate({
|
|
24
|
-
* name: 'og-image',
|
|
25
|
-
* type: 'image',
|
|
26
|
-
* size: { width: 1200, height: 630 },
|
|
27
|
-
* render: ({ tw, title, description }) => (
|
|
28
|
-
* <div style={tw('flex flex-col w-full h-full bg-white p-12')}>
|
|
29
|
-
* <h1 style={tw('text-6xl font-bold')}>{title}</h1>
|
|
30
|
-
* <p style={tw('text-2xl text-gray-600')}>{description}</p>
|
|
31
|
-
* </div>
|
|
32
|
-
* )
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export declare function defineTemplate<TProps = any>(definition: TemplateDefinition<TProps>): TemplateDefinition<TProps>;
|
|
37
18
|
/**
|
|
38
19
|
* Convert a template definition to template metadata
|
|
39
20
|
* @internal
|
|
@@ -42,27 +23,69 @@ export declare function templateToMeta(template: TemplateDefinition): TemplateMe
|
|
|
42
23
|
/**
|
|
43
24
|
* Define a template from an imported template file
|
|
44
25
|
*
|
|
45
|
-
*
|
|
26
|
+
* The standard way to create templates in loopwind. Import a template file
|
|
27
|
+
* and pass it to defineTemplate() with optional config overrides.
|
|
46
28
|
*
|
|
47
29
|
* @example
|
|
48
30
|
* ```typescript
|
|
49
|
-
* import * as
|
|
31
|
+
* import * as ogImage from './_loopwind/templates/og-image/template';
|
|
50
32
|
*
|
|
51
|
-
* const template =
|
|
33
|
+
* const template = defineTemplate(ogImage, {
|
|
52
34
|
* config: {
|
|
53
35
|
* colors: { primary: '#ff0000' }
|
|
54
36
|
* }
|
|
55
37
|
* });
|
|
56
38
|
*
|
|
57
|
-
* const
|
|
39
|
+
* const png = await renderImage(template, { title: 'Hello' });
|
|
58
40
|
* ```
|
|
59
41
|
*/
|
|
60
|
-
export declare function
|
|
42
|
+
export declare function defineTemplate<TProps = any>(templateModule: {
|
|
61
43
|
meta: TemplateMeta;
|
|
62
44
|
default: (props: TProps & TemplateProps) => JSX.Element;
|
|
63
45
|
}, options?: {
|
|
64
46
|
config?: StyleConfig;
|
|
65
47
|
}): TemplateDefinition<TProps>;
|
|
48
|
+
/**
|
|
49
|
+
* Define a template from a source code string
|
|
50
|
+
*
|
|
51
|
+
* **IMPORTANT:** This function expects **pre-compiled JavaScript** with React.createElement
|
|
52
|
+
* calls. If you have JSX source code, compile it first using `compileTemplate()` from
|
|
53
|
+
* 'loopwind/sdk/compiler'.
|
|
54
|
+
*
|
|
55
|
+
* **WARNING:** This function evaluates code strings using `new Function()`.
|
|
56
|
+
* Only use with **trusted** template sources. Never use with user input from
|
|
57
|
+
* untrusted sources as it can execute arbitrary code.
|
|
58
|
+
*
|
|
59
|
+
* **Recommended workflow:**
|
|
60
|
+
* 1. In your admin panel: Use `compileTemplate()` to transform JSX → JavaScript
|
|
61
|
+
* 2. Store the compiled JavaScript in your database
|
|
62
|
+
* 3. In production: Load compiled code and pass to `defineTemplateFromSource()`
|
|
63
|
+
*
|
|
64
|
+
* This approach keeps @babel/standalone out of your production bundle!
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Admin panel: Compile and save
|
|
69
|
+
* import { compileTemplate } from 'loopwind/sdk/compiler';
|
|
70
|
+
*
|
|
71
|
+
* const jsxCode = getUserCodeFromEditor(); // Contains JSX
|
|
72
|
+
* const compiled = compileTemplate(jsxCode);
|
|
73
|
+
* await db.templates.save({ code: compiled });
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Production API: Load and render
|
|
79
|
+
* import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
|
|
80
|
+
*
|
|
81
|
+
* const template = await db.templates.findById(id);
|
|
82
|
+
* const templateDef = defineTemplateFromSource(template.code, {
|
|
83
|
+
* config: { colors: { primary: '#3b82f6' } }
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* const png = await renderImage(templateDef, { title: 'Hello' });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
66
89
|
export declare function defineTemplateFromSource<TProps = any>(sourceCode: string, options?: {
|
|
67
90
|
config?: StyleConfig;
|
|
68
91
|
}): TemplateDefinition<TProps>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC;AAEvC,MAAM,WAAW,kBAAkB,CAAC,MAAM,GAAG,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,KAAK,GAAG,CAAC,OAAO,CAAC;CACxD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,YAAY,CASzE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAG,GAAG,EACzC,cAAc,EAAE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,KAAK,GAAG,CAAC,OAAO,CAAA;CAAE,EAC/F,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,kBAAkB,CAAC,MAAM,CAAC,CA0B5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,GAAG,GAAG,EACnD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,kBAAkB,CAAC,MAAM,CAAC,CAgC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,KAAK,CAAC;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,GAAG,GAAG,EACnD,MAAM,EAAE,cAAc,GACrB,kBAAkB,CAAC,MAAM,CAAC,CAkE5B"}
|
package/dist/sdk/template.js
CHANGED
|
@@ -1,44 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import * as Babel from '@babel/standalone';
|
|
3
|
-
/**
|
|
4
|
-
* Define a template programmatically for use in serverless environments
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* const ogImage = defineTemplate({
|
|
9
|
-
* name: 'og-image',
|
|
10
|
-
* type: 'image',
|
|
11
|
-
* size: { width: 1200, height: 630 },
|
|
12
|
-
* render: ({ tw, title, description }) => (
|
|
13
|
-
* <div style={tw('flex flex-col w-full h-full bg-white p-12')}>
|
|
14
|
-
* <h1 style={tw('text-6xl font-bold')}>{title}</h1>
|
|
15
|
-
* <p style={tw('text-2xl text-gray-600')}>{description}</p>
|
|
16
|
-
* </div>
|
|
17
|
-
* )
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function defineTemplate(definition) {
|
|
22
|
-
// Validate definition
|
|
23
|
-
if (!definition.name) {
|
|
24
|
-
throw new Error('Template definition must have a name');
|
|
25
|
-
}
|
|
26
|
-
if (!definition.size || !definition.size.width || !definition.size.height) {
|
|
27
|
-
throw new Error('Template definition must have a size with width and height');
|
|
28
|
-
}
|
|
29
|
-
if (!definition.render || typeof definition.render !== 'function') {
|
|
30
|
-
throw new Error('Template definition must have a render function');
|
|
31
|
-
}
|
|
32
|
-
// Set defaults
|
|
33
|
-
const type = definition.type || 'image';
|
|
34
|
-
if (type === 'video' && !definition.video) {
|
|
35
|
-
throw new Error('Video templates must have video metadata (fps, duration)');
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
...definition,
|
|
39
|
-
type,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
2
|
/**
|
|
43
3
|
* Convert a template definition to template metadata
|
|
44
4
|
* @internal
|
|
@@ -56,93 +16,91 @@ export function templateToMeta(template) {
|
|
|
56
16
|
/**
|
|
57
17
|
* Define a template from an imported template file
|
|
58
18
|
*
|
|
59
|
-
*
|
|
19
|
+
* The standard way to create templates in loopwind. Import a template file
|
|
20
|
+
* and pass it to defineTemplate() with optional config overrides.
|
|
60
21
|
*
|
|
61
22
|
* @example
|
|
62
23
|
* ```typescript
|
|
63
|
-
* import * as
|
|
24
|
+
* import * as ogImage from './_loopwind/templates/og-image/template';
|
|
64
25
|
*
|
|
65
|
-
* const template =
|
|
26
|
+
* const template = defineTemplate(ogImage, {
|
|
66
27
|
* config: {
|
|
67
28
|
* colors: { primary: '#ff0000' }
|
|
68
29
|
* }
|
|
69
30
|
* });
|
|
70
31
|
*
|
|
71
|
-
* const
|
|
32
|
+
* const png = await renderImage(template, { title: 'Hello' });
|
|
72
33
|
* ```
|
|
73
34
|
*/
|
|
74
|
-
export function
|
|
35
|
+
export function defineTemplate(templateModule, options = {}) {
|
|
75
36
|
const { meta, default: render } = templateModule;
|
|
76
|
-
|
|
37
|
+
// Validate meta
|
|
38
|
+
if (!meta.name) {
|
|
39
|
+
throw new Error('Template meta must have a name');
|
|
40
|
+
}
|
|
41
|
+
if (!meta.size || !meta.size.width || !meta.size.height) {
|
|
42
|
+
throw new Error('Template meta must have a size with width and height');
|
|
43
|
+
}
|
|
44
|
+
const type = meta.type || 'image';
|
|
45
|
+
if (type === 'video' && !meta.video) {
|
|
46
|
+
throw new Error('Video templates must have video metadata (fps, duration)');
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
77
49
|
name: meta.name,
|
|
78
|
-
type:
|
|
50
|
+
type: type,
|
|
79
51
|
size: meta.size,
|
|
80
52
|
video: meta.video,
|
|
81
53
|
config: options.config,
|
|
82
54
|
render,
|
|
83
|
-
}
|
|
55
|
+
};
|
|
84
56
|
}
|
|
85
57
|
/**
|
|
86
58
|
* Define a template from a source code string
|
|
87
59
|
*
|
|
60
|
+
* **IMPORTANT:** This function expects **pre-compiled JavaScript** with React.createElement
|
|
61
|
+
* calls. If you have JSX source code, compile it first using `compileTemplate()` from
|
|
62
|
+
* 'loopwind/sdk/compiler'.
|
|
63
|
+
*
|
|
88
64
|
* **WARNING:** This function evaluates code strings using `new Function()`.
|
|
89
65
|
* Only use with **trusted** template sources. Never use with user input from
|
|
90
66
|
* untrusted sources as it can execute arbitrary code.
|
|
91
67
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
68
|
+
* **Recommended workflow:**
|
|
69
|
+
* 1. In your admin panel: Use `compileTemplate()` to transform JSX → JavaScript
|
|
70
|
+
* 2. Store the compiled JavaScript in your database
|
|
71
|
+
* 3. In production: Load compiled code and pass to `defineTemplateFromSource()`
|
|
72
|
+
*
|
|
73
|
+
* This approach keeps @babel/standalone out of your production bundle!
|
|
96
74
|
*
|
|
97
75
|
* @example
|
|
98
76
|
* ```typescript
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* name: 'user-template',
|
|
102
|
-
* type: 'image',
|
|
103
|
-
* size: { width: 1200, height: 630 }
|
|
104
|
-
* };
|
|
77
|
+
* // Admin panel: Compile and save
|
|
78
|
+
* import { compileTemplate } from 'loopwind/sdk/compiler';
|
|
105
79
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* );
|
|
111
|
-
* };
|
|
112
|
-
* `;
|
|
80
|
+
* const jsxCode = getUserCodeFromEditor(); // Contains JSX
|
|
81
|
+
* const compiled = compileTemplate(jsxCode);
|
|
82
|
+
* await db.templates.save({ code: compiled });
|
|
83
|
+
* ```
|
|
113
84
|
*
|
|
114
|
-
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* // Production API: Load and render
|
|
88
|
+
* import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
|
|
89
|
+
*
|
|
90
|
+
* const template = await db.templates.findById(id);
|
|
91
|
+
* const templateDef = defineTemplateFromSource(template.code, {
|
|
115
92
|
* config: { colors: { primary: '#3b82f6' } }
|
|
116
93
|
* });
|
|
117
94
|
*
|
|
118
|
-
* const png = await renderImage(
|
|
95
|
+
* const png = await renderImage(templateDef, { title: 'Hello' });
|
|
119
96
|
* ```
|
|
120
97
|
*/
|
|
121
|
-
/**
|
|
122
|
-
* Transform JSX to JavaScript using Babel
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
|
-
function transformJSX(code) {
|
|
126
|
-
try {
|
|
127
|
-
const result = Babel.transform(code, {
|
|
128
|
-
presets: ['react'],
|
|
129
|
-
filename: 'template.tsx',
|
|
130
|
-
});
|
|
131
|
-
return result.code || code;
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
// If Babel fails, return original code (might be plain JS without JSX)
|
|
135
|
-
return code;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
98
|
export function defineTemplateFromSource(sourceCode, options = {}) {
|
|
139
99
|
try {
|
|
140
|
-
// Transform JSX to React.createElement calls
|
|
141
|
-
let processedSource = transformJSX(sourceCode);
|
|
142
100
|
// Strip export keywords to make it work in function context
|
|
143
101
|
// Convert "export const meta = ..." to "const meta = ..."
|
|
144
102
|
// Convert "export default ..." to "const defaultExport = ..."
|
|
145
|
-
processedSource =
|
|
103
|
+
let processedSource = sourceCode
|
|
146
104
|
.replace(/export\s+const\s+meta\s*=/g, 'const meta =')
|
|
147
105
|
.replace(/export\s+default\s+/g, 'const defaultExport = ');
|
|
148
106
|
// Create a module evaluation function
|
|
@@ -160,7 +118,7 @@ export function defineTemplateFromSource(sourceCode, options = {}) {
|
|
|
160
118
|
if (!templateModule.default || typeof templateModule.default !== 'function') {
|
|
161
119
|
throw new Error('Template source must have a default export function');
|
|
162
120
|
}
|
|
163
|
-
return
|
|
121
|
+
return defineTemplate(templateModule, options);
|
|
164
122
|
}
|
|
165
123
|
catch (error) {
|
|
166
124
|
throw new Error(`Failed to parse template source: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -259,13 +217,15 @@ export function defineTemplateFromSchema(schema) {
|
|
|
259
217
|
return h(React.Fragment, null, elements.map((el, i) => buildElement(el, i)));
|
|
260
218
|
}
|
|
261
219
|
`)(React);
|
|
262
|
-
|
|
220
|
+
// Create a template module from the schema
|
|
221
|
+
const meta = {
|
|
263
222
|
name: schema.name,
|
|
264
|
-
|
|
223
|
+
description: schema.name,
|
|
224
|
+
type: schema.type || 'image',
|
|
265
225
|
size: schema.size,
|
|
266
226
|
video: schema.video,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
227
|
+
props: {},
|
|
228
|
+
};
|
|
229
|
+
return defineTemplate({ meta, default: render }, { config: schema.config });
|
|
270
230
|
}
|
|
271
231
|
//# sourceMappingURL=template.js.map
|
package/dist/sdk/template.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
9
|
-
import {
|
|
9
|
+
import { defineTemplate, renderVideo } from 'loopwind/sdk';
|
|
10
10
|
|
|
11
11
|
// Import your template file (Next.js/bundler handles TSX automatically)
|
|
12
12
|
import * as videoIntroTemplate from '../../_loopwind/templates/video-intro/template';
|
|
13
13
|
|
|
14
14
|
// Create SDK template from file with custom config
|
|
15
|
-
const template =
|
|
15
|
+
const template = defineTemplate(videoIntroTemplate, {
|
|
16
16
|
config: {
|
|
17
17
|
colors: {
|
|
18
18
|
primary: '#ff0000', // Override gradient colors
|