@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.js
CHANGED
|
@@ -34777,12 +34777,15 @@ const whiteboardReducer = (state, action) => {
|
|
|
34777
34777
|
// Prevent duplicate shapes during simultaneous drawing
|
|
34778
34778
|
const newShape = action.payload;
|
|
34779
34779
|
const existingShapeIndex = state.shapes.findIndex(shape => shape.id === newShape.id);
|
|
34780
|
+
// Ensure shape has timestamp for proper ordering and stale action filtering
|
|
34781
|
+
const timestamp = newShape.timestamp || Date.now();
|
|
34780
34782
|
// If shape already exists, update it instead of adding duplicate
|
|
34781
34783
|
if (existingShapeIndex >= 0) {
|
|
34782
34784
|
const updatedShapes = [...state.shapes];
|
|
34783
34785
|
updatedShapes[existingShapeIndex] = {
|
|
34784
34786
|
...newShape,
|
|
34785
|
-
|
|
34787
|
+
timestamp,
|
|
34788
|
+
drawingSessionId: newShape.drawingSessionId || `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`
|
|
34786
34789
|
};
|
|
34787
34790
|
return {
|
|
34788
34791
|
...state,
|
|
@@ -34790,10 +34793,11 @@ const whiteboardReducer = (state, action) => {
|
|
|
34790
34793
|
currentDrawingShape: undefined,
|
|
34791
34794
|
};
|
|
34792
34795
|
}
|
|
34793
|
-
// Add new shape with session tracking
|
|
34796
|
+
// Add new shape with session tracking and timestamp
|
|
34794
34797
|
const shapeWithSession = {
|
|
34795
34798
|
...newShape,
|
|
34796
|
-
|
|
34799
|
+
timestamp,
|
|
34800
|
+
drawingSessionId: newShape.drawingSessionId || `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`
|
|
34797
34801
|
};
|
|
34798
34802
|
const newShapes = [...state.shapes, shapeWithSession];
|
|
34799
34803
|
const newHistory = state.history.slice(0, state.historyIndex + 1);
|
|
@@ -34987,11 +34991,13 @@ const whiteboardReducer = (state, action) => {
|
|
|
34987
34991
|
const userShapes = state.shapes
|
|
34988
34992
|
.filter(shape => shape.userId === userId)
|
|
34989
34993
|
.sort((a, b) => {
|
|
34990
|
-
// Sort by
|
|
34991
|
-
|
|
34994
|
+
// Sort by timestamp to ensure LIFO (Last In First Out) order
|
|
34995
|
+
const timestampA = a.timestamp || 0;
|
|
34996
|
+
const timestampB = b.timestamp || 0;
|
|
34997
|
+
return timestampA - timestampB;
|
|
34992
34998
|
});
|
|
34993
34999
|
if (userShapes.length > 0) {
|
|
34994
|
-
// Get the most recent shape
|
|
35000
|
+
// Get the most recent shape (last in sorted array)
|
|
34995
35001
|
const lastUserShape = userShapes[userShapes.length - 1];
|
|
34996
35002
|
// Check if there are multiple shapes that were part of the same drawing session
|
|
34997
35003
|
// Use drawingSessionId if available, otherwise fall back to time-based detection
|
|
@@ -35285,9 +35291,16 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35285
35291
|
// Drawing actions should be filtered if they're older than the last clear
|
|
35286
35292
|
if (action.type === 'start_draw' || action.type === 'continue_draw' || action.type === 'end_draw' || action.type === 'add') {
|
|
35287
35293
|
const actionTimestamp = action.timestamp || 0;
|
|
35288
|
-
if (actionTimestamp <= state.lastClearTimestamp) {
|
|
35294
|
+
if (actionTimestamp > 0 && actionTimestamp <= state.lastClearTimestamp) {
|
|
35289
35295
|
return true;
|
|
35290
35296
|
}
|
|
35297
|
+
// Also check timestamp in the payload if it exists
|
|
35298
|
+
if (typeof action.payload === 'object' && action.payload !== null && !Array.isArray(action.payload)) {
|
|
35299
|
+
const payloadTimestamp = action.payload.timestamp || 0;
|
|
35300
|
+
if (payloadTimestamp > 0 && payloadTimestamp <= state.lastClearTimestamp) {
|
|
35301
|
+
return true;
|
|
35302
|
+
}
|
|
35303
|
+
}
|
|
35291
35304
|
}
|
|
35292
35305
|
return false;
|
|
35293
35306
|
};
|
|
@@ -35317,10 +35330,15 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35317
35330
|
if (syncState.shapes.length === 0) {
|
|
35318
35331
|
return; // Don't apply empty state or keep requesting
|
|
35319
35332
|
}
|
|
35320
|
-
//
|
|
35321
|
-
|
|
35333
|
+
// Filter out shapes that are older than our last clear
|
|
35334
|
+
const validShapes = syncState.shapes.filter(shape => {
|
|
35335
|
+
const shapeTimestamp = shape.timestamp || 0;
|
|
35336
|
+
return shapeTimestamp === 0 || shapeTimestamp > state.lastClearTimestamp;
|
|
35337
|
+
});
|
|
35338
|
+
// Only apply if the received state has valid shapes
|
|
35339
|
+
if (validShapes.length > 0 && (state.shapes.length === 0 || validShapes.length > state.shapes.length)) {
|
|
35322
35340
|
// All shapes from sync_state should have normalized coordinates, denormalize them
|
|
35323
|
-
const denormalizedShapes =
|
|
35341
|
+
const denormalizedShapes = validShapes.map((shape, index) => {
|
|
35324
35342
|
return denormalizeShape(shape);
|
|
35325
35343
|
});
|
|
35326
35344
|
// Apply the synchronized state
|
|
@@ -35338,7 +35356,8 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35338
35356
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35339
35357
|
// Additional check to prevent adding shapes from before the last clear
|
|
35340
35358
|
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35341
|
-
if (shapeTimestamp <= state.lastClearTimestamp) {
|
|
35359
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35360
|
+
console.warn(`[APPLY_ACTION] Skipping stale shape from before clear - shape timestamp: ${shapeTimestamp}, clear timestamp: ${state.lastClearTimestamp}`);
|
|
35342
35361
|
break;
|
|
35343
35362
|
}
|
|
35344
35363
|
dispatch({ type: 'ADD_SHAPE', payload: denormalizedShape });
|
|
@@ -35392,6 +35411,11 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35392
35411
|
case 'start_draw':
|
|
35393
35412
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35394
35413
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35414
|
+
// Check if this shape is from before the last clear
|
|
35415
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35416
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35417
|
+
break; // Skip stale shapes
|
|
35418
|
+
}
|
|
35395
35419
|
// Only apply collaborative start_draw if it's from another user
|
|
35396
35420
|
if (denormalizedShape.userId !== state.userId) {
|
|
35397
35421
|
// Add to active drawings for real-time collaborative visibility
|
|
@@ -35405,6 +35429,11 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35405
35429
|
case 'continue_draw':
|
|
35406
35430
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35407
35431
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35432
|
+
// Check if this shape is from before the last clear
|
|
35433
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35434
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35435
|
+
break; // Skip stale shapes
|
|
35436
|
+
}
|
|
35408
35437
|
// Only apply collaborative drawing updates if it's not from the current user
|
|
35409
35438
|
// to avoid interfering with local real-time drawing
|
|
35410
35439
|
if (denormalizedShape.userId !== state.userId) {
|
|
@@ -35419,6 +35448,12 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35419
35448
|
case 'end_draw':
|
|
35420
35449
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35421
35450
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35451
|
+
// Check if this shape is from before the last clear
|
|
35452
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35453
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35454
|
+
console.warn(`[APPLY_ACTION] Skipping stale end_draw from before clear - shape timestamp: ${shapeTimestamp}, clear timestamp: ${state.lastClearTimestamp}`);
|
|
35455
|
+
break; // Skip stale shapes
|
|
35456
|
+
}
|
|
35422
35457
|
// Only apply collaborative end_draw if it's from another user
|
|
35423
35458
|
// Local user's end_draw is handled directly in Board component via ADD_SHAPE
|
|
35424
35459
|
if (denormalizedShape.userId !== state.userId) {
|
|
@@ -36446,7 +36481,8 @@ const BoardComponent = React.forwardRef(({ roomId = 'default-room', queueAction,
|
|
|
36446
36481
|
}
|
|
36447
36482
|
// Create new shape ID for regular drawing tools
|
|
36448
36483
|
const newShapeId = v4();
|
|
36449
|
-
const
|
|
36484
|
+
const timestamp = Date.now();
|
|
36485
|
+
const newDrawingSessionId = `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`;
|
|
36450
36486
|
setCurrentShapeId(newShapeId);
|
|
36451
36487
|
setCurrentDrawingSessionId(newDrawingSessionId);
|
|
36452
36488
|
// Start drawing
|
|
@@ -36463,6 +36499,7 @@ const BoardComponent = React.forwardRef(({ roomId = 'default-room', queueAction,
|
|
|
36463
36499
|
strokeStyle: state.strokeStyle,
|
|
36464
36500
|
opacity: state.opacity,
|
|
36465
36501
|
drawingSessionId: newDrawingSessionId,
|
|
36502
|
+
timestamp,
|
|
36466
36503
|
// Initialize transformation properties
|
|
36467
36504
|
x: 0,
|
|
36468
36505
|
y: 0,
|
|
@@ -36575,6 +36612,7 @@ const BoardComponent = React.forwardRef(({ roomId = 'default-room', queueAction,
|
|
|
36575
36612
|
strokeStyle: state.strokeStyle,
|
|
36576
36613
|
opacity: state.opacity,
|
|
36577
36614
|
drawingSessionId: currentDrawingSessionId,
|
|
36615
|
+
timestamp: Date.now(),
|
|
36578
36616
|
// Initialize transformation properties
|
|
36579
36617
|
x: 0,
|
|
36580
36618
|
y: 0,
|
|
@@ -36654,6 +36692,7 @@ const BoardComponent = React.forwardRef(({ roomId = 'default-room', queueAction,
|
|
|
36654
36692
|
strokeStyle: state.strokeStyle,
|
|
36655
36693
|
opacity: state.opacity,
|
|
36656
36694
|
drawingSessionId: currentDrawingSessionId,
|
|
36695
|
+
timestamp: Date.now(),
|
|
36657
36696
|
// Initialize transformation properties
|
|
36658
36697
|
x: 0,
|
|
36659
36698
|
y: 0,
|
|
@@ -42755,6 +42794,28 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transp
|
|
|
42755
42794
|
leaveRoom(roomId);
|
|
42756
42795
|
};
|
|
42757
42796
|
}, [roomId]);
|
|
42797
|
+
// Clear all canvases on component unmount if admin leaves
|
|
42798
|
+
React.useEffect(() => {
|
|
42799
|
+
return () => {
|
|
42800
|
+
// If admin leaves, clear all users' canvases
|
|
42801
|
+
if (isAdmin && queueAction) {
|
|
42802
|
+
const clearTimestamp = Date.now();
|
|
42803
|
+
// Clear local state immediately
|
|
42804
|
+
dispatch({ type: 'CLEAR_CANVAS' });
|
|
42805
|
+
dispatch({ type: 'CLEAR_ACTIVE_DRAWINGS' });
|
|
42806
|
+
// Send clear action to all users
|
|
42807
|
+
queueAction({
|
|
42808
|
+
type: 'clear',
|
|
42809
|
+
payload: {
|
|
42810
|
+
timestamp: clearTimestamp,
|
|
42811
|
+
adminId: userId,
|
|
42812
|
+
},
|
|
42813
|
+
userId: userId,
|
|
42814
|
+
timestamp: clearTimestamp,
|
|
42815
|
+
});
|
|
42816
|
+
}
|
|
42817
|
+
};
|
|
42818
|
+
}, [isAdmin, queueAction, userId, dispatch]);
|
|
42758
42819
|
// Global cleanup on app unmount
|
|
42759
42820
|
React.useEffect(() => {
|
|
42760
42821
|
const handleBeforeUnload = () => {
|