@buildcores/render-client 1.0.4 → 1.0.6

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/index.js CHANGED
@@ -322,27 +322,41 @@ function useBouncePatternProgress(enabled = true) {
322
322
  }
323
323
 
324
324
  // API Configuration
325
- const API_BASE_URL = "https://squid-app-7aeyk.ondigitalocean.app";
325
+ 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
329
  AVAILABLE_PARTS: "/available-parts",
330
330
  };
331
331
  // API URL helpers
332
- const buildApiUrl = (endpoint) => {
333
- return `${API_BASE_URL}${endpoint}`;
332
+ const buildApiUrl = (endpoint, config) => {
333
+ const baseUrl = `${API_BASE_URL}${endpoint}`;
334
+ if (config?.environment) {
335
+ const separator = endpoint.includes("?") ? "&" : "?";
336
+ return `${baseUrl}${separator}environment=${config.environment}`;
337
+ }
338
+ return baseUrl;
339
+ };
340
+ // Helper to build request headers with auth token
341
+ const buildHeaders = (config) => {
342
+ const headers = {
343
+ "Content-Type": "application/json",
344
+ accept: "application/json",
345
+ };
346
+ if (config?.authToken) {
347
+ headers["Authorization"] = `Bearer ${config.authToken}`;
348
+ }
349
+ return headers;
334
350
  };
335
351
  // API Implementation
336
- const renderBuildExperimental = async (request) => {
352
+ const renderBuildExperimental = async (request, config) => {
337
353
  const requestWithFormat = {
338
354
  ...request,
339
355
  format: request.format || "video", // Default to video format
340
356
  };
341
- const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL), {
357
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
342
358
  method: "POST",
343
- headers: {
344
- "Content-Type": "application/json",
345
- },
359
+ headers: buildHeaders(config),
346
360
  body: JSON.stringify(requestWithFormat),
347
361
  });
348
362
  if (!response.ok) {
@@ -357,16 +371,14 @@ const renderBuildExperimental = async (request) => {
357
371
  },
358
372
  };
359
373
  };
360
- const renderSpriteExperimental = async (request) => {
374
+ const renderSpriteExperimental = async (request, config) => {
361
375
  const requestWithFormat = {
362
376
  ...request,
363
377
  format: "sprite",
364
378
  };
365
- const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL), {
379
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
366
380
  method: "POST",
367
- headers: {
368
- "Content-Type": "application/json",
369
- },
381
+ headers: buildHeaders(config),
370
382
  body: JSON.stringify(requestWithFormat),
371
383
  });
372
384
  if (!response.ok) {
@@ -384,12 +396,10 @@ const renderSpriteExperimental = async (request) => {
384
396
  },
385
397
  };
386
398
  };
