@ngenux/ngage-whiteboarding 1.0.7 → 1.0.8
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 +344 -344
- package/dist/index.esm.js +73 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +73 -12
- package/dist/index.js.map +1 -1
- package/dist/src/components/Whiteboard/Board.d.ts.map +1 -1
- package/dist/src/components/Whiteboard/index.d.ts.map +1 -1
- package/dist/src/context/WhiteboardContext.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/src/utils/video-coordinates.d.ts +0 -36
- package/dist/src/utils/video-coordinates.d.ts.map +0 -1
package/dist/index.esm.js
CHANGED
|
@@ -34757,12 +34757,15 @@ const whiteboardReducer = (state, action) => {
|
|
|
34757
34757
|
// Prevent duplicate shapes during simultaneous drawing
|
|
34758
34758
|
const newShape = action.payload;
|
|
34759
34759
|
const existingShapeIndex = state.shapes.findIndex(shape => shape.id === newShape.id);
|
|
34760
|
+
// Ensure shape has timestamp for proper ordering and stale action filtering
|
|
34761
|
+
const timestamp = newShape.timestamp || Date.now();
|
|
34760
34762
|
// If shape already exists, update it instead of adding duplicate
|
|
34761
34763
|
if (existingShapeIndex >= 0) {
|
|
34762
34764
|
const updatedShapes = [...state.shapes];
|
|
34763
34765
|
updatedShapes[existingShapeIndex] = {
|
|
34764
34766
|
...newShape,
|
|
34765
|
-
|
|
34767
|
+
timestamp,
|
|
34768
|
+
drawingSessionId: newShape.drawingSessionId || `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`
|
|
34766
34769
|
};
|
|
34767
34770
|
return {
|
|
34768
34771
|
...state,
|
|
@@ -34770,10 +34773,11 @@ const whiteboardReducer = (state, action) => {
|
|
|
34770
34773
|
currentDrawingShape: undefined,
|
|
34771
34774
|
};
|
|
34772
34775
|
}
|
|
34773
|
-
// Add new shape with session tracking
|
|
34776
|
+
// Add new shape with session tracking and timestamp
|
|
34774
34777
|
const shapeWithSession = {
|
|
34775
34778
|
...newShape,
|
|
34776
|
-
|
|
34779
|
+
timestamp,
|
|
34780
|
+
drawingSessionId: newShape.drawingSessionId || `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`
|
|
34777
34781
|
};
|
|
34778
34782
|
const newShapes = [...state.shapes, shapeWithSession];
|
|
34779
34783
|
const newHistory = state.history.slice(0, state.historyIndex + 1);
|
|
@@ -34967,11 +34971,13 @@ const whiteboardReducer = (state, action) => {
|
|
|
34967
34971
|
const userShapes = state.shapes
|
|
34968
34972
|
.filter(shape => shape.userId === userId)
|
|
34969
34973
|
.sort((a, b) => {
|
|
34970
|
-
// Sort by
|
|
34971
|
-
|
|
34974
|
+
// Sort by timestamp to ensure LIFO (Last In First Out) order
|
|
34975
|
+
const timestampA = a.timestamp || 0;
|
|
34976
|
+
const timestampB = b.timestamp || 0;
|
|
34977
|
+
return timestampA - timestampB;
|
|
34972
34978
|
});
|
|
34973
34979
|
if (userShapes.length > 0) {
|
|
34974
|
-
// Get the most recent shape
|
|
34980
|
+
// Get the most recent shape (last in sorted array)
|
|
34975
34981
|
const lastUserShape = userShapes[userShapes.length - 1];
|
|
34976
34982
|
// Check if there are multiple shapes that were part of the same drawing session
|
|
34977
34983
|
// Use drawingSessionId if available, otherwise fall back to time-based detection
|
|
@@ -35265,9 +35271,16 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35265
35271
|
// Drawing actions should be filtered if they're older than the last clear
|
|
35266
35272
|
if (action.type === 'start_draw' || action.type === 'continue_draw' || action.type === 'end_draw' || action.type === 'add') {
|
|
35267
35273
|
const actionTimestamp = action.timestamp || 0;
|
|
35268
|
-
if (actionTimestamp <= state.lastClearTimestamp) {
|
|
35274
|
+
if (actionTimestamp > 0 && actionTimestamp <= state.lastClearTimestamp) {
|
|
35269
35275
|
return true;
|
|
35270
35276
|
}
|
|
35277
|
+
// Also check timestamp in the payload if it exists
|
|
35278
|
+
if (typeof action.payload === 'object' && action.payload !== null && !Array.isArray(action.payload)) {
|
|
35279
|
+
const payloadTimestamp = action.payload.timestamp || 0;
|
|
35280
|
+
if (payloadTimestamp > 0 && payloadTimestamp <= state.lastClearTimestamp) {
|
|
35281
|
+
return true;
|
|
35282
|
+
}
|
|
35283
|
+
}
|
|
35271
35284
|
}
|
|
35272
35285
|
return false;
|
|
35273
35286
|
};
|
|
@@ -35297,10 +35310,15 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35297
35310
|
if (syncState.shapes.length === 0) {
|
|
35298
35311
|
return; // Don't apply empty state or keep requesting
|
|
35299
35312
|
}
|
|
35300
|
-
//
|
|
35301
|
-
|
|
35313
|
+
// Filter out shapes that are older than our last clear
|
|
35314
|
+
const validShapes = syncState.shapes.filter(shape => {
|
|
35315
|
+
const shapeTimestamp = shape.timestamp || 0;
|
|
35316
|
+
return shapeTimestamp === 0 || shapeTimestamp > state.lastClearTimestamp;
|
|
35317
|
+
});
|
|
35318
|
+
// Only apply if the received state has valid shapes
|
|
35319
|
+
if (validShapes.length > 0 && (state.shapes.length === 0 || validShapes.length > state.shapes.length)) {
|
|
35302
35320
|
// All shapes from sync_state should have normalized coordinates, denormalize them
|
|
35303
|
-
const denormalizedShapes =
|
|
35321
|
+
const denormalizedShapes = validShapes.map((shape, index) => {
|
|
35304
35322
|
return denormalizeShape(shape);
|
|
35305
35323
|
});
|
|
35306
35324
|
// Apply the synchronized state
|
|
@@ -35318,7 +35336,8 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35318
35336
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35319
35337
|
// Additional check to prevent adding shapes from before the last clear
|
|
35320
35338
|
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35321
|
-
if (shapeTimestamp <= state.lastClearTimestamp) {
|
|
35339
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35340
|
+
console.warn(`[APPLY_ACTION] Skipping stale shape from before clear - shape timestamp: ${shapeTimestamp}, clear timestamp: ${state.lastClearTimestamp}`);
|
|
35322
35341
|
break;
|
|
35323
35342
|
}
|
|
35324
35343
|
dispatch({ type: 'ADD_SHAPE', payload: denormalizedShape });
|
|
@@ -35372,6 +35391,11 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35372
35391
|
case 'start_draw':
|
|
35373
35392
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35374
35393
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35394
|
+
// Check if this shape is from before the last clear
|
|
35395
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35396
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35397
|
+
break; // Skip stale shapes
|
|
35398
|
+
}
|
|
35375
35399
|
// Only apply collaborative start_draw if it's from another user
|
|
35376
35400
|
if (denormalizedShape.userId !== state.userId) {
|
|
35377
35401
|
// Add to active drawings for real-time collaborative visibility
|
|
@@ -35385,6 +35409,11 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35385
35409
|
case 'continue_draw':
|
|
35386
35410
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35387
35411
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35412
|
+
// Check if this shape is from before the last clear
|
|
35413
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35414
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35415
|
+
break; // Skip stale shapes
|
|
35416
|
+
}
|
|
35388
35417
|
// Only apply collaborative drawing updates if it's not from the current user
|
|
35389
35418
|
// to avoid interfering with local real-time drawing
|
|
35390
35419
|
if (denormalizedShape.userId !== state.userId) {
|
|
@@ -35399,6 +35428,12 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35399
35428
|
case 'end_draw':
|
|
35400
35429
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35401
35430
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35431
|
+
// Check if this shape is from before the last clear
|
|
35432
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35433
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35434
|
+
console.warn(`[APPLY_ACTION] Skipping stale end_draw from before clear - shape timestamp: ${shapeTimestamp}, clear timestamp: ${state.lastClearTimestamp}`);
|
|
35435
|
+
break; // Skip stale shapes
|
|
35436
|
+
}
|
|
35402
35437
|
// Only apply collaborative end_draw if it's from another user
|
|
35403
35438
|
// Local user's end_draw is handled directly in Board component via ADD_SHAPE
|
|
35404
35439
|
if (denormalizedShape.userId !== state.userId) {
|
|
@@ -36426,7 +36461,8 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36426
36461
|
}
|
|
36427
36462
|
// Create new shape ID for regular drawing tools
|
|
36428
36463
|
const newShapeId = v4();
|
|
36429
|
-
const
|
|
36464
|
+
const timestamp = Date.now();
|
|
36465
|
+
const newDrawingSessionId = `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`;
|
|
36430
36466
|
setCurrentShapeId(newShapeId);
|
|
36431
36467
|
setCurrentDrawingSessionId(newDrawingSessionId);
|
|
36432
36468
|
// Start drawing
|
|
@@ -36443,6 +36479,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36443
36479
|
strokeStyle: state.strokeStyle,
|
|
36444
36480
|
opacity: state.opacity,
|
|
36445
36481
|
drawingSessionId: newDrawingSessionId,
|
|
36482
|
+
timestamp,
|
|
36446
36483
|
// Initialize transformation properties
|
|
36447
36484
|
x: 0,
|
|
36448
36485
|
y: 0,
|
|
@@ -36555,6 +36592,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36555
36592
|
strokeStyle: state.strokeStyle,
|
|
36556
36593
|
opacity: state.opacity,
|
|
36557
36594
|
drawingSessionId: currentDrawingSessionId,
|
|
36595
|
+
timestamp: Date.now(),
|
|
36558
36596
|
// Initialize transformation properties
|
|
36559
36597
|
x: 0,
|
|
36560
36598
|
y: 0,
|
|
@@ -36634,6 +36672,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36634
36672
|
strokeStyle: state.strokeStyle,
|
|
36635
36673
|
opacity: state.opacity,
|
|
36636
36674
|
drawingSessionId: currentDrawingSessionId,
|
|
36675
|
+
timestamp: Date.now(),
|
|
36637
36676
|
// Initialize transformation properties
|
|
36638
36677
|
x: 0,
|
|
36639
36678
|
y: 0,
|
|
@@ -42735,6 +42774,28 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transp
|
|
|
42735
42774
|
leaveRoom(roomId);
|
|
42736
42775
|
};
|
|
42737
42776
|
}, [roomId]);
|
|
42777
|
+
// Clear all canvases on component unmount if admin leaves
|
|
42778
|
+
useEffect(() => {
|
|
42779
|
+
return () => {
|
|
42780
|
+
// If admin leaves, clear all users' canvases
|
|
42781
|
+
if (isAdmin && queueAction) {
|
|
42782
|
+
const clearTimestamp = Date.now();
|
|
42783
|
+
// Clear local state immediately
|
|
42784
|
+
dispatch({ type: 'CLEAR_CANVAS' });
|
|
42785
|
+
dispatch({ type: 'CLEAR_ACTIVE_DRAWINGS' });
|
|
42786
|
+
// Send clear action to all users
|
|
42787
|
+
queueAction({
|
|
42788
|
+
type: 'clear',
|
|
42789
|
+
payload: {
|
|
42790
|
+
timestamp: clearTimestamp,
|
|
42791
|
+
adminId: userId,
|
|
42792
|
+
},
|
|
42793
|
+
userId: userId,
|
|
42794
|
+
timestamp: clearTimestamp,
|
|
42795
|
+
});
|
|
42796
|
+
}
|
|
42797
|
+
};
|
|
42798
|
+
}, [isAdmin, queueAction, userId, dispatch]);
|
|
42738
42799
|
// Global cleanup on app unmount
|
|
42739
42800
|
useEffect(() => {
|
|
42740
42801
|
const handleBeforeUnload = () => {
|