@snowcone-app/ui 0.9.2 → 0.9.3
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/CHANGELOG.md +8 -0
- package/dist/index.cjs +142 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +142 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/composed/HeroProductImage.tsx +59 -0
- package/src/patterns/MockupPriorityProvider.tsx +49 -7
- package/src/patterns/RealtimeProvider.tsx +123 -5
- package/src/patterns/__tests__/MockupPriorityProvider.dragStop.test.tsx +112 -0
- package/src/patterns/__tests__/RealtimeProvider.cropDelivery.test.tsx +103 -3
package/dist/index.d.cts
CHANGED
|
@@ -2928,6 +2928,8 @@ declare function useShopOptional(): ShopContextValue | undefined;
|
|
|
2928
2928
|
interface MockupResult {
|
|
2929
2929
|
mockupId: string;
|
|
2930
2930
|
imageUrl: string;
|
|
2931
|
+
/** The canvas version this frame was rendered from (see getCanvasVersion). */
|
|
2932
|
+
requestVersion?: number;
|
|
2931
2933
|
}
|
|
2932
2934
|
type RealtimeState = RealtimeContextValue;
|
|
2933
2935
|
interface RealtimeContextValue {
|
|
@@ -3043,6 +3045,13 @@ interface RealtimeContextValue {
|
|
|
3043
3045
|
* subsequent blobs to the same placement.
|
|
3044
3046
|
*/
|
|
3045
3047
|
subscribeBlobSent: (callback: (placement: string) => void) => () => void;
|
|
3048
|
+
/**
|
|
3049
|
+
* The canvas version the session last sent (0 before the first edit
|
|
3050
|
+
* reaches the server). Every rendered result carries the `requestVersion`
|
|
3051
|
+
* it was rendered from: a picture whose version is below this one shows an
|
|
3052
|
+
* older design. Read it after a `subscribeBlobSent` notification.
|
|
3053
|
+
*/
|
|
3054
|
+
getCanvasVersion: () => number;
|
|
3046
3055
|
flushPendingBlobs: () => void;
|
|
3047
3056
|
hasPendingBlobs: () => boolean;
|
|
3048
3057
|
subscribePipelineSettled: (callback: () => void) => () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -2928,6 +2928,8 @@ declare function useShopOptional(): ShopContextValue | undefined;
|
|
|
2928
2928
|
interface MockupResult {
|
|
2929
2929
|
mockupId: string;
|
|
2930
2930
|
imageUrl: string;
|
|
2931
|
+
/** The canvas version this frame was rendered from (see getCanvasVersion). */
|
|
2932
|
+
requestVersion?: number;
|
|
2931
2933
|
}
|
|
2932
2934
|
type RealtimeState = RealtimeContextValue;
|
|
2933
2935
|
interface RealtimeContextValue {
|
|
@@ -3043,6 +3045,13 @@ interface RealtimeContextValue {
|
|
|
3043
3045
|
* subsequent blobs to the same placement.
|
|
3044
3046
|
*/
|
|
3045
3047
|
subscribeBlobSent: (callback: (placement: string) => void) => () => void;
|
|
3048
|
+
/**
|
|
3049
|
+
* The canvas version the session last sent (0 before the first edit
|
|
3050
|
+
* reaches the server). Every rendered result carries the `requestVersion`
|
|
3051
|
+
* it was rendered from: a picture whose version is below this one shows an
|
|
3052
|
+
* older design. Read it after a `subscribeBlobSent` notification.
|
|
3053
|
+
*/
|
|
3054
|
+
getCanvasVersion: () => number;
|
|
3046
3055
|
flushPendingBlobs: () => void;
|
|
3047
3056
|
hasPendingBlobs: () => boolean;
|
|
3048
3057
|
subscribePipelineSettled: (callback: () => void) => () => void;
|
package/dist/index.js
CHANGED
|
@@ -5703,6 +5703,11 @@ async function compositeCroppedFrame(args) {
|
|
|
5703
5703
|
import { jsx as jsx52 } from "react/jsx-runtime";
|
|
5704
5704
|
var DEFAULT_ARTBOARD_SIZE = 2e3;
|
|
5705
5705
|
var MAX_CONCURRENT_BLOBS = 5;
|
|
5706
|
+
function mergeMockupIds(current, extra) {
|
|
5707
|
+
const out = [...current];
|
|
5708
|
+
for (const id of extra) if (!out.includes(id)) out.push(id);
|
|
5709
|
+
return out;
|
|
5710
|
+
}
|
|
5706
5711
|
var RealtimeContext = createContext8(
|
|
5707
5712
|
void 0
|
|
5708
5713
|
);
|
|
@@ -5843,6 +5848,15 @@ function RealtimeProvider({
|
|
|
5843
5848
|
const compositeQueueRef = useRef12(/* @__PURE__ */ new Map());
|
|
5844
5849
|
const compositeBusyRef = useRef12(/* @__PURE__ */ new Set());
|
|
5845
5850
|
const compositeCanvasRef = useRef12(null);
|
|
5851
|
+
const requestedMockupIdsRef = useRef12([]);
|
|
5852
|
+
const fullFrameRerenderIdsRef = useRef12(/* @__PURE__ */ new Set());
|
|
5853
|
+
const fullFrameRerenderTimerRef = useRef12(null);
|
|
5854
|
+
useEffect17(
|
|
5855
|
+
() => () => {
|
|
5856
|
+
if (fullFrameRerenderTimerRef.current) clearTimeout(fullFrameRerenderTimerRef.current);
|
|
5857
|
+
},
|
|
5858
|
+
[]
|
|
5859
|
+
);
|
|
5846
5860
|
const registerBlobUrl = useCallback10((blobOrUrl, placementName) => {
|
|
5847
5861
|
let url;
|
|
5848
5862
|
if (blobOrUrl instanceof Blob) {
|
|
@@ -5874,6 +5888,7 @@ function RealtimeProvider({
|
|
|
5874
5888
|
const base = lastConfigBaseRef.current;
|
|
5875
5889
|
if (!base) return;
|
|
5876
5890
|
croppedActiveRef.current = withCrop;
|
|
5891
|
+
requestedMockupIdsRef.current = base.mockupIds ?? [];
|
|
5877
5892
|
sendConfigRaw({
|
|
5878
5893
|
...base,
|
|
5879
5894
|
placementSettings: { ...placementSettingsOverridesRef.current },
|
|
@@ -5886,6 +5901,44 @@ function RealtimeProvider({
|
|
|
5886
5901
|
if (!croppedActiveRef.current) return;
|
|
5887
5902
|
sendCropConfig(false);
|
|
5888
5903
|
}, [sendCropConfig]);
|
|
5904
|
+
const rerenderFullFrame = useCallback10(
|
|
5905
|
+
(mockupId) => {
|
|
5906
|
+
fullFrameRerenderIdsRef.current.add(mockupId);
|
|
5907
|
+
if (fullFrameRerenderTimerRef.current) return;
|
|
5908
|
+
fullFrameRerenderTimerRef.current = setTimeout(() => {
|
|
5909
|
+
fullFrameRerenderTimerRef.current = null;
|
|
5910
|
+
const failed = [...fullFrameRerenderIdsRef.current];
|
|
5911
|
+
fullFrameRerenderIdsRef.current.clear();
|
|
5912
|
+
const base = lastConfigBaseRef.current;
|
|
5913
|
+
if (!base || failed.length === 0) return;
|
|
5914
|
+
const previous = requestedMockupIdsRef.current;
|
|
5915
|
+
const ids = mergeMockupIds(previous, failed);
|
|
5916
|
+
croppedActiveRef.current = false;
|
|
5917
|
+
requestedMockupIdsRef.current = ids;
|
|
5918
|
+
const send = (mockupIds) => sendConfigRaw({
|
|
5919
|
+
...base,
|
|
5920
|
+
mockupIds,
|
|
5921
|
+
placementSettings: { ...placementSettingsOverridesRef.current }
|
|
5922
|
+
});
|
|
5923
|
+
const fingerprint = (list) => [...list].sort().join(",");
|
|
5924
|
+
if (fingerprint(ids) !== fingerprint(previous)) {
|
|
5925
|
+
send(ids);
|
|
5926
|
+
return;
|
|
5927
|
+
}
|
|
5928
|
+
const step = fingerprint(failed) !== fingerprint(previous) ? failed : ids.slice(0, -1);
|
|
5929
|
+
if (step.length === 0) {
|
|
5930
|
+
console.error(
|
|
5931
|
+
`[RealtimeProvider] E_OVERLAY_RERENDER_UNAVAILABLE: cannot force a full-frame re-render of ${failed.join(", ")} (single-mockup session); it updates on the next edit.`
|
|
5932
|
+
);
|
|
5933
|
+
send(ids);
|
|
5934
|
+
return;
|
|
5935
|
+
}
|
|
5936
|
+
send(step);
|
|
5937
|
+
send(ids);
|
|
5938
|
+
}, 0);
|
|
5939
|
+
},
|
|
5940
|
+
[sendConfigRaw]
|
|
5941
|
+
);
|
|
5889
5942
|
const maybeFlipCrop = useCallback10(() => {
|
|
5890
5943
|
if (!croppedDeliveryEnabled || !cropCapableRef.current) return;
|
|
5891
5944
|
if (croppedActiveRef.current) return;
|
|
@@ -5985,8 +6038,7 @@ function RealtimeProvider({
|
|
|
5985
6038
|
console.error(
|
|
5986
6039
|
`[RealtimeProvider] E_OVERLAY_NO_BACKGROUND: cropped frame for ${result.mockupId} has no frozen background \u2014 falling back to full-frame and re-rendering it.`
|
|
5987
6040
|
);
|
|
5988
|
-
|
|
5989
|
-
updateMockupIdsRaw([result.mockupId]);
|
|
6041
|
+
rerenderFullFrame(result.mockupId);
|
|
5990
6042
|
return;
|
|
5991
6043
|
}
|
|
5992
6044
|
try {
|
|
@@ -6004,16 +6056,18 @@ function RealtimeProvider({
|
|
|
6004
6056
|
if (gen !== sceneGenRef.current) return;
|
|
6005
6057
|
deliverResults([{ ...result, imageUrl: registerBlobUrl(blob) }]);
|
|
6006
6058
|
} catch (err) {
|
|
6059
|
+
if (gen !== sceneGenRef.current || compositeQueueRef.current.has(result.mockupId) || processedFrameRef.current.get(result.mockupId) !== result.imageUrl) {
|
|
6060
|
+
return;
|
|
6061
|
+
}
|
|
6007
6062
|
console.error(
|
|
6008
6063
|
"[RealtimeProvider] E_OVERLAY_COMPOSITE_FAILED: cropped-frame composite failed \u2014 full-frame delivery for the rest of this session.",
|
|
6009
6064
|
err
|
|
6010
6065
|
);
|
|
6011
6066
|
cropCapableRef.current = false;
|
|
6012
|
-
|
|
6013
|
-
updateMockupIdsRaw([result.mockupId]);
|
|
6067
|
+
rerenderFullFrame(result.mockupId);
|
|
6014
6068
|
}
|
|
6015
6069
|
},
|
|
6016
|
-
[deliverResults, registerBlobUrl,
|
|
6070
|
+
[deliverResults, registerBlobUrl, rerenderFullFrame]
|
|
6017
6071
|
);
|
|
6018
6072
|
const pumpCompositeQueue = useCallback10(
|
|
6019
6073
|
(mockupId) => {
|
|
@@ -6201,6 +6255,7 @@ function RealtimeProvider({
|
|
|
6201
6255
|
});
|
|
6202
6256
|
}
|
|
6203
6257
|
lastConfigBaseRef.current = configPayload;
|
|
6258
|
+
requestedMockupIdsRef.current = mockupIds;
|
|
6204
6259
|
sendConfigRaw(
|
|
6205
6260
|
croppedActiveRef.current ? { ...configPayload, overlayCrop: "auto" } : configPayload
|
|
6206
6261
|
);
|
|
@@ -6260,6 +6315,7 @@ function RealtimeProvider({
|
|
|
6260
6315
|
if (croppedActiveRef.current && mockupIds.some((id) => !backgroundsRef.current.has(id))) {
|
|
6261
6316
|
unflipCrop();
|
|
6262
6317
|
}
|
|
6318
|
+
requestedMockupIdsRef.current = [...mockupIds];
|
|
6263
6319
|
return updateMockupIdsRaw(mockupIds);
|
|
6264
6320
|
},
|
|
6265
6321
|
[updateMockupIdsRaw, unflipCrop]
|
|
@@ -6516,6 +6572,9 @@ function RealtimeProvider({
|
|
|
6516
6572
|
},
|
|
6517
6573
|
[]
|
|
6518
6574
|
);
|
|
6575
|
+
const getLastSentVersionRef = useRef12(getLastSentVersion);
|
|
6576
|
+
getLastSentVersionRef.current = getLastSentVersion;
|
|
6577
|
+
const getCanvasVersion = useCallback10(() => getLastSentVersionRef.current?.() ?? 0, []);
|
|
6519
6578
|
const stableSubscribePipelineSettled = useCallback10(
|
|
6520
6579
|
(callback) => {
|
|
6521
6580
|
return subscribePipelineSettledRef.current(callback);
|
|
@@ -6564,6 +6623,7 @@ function RealtimeProvider({
|
|
|
6564
6623
|
// Centralized URL management
|
|
6565
6624
|
getMockupUrl: getMockupUrl3,
|
|
6566
6625
|
getMockupUrls,
|
|
6626
|
+
getCanvasVersion,
|
|
6567
6627
|
isMockupLoading,
|
|
6568
6628
|
registerBlobUrl,
|
|
6569
6629
|
// WebRTC streaming
|
|
@@ -6595,6 +6655,7 @@ function RealtimeProvider({
|
|
|
6595
6655
|
hasPendingBlobs,
|
|
6596
6656
|
getMockupUrl3,
|
|
6597
6657
|
getMockupUrls,
|
|
6658
|
+
getCanvasVersion,
|
|
6598
6659
|
isMockupLoading,
|
|
6599
6660
|
registerBlobUrl
|
|
6600
6661
|
]
|
|
@@ -7471,17 +7532,33 @@ function MockupPriorityProvider({ children }) {
|
|
|
7471
7532
|
if (!realtimeContext?.updateMockupIds) {
|
|
7472
7533
|
return;
|
|
7473
7534
|
}
|
|
7535
|
+
const mobile = mobileCarouselRef.current;
|
|
7536
|
+
const universe = mobile && mobile.mockupIds.length > 0 ? mobile.mockupIds : registrationOrderRef.current;
|
|
7537
|
+
const inView = [];
|
|
7538
|
+
if (level1MockupRef.current) {
|
|
7539
|
+
inView.push(level1MockupRef.current);
|
|
7540
|
+
if (!mobile || mobile.mockupIds.length === 0) {
|
|
7541
|
+
visibilityMapRef.current.forEach((info, id) => {
|
|
7542
|
+
if (info.visibleArea > 0 && !inView.includes(id)) inView.push(id);
|
|
7543
|
+
});
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7474
7546
|
let mockupIds;
|
|
7475
7547
|
switch (level) {
|
|
7476
7548
|
case 1:
|
|
7477
|
-
mockupIds =
|
|
7549
|
+
mockupIds = inView;
|
|
7478
7550
|
break;
|
|
7479
7551
|
case 2:
|
|
7480
|
-
mockupIds =
|
|
7552
|
+
mockupIds = level2MockupsRef.current.filter((id) => !inView.includes(id));
|
|
7481
7553
|
break;
|
|
7482
|
-
case 3:
|
|
7483
|
-
|
|
7554
|
+
case 3: {
|
|
7555
|
+
const earlier = /* @__PURE__ */ new Set([...inView, ...level2MockupsRef.current]);
|
|
7556
|
+
mockupIds = [
|
|
7557
|
+
...level3QueueRef.current.filter((id) => !earlier.has(id)),
|
|
7558
|
+
...universe.filter((id) => !earlier.has(id) && !level3QueueRef.current.includes(id))
|
|
7559
|
+
];
|
|
7484
7560
|
break;
|
|
7561
|
+
}
|
|
7485
7562
|
default:
|
|
7486
7563
|
mockupIds = [];
|
|
7487
7564
|
}
|
|
@@ -7506,6 +7583,16 @@ function MockupPriorityProvider({ children }) {
|
|
|
7506
7583
|
const isStillDragging = timeSinceLastPendingBlob < DRAG_STOP_DEBOUNCE_MS;
|
|
7507
7584
|
if (currentLevel === 1 && isStillDragging) {
|
|
7508
7585
|
currentRenderingLevelRef.current = null;
|
|
7586
|
+
if (levelAdvanceTimeoutRef.current) {
|
|
7587
|
+
clearTimeout(levelAdvanceTimeoutRef.current);
|
|
7588
|
+
}
|
|
7589
|
+
levelAdvanceTimeoutRef.current = setTimeout(() => {
|
|
7590
|
+
levelAdvanceTimeoutRef.current = null;
|
|
7591
|
+
const quiet = Date.now() - lastPendingBlobTimeRef.current >= DRAG_STOP_DEBOUNCE_MS;
|
|
7592
|
+
if (quiet && currentRenderingLevelRef.current === null && pendingMockupsRef.current.size === 0) {
|
|
7593
|
+
requestMockupsForLevel(2);
|
|
7594
|
+
}
|
|
7595
|
+
}, DRAG_STOP_DEBOUNCE_MS - timeSinceLastPendingBlob + 10);
|
|
7509
7596
|
return;
|
|
7510
7597
|
}
|
|
7511
7598
|
if (isStillDragging) {
|
|
@@ -10363,6 +10450,52 @@ var HeroProductImage = memo3(function HeroProductImage2({
|
|
|
10363
10450
|
unsubscribe?.();
|
|
10364
10451
|
};
|
|
10365
10452
|
}, [mode, realtimeContext?.subscribePendingBlob, placement]);
|
|
10453
|
+
const renderVersionRef = useRef23(0);
|
|
10454
|
+
const refusalCodeRef = useRef23(null);
|
|
10455
|
+
refusalCodeRef.current = unavailable?.code ?? null;
|
|
10456
|
+
const sessionErrorCodeRef = useRef23(null);
|
|
10457
|
+
sessionErrorCodeRef.current = realtimeContext?.renderError?.code ?? null;
|
|
10458
|
+
const getCanvasVersion = realtimeContext?.getCanvasVersion;
|
|
10459
|
+
const applyRenderStamp = useCallback18(() => {
|
|
10460
|
+
const node = containerRef.current;
|
|
10461
|
+
if (!node) return;
|
|
10462
|
+
const canvas = getCanvasVersion?.() ?? 0;
|
|
10463
|
+
if (canvas <= 0) {
|
|
10464
|
+
delete node.dataset.canvasVersion;
|
|
10465
|
+
delete node.dataset.renderVersion;
|
|
10466
|
+
delete node.dataset.renderState;
|
|
10467
|
+
return;
|
|
10468
|
+
}
|
|
10469
|
+
const rendered = renderVersionRef.current;
|
|
10470
|
+
const behind = rendered < canvas;
|
|
10471
|
+
node.dataset.canvasVersion = String(canvas);
|
|
10472
|
+
node.dataset.renderVersion = String(rendered);
|
|
10473
|
+
node.dataset.renderState = refusalCodeRef.current || behind && sessionErrorCodeRef.current ? "refused" : behind ? "stale" : "current";
|
|
10474
|
+
}, [getCanvasVersion]);
|
|
10475
|
+
useEffect32(() => {
|
|
10476
|
+
applyRenderStamp();
|
|
10477
|
+
});
|
|
10478
|
+
useEffect32(() => {
|
|
10479
|
+
const subscribeSent = realtimeContext?.subscribeBlobSent;
|
|
10480
|
+
const subscribeResult = realtimeContext?.subscribeMockupResultById;
|
|
10481
|
+
if (!actualMockupId || !subscribeSent || !subscribeResult) return;
|
|
10482
|
+
const offSent = subscribeSent(() => applyRenderStamp());
|
|
10483
|
+
const offResult = subscribeResult(actualMockupId, (result) => {
|
|
10484
|
+
if (typeof result?.requestVersion === "number" && result.requestVersion > renderVersionRef.current) {
|
|
10485
|
+
renderVersionRef.current = result.requestVersion;
|
|
10486
|
+
}
|
|
10487
|
+
applyRenderStamp();
|
|
10488
|
+
});
|
|
10489
|
+
return () => {
|
|
10490
|
+
offSent?.();
|
|
10491
|
+
offResult?.();
|
|
10492
|
+
};
|
|
10493
|
+
}, [
|
|
10494
|
+
actualMockupId,
|
|
10495
|
+
realtimeContext?.subscribeBlobSent,
|
|
10496
|
+
realtimeContext?.subscribeMockupResultById,
|
|
10497
|
+
applyRenderStamp
|
|
10498
|
+
]);
|
|
10366
10499
|
useEffect32(() => {
|
|
10367
10500
|
if (!priorityContext || !actualMockupId || !placement) return;
|
|
10368
10501
|
priorityContext.registerMockup(actualMockupId, placement, initialPriority);
|