387
- const getAvailableParts = async () => {
388
- const response = await fetch(buildApiUrl(API_ENDPOINTS.AVAILABLE_PARTS), {
399
+ const getAvailableParts = async (config) => {
400
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.AVAILABLE_PARTS, config), {
389
401
  method: "GET",
390
- headers: {
391
- "Content-Type": "application/json",
392
- },
402
+ headers: buildHeaders(config),
393
403
  });
394
404
  if (!response.ok) {
395
405
  throw new Error(`Get available parts failed: ${response.status} ${response.statusText}`);
@@ -397,7 +407,6 @@ const getAvailableParts = async () => {
397
407
  return response.json();
398
408
  };
399
409
 
400
- // API Types
401
410
  /**
402
411
  * Enum defining all available PC part categories that can be rendered.
403
412
  *
@@ -466,7 +475,7 @@ const arePartsEqual = (parts1, parts2) => {
466
475
  }
467
476
  return true;
468
477
  };
469
- const useBuildRender = (parts, onLoadStart) => {
478
+ const useBuildRender = (parts, apiConfig, onLoadStart) => {
470
479
  const [videoSrc, setVideoSrc] = React.useState(null);
471
480
  const [isRenderingBuild, setIsRenderingBuild] = React.useState(false);
472
481
  const [renderError, setRenderError] = React.useState(null);
@@ -476,7 +485,7 @@ const useBuildRender = (parts, onLoadStart) => {
476
485
  setIsRenderingBuild(true);
477
486
  setRenderError(null);
478
487
  onLoadStart?.();
479
- const response = await renderBuildExperimental(currentParts);
488
+ const response = await renderBuildExperimental(currentParts, apiConfig);
480
489
  const objectUrl = URL.createObjectURL(response.video);
481
490
  // Clean up previous video URL before setting new one
482
491
  setVideoSrc((prevSrc) => {
@@ -492,7 +501,7 @@ const useBuildRender = (parts, onLoadStart) => {
492
501
  finally {
493
502
  setIsRenderingBuild(false);
494
503
  }
495
- }, [onLoadStart]);
504
+ }, [apiConfig, onLoadStart]);
496
505
  // Effect to call API when parts content changes (using custom equality check)
497
506
  React.useEffect(() => {
498
507
  const shouldFetch = previousPartsRef.current === null ||
@@ -517,7 +526,7 @@ const useBuildRender = (parts, onLoadStart) => {
517
526
  };
518
527
  };
519
528
 
520
- const useSpriteRender = (parts, onLoadStart) => {
529
+ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
521
530
  const [spriteSrc, setSpriteSrc] = React.useState(null);
522
531
  const [isRenderingSprite, setIsRenderingSprite] = React.useState(false);
523
532
  const [renderError, setRenderError] = React.useState(null);
@@ -528,7 +537,7 @@ const useSpriteRender = (parts, onLoadStart) => {
528
537
  setIsRenderingSprite(true);
529
538
  setRenderError(null);
530
539
  onLoadStart?.();
531
- const response = await renderSpriteExperimental(currentParts);
540
+ const response = await renderSpriteExperimental(currentParts, apiConfig);
532
541
  const objectUrl = URL.createObjectURL(response.sprite);
533
542
  // Clean up previous sprite URL before setting new one
534
543
  setSpriteSrc((prevSrc) => {
@@ -550,7 +559,7 @@ const useSpriteRender = (parts, onLoadStart) => {
550
559
  finally {
551
560
  setIsRenderingSprite(false);
552
561
  }
553
- }, [onLoadStart]);
562
+ }, [apiConfig, onLoadStart]);
554
563
  // Effect to call API when parts content changes (using custom equality check)
555
564
  React.useEffect(() => {
556
565
  const shouldFetch = previousPartsRef.current === null ||
@@ -585,18 +594,19 @@ const LoadingErrorOverlay = ({ isVisible, renderError, size, }) => {
585
594
  left: 0,
586
595
  right: 0,
587
596
  bottom: 0,
588
- backgroundColor: "rgba(0, 0, 0, 0.7)",
597
+ backgroundColor: "rgba(0, 0, 0, 1)",
589
598
  display: "flex",
590
599
  flexDirection: "column",
591
600
  alignItems: "center",
592
601
  justifyContent: "center",
593
602
  color: "white",
594
603
  zIndex: 10,
595
- }, children: renderError ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: { marginBottom: "20px", fontSize: "18px" }, children: "Render Failed" }), jsxRuntime.jsx("div", { style: {
604
+ }, children: renderError ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: { marginBottom: "20px", fontSize: "18px", color: "white" }, children: "Render Failed" }), jsxRuntime.jsx("div", { style: {
596
605
  fontSize: "14px",
597
606
  textAlign: "center",
598
607
  maxWidth: size * 0.8,
599
- }, children: renderError })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("div", { style: { marginBottom: "20px", fontSize: "18px" }, children: "Loading Build..." }) })) }));
608
+ color: "white",
609
+ }, children: renderError })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx("div", { style: { marginBottom: "20px", fontSize: "18px", color: "white" }, children: "Loading Build..." }) })) }));
600
610
  };
601
611
 
602
612
  const DragIcon = ({ width = 24, height = 24, className, style, ...props }) => {
@@ -630,13 +640,13 @@ const InstructionTooltip = ({ isVisible, progressValue, instructionIcon, }) => {
630
640
  } })) }));
631
641
  };
632
642
 
633
- const BuildRender = ({ parts, size, mouseSensitivity = 0.2, touchSensitivity = 0.2, }) => {
643
+ const BuildRender = ({ parts, size, apiConfig, mouseSensitivity = 0.2, touchSensitivity = 0.2, }) => {
634
644
  const canvasRef = React.useRef(null);
635
645
  const [img, setImg] = React.useState(null);
636
646
  const [isLoading, setIsLoading] = React.useState(true);
637
647
  const [bouncingAllowed, setBouncingAllowed] = React.useState(false);
638
648
  // Use custom hook for sprite rendering
639
- const { spriteSrc, isRenderingSprite, renderError, spriteMetadata } = useSpriteRender(parts);
649
+ const { spriteSrc, isRenderingSprite, renderError, spriteMetadata } = useSpriteRender(parts, apiConfig);
640
650
  const { value: progressValue, isBouncing } = useBouncePatternProgress(bouncingAllowed);
641
651
  const total = spriteMetadata ? spriteMetadata.totalFrames : 72;
642
652
  const cols = spriteMetadata ? spriteMetadata.cols : 12;
@@ -729,7 +739,12 @@ const BuildRender = ({ parts, size, mouseSensitivity = 0.2, touchSensitivity = 0
729
739
  draw(frameRef.current);
730
740
  }
731
741
  }, [img, isLoading, draw]);
732
- return (jsxRuntime.jsxs("div", { style: { position: "relative", width: size, height: size }, children: [img && (jsxRuntime.jsx("canvas", { ref: canvasRef, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, style: {
742
+ return (jsxRuntime.jsxs("div", { style: {
743
+ position: "relative",
744
+ width: size,
745
+ height: size,
746
+ backgroundColor: "black",
747
+ }, children: [img && (jsxRuntime.jsx("canvas", { ref: canvasRef, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, style: {
733
748
  width: size,
734
749
  height: size,
735
750
  cursor: isDragging ? "grabbing" : "grab",
@@ -838,14 +853,74 @@ const useVideoScrubbing = (videoRef, options = {}) => {
838
853
  };
839
854
  };
840
855
 
856
+ const BuildRenderVideo = ({ parts, size, apiConfig, mouseSensitivity = 0.01, touchSensitivity = 0.01, }) => {
857
+ const videoRef = React.useRef(null);
858
+ const [isLoading, setIsLoading] = React.useState(true);
859
+ const [bouncingAllowed, setBouncingAllowed] = React.useState(false);
860
+ // Use custom hook for build rendering
861
+ const { videoSrc, isRenderingBuild, renderError } = useBuildRender(parts, apiConfig);
862
+ const { value: progressValue, isBouncing } = useBouncePatternProgress(bouncingAllowed);
863
+ const { isDragging, handleMouseDown, handleTouchStart, hasDragged } = useVideoScrubbing(videoRef, {
864
+ mouseSensitivity,
865
+ touchSensitivity,
866
+ });
867
+ const handleLoadStartInternal = React.useCallback(() => {
868
+ setIsLoading(true);
869
+ setBouncingAllowed(false);
870
+ }, []);
871
+ const handleCanPlayInternal = React.useCallback(() => {
872
+ setIsLoading(false);
873
+ // Start bouncing animation after delay
874
+ setTimeout(() => {
875
+ setBouncingAllowed(true);
876
+ }, 2000);
877
+ }, []);
878
+ React.useEffect(() => {
879
+ if (hasDragged.current || !videoRef.current)
880
+ return;
881
+ const duration = videoRef.current.duration;
882
+ if (!isFinite(duration))
883
+ return;
884
+ const time = calculateCircularTime(0, progressValue, 0.5, duration);
885
+ if (isFinite(time)) {
886
+ videoRef.current.currentTime = time;
887
+ }
888
+ }, [progressValue, hasDragged]);
889
+ return (jsxRuntime.jsxs("div", { style: { position: "relative", width: size, height: size }, children: [videoSrc && (jsxRuntime.jsx("video", { ref: videoRef, src: videoSrc, width: size, height: size, autoPlay: true, preload: "metadata", muted: true, playsInline: true, controls: false, disablePictureInPicture: true, controlsList: "nodownload nofullscreen noremoteplayback", ...{ "x-webkit-airplay": "deny" }, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onLoadStart: handleLoadStartInternal, onCanPlay: handleCanPlayInternal, onLoadedData: () => {
890
+ if (videoRef.current) {
891
+ videoRef.current.pause();
892
+ }
893
+ }, style: {
894
+ cursor: isDragging ? "grabbing" : "grab",
895
+ touchAction: "none", // Prevents default touch behaviors like scrolling
896
+ display: "block",
897
+ // Completely hide video controls on all browsers including mobile
898
+ WebkitMediaControls: "none",
899
+ MozMediaControls: "none",
900
+ OMediaControls: "none",
901
+ msMediaControls: "none",
902
+ mediaControls: "none",
903
+ // Additional iOS-specific properties
904
+ WebkitTouchCallout: "none",
905
+ WebkitUserSelect: "none",
906
+ userSelect: "none",
907
+ }, children: "Your browser does not support the video tag." }, videoSrc)), jsxRuntime.jsx(LoadingErrorOverlay, { isVisible: isLoading || isRenderingBuild || !!renderError, renderError: renderError || undefined, size: size }), jsxRuntime.jsx(InstructionTooltip, { isVisible: !isLoading &&
908
+ !isRenderingBuild &&
909
+ !renderError &&
910
+ isBouncing &&
911
+ !hasDragged.current, progressValue: progressValue })] }));
912
+ };
913
+
841
914
  exports.API_BASE_URL = API_BASE_URL;
842
915
  exports.API_ENDPOINTS = API_ENDPOINTS;
843
916
  exports.BuildRender = BuildRender;
917
+ exports.BuildRenderVideo = BuildRenderVideo;
844
918
  exports.DragIcon = DragIcon;
845
919
  exports.InstructionTooltip = InstructionTooltip;
846
920
  exports.LoadingErrorOverlay = LoadingErrorOverlay;
847
921
  exports.arePartsEqual = arePartsEqual;
848
922
  exports.buildApiUrl = buildApiUrl;
923
+ exports.buildHeaders = buildHeaders;
849
924
  exports.calculateCircularFrame = calculateCircularFrame;
850
925
  exports.calculateCircularTime = calculateCircularTime;
851
926
  exports.getAvailableParts = getAvailableParts;