@buildcores/render-client 1.0.7 → 1.0.9
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/dist/api.d.ts +25 -5
- package/dist/hooks/useBuildRender.d.ts +9 -1
- package/dist/hooks/useSpriteRender.d.ts +9 -1
- package/dist/index.d.ts +23 -10
- package/dist/index.esm.js +121 -30
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +121 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/SpriteRender.d.ts +0 -2
package/dist/index.js
CHANGED
|
@@ -326,6 +326,7 @@ const API_BASE_URL = "https://www.renderapi.buildcores.com";
|
|
|
326
326
|
// API Endpoints
|
|
327
327
|
const API_ENDPOINTS = {
|
|
328
328
|
RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental",
|
|
329
|
+
RENDER_BUILD: "/render-build",
|
|
329
330
|
AVAILABLE_PARTS: "/available-parts",
|
|
330
331
|
};
|
|
331
332
|
// API URL helpers
|
|
@@ -348,6 +349,7 @@ const buildHeaders = (config) => {
|
|
|
348
349
|
}
|
|
349
350
|
return headers;
|
|
350
351
|
};
|
|
352
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
351
353
|
// API Implementation
|
|
352
354
|
const renderBuildExperimental = async (request, config) => {
|
|
353
355
|
const requestWithFormat = {
|
|
@@ -371,6 +373,68 @@ const renderBuildExperimental = async (request, config) => {
|
|
|
371
373
|
},
|
|
372
374
|
};
|
|
373
375
|
};
|
|
376
|
+
// New async endpoints implementation
|
|
377
|
+
const createRenderBuildJob = async (request, config) => {
|
|
378
|
+
const body = {
|
|
379
|
+
parts: request.parts,
|
|
380
|
+
// If provided, forward format; default handled server-side but we keep explicit default
|
|
381
|
+
...(request.format ? { format: request.format } : {}),
|
|
382
|
+
};
|
|
383
|
+
const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD, config), {
|
|
384
|
+
method: "POST",
|
|
385
|
+
headers: buildHeaders(config),
|
|
386
|
+
body: JSON.stringify(body),
|
|
387
|
+
});
|
|
388
|
+
if (!response.ok) {
|
|
389
|
+
throw new Error(`Create render job failed: ${response.status} ${response.statusText}`);
|
|
390
|
+
}
|
|
391
|
+
const data = (await response.json());
|
|
392
|
+
if (!data?.job_id) {
|
|
393
|
+
throw new Error("Create render job failed: missing job_id in response");
|
|
394
|
+
}
|
|
395
|
+
return data;
|
|
396
|
+
};
|
|
397
|
+
const getRenderBuildStatus = async (jobId, config) => {
|
|
398
|
+
const url = buildApiUrl(`${API_ENDPOINTS.RENDER_BUILD}/${encodeURIComponent(jobId)}`, config);
|
|
399
|
+
const response = await fetch(url, {
|
|
400
|
+
method: "GET",
|
|
401
|
+
headers: buildHeaders(config),
|
|
402
|
+
});
|
|
403
|
+
if (response.status === 404) {
|
|
404
|
+
throw new Error("Render job not found");
|
|
405
|
+
}
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
throw new Error(`Get render job status failed: ${response.status} ${response.statusText}`);
|
|
408
|
+
}
|
|
409
|
+
return (await response.json());
|
|
410
|
+
};
|
|
411
|
+
const renderBuild = async (request, config, options) => {
|
|
412
|
+
const pollIntervalMs = 1500;
|
|
413
|
+
const timeoutMs = 120000; // 2 minutes default
|
|
414
|
+
const { job_id } = await createRenderBuildJob(request, config);
|
|
415
|
+
const start = Date.now();
|
|
416
|
+
// Poll until completed or error or timeout
|
|
417
|
+
for (;;) {
|
|
418
|
+
const status = await getRenderBuildStatus(job_id, config);
|
|
419
|
+
if (status.status === "completed") {
|
|
420
|
+
const requestedFormat = request.format ?? "video";
|
|
421
|
+
const finalUrl = (requestedFormat === "sprite"
|
|
422
|
+
? status.sprite_url || status.url || undefined
|
|
423
|
+
: status.video_url || status.url || undefined);
|
|
424
|
+
if (!finalUrl) {
|
|
425
|
+
throw new Error("Render job completed but no URL returned");
|
|
426
|
+
}
|
|
427
|
+
return { videoUrl: finalUrl };
|
|
428
|
+
}
|
|
429
|
+
if (status.status === "error") {
|
|
430
|
+
throw new Error(status.error || "Render job failed");
|
|
431
|
+
}
|
|
432
|
+
if (Date.now() - start > timeoutMs) {
|
|
433
|
+
throw new Error("Timed out waiting for render job to complete");
|
|
434
|
+
}
|
|
435
|
+
await sleep(pollIntervalMs);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
374
438
|
const renderSpriteExperimental = async (request, config) => {
|
|
375
439
|
const requestWithFormat = {
|
|
376
440
|
...request,
|
|
@@ -475,7 +539,7 @@ const arePartsEqual = (parts1, parts2) => {
|
|
|
475
539
|
}
|
|
476
540
|
return true;
|
|
477
541
|
};
|
|
478
|
-
const useBuildRender = (parts, apiConfig, onLoadStart) => {
|
|
542
|
+
const useBuildRender = (parts, apiConfig, onLoadStart, options) => {
|
|
479
543
|
const [videoSrc, setVideoSrc] = React.useState(null);
|
|
480
544
|
const [isRenderingBuild, setIsRenderingBuild] = React.useState(false);
|
|
481
545
|
const [renderError, setRenderError] = React.useState(null);
|
|
@@ -485,15 +549,27 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
|
|
|
485
549
|
setIsRenderingBuild(true);
|
|
486
550
|
setRenderError(null);
|
|
487
551
|
onLoadStart?.();
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
552
|
+
const mode = options?.mode ?? "async";
|
|
553
|
+
if (mode === "experimental") {
|
|
554
|
+
const response = await renderBuildExperimental(currentParts, apiConfig);
|
|
555
|
+
const objectUrl = URL.createObjectURL(response.video);
|
|
556
|
+
setVideoSrc((prevSrc) => {
|
|
557
|
+
if (prevSrc && prevSrc.startsWith("blob:")) {
|
|
558
|
+
URL.revokeObjectURL(prevSrc);
|
|
559
|
+
}
|
|
560
|
+
return objectUrl;
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
const { videoUrl } = await renderBuild(currentParts, apiConfig);
|
|
565
|
+
// Clean up previous object URL (if any) before setting new one
|
|
566
|
+
setVideoSrc((prevSrc) => {
|
|
567
|
+
if (prevSrc && prevSrc.startsWith("blob:")) {
|
|
568
|
+
URL.revokeObjectURL(prevSrc);
|
|
569
|
+
}
|
|
570
|
+
return videoUrl;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
497
573
|
}
|
|
498
574
|
catch (error) {
|
|
499
575
|
setRenderError(error instanceof Error ? error.message : "Failed to render build");
|
|
@@ -501,7 +577,7 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
|
|
|
501
577
|
finally {
|
|
502
578
|
setIsRenderingBuild(false);
|
|
503
579
|
}
|
|
504
|
-
}, [apiConfig, onLoadStart]);
|
|
580
|
+
}, [apiConfig, onLoadStart, options?.mode]);
|
|
505
581
|
// Effect to call API when parts content changes (using custom equality check)
|
|
506
582
|
React.useEffect(() => {
|
|
507
583
|
const shouldFetch = previousPartsRef.current === null ||
|
|
@@ -514,7 +590,7 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
|
|
|
514
590
|
// Cleanup effect for component unmount
|
|
515
591
|
React.useEffect(() => {
|
|
516
592
|
return () => {
|
|
517
|
-
if (videoSrc) {
|
|
593
|
+
if (videoSrc && videoSrc.startsWith("blob:")) {
|
|
518
594
|
URL.revokeObjectURL(videoSrc);
|
|
519
595
|
}
|
|
520
596
|
};
|
|
@@ -526,7 +602,7 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
|
|
|
526
602
|
};
|
|
527
603
|
};
|
|
528
604
|
|
|
529
|
-
const useSpriteRender = (parts, apiConfig, onLoadStart) => {
|
|
605
|
+
const useSpriteRender = (parts, apiConfig, onLoadStart, options) => {
|
|
530
606
|
const [spriteSrc, setSpriteSrc] = React.useState(null);
|
|
531
607
|
const [isRenderingSprite, setIsRenderingSprite] = React.useState(false);
|
|
532
608
|
const [renderError, setRenderError] = React.useState(null);
|
|
@@ -537,21 +613,36 @@ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
|
|
|
537
613
|
setIsRenderingSprite(true);
|
|
538
614
|
setRenderError(null);
|
|
539
615
|
onLoadStart?.();
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
616
|
+
const mode = options?.mode ?? "async";
|
|
617
|
+
if (mode === "experimental") {
|
|
618
|
+
const response = await renderSpriteExperimental(currentParts, apiConfig);
|
|
619
|
+
const objectUrl = URL.createObjectURL(response.sprite);
|
|
620
|
+
// Clean up previous sprite URL before setting new one
|
|
621
|
+
setSpriteSrc((prevSrc) => {
|
|
622
|
+
if (prevSrc && prevSrc.startsWith("blob:")) {
|
|
623
|
+
URL.revokeObjectURL(prevSrc);
|
|
624
|
+
}
|
|
625
|
+
return objectUrl;
|
|
626
|
+
});
|
|
627
|
+
// Set sprite metadata
|
|
628
|
+
setSpriteMetadata({
|
|
629
|
+
cols: response.metadata?.cols || 12,
|
|
630
|
+
rows: response.metadata?.rows || 6,
|
|
631
|
+
totalFrames: response.metadata?.totalFrames || 72,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
// Async job-based flow: request sprite format and use returned URL
|
|
636
|
+
const { videoUrl: spriteUrl } = await renderBuild({ ...currentParts, format: "sprite" }, apiConfig);
|
|
637
|
+
setSpriteSrc((prevSrc) => {
|
|
638
|
+
if (prevSrc && prevSrc.startsWith("blob:")) {
|
|
639
|
+
URL.revokeObjectURL(prevSrc);
|
|
640
|
+
}
|
|
641
|
+
return spriteUrl;
|
|
642
|
+
});
|
|
643
|
+
// No metadata from async endpoint; keep defaults
|
|
644
|
+
setSpriteMetadata({ cols: 12, rows: 6, totalFrames: 72 });
|
|
645
|
+
}
|
|
555
646
|
}
|
|
556
647
|
catch (error) {
|
|
557
648
|
setRenderError(error instanceof Error ? error.message : "Failed to render sprite");
|
|
@@ -559,7 +650,7 @@ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
|
|
|
559
650
|
finally {
|
|
560
651
|
setIsRenderingSprite(false);
|
|
561
652
|
}
|
|
562
|
-
}, [apiConfig, onLoadStart]);
|
|
653
|
+
}, [apiConfig, onLoadStart, options?.mode]);
|
|
563
654
|
// Effect to call API when parts content changes (using custom equality check)
|
|
564
655
|
React.useEffect(() => {
|
|
565
656
|
const shouldFetch = previousPartsRef.current === null ||
|
|
@@ -572,7 +663,7 @@ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
|
|
|
572
663
|
// Cleanup effect for component unmount
|
|
573
664
|
React.useEffect(() => {
|
|
574
665
|
return () => {
|
|
575
|
-
if (spriteSrc) {
|
|
666
|
+
if (spriteSrc && spriteSrc.startsWith("blob:")) {
|
|
576
667
|
URL.revokeObjectURL(spriteSrc);
|
|
577
668
|
}
|
|
578
669
|
};
|