@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 +341 -0
- package/dist/BuildRender.d.ts +2 -0
- package/dist/api.d.ts +37 -0
- package/dist/components/DragIcon.d.ts +9 -0
- package/dist/components/InstructionTooltip.d.ts +8 -0
- package/dist/components/LoadingErrorOverlay.d.ts +8 -0
- package/dist/hooks/useBuildRender.d.ts +12 -0
- package/dist/hooks/useProgressOneSecond.d.ts +4 -0
- package/dist/hooks/useVideoScrubbing.d.ts +15 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.esm.js +582 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +597 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +282 -0
- package/package.json +47 -0
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
|
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,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,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 {};
|