@slopmachine/react 0.6.0 → 0.8.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.
@@ -1,350 +0,0 @@
1
- import React, { useMemo, useState, useRef, useEffect } from "react";
2
- import { clsx, type ClassValue } from "clsx";
3
- import { twMerge } from "tailwind-merge";
4
-
5
- import {
6
- buildVideoUrl,
7
- type SlopVideoOptions,
8
- type VideoAspectRatio,
9
- } from "@slopmachine/core";
10
-
11
- // Utility for Tailwind classes
12
- function cn(...inputs: ClassValue[]) {
13
- return twMerge(clsx(inputs));
14
- }
15
-
16
- export interface SlopVideoProps
17
- extends
18
- Omit<React.VideoHTMLAttributes<HTMLVideoElement>, "src">,
19
- Omit<SlopVideoOptions, "aspectRatio"> {
20
- /**
21
- * The aspect ratio of the generated video. Defaults to "16:9".
22
- */
23
- aspectRatio?: VideoAspectRatio;
24
- /**
25
- * Custom React node to display while the video is loading.
26
- * If not provided, a default spinner and shimmer effect will be shown.
27
- */
28
- loader?: React.ReactNode;
29
- }
30
-
31
- /**
32
- * Renders a video generated by Slop Machine.
33
- *
34
- * This component automatically constructs the correct URL for the Slop Machine API,
35
- * displays a loading skeleton or custom loader while fetching, and handles errors.
36
- *
37
- * @example
38
- * ```tsx
39
- * // Using a Bucket
40
- * <SlopVideo
41
- * bucketId="my-bucket"
42
- * resultId="some-result"
43
- * aspectRatio="16:9"
44
- * className="rounded-lg"
45
- * autoPlay
46
- * loop
47
- * muted
48
- * />
49
- *
50
- * // Using a Pipeline with runtime prompt
51
- * <SlopVideo
52
- * pipelineId="pipe_live_abc123"
53
- * prompt="Drone shot of a cyberpunk city in rain"
54
- * aspectRatio="16:9"
55
- * metadata={{ campaign: "spring_launch" }}
56
- * />
57
- * ```
58
- *
59
- * @version 0.6.0
60
- */
61
- export const SlopVideo: React.FC<SlopVideoProps> = ({
62
- bucketId,
63
- pipelineId,
64
- siloId,
65
- prompt,
66
- metadata,
67
- className,
68
- aspectRatio = "16:9",
69
- version,
70
- resultId,
71
- variables = {},
72
- duration,
73
- baseUrl,
74
- original,
75
- attachments,
76
- loader,
77
- autoPlay = true,
78
- loop = true,
79
- muted = true,
80
- playsInline = true,
81
- onLoadedData,
82
- onError,
83
- ...props
84
- }) => {
85
- // Construct API URL
86
- const rawSrc = useMemo(
87
- () =>
88
- buildVideoUrl({
89
- bucketId,
90
- pipelineId,
91
- siloId,
92
- prompt,
93
- metadata,
94
- aspectRatio,
95
- version,
96
- resultId,
97
- variables,
98
- duration,
99
- baseUrl,
100
- original,
101
- attachments,
102
- }),
103
- [
104
- bucketId,
105
- pipelineId,
106
- siloId,
107
- prompt,
108
- metadata,
109
- aspectRatio,
110
- version,
111
- resultId,
112
- variables,
113
- duration,
114
- baseUrl,
115
- original,
116
- attachments,
117
- ],
118
- );
119
-
120
- const [src, setSrc] = useState(rawSrc);
121
-
122
- useEffect(() => {
123
- const timer = setTimeout(() => {
124
- setSrc(rawSrc);
125
- }, 100);
126
- return () => clearTimeout(timer);
127
- }, [rawSrc]);
128
-
129
- const [isLoading, setIsLoading] = useState(true);
130
- const [currentSrc, setCurrentSrc] = useState(src);
131
- const videoRef = useRef<HTMLVideoElement>(null);
132
-
133
- if (src !== currentSrc) {
134
- setCurrentSrc(src);
135
- setIsLoading(true);
136
- }
137
-
138
- useEffect(() => {
139
- if (!src) return;
140
-
141
- fetch(src, { method: "HEAD" })
142
- .then(async (res) => {
143
- if (!res.ok) {
144
- let errorMessage = res.statusText;
145
- try {
146
- const errRes = await fetch(src);
147
- const errorText = await errRes.text();
148
- const errorData = JSON.parse(errorText);
149
- if (errorData.error) {
150
- errorMessage = errorData.error;
151
- }
152
- } catch (e) {
153
- // Failed to fetch or parse error details
154
- }
155
- console.error("SlopVideo error:", res.status, errorMessage);
156
- setIsLoading(false);
157
- }
158
- })
159
- .catch((err) => {
160
- console.error(`Error fetching video from ${src}:`, err);
161
- setIsLoading(false);
162
- });
163
- }, [src]);
164
-
165
- return (
166
- <>
167
- <style>{`
168
- @keyframes slop-shimmer {
169
- 0% {
170
- background-position: 200% 0;
171
- }
172
- 100% {
173
- background-position: -200% 0;
174
- }
175
- }
176
- @keyframes slop-spin {
177
- from {
178
- transform: rotate(0deg);
179
- }
180
- to {
181
- transform: rotate(360deg);
182
- }
183
- }
184
- `}</style>
185
- <div className={className} style={props.style}>
186
- <div
187
- className="slop-wrapper relative overflow-hidden w-full h-full"
188
- style={{
189
- aspectRatio: String(aspectRatio).replace(":", "/"),
190
- position: "relative",
191
- overflow: "hidden",
192
- width: "100%",
193
- height: "100%",
194
- }}
195
- >
196
- {/* Loading UI */}
197
- {loader ? (
198
- <div
199
- className={cn(
200
- "absolute inset-0 z-10 flex items-center justify-center transition-opacity duration-300",
201
- isLoading ? "opacity-100" : "opacity-0 pointer-events-none",
202
- )}
203
- style={{
204
- position: "absolute",
205
- top: 0,
206
- left: 0,
207
- right: 0,
208
- bottom: 0,
209
- zIndex: 10,
210
- display: "flex",
211
- alignItems: "center",
212
- justifyContent: "center",
213
- opacity: isLoading ? 1 : 0,
214
- pointerEvents: isLoading ? "auto" : "none",
215
- transition: "opacity 300ms ease-in-out",
216
- }}
217
- >
218
- {loader}
219
- </div>
220
- ) : (
221
- <>
222
- {/* Loading overlay */}
223
- <div
224
- className={cn(
225
- "absolute inset-0 z-10 flex items-center justify-center bg-muted transition-opacity duration-300",
226
- isLoading ? "opacity-100" : "opacity-0 pointer-events-none",
227
- )}
228
- style={{
229
- position: "absolute",
230
- top: 0,
231
- left: 0,
232
- right: 0,
233
- bottom: 0,
234
- zIndex: 10,
235
- display: "flex",
236
- alignItems: "center",
237
- justifyContent: "center",
238
- backgroundColor: "var(--muted, #f3f4f6)",
239
- opacity: isLoading ? 1 : 0,
240
- pointerEvents: isLoading ? "auto" : "none",
241
- transition: "opacity 300ms ease-in-out",
242
- }}
243
- >
244
- <div
245
- className="flex flex-col items-center gap-2"
246
- style={{
247
- display: "flex",
248
- flexDirection: "column",
249
- alignItems: "center",
250
- gap: "0.5rem",
251
- }}
252
- >
253
- <svg
254
- className="spinner size-6 text-muted-foreground"
255
- xmlns="http://www.w3.org/2000/svg"
256
- fill="none"
257
- viewBox="0 0 24 24"
258
- style={{
259
- width: "24px",
260
- height: "24px",
261
- color: "var(--muted-foreground, #6b7280)",
262
- animation: "slop-spin 1s linear infinite",
263
- }}
264
- >
265
- <circle
266
- className="opacity-25"
267
- cx="12"
268
- cy="12"
269
- r="10"
270
- stroke="currentColor"
271
- strokeWidth="4"
272
- style={{ opacity: 0.25 }}
273
- />
274
- <path
275
- className="opacity-75"
276
- fill="currentColor"
277
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
278
- style={{ opacity: 0.75 }}
279
- />
280
- </svg>
281
- <span
282
- className="text-xs text-muted-foreground"
283
- style={{
284
- fontSize: "0.75rem",
285
- color: "var(--muted-foreground, #6b7280)",
286
- }}
287
- >
288
- Loading...
289
- </span>
290
- </div>
291
- </div>
292
-
293
- {/* Shimmer effect underneath */}
294
- <div
295
- className={cn(
296
- "shimmer-effect absolute inset-0 bg-gradient-to-r from-muted via-muted/50 to-muted",
297
- isLoading ? "opacity-100" : "opacity-0",
298
- )}
299
- style={{
300
- position: "absolute",
301
- top: 0,
302
- left: 0,
303
- right: 0,
304
- bottom: 0,
305
- background:
306
- "linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)",
307
- backgroundSize: "200% 100%",
308
- animation: "slop-shimmer 2s ease-in-out infinite",
309
- opacity: isLoading ? 1 : 0,
310
- transition: "opacity 300ms",
311
- }}
312
- />
313
- </>
314
- )}
315
-
316
- {/* Video */}
317
- <video
318
- key={src}
319
- ref={videoRef}
320
- src={src}
321
- autoPlay={autoPlay}
322
- loop={loop}
323
- muted={muted}
324
- playsInline={playsInline}
325
- {...props}
326
- onLoadedData={(e) => {
327
- setIsLoading(false);
328
- onLoadedData?.(e);
329
- }}
330
- onError={(e) => {
331
- setIsLoading(false);
332
- onError?.(e);
333
- }}
334
- className={cn(
335
- "h-full w-full object-cover transition-opacity duration-500",
336
- isLoading ? "opacity-0" : "opacity-100",
337
- )}
338
- style={{
339
- height: "100%",
340
- width: "100%",
341
- objectFit: "cover",
342
- transition: "opacity 500ms",
343
- opacity: isLoading ? 0 : 1,
344
- }}
345
- />
346
- </div>
347
- </div>
348
- </>
349
- );
350
- };
package/src/index.ts DELETED
@@ -1,21 +0,0 @@
1
- export { SlopImage, type SlopImageProps } from "./components/SlopImage";
2
- export { SlopVideo, type SlopVideoProps } from "./components/SlopVideo";
3
- export { SlopText, type SlopTextProps } from "./components/SlopText";
4
-
5
- export type {
6
- ImageAspectRatio,
7
- VideoAspectRatio,
8
- SlopImageOptions,
9
- SlopVideoOptions,
10
- SlopTextOptions,
11
- ResultRating,
12
- RateResultOptions,
13
- RateResultResponse,
14
- } from "@slopmachine/core";
15
-
16
- export {
17
- preloadImage,
18
- preloadVideo,
19
- preloadText,
20
- rateResult,
21
- } from "@slopmachine/core";
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
-
9
- /* Bundler mode */
10
- "moduleResolution": "bundler",
11
- "allowImportingTsExtensions": true,
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "noEmit": true,
15
- "jsx": "react-jsx",
16
-
17
- /* Linting */
18
- "strict": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "noFallthroughCasesInSwitch": true,
22
-
23
- "declaration": true,
24
- "declarationMap": true,
25
- "baseUrl": "."
26
- },
27
- "include": ["src"],
28
- "exclude": ["node_modules", "dist"]
29
- }
package/tsup.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts"],
5
- format: ["cjs", "esm"],
6
- dts: true,
7
- sourcemap: true,
8
- clean: true,
9
- external: ["react", "react-dom"],
10
- });