@buildcores/render-client 1.0.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 ADDED
@@ -0,0 +1,341 @@
1
+ # @buildcores/render-client
2
+
3
+ A React component library for interactive 360-degree PC build rendering with drag-to-scrub functionality. Create immersive 3D visualizations of computer builds that users can rotate and explore.
4
+
5
+ <img src="./render-preview.png" alt="BuildRender Component" width="400">
6
+
7
+ _Interactive 3D PC build that users can rotate by dragging_
8
+
9
+ ## 🚀 Installation
10
+
11
+ ```bash
12
+ npm install @buildcores/render-client
13
+ ```
14
+
15
+ ## 📖 Quick Start
16
+
17
+ ```tsx
18
+ import React from "react";
19
+ import { BuildRender, getAvailableParts } from "@buildcores/render-client";
20
+
21
+ function App() {
22
+ const parts = {
23
+ parts: {
24
+ CPU: ["7xjqsomhr"], // AMD Ryzen 7 9800X3D
25
+ GPU: ["z7pyphm9k"], // ASUS GeForce RTX 5080 ASTRAL
26
+ RAM: ["dpl1iyvb5"], // PNY DDR5
27
+ Motherboard: ["iwin2u9vx"], // Asus ROG STRIX X870E-E GAMING WIFI
28
+ PSU: ["m4kilv190"], // LIAN LI 1300W
29
+ Storage: ["0bkvs17po"], // SAMSUNG 990 EVO
30
+ PCCase: ["qq9jamk7c"], // MONTECH KING 95 PRO
31
+ CPUCooler: ["62d8zelr5"], // ARCTIC LIQUID FREEZER 360
32
+ },
33
+ };
34
+
35
+ return (
36
+ <div>
37
+ <BuildRender
38
+ parts={parts}
39
+ size={500}
40
+ mouseSensitivity={0.01}
41
+ touchSensitivity={0.01}
42
+ />
43
+ </div>
44
+ );
45
+ }
46
+ ```
47
+
48
+ ## 🔧 API Reference
49
+
50
+ ### `BuildRender` Component
51
+
52
+ The main React component for rendering interactive 3D PC builds.
53
+
54
+ #### Props
55
+
56
+ | Prop | Type | Required | Default | Description |
57
+ | ------------------ | -------------------- | -------- | ------- | ---------------------------------------- |
58
+ | `parts` | `RenderBuildRequest` | ✅ | - | PC parts configuration object |
59
+ | `size` | `number` | ✅ | - | Video size in pixels (square dimensions) |
60
+ | `mouseSensitivity` | `number` | ❌ | `0.01` | Mouse drag sensitivity (0.001-0.1) |
61
+ | `touchSensitivity` | `number` | ❌ | `0.01` | Touch drag sensitivity (0.001-0.1) |
62
+
63
+ #### Parts Configuration
64
+
65
+ The `parts` prop uses the `RenderBuildRequest` interface:
66
+
67
+ ```tsx
68
+ interface RenderBuildRequest {
69
+ parts: {
70
+ [K in PartCategory]?: string[];
71
+ };
72
+ }
73
+
74
+ // Available part categories
75
+ enum PartCategory {
76
+ CPU = "CPU",
77
+ GPU = "GPU",
78
+ RAM = "RAM",
79
+ Motherboard = "Motherboard",
80
+ PSU = "PSU",
81
+ Storage = "Storage",
82
+ PCCase = "PCCase",
83
+ CPUCooler = "CPUCooler",
84
+ }
85
+ ```
86
+
87
+ **Current Limitation**: Each category array must contain exactly one part ID. Multiple parts per category will be supported in future versions.
88
+
89
+ #### Examples
90
+
91
+ **Complete Build (All Components)**
92
+
93
+ ```tsx
94
+ const completeBuild = {
95
+ parts: {
96
+ CPU: ["7xjqsomhr"], // AMD Ryzen 7 9800X3D
97
+ GPU: ["z7pyphm9k"], // ASUS GeForce RTX 5080 ASTRAL
98
+ RAM: ["dpl1iyvb5"], // PNY DDR5
99
+ Motherboard: ["iwin2u9vx"], // Asus ROG STRIX X870E-E GAMING WIFI
100
+ PSU: ["m4kilv190"], // LIAN LI 1300W
101
+ Storage: ["0bkvs17po"], // SAMSUNG 990 EVO
102
+ PCCase: ["qq9jamk7c"], // MONTECH KING 95 PRO
103
+ CPUCooler: ["62d8zelr5"], // ARCTIC LIQUID FREEZER 360
104
+ },
105
+ };
106
+
107
+ <BuildRender parts={completeBuild} size={500} />;
108
+ ```
109
+
110
+ **Case Only (Minimal)**
111
+
112
+ ```tsx
113
+ const caseOnly = {
114
+ parts: {
115
+ PCCase: ["qq9jamk7c"], // MONTECH KING 95 PRO
116
+ },
117
+ };
118
+
119
+ <BuildRender parts={caseOnly} size={500} />;
120
+ ```
121
+
122
+ ### `getAvailableParts()` Function
123
+
124
+ Fetches all available PC parts from the BuildCores API.
125
+
126
+ ```tsx
127
+ function getAvailableParts(): Promise<AvailablePartsResponse>;
128
+ ```
129
+
130
+ #### Return Type
131
+
132
+ ```tsx
133
+ interface PartDetails {
134
+ id: string; // Unique part identifier
135
+ name: string; // Human-readable part name
136
+ image: string; // URL to part image
137
+ }
138
+
139
+ type AvailablePartsResponse = {
140
+ [K in PartCategory]: PartDetails[];
141
+ };
142
+ ```
143
+
144
+ #### Example Response
145
+
146
+ ```json
147
+ {
148
+ "CPU": [
149
+ {
150
+ "id": "7xjqsomhr",
151
+ "name": "AMD Ryzen 7 9800X3D",
152
+ "image": "https://example.com/cpu1.jpg"
153
+ },
154
+ {
155
+ "id": "x2thvstj3",
156
+ "name": "AMD Ryzen 7 9700X",
157
+ "image": "https://example.com/cpu2.jpg"
158
+ }
159
+ ],
160
+ "GPU": [
161
+ {
162
+ "id": "z7pyphm9k",
163
+ "name": "ASUS GeForce RTX 5080 ASTRAL",
164
+ "image": "https://example.com/gpu1.jpg"
165
+ },
166
+ {
167
+ "id": "4a0mjb360",
168
+ "name": "PNY GeForce RTX 5060 Ti 16GB",
169
+ "image": "https://example.com/gpu2.jpg"
170
+ }
171
+ ],
172
+ "RAM": [
173
+ {
174
+ "id": "dpl1iyvb5",
175
+ "name": "PNY DDR5",
176
+ "image": "https://example.com/ram1.jpg"
177
+ }
178
+ ],
179
+ "Motherboard": [
180
+ {
181
+ "id": "iwin2u9vx",
182
+ "name": "Asus ROG STRIX X870E-E GAMING WIFI",
183
+ "image": "https://example.com/mb1.jpg"
184
+ }
185
+ ],
186
+ "PSU": [
187
+ {
188
+ "id": "m4kilv190",
189
+ "name": "LIAN LI 1300W",
190
+ "image": "https://example.com/psu1.jpg"
191
+ }
192
+ ],
193
+ "Storage": [
194
+ {
195
+ "id": "0bkvs17po",
196
+ "name": "SAMSUNG 990 EVO",
197
+ "image": "https://example.com/storage1.jpg"
198
+ }
199
+ ],
200
+ "PCCase": [
201
+ {
202
+ "id": "qq9jamk7c",
203
+ "name": "MONTECH KING 95 PRO",
204
+ "image": "https://example.com/case1.jpg"
205
+ }
206
+ ],
207
+ "CPUCooler": [
208
+ {
209
+ "id": "62d8zelr5",
210
+ "name": "ARCTIC LIQUID FREEZER 360",
211
+ "image": "https://example.com/cooler1.jpg"
212
+ }
213
+ ]
214
+ }
215
+ ```
216
+
217
+ #### Usage Example
218
+
219
+ ```tsx
220
+ import { useState, useEffect } from "react";
221
+ import {
222
+ BuildRender,
223
+ getAvailableParts,
224
+ type AvailablePartsResponse,
225
+ } from "@buildcores/render-client";
226
+
227
+ function PCBuildConfigurator() {
228
+ const [availableParts, setAvailableParts] = useState<AvailablePartsResponse>(
229
+ {} as AvailablePartsResponse
230
+ );
231
+ const [selectedParts, setSelectedParts] = useState<Record<string, string>>(
232
+ {}
233
+ );
234
+ const [isLoading, setIsLoading] = useState(true);
235
+
236
+ useEffect(() => {
237
+ const fetchParts = async () => {
238
+ try {
239
+ const parts = await getAvailableParts();
240
+ setAvailableParts(parts);
241
+
242
+ // Set default selections (first part of each category)
243
+ const defaults: Record<string, string> = {};
244
+ Object.entries(parts).forEach(([category, partList]) => {
245
+ if (partList.length > 0) {
246
+ defaults[category] = partList[0].id;
247
+ }
248
+ });
249
+ setSelectedParts(defaults);
250
+ } catch (error) {
251
+ console.error("Failed to fetch available parts:", error);
252
+ } finally {
253
+ setIsLoading(false);
254
+ }
255
+ };
256
+
257
+ fetchParts();
258
+ }, []);
259
+
260
+ if (isLoading) return <div>Loading parts...</div>;
261
+
262
+ // Build the parts object for BuildRender
263
+ const buildParts = {
264
+ parts: Object.fromEntries(
265
+ Object.entries(selectedParts).map(([category, partId]) => [
266
+ category,
267
+ [partId], // Wrap in array as required
268
+ ])
269
+ ),
270
+ };
271
+
272
+ return (
273
+ <div>
274
+ <BuildRender parts={buildParts} size={500} />
275
+
276
+ {/* Part selection UI */}
277
+ {Object.entries(availableParts).map(([category, parts]) => (
278
+ <div key={category}>
279
+ <h3>{category}</h3>
280
+ <select
281
+ value={selectedParts[category] || ""}
282
+ onChange={(e) =>
283
+ setSelectedParts((prev) => ({
284
+ ...prev,
285
+ [category]: e.target.value,
286
+ }))
287
+ }
288
+ >
289
+ {parts.map((part) => (
290
+ <option key={part.id} value={part.id}>
291
+ {part.name}
292
+ </option>
293
+ ))}
294
+ </select>
295
+ </div>
296
+ ))}
297
+ </div>
298
+ );
299
+ }
300
+ ```
301
+
302
+ ## ✨ Features
303
+
304
+ - **🎮 Interactive Controls**: Drag with mouse or touch to rotate the 3D model
305
+ - **📱 Mobile Support**: Optimized touch controls for mobile devices
306
+ - **⚡ Real-time Rendering**: Dynamic video generation based on part selection
307
+ - **💡 User Guidance**: Animated hints showing users they can interact
308
+ - **📏 Responsive**: Adapts to any size you specify
309
+ - **🔒 TypeScript**: Full TypeScript support with comprehensive types
310
+
311
+ ## 🔧 Configuration
312
+
313
+ ### Sensitivity Settings
314
+
315
+ Fine-tune the drag sensitivity for different use cases:
316
+
317
+ ```tsx
318
+ <BuildRender parts={parts} size={500} mouseSensitivity={0.003} />
319
+ <BuildRender parts={parts} size={500} mouseSensitivity={0.01} />
320
+ <BuildRender parts={parts} size={500} mouseSensitivity={0.02} />
321
+ ```
322
+
323
+ ### Size Configuration
324
+
325
+ Set the display size based on your layout needs:
326
+
327
+ ```tsx
328
+ <BuildRender parts={parts} size={500} />
329
+ ```
330
+
331
+ ## 📝 License
332
+
333
+ ISC
334
+
335
+ ## 🤝 Contributing
336
+
337
+ Issues and pull requests are welcome! Please ensure TypeScript types are properly maintained.
338
+
339
+ ---
340
+
341
+ Built with ❤️ for the PC building community
@@ -0,0 +1,2 @@
1
+ import { BuildRenderProps } from "./types";
2
+ export declare const BuildRender: React.FC<BuildRenderProps>;
package/dist/api.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { RenderBuildRequest, AvailablePartsResponse } from "./types";
2
+ declare const API_BASE_URL = "https://squid-app-7aeyk.ondigitalocean.app";
3
+ export declare const API_ENDPOINTS: {
4
+ readonly RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental";
5
+ readonly AVAILABLE_PARTS: "/available-parts";
6
+ };
7
+ export interface RenderBuildResponse {
8
+ /**
9
+ * The rendered MP4 video as a Blob
10
+ */
11
+ video: Blob;
12
+ /**
13
+ * Optional metadata about the render
14
+ */
15
+ metadata?: {
16
+ duration?: number;
17
+ size?: number;
18
+ format?: string;
19
+ };
20
+ }
21
+ export interface RenderAPIService {
22
+ /**
23
+ * Submit a render build request
24
+ * @param parts - The parts configuration for the build
25
+ * @returns Promise with the rendered MP4 video
26
+ */
27
+ renderBuildExperimental(parts: RenderBuildRequest): Promise<RenderBuildResponse>;
28
+ /**
29
+ * Get available parts for building
30
+ * @returns Promise with available parts by category
31
+ */
32
+ getAvailableParts(): Promise<AvailablePartsResponse>;
33
+ }
34
+ export declare const buildApiUrl: (endpoint: string) => string;
35
+ export declare const renderBuildExperimental: (request: RenderBuildRequest) => Promise<RenderBuildResponse>;
36
+ export declare const getAvailableParts: () => Promise<AvailablePartsResponse>;
37
+ export { API_BASE_URL };
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ interface DragIconProps {
3
+ width?: number;
4
+ height?: number;
5
+ className?: string;
6
+ style?: React.CSSProperties;
7
+ }
8
+ export declare const DragIcon: React.FC<DragIconProps>;
9
+ export {};
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ interface InstructionTooltipProps {
3
+ isVisible: boolean;
4
+ progressValue: number;
5
+ instructionIcon?: string;
6
+ }
7
+ export declare const InstructionTooltip: React.FC<InstructionTooltipProps>;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ interface LoadingErrorOverlayProps {
3
+ isVisible: boolean;
4
+ renderError?: string;
5
+ size: number;
6
+ }
7
+ export declare const LoadingErrorOverlay: React.FC<LoadingErrorOverlayProps>;
8
+ export {};
@@ -0,0 +1,12 @@
1
+ import { RenderBuildRequest } from "../types";
2
+ /**
3
+ * Compares two RenderBuildRequest objects for equality by checking if the same IDs
4
+ * are present in each category array, regardless of order.
5
+ */
6
+ export declare const arePartsEqual: (parts1: RenderBuildRequest, parts2: RenderBuildRequest) => boolean;
7
+ export interface UseBuildRenderReturn {
8
+ videoSrc: string | null;
9
+ isRenderingBuild: boolean;
10
+ renderError: string | null;
11
+ }
12
+ export declare const useBuildRender: (parts: RenderBuildRequest, onLoadStart?: () => void) => UseBuildRenderReturn;
@@ -0,0 +1,4 @@
1
+ export declare function useBouncePatternProgress(enabled?: boolean): {
2
+ value: number;
3
+ isBouncing: boolean;
4
+ };
@@ -0,0 +1,15 @@
1
+ import { type RefObject } from "react";
2
+ export declare const calculateCircularTime: (startTime: number, deltaX: number, sensitivity: number, duration: number) => number;
3
+ interface UseVideoScrubbingOptions {
4
+ mouseSensitivity?: number;
5
+ touchSensitivity?: number;
6
+ progressSensitivity?: number;
7
+ useProgressScrubbing?: boolean;
8
+ }
9
+ export declare const useVideoScrubbing: (videoRef: RefObject<HTMLVideoElement | null>, options?: UseVideoScrubbingOptions) => {
10
+ isDragging: boolean;
11
+ handleMouseDown: (e: React.MouseEvent) => void;
12
+ handleTouchStart: (e: React.TouchEvent) => void;
13
+ hasDragged: import("react").MutableRefObject<boolean>;
14
+ };
15
+ export {};