@buildcores/render-client 1.6.0 → 1.7.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 +34 -0
- package/dist/api.d.ts +2 -1
- package/dist/hooks/useSpriteRender.d.ts +8 -0
- package/dist/index.d.ts +88 -2
- package/dist/index.esm.js +154 -43
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +154 -43
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +64 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,33 @@ function App() {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
## 🔐 Authentication Modes
|
|
49
|
+
|
|
50
|
+
`@buildcores/render-client` supports two auth modes:
|
|
51
|
+
|
|
52
|
+
- `legacy` (backward-compatible): sends a long-lived API key from `apiConfig.authToken`.
|
|
53
|
+
- `session` (recommended): fetches short-lived delegated tokens from your backend via `apiConfig.getRenderSessionToken`.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const apiConfig = {
|
|
57
|
+
environment: "prod",
|
|
58
|
+
authMode: "session",
|
|
59
|
+
getRenderSessionToken: async () => {
|
|
60
|
+
// Your backend endpoint should broker BuildCores /auth/render-session
|
|
61
|
+
const res = await fetch("/buildcores/session", { method: "POST" });
|
|
62
|
+
if (!res.ok) throw new Error("Failed to mint BuildCores session");
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
return {
|
|
65
|
+
token: data.session_token,
|
|
66
|
+
expiresAt: data.expires_at,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Legacy browser API key flow is deprecated and should only be used for temporary compatibility.
|
|
73
|
+
`/render-build-experimental` behavior is unchanged.
|
|
74
|
+
|
|
48
75
|
## 🔧 API Reference
|
|
49
76
|
|
|
50
77
|
### `BuildRender` Component
|
|
@@ -72,6 +99,11 @@ interface RenderBuildRequest {
|
|
|
72
99
|
format?: "video" | "sprite";
|
|
73
100
|
width?: number; // Optional: Canvas pixel width (256-2000)
|
|
74
101
|
height?: number; // Optional: Canvas pixel height (256-2000)
|
|
102
|
+
scene?: "sunset" | "dawn" | "night" | "warehouse" | "forest" | "apartment" | "studio" | "studio_v2" | "city" | "park" | "lobby";
|
|
103
|
+
showBackground?: boolean;
|
|
104
|
+
showGrid?: boolean;
|
|
105
|
+
winterMode?: boolean; // mutually exclusive with springMode
|
|
106
|
+
springMode?: boolean; // mutually exclusive with winterMode
|
|
75
107
|
}
|
|
76
108
|
|
|
77
109
|
// Available part categories
|
|
@@ -91,6 +123,8 @@ enum PartCategory {
|
|
|
91
123
|
|
|
92
124
|
**Resolution Control**: You can specify custom `width` and `height` (both must be provided together, 256-2000 pixels) for higher or lower quality renders. If not specified, the default resolution is used.
|
|
93
125
|
|
|
126
|
+
**Environment Controls**: You can configure `scene`, `showBackground`, `showGrid`, and seasonal toggles (`winterMode` / `springMode`) for async render endpoints.
|
|
127
|
+
|
|
94
128
|
#### Examples
|
|
95
129
|
|
|
96
130
|
**Complete Build (All Components)**
|
package/dist/api.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface RenderJobStatusResponse {
|
|
|
32
32
|
url?: string | null;
|
|
33
33
|
video_url?: string | null;
|
|
34
34
|
sprite_url?: string | null;
|
|
35
|
+
screenshot_url?: string | null;
|
|
35
36
|
error?: string | null;
|
|
36
37
|
end_time?: string | null;
|
|
37
38
|
}
|
|
@@ -71,7 +72,7 @@ export interface RenderAPIService {
|
|
|
71
72
|
getAvailableParts(category: PartCategory, config: ApiConfig, options?: GetAvailablePartsOptions): Promise<AvailablePartsResponse>;
|
|
72
73
|
}
|
|
73
74
|
export declare const buildApiUrl: (endpoint: string, config: ApiConfig) => string;
|
|
74
|
-
export declare const buildHeaders: (config: ApiConfig) => Record<string, string>;
|
|
75
|
+
export declare const buildHeaders: (config: ApiConfig, authTokenOverride?: string) => Record<string, string>;
|
|
75
76
|
export declare const renderBuildExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderBuildResponse>;
|
|
76
77
|
export declare const createRenderBuildJob: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderJobCreateResponse>;
|
|
77
78
|
export declare const getRenderBuildStatus: (jobId: string, config: ApiConfig) => Promise<RenderJobStatusResponse>;
|
|
@@ -34,6 +34,10 @@ export type SpriteRenderInput = {
|
|
|
34
34
|
type: 'parts';
|
|
35
35
|
parts: RenderBuildRequest;
|
|
36
36
|
showGrid?: boolean;
|
|
37
|
+
scene?: RenderBuildRequest["scene"];
|
|
38
|
+
showBackground?: boolean;
|
|
39
|
+
winterMode?: boolean;
|
|
40
|
+
springMode?: boolean;
|
|
37
41
|
cameraOffsetX?: number;
|
|
38
42
|
cameraZoom?: number;
|
|
39
43
|
gridSettings?: RenderGridSettings;
|
|
@@ -43,6 +47,10 @@ export type SpriteRenderInput = {
|
|
|
43
47
|
shareCode: string;
|
|
44
48
|
profile?: 'cinematic' | 'flat' | 'fast';
|
|
45
49
|
showGrid?: boolean;
|
|
50
|
+
scene?: RenderBuildRequest["scene"];
|
|
51
|
+
showBackground?: boolean;
|
|
52
|
+
winterMode?: boolean;
|
|
53
|
+
springMode?: boolean;
|
|
46
54
|
cameraOffsetX?: number;
|
|
47
55
|
cameraZoom?: number;
|
|
48
56
|
gridSettings?: RenderGridSettings;
|
package/dist/index.d.ts
CHANGED
|
@@ -229,6 +229,24 @@ interface BuildRenderProps {
|
|
|
229
229
|
* Works for both parts and shareCode rendering.
|
|
230
230
|
*/
|
|
231
231
|
showGrid?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Environment scene preset for rendering.
|
|
234
|
+
*/
|
|
235
|
+
scene?: RenderScene;
|
|
236
|
+
/**
|
|
237
|
+
* Whether to show the environment background.
|
|
238
|
+
*/
|
|
239
|
+
showBackground?: boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Enable winter mode effects.
|
|
242
|
+
* Mutually exclusive with springMode.
|
|
243
|
+
*/
|
|
244
|
+
winterMode?: boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Enable spring mode effects.
|
|
247
|
+
* Mutually exclusive with winterMode.
|
|
248
|
+
*/
|
|
249
|
+
springMode?: boolean;
|
|
232
250
|
/**
|
|
233
251
|
* Camera offset X for composition.
|
|
234
252
|
* Positive values shift the build to the right, leaving room for text overlay on the left.
|
|
@@ -386,6 +404,22 @@ interface ApiConfig {
|
|
|
386
404
|
* ```
|
|
387
405
|
*/
|
|
388
406
|
authToken?: string;
|
|
407
|
+
/**
|
|
408
|
+
* Auth mode for API requests.
|
|
409
|
+
* - legacy: Sends `authToken` directly as bearer token.
|
|
410
|
+
* - session: Uses `getRenderSessionToken` to fetch short-lived delegated tokens.
|
|
411
|
+
*
|
|
412
|
+
* @default "legacy"
|
|
413
|
+
*/
|
|
414
|
+
authMode?: 'legacy' | 'session';
|
|
415
|
+
/**
|
|
416
|
+
* Session token supplier used when `authMode` is "session".
|
|
417
|
+
* Should call your backend endpoint that brokers BuildCores session tokens.
|
|
418
|
+
*/
|
|
419
|
+
getRenderSessionToken?: () => Promise<{
|
|
420
|
+
token: string;
|
|
421
|
+
expiresAt: string;
|
|
422
|
+
}>;
|
|
389
423
|
}
|
|
390
424
|
/**
|
|
391
425
|
* Enum defining all available PC part categories that can be rendered.
|
|
@@ -552,6 +586,24 @@ interface RenderBuildRequest {
|
|
|
552
586
|
* Defaults to true for cinematic profile, false otherwise.
|
|
553
587
|
*/
|
|
554
588
|
showGrid?: boolean;
|
|
589
|
+
/**
|
|
590
|
+
* Environment scene preset.
|
|
591
|
+
*/
|
|
592
|
+
scene?: RenderScene;
|
|
593
|
+
/**
|
|
594
|
+
* Whether to show the environment background.
|
|
595
|
+
*/
|
|
596
|
+
showBackground?: boolean;
|
|
597
|
+
/**
|
|
598
|
+
* Enable winter mode effects.
|
|
599
|
+
* Mutually exclusive with springMode.
|
|
600
|
+
*/
|
|
601
|
+
winterMode?: boolean;
|
|
602
|
+
/**
|
|
603
|
+
* Enable spring mode effects.
|
|
604
|
+
* Mutually exclusive with winterMode.
|
|
605
|
+
*/
|
|
606
|
+
springMode?: boolean;
|
|
555
607
|
/**
|
|
556
608
|
* Horizontal offset for the camera view.
|
|
557
609
|
* Positive values shift the build to the right, leaving room for text overlay on the left.
|
|
@@ -759,6 +811,10 @@ interface GridSettings {
|
|
|
759
811
|
/** Render order for depth sorting (default: 0, use -1 to render before other objects) */
|
|
760
812
|
renderOrder?: number;
|
|
761
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Supported environment scene presets for render API endpoints.
|
|
816
|
+
*/
|
|
817
|
+
type RenderScene = "sunset" | "dawn" | "night" | "warehouse" | "forest" | "apartment" | "studio" | "studio_v2" | "city" | "park" | "lobby";
|
|
762
818
|
/**
|
|
763
819
|
* Options for rendering a build by share code
|
|
764
820
|
*/
|
|
@@ -771,8 +827,16 @@ interface RenderByShareCodeOptions {
|
|
|
771
827
|
height?: number;
|
|
772
828
|
/** Render quality profile */
|
|
773
829
|
profile?: "cinematic" | "flat" | "fast";
|
|
830
|
+
/** Environment scene preset */
|
|
831
|
+
scene?: RenderScene;
|
|
832
|
+
/** Whether to show the environment background */
|
|
833
|
+
showBackground?: boolean;
|
|
774
834
|
/** Show grid in render (default: true for cinematic profile) */
|
|
775
835
|
showGrid?: boolean;
|
|
836
|
+
/** Enable winter mode effects (mutually exclusive with springMode) */
|
|
837
|
+
winterMode?: boolean;
|
|
838
|
+
/** Enable spring mode effects (mutually exclusive with winterMode) */
|
|
839
|
+
springMode?: boolean;
|
|
776
840
|
/** Camera offset X for composition (positive = shift build right to leave room for text overlay) */
|
|
777
841
|
cameraOffsetX?: number;
|
|
778
842
|
/** Grid appearance settings (for thicker/more visible grid in renders) */
|
|
@@ -922,6 +986,10 @@ type SpriteRenderInput = {
|
|
|
922
986
|
type: 'parts';
|
|
923
987
|
parts: RenderBuildRequest;
|
|
924
988
|
showGrid?: boolean;
|
|
989
|
+
scene?: RenderBuildRequest["scene"];
|
|
990
|
+
showBackground?: boolean;
|
|
991
|
+
winterMode?: boolean;
|
|
992
|
+
springMode?: boolean;
|
|
925
993
|
cameraOffsetX?: number;
|
|
926
994
|
cameraZoom?: number;
|
|
927
995
|
gridSettings?: RenderGridSettings;
|
|
@@ -931,6 +999,10 @@ type SpriteRenderInput = {
|
|
|
931
999
|
shareCode: string;
|
|
932
1000
|
profile?: 'cinematic' | 'flat' | 'fast';
|
|
933
1001
|
showGrid?: boolean;
|
|
1002
|
+
scene?: RenderBuildRequest["scene"];
|
|
1003
|
+
showBackground?: boolean;
|
|
1004
|
+
winterMode?: boolean;
|
|
1005
|
+
springMode?: boolean;
|
|
934
1006
|
cameraOffsetX?: number;
|
|
935
1007
|
cameraZoom?: number;
|
|
936
1008
|
gridSettings?: RenderGridSettings;
|
|
@@ -983,6 +1055,20 @@ interface RenderBuildResponse {
|
|
|
983
1055
|
format?: string;
|
|
984
1056
|
};
|
|
985
1057
|
}
|
|
1058
|
+
interface RenderJobCreateResponse {
|
|
1059
|
+
job_id: string;
|
|
1060
|
+
status: "queued" | "processing" | "completed" | "error";
|
|
1061
|
+
}
|
|
1062
|
+
interface RenderJobStatusResponse {
|
|
1063
|
+
job_id: string;
|
|
1064
|
+
status: "queued" | "processing" | "completed" | "error";
|
|
1065
|
+
url?: string | null;
|
|
1066
|
+
video_url?: string | null;
|
|
1067
|
+
sprite_url?: string | null;
|
|
1068
|
+
screenshot_url?: string | null;
|
|
1069
|
+
error?: string | null;
|
|
1070
|
+
end_time?: string | null;
|
|
1071
|
+
}
|
|
986
1072
|
interface RenderSpriteResponse {
|
|
987
1073
|
/**
|
|
988
1074
|
* The rendered sprite sheet as a Blob (when format is "sprite")
|
|
@@ -1015,7 +1101,7 @@ interface RenderAPIService {
|
|
|
1015
1101
|
getAvailableParts(category: PartCategory, config: ApiConfig, options?: GetAvailablePartsOptions): Promise<AvailablePartsResponse>;
|
|
1016
1102
|
}
|
|
1017
1103
|
declare const buildApiUrl: (endpoint: string, config: ApiConfig) => string;
|
|
1018
|
-
declare const buildHeaders: (config: ApiConfig) => Record<string, string>;
|
|
1104
|
+
declare const buildHeaders: (config: ApiConfig, authTokenOverride?: string) => Record<string, string>;
|
|
1019
1105
|
declare const renderBuildExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderBuildResponse>;
|
|
1020
1106
|
declare const renderSpriteExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderSpriteResponse>;
|
|
1021
1107
|
declare const getAvailableParts: (category: PartCategory, config: ApiConfig, options?: GetAvailablePartsOptions) => Promise<AvailablePartsResponse>;
|
|
@@ -1103,4 +1189,4 @@ declare const createRenderByShareCodeJob: (shareCode: string, config: ApiConfig,
|
|
|
1103
1189
|
declare const renderByShareCode: (shareCode: string, config: ApiConfig, options?: RenderByShareCodeOptions) => Promise<RenderByShareCodeResponse>;
|
|
1104
1190
|
|
|
1105
1191
|
export { API_BASE_URL, API_ENDPOINTS, BuildRender, BuildRenderVideo, DragIcon, InstructionTooltip, LoadingErrorOverlay, PartCategory, arePartsEqual, buildApiUrl, buildHeaders, calculateCircularFrame, calculateCircularTime, createRenderByShareCodeJob, getAvailableParts, getBuildByShareCode, getPartsByIds, renderBuildExperimental, renderByShareCode, renderSpriteExperimental, useBouncePatternProgress, useBuildRender, useContinuousSpin, useSpriteRender, useSpriteScrubbing, useVideoScrubbing };
|
|
1106
|
-
export type { ApiConfig, AvailablePartsResponse, BuildRenderProps, BuildRenderVideoProps, BuildResponse, GetAvailablePartsOptions, GridSettings, PartDetails, PartDetailsWithCategory, PartsResponse, RenderAPIService, RenderBuildRequest, RenderBuildResponse, RenderByShareCodeJobResponse, RenderByShareCodeOptions, RenderByShareCodeResponse, RenderSpriteResponse, SpriteRenderInput, UseBuildRenderOptions, UseBuildRenderReturn, UseSpriteRenderOptions, UseSpriteRenderReturn };
|
|
1192
|
+
export type { ApiConfig, AvailablePartsResponse, BuildRenderProps, BuildRenderVideoProps, BuildResponse, GetAvailablePartsOptions, GridSettings, PartDetails, PartDetailsWithCategory, PartsResponse, RenderAPIService, RenderBuildRequest, RenderBuildResponse, RenderByShareCodeJobResponse, RenderByShareCodeOptions, RenderByShareCodeResponse, RenderJobCreateResponse, RenderJobStatusResponse, RenderScene, RenderSpriteResponse, SpriteRenderInput, UseBuildRenderOptions, UseBuildRenderReturn, UseSpriteRenderOptions, UseSpriteRenderReturn };
|
package/dist/index.esm.js
CHANGED
|
@@ -211,17 +211,73 @@ const buildApiUrl = (endpoint, config) => {
|
|
|
211
211
|
}
|
|
212
212
|
return baseUrl;
|
|
213
213
|
};
|
|
214
|
+
const SESSION_TOKEN_REFRESH_WINDOW_MS = 15000;
|
|
215
|
+
const sessionTokenCache = new WeakMap();
|
|
216
|
+
const resolveAuthMode = (config) => config.authMode ?? (config.getRenderSessionToken ? "session" : "legacy");
|
|
217
|
+
const isSessionAuthMode = (config) => resolveAuthMode(config) === "session";
|
|
218
|
+
const parseExpiresAtMs = (expiresAt) => {
|
|
219
|
+
const parsed = Date.parse(expiresAt);
|
|
220
|
+
if (!Number.isFinite(parsed)) {
|
|
221
|
+
throw new Error("Invalid session token expiry (expiresAt)");
|
|
222
|
+
}
|
|
223
|
+
return parsed;
|
|
224
|
+
};
|
|
225
|
+
const resolveSessionToken = async (config, forceRefresh) => {
|
|
226
|
+
const supplier = config.getRenderSessionToken;
|
|
227
|
+
if (!supplier) {
|
|
228
|
+
throw new Error("authMode=session requires getRenderSessionToken");
|
|
229
|
+
}
|
|
230
|
+
const cached = sessionTokenCache.get(supplier);
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
if (!forceRefresh &&
|
|
233
|
+
cached &&
|
|
234
|
+
cached.expiresAtMs - SESSION_TOKEN_REFRESH_WINDOW_MS > now) {
|
|
235
|
+
return cached.token;
|
|
236
|
+
}
|
|
237
|
+
const session = await supplier();
|
|
238
|
+
if (!session?.token || !session?.expiresAt) {
|
|
239
|
+
throw new Error("getRenderSessionToken must return { token, expiresAt }");
|
|
240
|
+
}
|
|
241
|
+
const expiresAtMs = parseExpiresAtMs(session.expiresAt);
|
|
242
|
+
sessionTokenCache.set(supplier, {
|
|
243
|
+
token: session.token,
|
|
244
|
+
expiresAtMs,
|
|
245
|
+
});
|
|
246
|
+
return session.token;
|
|
247
|
+
};
|
|
248
|
+
const resolveAuthToken = async (config, forceRefresh) => {
|
|
249
|
+
if (isSessionAuthMode(config)) {
|
|
250
|
+
return resolveSessionToken(config, forceRefresh);
|
|
251
|
+
}
|
|
252
|
+
return config.authToken;
|
|
253
|
+
};
|
|
214
254
|
// Helper to build request headers with auth token
|
|
215
|
-
const buildHeaders = (config) => {
|
|
255
|
+
const buildHeaders = (config, authTokenOverride) => {
|
|
216
256
|
const headers = {
|
|
217
257
|
"Content-Type": "application/json",
|
|
218
258
|
accept: "application/json",
|
|
219
259
|
};
|
|
220
|
-
|
|
221
|
-
|
|
260
|
+
const authToken = authTokenOverride ?? config.authToken;
|
|
261
|
+
if (authToken) {
|
|
262
|
+
headers["Authorization"] = `Bearer ${authToken}`;
|
|
222
263
|
}
|
|
223
264
|
return headers;
|
|
224
265
|
};
|
|
266
|
+
const fetchWithApiAuth = async (url, init, config) => {
|
|
267
|
+
const firstToken = await resolveAuthToken(config, false);
|
|
268
|
+
const firstResponse = await fetch(url, {
|
|
269
|
+
...init,
|
|
270
|
+
headers: buildHeaders(config, firstToken),
|
|
271
|
+
});
|
|
272
|
+
if (!isSessionAuthMode(config) || firstResponse.status !== 401) {
|
|
273
|
+
return firstResponse;
|
|
274
|
+
}
|
|
275
|
+
const refreshedToken = await resolveAuthToken(config, true);
|
|
276
|
+
return fetch(url, {
|
|
277
|
+
...init,
|
|
278
|
+
headers: buildHeaders(config, refreshedToken),
|
|
279
|
+
});
|
|
280
|
+
};
|
|
225
281
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
226
282
|
// API Implementation
|
|
227
283
|
const renderBuildExperimental = async (request, config) => {
|
|
@@ -233,12 +289,16 @@ const renderBuildExperimental = async (request, config) => {
|
|
|
233
289
|
...(request.height !== undefined ? { height: request.height } : {}),
|
|
234
290
|
// Include profile if provided
|
|
235
291
|
...(request.profile ? { profile: request.profile } : {}),
|
|
292
|
+
...(request.scene ? { scene: request.scene } : {}),
|
|
293
|
+
...(request.showBackground !== undefined ? { showBackground: request.showBackground } : {}),
|
|
294
|
+
...(request.showGrid !== undefined ? { showGrid: request.showGrid } : {}),
|
|
295
|
+
...(request.winterMode !== undefined ? { winterMode: request.winterMode } : {}),
|
|
296
|
+
...(request.springMode !== undefined ? { springMode: request.springMode } : {}),
|
|
236
297
|
};
|
|
237
|
-
const response = await
|
|
298
|
+
const response = await fetchWithApiAuth(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
|
|
238
299
|
method: "POST",
|
|
239
|
-
headers: buildHeaders(config),
|
|
240
300
|
body: JSON.stringify(requestWithFormat),
|
|
241
|
-
});
|
|
301
|
+
}, config);
|
|
242
302
|
if (!response.ok) {
|
|
243
303
|
throw new Error(`Render build failed: ${response.status} ${response.statusText}`);
|
|
244
304
|
}
|
|
@@ -262,8 +322,12 @@ const createRenderBuildJob = async (request, config) => {
|
|
|
262
322
|
...(request.height !== undefined ? { height: request.height } : {}),
|
|
263
323
|
// Include profile if provided
|
|
264
324
|
...(request.profile ? { profile: request.profile } : {}),
|
|
325
|
+
...(request.scene ? { scene: request.scene } : {}),
|
|
326
|
+
...(request.showBackground !== undefined ? { showBackground: request.showBackground } : {}),
|
|
265
327
|
// Include composition settings
|
|
266
328
|
...(request.showGrid !== undefined ? { showGrid: request.showGrid } : {}),
|
|
329
|
+
...(request.winterMode !== undefined ? { winterMode: request.winterMode } : {}),
|
|
330
|
+
...(request.springMode !== undefined ? { springMode: request.springMode } : {}),
|
|
267
331
|
...(request.cameraOffsetX !== undefined ? { cameraOffsetX: request.cameraOffsetX } : {}),
|
|
268
332
|
...(request.gridSettings ? { gridSettings: request.gridSettings } : {}),
|
|
269
333
|
// Include frame quality for sprite rendering
|
|
@@ -271,11 +335,10 @@ const createRenderBuildJob = async (request, config) => {
|
|
|
271
335
|
// Include camera zoom for render-time scaling
|
|
272
336
|
...(request.cameraZoom !== undefined ? { cameraZoom: request.cameraZoom } : {}),
|
|
273
337
|
};
|
|
274
|
-
const response = await
|
|
338
|
+
const response = await fetchWithApiAuth(buildApiUrl(API_ENDPOINTS.RENDER_BUILD, config), {
|
|
275
339
|
method: "POST",
|
|
276
|
-
headers: buildHeaders(config),
|
|
277
340
|
body: JSON.stringify(body),
|
|
278
|
-
});
|
|
341
|
+
}, config);
|
|
279
342
|
if (!response.ok) {
|
|
280
343
|
throw new Error(`Create render job failed: ${response.status} ${response.statusText}`);
|
|
281
344
|
}
|
|
@@ -287,10 +350,7 @@ const createRenderBuildJob = async (request, config) => {
|
|
|
287
350
|
};
|
|
288
351
|
const getRenderBuildStatus = async (jobId, config) => {
|
|
289
352
|
const url = buildApiUrl(`${API_ENDPOINTS.RENDER_BUILD}/${encodeURIComponent(jobId)}`, config);
|
|
290
|
-
const response = await
|
|
291
|
-
method: "GET",
|
|
292
|
-
headers: buildHeaders(config),
|
|
293
|
-
});
|
|
353
|
+
const response = await fetchWithApiAuth(url, { method: "GET" }, config);
|
|
294
354
|
if (response.status === 404) {
|
|
295
355
|
throw new Error("Render job not found");
|
|
296
356
|
}
|
|
@@ -335,12 +395,16 @@ const renderSpriteExperimental = async (request, config) => {
|
|
|
335
395
|
...(request.height !== undefined ? { height: request.height } : {}),
|
|
336
396
|
// Include profile if provided
|
|
337
397
|
...(request.profile ? { profile: request.profile } : {}),
|
|
398
|
+
...(request.scene ? { scene: request.scene } : {}),
|
|
399
|
+
...(request.showBackground !== undefined ? { showBackground: request.showBackground } : {}),
|
|
400
|
+
...(request.showGrid !== undefined ? { showGrid: request.showGrid } : {}),
|
|
401
|
+
...(request.winterMode !== undefined ? { winterMode: request.winterMode } : {}),
|
|
402
|
+
...(request.springMode !== undefined ? { springMode: request.springMode } : {}),
|
|
338
403
|
};
|
|
339
|
-
const response = await
|
|
404
|
+
const response = await fetchWithApiAuth(buildApiUrl(API_ENDPOINTS.RENDER_BUILD_EXPERIMENTAL, config), {
|
|
340
405
|
method: "POST",
|
|
341
|
-
headers: buildHeaders(config),
|
|
342
406
|
body: JSON.stringify(requestWithFormat),
|
|
343
|
-
});
|
|
407
|
+
}, config);
|
|
344
408
|
if (!response.ok) {
|
|
345
409
|
throw new Error(`Render sprite failed: ${response.status} ${response.statusText}`);
|
|
346
410
|
}
|
|
@@ -366,10 +430,7 @@ const getAvailableParts = async (category, config, options) => {
|
|
|
366
430
|
params.set("skip", String(options.skip));
|
|
367
431
|
const separator = base.includes("?") ? "&" : "?";
|
|
368
432
|
const url = `${base}${separator}${params.toString()}`;
|
|
369
|
-
const response = await
|
|
370
|
-
method: "GET",
|
|
371
|
-
headers: buildHeaders(config),
|
|
372
|
-
});
|
|
433
|
+
const response = await fetchWithApiAuth(url, { method: "GET" }, config);
|
|
373
434
|
if (!response.ok) {
|
|
374
435
|
throw new Error(`Get available parts failed: ${response.status} ${response.statusText}`);
|
|
375
436
|
}
|
|
@@ -402,10 +463,7 @@ const getAvailableParts = async (category, config, options) => {
|
|
|
402
463
|
*/
|
|
403
464
|
const getBuildByShareCode = async (shareCode, config) => {
|
|
404
465
|
const url = buildApiUrl(`${API_ENDPOINTS.BUILD}/${encodeURIComponent(shareCode)}`, config);
|
|
405
|
-
const response = await
|
|
406
|
-
method: "GET",
|
|
407
|
-
headers: buildHeaders(config),
|
|
408
|
-
});
|
|
466
|
+
const response = await fetchWithApiAuth(url, { method: "GET" }, config);
|
|
409
467
|
if (response.status === 404) {
|
|
410
468
|
throw new Error("Build not found");
|
|
411
469
|
}
|
|
@@ -435,11 +493,10 @@ const getBuildByShareCode = async (shareCode, config) => {
|
|
|
435
493
|
*/
|
|
436
494
|
const getPartsByIds = async (partIds, config) => {
|
|
437
495
|
const url = buildApiUrl(API_ENDPOINTS.PARTS, config);
|
|
438
|
-
const response = await
|
|
496
|
+
const response = await fetchWithApiAuth(url, {
|
|
439
497
|
method: "POST",
|
|
440
|
-
headers: buildHeaders(config),
|
|
441
498
|
body: JSON.stringify({ ids: partIds }),
|
|
442
|
-
});
|
|
499
|
+
}, config);
|
|
443
500
|
if (!response.ok) {
|
|
444
501
|
throw new Error(`Get parts by IDs failed: ${response.status} ${response.statusText}`);
|
|
445
502
|
}
|
|
@@ -462,17 +519,20 @@ const createRenderByShareCodeJob = async (shareCode, config, options) => {
|
|
|
462
519
|
...(options?.width !== undefined ? { width: options.width } : {}),
|
|
463
520
|
...(options?.height !== undefined ? { height: options.height } : {}),
|
|
464
521
|
...(options?.profile ? { profile: options.profile } : {}),
|
|
522
|
+
...(options?.scene ? { scene: options.scene } : {}),
|
|
523
|
+
...(options?.showBackground !== undefined ? { showBackground: options.showBackground } : {}),
|
|
465
524
|
...(options?.showGrid !== undefined ? { showGrid: options.showGrid } : {}),
|
|
525
|
+
...(options?.winterMode !== undefined ? { winterMode: options.winterMode } : {}),
|
|
526
|
+
...(options?.springMode !== undefined ? { springMode: options.springMode } : {}),
|
|
466
527
|
...(options?.cameraOffsetX !== undefined ? { cameraOffsetX: options.cameraOffsetX } : {}),
|
|
467
528
|
...(options?.gridSettings ? { gridSettings: options.gridSettings } : {}),
|
|
468
529
|
...(options?.frameQuality ? { frameQuality: options.frameQuality } : {}),
|
|
469
530
|
...(options?.cameraZoom !== undefined ? { cameraZoom: options.cameraZoom } : {}),
|
|
470
531
|
};
|
|
471
|
-
const response = await
|
|
532
|
+
const response = await fetchWithApiAuth(url, {
|
|
472
533
|
method: "POST",
|
|
473
|
-
headers: buildHeaders(config),
|
|
474
534
|
body: JSON.stringify(body),
|
|
475
|
-
});
|
|
535
|
+
}, config);
|
|
476
536
|
if (response.status === 404) {
|
|
477
537
|
throw new Error("Build not found");
|
|
478
538
|
}
|
|
@@ -681,7 +741,21 @@ const useSpriteRender = (input, apiConfig, onLoadStart, options) => {
|
|
|
681
741
|
const [spriteMetadata, setSpriteMetadata] = useState(null);
|
|
682
742
|
const previousInputRef = useRef(null);
|
|
683
743
|
// Normalize input to SpriteRenderInput format
|
|
684
|
-
const normalizedInput = 'type' in input
|
|
744
|
+
const normalizedInput = 'type' in input
|
|
745
|
+
? input
|
|
746
|
+
: {
|
|
747
|
+
type: 'parts',
|
|
748
|
+
parts: input,
|
|
749
|
+
showGrid: input.showGrid,
|
|
750
|
+
scene: input.scene,
|
|
751
|
+
showBackground: input.showBackground,
|
|
752
|
+
winterMode: input.winterMode,
|
|
753
|
+
springMode: input.springMode,
|
|
754
|
+
cameraOffsetX: input.cameraOffsetX,
|
|
755
|
+
cameraZoom: input.cameraZoom,
|
|
756
|
+
gridSettings: input.gridSettings,
|
|
757
|
+
frameQuality: input.frameQuality
|
|
758
|
+
};
|
|
685
759
|
const fetchRenderSprite = useCallback(async (currentInput) => {
|
|
686
760
|
try {
|
|
687
761
|
setIsRenderingSprite(true);
|
|
@@ -693,6 +767,10 @@ const useSpriteRender = (input, apiConfig, onLoadStart, options) => {
|
|
|
693
767
|
format: 'sprite',
|
|
694
768
|
profile: currentInput.profile,
|
|
695
769
|
showGrid: currentInput.showGrid,
|
|
770
|
+
scene: currentInput.scene,
|
|
771
|
+
showBackground: currentInput.showBackground,
|
|
772
|
+
winterMode: currentInput.winterMode,
|
|
773
|
+
springMode: currentInput.springMode,
|
|
696
774
|
cameraOffsetX: currentInput.cameraOffsetX,
|
|
697
775
|
cameraZoom: currentInput.cameraZoom,
|
|
698
776
|
gridSettings: currentInput.gridSettings,
|
|
@@ -719,6 +797,10 @@ const useSpriteRender = (input, apiConfig, onLoadStart, options) => {
|
|
|
719
797
|
const response = await renderSpriteExperimental({
|
|
720
798
|
...currentParts,
|
|
721
799
|
showGrid: currentInput.showGrid,
|
|
800
|
+
scene: currentInput.scene,
|
|
801
|
+
showBackground: currentInput.showBackground,
|
|
802
|
+
winterMode: currentInput.winterMode,
|
|
803
|
+
springMode: currentInput.springMode,
|
|
722
804
|
cameraOffsetX: currentInput.cameraOffsetX,
|
|
723
805
|
cameraZoom: currentInput.cameraZoom,
|
|
724
806
|
gridSettings: currentInput.gridSettings,
|
|
@@ -745,6 +827,10 @@ const useSpriteRender = (input, apiConfig, onLoadStart, options) => {
|
|
|
745
827
|
...currentParts,
|
|
746
828
|
format: "sprite",
|
|
747
829
|
showGrid: currentInput.showGrid,
|
|
830
|
+
scene: currentInput.scene,
|
|
831
|
+
showBackground: currentInput.showBackground,
|
|
832
|
+
winterMode: currentInput.winterMode,
|
|
833
|
+
springMode: currentInput.springMode,
|
|
748
834
|
cameraOffsetX: currentInput.cameraOffsetX,
|
|
749
835
|
cameraZoom: currentInput.cameraZoom,
|
|
750
836
|
gridSettings: currentInput.gridSettings,
|
|
@@ -779,6 +865,10 @@ const useSpriteRender = (input, apiConfig, onLoadStart, options) => {
|
|
|
779
865
|
return a.shareCode === b.shareCode &&
|
|
780
866
|
a.profile === b.profile &&
|
|
781
867
|
a.showGrid === b.showGrid &&
|
|
868
|
+
a.scene === b.scene &&
|
|
869
|
+
a.showBackground === b.showBackground &&
|
|
870
|
+
a.winterMode === b.winterMode &&
|
|
871
|
+
a.springMode === b.springMode &&
|
|
782
872
|
a.cameraOffsetX === b.cameraOffsetX &&
|
|
783
873
|
a.cameraZoom === b.cameraZoom &&
|
|
784
874
|
a.frameQuality === b.frameQuality &&
|
|
@@ -789,6 +879,10 @@ const useSpriteRender = (input, apiConfig, onLoadStart, options) => {
|
|
|
789
879
|
const gridSettingsEqual = JSON.stringify(a.gridSettings ?? {}) === JSON.stringify(b.gridSettings ?? {});
|
|
790
880
|
return arePartsEqual(a.parts, b.parts) &&
|
|
791
881
|
a.showGrid === b.showGrid &&
|
|
882
|
+
a.scene === b.scene &&
|
|
883
|
+
a.showBackground === b.showBackground &&
|
|
884
|
+
a.winterMode === b.winterMode &&
|
|
885
|
+
a.springMode === b.springMode &&
|
|
792
886
|
a.cameraOffsetX === b.cameraOffsetX &&
|
|
793
887
|
a.cameraZoom === b.cameraZoom &&
|
|
794
888
|
a.frameQuality === b.frameQuality &&
|
|
@@ -967,7 +1061,7 @@ const useZoomPan = ({ displayWidth, displayHeight, minScale = 0.5, maxScale = 2.
|
|
|
967
1061
|
};
|
|
968
1062
|
};
|
|
969
1063
|
|
|
970
|
-
const BuildRender = ({ parts, shareCode, width, height, size, apiConfig, useSpriteRenderOptions, mouseSensitivity = 0.2, touchSensitivity = 0.2, showGrid, cameraOffsetX, cameraZoom, gridSettings, animationMode = 'bounce', spinDuration = 10000, interactive = true, frameQuality, zoom = 1, }) => {
|
|
1064
|
+
const BuildRender = ({ parts, shareCode, width, height, size, apiConfig, useSpriteRenderOptions, mouseSensitivity = 0.2, touchSensitivity = 0.2, showGrid, scene, showBackground, winterMode, springMode, cameraOffsetX, cameraZoom, gridSettings, animationMode = 'bounce', spinDuration = 10000, interactive = true, frameQuality, zoom = 1, }) => {
|
|
971
1065
|
const canvasRef = useRef(null);
|
|
972
1066
|
const containerRef = useRef(null);
|
|
973
1067
|
const [img, setImg] = useState(null);
|
|
@@ -977,28 +1071,45 @@ const BuildRender = ({ parts, shareCode, width, height, size, apiConfig, useSpri
|
|
|
977
1071
|
const displayH = height ?? size ?? 300;
|
|
978
1072
|
// Build the render input - prefer shareCode if provided (preserves interactive state like case fan slots)
|
|
979
1073
|
const renderInput = useMemo(() => {
|
|
1074
|
+
const resolvedShowGrid = showGrid ?? parts?.showGrid;
|
|
1075
|
+
const resolvedScene = scene ?? parts?.scene;
|
|
1076
|
+
const resolvedShowBackground = showBackground ?? parts?.showBackground;
|
|
1077
|
+
const resolvedWinterMode = winterMode ?? parts?.winterMode;
|
|
1078
|
+
const resolvedSpringMode = springMode ?? parts?.springMode;
|
|
1079
|
+
const resolvedCameraOffsetX = cameraOffsetX ?? parts?.cameraOffsetX;
|
|
1080
|
+
const resolvedCameraZoom = cameraZoom ?? parts?.cameraZoom;
|
|
1081
|
+
const resolvedGridSettings = gridSettings ?? parts?.gridSettings;
|
|
1082
|
+
const resolvedFrameQuality = frameQuality ?? parts?.frameQuality;
|
|
980
1083
|
if (shareCode) {
|
|
981
1084
|
return {
|
|
982
1085
|
type: 'shareCode',
|
|
983
1086
|
shareCode,
|
|
984
1087
|
profile: parts?.profile,
|
|
985
|
-
showGrid,
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1088
|
+
showGrid: resolvedShowGrid,
|
|
1089
|
+
scene: resolvedScene,
|
|
1090
|
+
showBackground: resolvedShowBackground,
|
|
1091
|
+
winterMode: resolvedWinterMode,
|
|
1092
|
+
springMode: resolvedSpringMode,
|
|
1093
|
+
cameraOffsetX: resolvedCameraOffsetX,
|
|
1094
|
+
cameraZoom: resolvedCameraZoom,
|
|
1095
|
+
gridSettings: resolvedGridSettings,
|
|
1096
|
+
frameQuality: resolvedFrameQuality,
|
|
990
1097
|
};
|
|
991
1098
|
}
|
|
992
1099
|
return {
|
|
993
1100
|
type: 'parts',
|
|
994
1101
|
parts: parts,
|
|
995
|
-
showGrid,
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1102
|
+
showGrid: resolvedShowGrid,
|
|
1103
|
+
scene: resolvedScene,
|
|
1104
|
+
showBackground: resolvedShowBackground,
|
|
1105
|
+
winterMode: resolvedWinterMode,
|
|
1106
|
+
springMode: resolvedSpringMode,
|
|
1107
|
+
cameraOffsetX: resolvedCameraOffsetX,
|
|
1108
|
+
cameraZoom: resolvedCameraZoom,
|
|
1109
|
+
gridSettings: resolvedGridSettings,
|
|
1110
|
+
frameQuality: resolvedFrameQuality,
|
|
1000
1111
|
};
|
|
1001
|
-
}, [shareCode, parts, showGrid, cameraOffsetX, cameraZoom, gridSettings, frameQuality]);
|
|
1112
|
+
}, [shareCode, parts, showGrid, scene, showBackground, winterMode, springMode, cameraOffsetX, cameraZoom, gridSettings, frameQuality]);
|
|
1002
1113
|
// Use custom hook for sprite rendering
|
|
1003
1114
|
const { spriteSrc, isRenderingSprite, renderError, spriteMetadata } = useSpriteRender(renderInput, apiConfig, undefined, useSpriteRenderOptions);
|
|
1004
1115
|
const total = spriteMetadata ? spriteMetadata.totalFrames : 72;
|