@ngenux/ngage-whiteboarding 1.0.7 → 1.0.9
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.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +178 -102
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +185 -101
- 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/dist/src/utils/socket-utility.d.ts +6 -1
- package/dist/src/utils/socket-utility.d.ts.map +1 -1
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/socket-utility.esm.js +168 -0
- package/dist/utils/socket-utility.esm.js.map +1 -0
- package/dist/utils/socket-utility.js +177 -0
- package/dist/utils/socket-utility.js.map +1 -0
- package/dist/utils/src/components/Shapes/Arrow.d.ts +11 -0
- package/dist/utils/src/components/Shapes/Arrow.d.ts.map +1 -0
- package/dist/utils/src/components/Shapes/Ellipse.d.ts +11 -0
- package/dist/utils/src/components/Shapes/Ellipse.d.ts.map +1 -0
- package/dist/utils/src/components/Shapes/ErasedShape.d.ts +11 -0
- package/dist/utils/src/components/Shapes/ErasedShape.d.ts.map +1 -0
- package/dist/utils/src/components/Shapes/FreehandDrawing.d.ts +11 -0
- package/dist/utils/src/components/Shapes/FreehandDrawing.d.ts.map +1 -0
- package/dist/utils/src/components/Shapes/Line.d.ts +11 -0
- package/dist/utils/src/components/Shapes/Line.d.ts.map +1 -0
- package/dist/utils/src/components/Shapes/Rectangle.d.ts +11 -0
- package/dist/utils/src/components/Shapes/Rectangle.d.ts.map +1 -0
- package/dist/utils/src/components/Whiteboard/Board.d.ts +15 -0
- package/dist/utils/src/components/Whiteboard/Board.d.ts.map +1 -0
- package/dist/utils/src/components/Whiteboard/Toolbar.d.ts +21 -0
- package/dist/utils/src/components/Whiteboard/Toolbar.d.ts.map +1 -0
- package/dist/utils/src/components/Whiteboard/index.d.ts +11 -0
- package/dist/utils/src/components/Whiteboard/index.d.ts.map +1 -0
- package/dist/utils/src/context/WhiteboardContext.d.ts +128 -0
- package/dist/utils/src/context/WhiteboardContext.d.ts.map +1 -0
- package/dist/utils/src/hooks/useCapture.d.ts +4 -0
- package/dist/utils/src/hooks/useCapture.d.ts.map +1 -0
- package/dist/utils/src/hooks/useCollaborativeWhiteboard.d.ts +27 -0
- package/dist/utils/src/hooks/useCollaborativeWhiteboard.d.ts.map +1 -0
- package/dist/utils/src/lib/utils.d.ts +3 -0
- package/dist/utils/src/lib/utils.d.ts.map +1 -0
- package/dist/utils/src/types/index.d.ts +123 -0
- package/dist/utils/src/types/index.d.ts.map +1 -0
- package/dist/utils/src/utils/compression.d.ts +14 -0
- package/dist/utils/src/utils/compression.d.ts.map +1 -0
- package/dist/utils/src/utils/socket-utility.d.ts +11 -0
- package/dist/utils/src/utils/socket-utility.d.ts.map +1 -0
- 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.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { WhiteboardProvider } from './src/context/WhiteboardContext';
|
|
|
4
4
|
import { useCapture } from './src/hooks/useCapture';
|
|
5
5
|
import { cn } from './src/lib/utils';
|
|
6
6
|
export { Whiteboard, WhiteboardProvider, useCapture as useWhiteboardStream, cn };
|
|
7
|
+
export { isSocketConnected, getSocket, onSocketStatusChange, onSend, onReceive, leaveRoom, disconnectSocket, waitForSocket } from './src/utils/socket-utility';
|
|
7
8
|
export type { WhiteboardProps } from './src/components/Whiteboard/index';
|
|
8
9
|
export type { ShapeProps, ToolType, StrokeStyle, WhiteboardState, DrawingAction, CompressedData } from './src/types';
|
|
9
10
|
export interface WhiteboardProviderProps {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.tsx"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,IAAI,mBAAmB,EAAE,EAAE,EAAE,CAAC;AAGjF,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,eAAe,EACf,aAAa,EACb,cAAc,EACf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.tsx"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,IAAI,mBAAmB,EAAE,EAAE,EAAE,CAAC;AAGjF,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,oBAAoB,EACpB,MAAM,EACN,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,aAAa,EACd,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,eAAe,EACf,aAAa,EACb,cAAc,EACf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
|
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
|
|
@@ -35249,12 +35255,16 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35249
35255
|
};
|
|
35250
35256
|
const requestStateFromPeers = () => {
|
|
35251
35257
|
if (currentQueueAction) {
|
|
35252
|
-
|
|
35253
|
-
|
|
35254
|
-
|
|
35255
|
-
|
|
35256
|
-
|
|
35257
|
-
|
|
35258
|
+
setTimeout(() => {
|
|
35259
|
+
if (currentQueueAction) {
|
|
35260
|
+
currentQueueAction({
|
|
35261
|
+
type: 'request_state',
|
|
35262
|
+
payload: '',
|
|
35263
|
+
requesterId: state.userId,
|
|
35264
|
+
timestamp: Date.now(),
|
|
35265
|
+
});
|
|
35266
|
+
}
|
|
35267
|
+
}, 2000);
|
|
35258
35268
|
}
|
|
35259
35269
|
else {
|
|
35260
35270
|
console.warn('[STATE_SYNC] No queue action available for state request');
|
|
@@ -35265,9 +35275,16 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35265
35275
|
// Drawing actions should be filtered if they're older than the last clear
|
|
35266
35276
|
if (action.type === 'start_draw' || action.type === 'continue_draw' || action.type === 'end_draw' || action.type === 'add') {
|
|
35267
35277
|
const actionTimestamp = action.timestamp || 0;
|
|
35268
|
-
if (actionTimestamp <= state.lastClearTimestamp) {
|
|
35278
|
+
if (actionTimestamp > 0 && actionTimestamp <= state.lastClearTimestamp) {
|
|
35269
35279
|
return true;
|
|
35270
35280
|
}
|
|
35281
|
+
// Also check timestamp in the payload if it exists
|
|
35282
|
+
if (typeof action.payload === 'object' && action.payload !== null && !Array.isArray(action.payload)) {
|
|
35283
|
+
const payloadTimestamp = action.payload.timestamp || 0;
|
|
35284
|
+
if (payloadTimestamp > 0 && payloadTimestamp <= state.lastClearTimestamp) {
|
|
35285
|
+
return true;
|
|
35286
|
+
}
|
|
35287
|
+
}
|
|
35271
35288
|
}
|
|
35272
35289
|
return false;
|
|
35273
35290
|
};
|
|
@@ -35297,10 +35314,15 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35297
35314
|
if (syncState.shapes.length === 0) {
|
|
35298
35315
|
return; // Don't apply empty state or keep requesting
|
|
35299
35316
|
}
|
|
35300
|
-
//
|
|
35301
|
-
|
|
35317
|
+
// Filter out shapes that are older than our last clear
|
|
35318
|
+
const validShapes = syncState.shapes.filter(shape => {
|
|
35319
|
+
const shapeTimestamp = shape.timestamp || 0;
|
|
35320
|
+
return shapeTimestamp === 0 || shapeTimestamp > state.lastClearTimestamp;
|
|
35321
|
+
});
|
|
35322
|
+
// Only apply if the received state has valid shapes
|
|
35323
|
+
if (validShapes.length > 0 && (state.shapes.length === 0 || validShapes.length > state.shapes.length)) {
|
|
35302
35324
|
// All shapes from sync_state should have normalized coordinates, denormalize them
|
|
35303
|
-
const denormalizedShapes =
|
|
35325
|
+
const denormalizedShapes = validShapes.map((shape, index) => {
|
|
35304
35326
|
return denormalizeShape(shape);
|
|
35305
35327
|
});
|
|
35306
35328
|
// Apply the synchronized state
|
|
@@ -35318,7 +35340,8 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35318
35340
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35319
35341
|
// Additional check to prevent adding shapes from before the last clear
|
|
35320
35342
|
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35321
|
-
if (shapeTimestamp <= state.lastClearTimestamp) {
|
|
35343
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35344
|
+
console.warn(`[APPLY_ACTION] Skipping stale shape from before clear - shape timestamp: ${shapeTimestamp}, clear timestamp: ${state.lastClearTimestamp}`);
|
|
35322
35345
|
break;
|
|
35323
35346
|
}
|
|
35324
35347
|
dispatch({ type: 'ADD_SHAPE', payload: denormalizedShape });
|
|
@@ -35372,6 +35395,11 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35372
35395
|
case 'start_draw':
|
|
35373
35396
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35374
35397
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35398
|
+
// Check if this shape is from before the last clear
|
|
35399
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35400
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35401
|
+
break; // Skip stale shapes
|
|
35402
|
+
}
|
|
35375
35403
|
// Only apply collaborative start_draw if it's from another user
|
|
35376
35404
|
if (denormalizedShape.userId !== state.userId) {
|
|
35377
35405
|
// Add to active drawings for real-time collaborative visibility
|
|
@@ -35385,6 +35413,11 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35385
35413
|
case 'continue_draw':
|
|
35386
35414
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35387
35415
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35416
|
+
// Check if this shape is from before the last clear
|
|
35417
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35418
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35419
|
+
break; // Skip stale shapes
|
|
35420
|
+
}
|
|
35388
35421
|
// Only apply collaborative drawing updates if it's not from the current user
|
|
35389
35422
|
// to avoid interfering with local real-time drawing
|
|
35390
35423
|
if (denormalizedShape.userId !== state.userId) {
|
|
@@ -35399,6 +35432,12 @@ const WhiteboardProvider = ({ children, webSocketUrl }) => {
|
|
|
35399
35432
|
case 'end_draw':
|
|
35400
35433
|
if (typeof action.payload !== 'string' && !Array.isArray(action.payload) && isCompleteShape(action.payload)) {
|
|
35401
35434
|
const denormalizedShape = denormalizeShape(action.payload);
|
|
35435
|
+
// Check if this shape is from before the last clear
|
|
35436
|
+
const shapeTimestamp = denormalizedShape.timestamp || action.timestamp || 0;
|
|
35437
|
+
if (shapeTimestamp > 0 && shapeTimestamp <= state.lastClearTimestamp) {
|
|
35438
|
+
console.warn(`[APPLY_ACTION] Skipping stale end_draw from before clear - shape timestamp: ${shapeTimestamp}, clear timestamp: ${state.lastClearTimestamp}`);
|
|
35439
|
+
break; // Skip stale shapes
|
|
35440
|
+
}
|
|
35402
35441
|
// Only apply collaborative end_draw if it's from another user
|
|
35403
35442
|
// Local user's end_draw is handled directly in Board component via ADD_SHAPE
|
|
35404
35443
|
if (denormalizedShape.userId !== state.userId) {
|
|
@@ -36426,7 +36465,8 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36426
36465
|
}
|
|
36427
36466
|
// Create new shape ID for regular drawing tools
|
|
36428
36467
|
const newShapeId = v4();
|
|
36429
|
-
const
|
|
36468
|
+
const timestamp = Date.now();
|
|
36469
|
+
const newDrawingSessionId = `session-${timestamp}-${Math.random().toString(36).substr(2, 9)}`;
|
|
36430
36470
|
setCurrentShapeId(newShapeId);
|
|
36431
36471
|
setCurrentDrawingSessionId(newDrawingSessionId);
|
|
36432
36472
|
// Start drawing
|
|
@@ -36443,6 +36483,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36443
36483
|
strokeStyle: state.strokeStyle,
|
|
36444
36484
|
opacity: state.opacity,
|
|
36445
36485
|
drawingSessionId: newDrawingSessionId,
|
|
36486
|
+
timestamp,
|
|
36446
36487
|
// Initialize transformation properties
|
|
36447
36488
|
x: 0,
|
|
36448
36489
|
y: 0,
|
|
@@ -36555,6 +36596,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36555
36596
|
strokeStyle: state.strokeStyle,
|
|
36556
36597
|
opacity: state.opacity,
|
|
36557
36598
|
drawingSessionId: currentDrawingSessionId,
|
|
36599
|
+
timestamp: Date.now(),
|
|
36558
36600
|
// Initialize transformation properties
|
|
36559
36601
|
x: 0,
|
|
36560
36602
|
y: 0,
|
|
@@ -36634,6 +36676,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
|
|
|
36634
36676
|
strokeStyle: state.strokeStyle,
|
|
36635
36677
|
opacity: state.opacity,
|
|
36636
36678
|
drawingSessionId: currentDrawingSessionId,
|
|
36679
|
+
timestamp: Date.now(),
|
|
36637
36680
|
// Initialize transformation properties
|
|
36638
36681
|
x: 0,
|
|
36639
36682
|
y: 0,
|
|
@@ -37030,32 +37073,6 @@ const Eraser = createLucideIcon("Eraser", [
|
|
|
37030
37073
|
*/
|
|
37031
37074
|
|
|
37032
37075
|
|
|
37033
|
-
const LockOpen = createLucideIcon("LockOpen", [
|
|
37034
|
-
["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2", key: "1w4ew1" }],
|
|
37035
|
-
["path", { d: "M7 11V7a5 5 0 0 1 9.9-1", key: "1mm8w8" }]
|
|
37036
|
-
]);
|
|
37037
|
-
|
|
37038
|
-
/**
|
|
37039
|
-
* @license lucide-react v0.460.0 - ISC
|
|
37040
|
-
*
|
|
37041
|
-
* This source code is licensed under the ISC license.
|
|
37042
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
37043
|
-
*/
|
|
37044
|
-
|
|
37045
|
-
|
|
37046
|
-
const Lock = createLucideIcon("Lock", [
|
|
37047
|
-
["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2", key: "1w4ew1" }],
|
|
37048
|
-
["path", { d: "M7 11V7a5 5 0 0 1 10 0v4", key: "fwvmzm" }]
|
|
37049
|
-
]);
|
|
37050
|
-
|
|
37051
|
-
/**
|
|
37052
|
-
* @license lucide-react v0.460.0 - ISC
|
|
37053
|
-
*
|
|
37054
|
-
* This source code is licensed under the ISC license.
|
|
37055
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
37056
|
-
*/
|
|
37057
|
-
|
|
37058
|
-
|
|
37059
37076
|
const Minus = createLucideIcon("Minus", [["path", { d: "M5 12h14", key: "1ays0h" }]]);
|
|
37060
37077
|
|
|
37061
37078
|
/**
|
|
@@ -37136,7 +37153,7 @@ const Undo2 = createLucideIcon("Undo2", [
|
|
|
37136
37153
|
]);
|
|
37137
37154
|
|
|
37138
37155
|
// Top Toolbar Component
|
|
37139
|
-
const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockToggle, isAdmin = false, hasToolAccess = false, isGloballyUnlocked =
|
|
37156
|
+
const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockToggle, isAdmin = false, hasToolAccess = false, isGloballyUnlocked = true, shouldBeOpenByDefault = true, hasVideoBackground = false }) => {
|
|
37140
37157
|
const { state, dispatch } = useWhiteboard();
|
|
37141
37158
|
const [isVisible, setIsVisible] = useState(shouldBeOpenByDefault);
|
|
37142
37159
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
@@ -37212,9 +37229,7 @@ const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockTog
|
|
|
37212
37229
|
if (!isInitialized) {
|
|
37213
37230
|
return null;
|
|
37214
37231
|
}
|
|
37215
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: "absolute top-5 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-10", children: [!isVisible && (jsx("button", { className: "w-10 h-10 flex items-center justify-center bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300", onClick: handleToggleVisibility, title: "Show Tools", children: jsx(ChevronDown, { size: 16, className: "text-current" }) })), isVisible && (jsx("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg", children: jsxs("div", { className: "flex items-center gap-1 p-1", children: [
|
|
37216
|
-
? 'bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 hover:bg-green-200 dark:hover:bg-green-900/70'
|
|
37217
|
-
: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'}`, onClick: handleLockToggle, title: isGloballyUnlocked ? 'Whiteboard unlocked for all users - Click to lock' : 'Whiteboard locked - Click to unlock for all users', children: isGloballyUnlocked ? jsx(LockOpen, { size: 16, className: "text-current" }) : jsx(Lock, { size: 16, className: "text-current" }) })), jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded ${hasToolAccess ? 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300' : 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'}`, onClick: handleUndo, disabled: !hasToolAccess, title: hasToolAccess ? 'Undo' : 'Access restricted', children: jsx(Undo2, { size: 16, className: "text-current" }) }), jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded ${hasToolAccess ? 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300' : 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'}`, onClick: handleRedo, disabled: !hasToolAccess, title: hasToolAccess ? 'Redo' : 'Access restricted', children: jsx(Redo2, { size: 16, className: "text-current" }) }), jsx("div", { className: "w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1" }), tools.map((tool) => (jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${!hasToolAccess
|
|
37232
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: "absolute top-5 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-10", children: [!isVisible && (jsx("button", { className: "w-10 h-10 flex items-center justify-center bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300", onClick: handleToggleVisibility, title: "Show Tools", children: jsx(ChevronDown, { size: 16, className: "text-current" }) })), isVisible && (jsx("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg", children: jsxs("div", { className: "flex items-center gap-1 p-1", children: [jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded ${hasToolAccess ? 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300' : 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'}`, onClick: handleUndo, disabled: !hasToolAccess, title: hasToolAccess ? 'Undo' : 'Access restricted', children: jsx(Undo2, { size: 16, className: "text-current" }) }), jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded ${hasToolAccess ? 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300' : 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'}`, onClick: handleRedo, disabled: !hasToolAccess, title: hasToolAccess ? 'Redo' : 'Access restricted', children: jsx(Redo2, { size: 16, className: "text-current" }) }), jsx("div", { className: "w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1" }), tools.map((tool) => (jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${!hasToolAccess
|
|
37218
37233
|
? 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'
|
|
37219
37234
|
: state.tool === tool.type
|
|
37220
37235
|
? 'bg-purple-100 dark:bg-purple-900/50 text-purple-600 dark:text-purple-300'
|
|
@@ -42272,6 +42287,7 @@ Object.assign(lookup, {
|
|
|
42272
42287
|
let socket = null;
|
|
42273
42288
|
const joinedRooms = new Set();
|
|
42274
42289
|
const setupCallbacks = new Map();
|
|
42290
|
+
const statusChangeCallbacks = new Set();
|
|
42275
42291
|
let currentWebSocketUrl = undefined;
|
|
42276
42292
|
// Initialize socket connection
|
|
42277
42293
|
const initializeSocket = (webSocketUrl) => {
|
|
@@ -42287,7 +42303,6 @@ const initializeSocket = (webSocketUrl) => {
|
|
|
42287
42303
|
setupCallbacks.clear();
|
|
42288
42304
|
}
|
|
42289
42305
|
currentWebSocketUrl = webSocketUrl;
|
|
42290
|
-
console.log('[SOCKET] Using socket server URL:', socketServerUrl);
|
|
42291
42306
|
if (!socketServerUrl) {
|
|
42292
42307
|
console.error('[SOCKET] No socket server URL provided');
|
|
42293
42308
|
}
|
|
@@ -42298,29 +42313,21 @@ const initializeSocket = (webSocketUrl) => {
|
|
|
42298
42313
|
reconnectionDelay: 1000,
|
|
42299
42314
|
});
|
|
42300
42315
|
socket.on('connect', () => {
|
|
42301
|
-
|
|
42302
|
-
// Re-join all rooms after reconnection
|
|
42316
|
+
statusChangeCallbacks.forEach(callback => callback(true));
|
|
42303
42317
|
joinedRooms.forEach(roomId => {
|
|
42304
42318
|
socket.emit('join-room', roomId);
|
|
42305
|
-
console.log('[SOCKET] Re-joined room:', roomId);
|
|
42306
42319
|
});
|
|
42307
42320
|
});
|
|
42308
42321
|
socket.on('disconnect', () => {
|
|
42309
|
-
|
|
42322
|
+
statusChangeCallbacks.forEach(callback => callback(false));
|
|
42310
42323
|
});
|
|
42311
|
-
socket.on('connect_error', (
|
|
42312
|
-
|
|
42324
|
+
socket.on('connect_error', () => {
|
|
42325
|
+
// Connection error handled by Socket.IO reconnection logic
|
|
42313
42326
|
});
|
|
42314
42327
|
// Set up the global receive-message listener once
|
|
42315
42328
|
socket.on('receive-message', (message) => {
|
|
42316
42329
|
const callback = setupCallbacks.get(message.roomId);
|
|
42317
42330
|
if (callback) {
|
|
42318
|
-
console.log('[SOCKET] Received message from room:', message.roomId, {
|
|
42319
|
-
compression: message.data.compressionType,
|
|
42320
|
-
originalSize: message.data.originalSize,
|
|
42321
|
-
compressedSize: message.data.compressedSize,
|
|
42322
|
-
from: message.from
|
|
42323
|
-
});
|
|
42324
42331
|
callback(message.data);
|
|
42325
42332
|
}
|
|
42326
42333
|
});
|
|
@@ -42354,18 +42361,23 @@ const onSend = (roomId, data, webSocketUrl) => {
|
|
|
42354
42361
|
console.error('[SOCKET] Error sending message:', error);
|
|
42355
42362
|
}
|
|
42356
42363
|
};
|
|
42357
|
-
const onReceive = (roomId, callback, webSocketUrl) => {
|
|
42364
|
+
const onReceive = (roomId, callback, webSocketUrl, onRoomJoined) => {
|
|
42358
42365
|
const socketInstance = initializeSocket(webSocketUrl);
|
|
42359
42366
|
// Store the callback for this room
|
|
42360
42367
|
setupCallbacks.set(roomId, callback);
|
|
42361
|
-
// Only join the room if we haven't already
|
|
42362
42368
|
if (!joinedRooms.has(roomId)) {
|
|
42363
42369
|
socketInstance.emit('join-room', roomId);
|
|
42364
42370
|
joinedRooms.add(roomId);
|
|
42365
|
-
|
|
42371
|
+
if (onRoomJoined) {
|
|
42372
|
+
setTimeout(() => {
|
|
42373
|
+
onRoomJoined();
|
|
42374
|
+
}, 100);
|
|
42375
|
+
}
|
|
42366
42376
|
}
|
|
42367
42377
|
else {
|
|
42368
|
-
|
|
42378
|
+
if (onRoomJoined) {
|
|
42379
|
+
onRoomJoined();
|
|
42380
|
+
}
|
|
42369
42381
|
}
|
|
42370
42382
|
};
|
|
42371
42383
|
const leaveRoom = (roomId) => {
|
|
@@ -42382,20 +42394,66 @@ const disconnectSocket = () => {
|
|
|
42382
42394
|
socket = null;
|
|
42383
42395
|
joinedRooms.clear();
|
|
42384
42396
|
setupCallbacks.clear();
|
|
42385
|
-
console.log('[SOCKET] Socket disconnected and cleaned up');
|
|
42386
42397
|
}
|
|
42387
42398
|
};
|
|
42399
|
+
// Get current socket connection status
|
|
42400
|
+
const isSocketConnected = () => {
|
|
42401
|
+
return socket?.connected ?? false;
|
|
42402
|
+
};
|
|
42403
|
+
// Get current socket instance
|
|
42404
|
+
const getSocket = () => {
|
|
42405
|
+
return socket;
|
|
42406
|
+
};
|
|
42407
|
+
// Subscribe to connection status changes
|
|
42408
|
+
const onSocketStatusChange = (callback) => {
|
|
42409
|
+
const socketInstance = socket || initializeSocket();
|
|
42410
|
+
statusChangeCallbacks.add(callback);
|
|
42411
|
+
callback(socketInstance.connected);
|
|
42412
|
+
return () => {
|
|
42413
|
+
statusChangeCallbacks.delete(callback);
|
|
42414
|
+
};
|
|
42415
|
+
};
|
|
42416
|
+
const waitForSocket = (webSocketUrl, timeoutMs = 5000) => {
|
|
42417
|
+
return new Promise((resolve) => {
|
|
42418
|
+
const socketInstance = initializeSocket(webSocketUrl);
|
|
42419
|
+
if (socketInstance.connected) {
|
|
42420
|
+
resolve();
|
|
42421
|
+
return;
|
|
42422
|
+
}
|
|
42423
|
+
const handleConnect = () => {
|
|
42424
|
+
socketInstance.off('connect', handleConnect);
|
|
42425
|
+
clearTimeout(timeoutHandle);
|
|
42426
|
+
resolve();
|
|
42427
|
+
};
|
|
42428
|
+
socketInstance.on('connect', handleConnect);
|
|
42429
|
+
const timeoutHandle = setTimeout(() => {
|
|
42430
|
+
socketInstance.off('connect', handleConnect);
|
|
42431
|
+
resolve();
|
|
42432
|
+
}, timeoutMs);
|
|
42433
|
+
});
|
|
42434
|
+
};
|
|
42388
42435
|
|
|
42389
42436
|
const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transparentBackground = false, videoStream }) => {
|
|
42390
42437
|
const { state, dispatch, setQueueAction, requestStateFromPeers, webSocketUrl } = useWhiteboard();
|
|
42391
42438
|
const [lastCollaborativeAction, setLastCollaborativeAction] = useState(null);
|
|
42392
42439
|
const [hasRequestedState, setHasRequestedState] = useState(false);
|
|
42393
42440
|
const [lastStateRequestTime, setLastStateRequestTime] = useState(0);
|
|
42394
|
-
const [
|
|
42441
|
+
const [isSocketConnected, setIsSocketConnected] = useState(false);
|
|
42442
|
+
const [isRoomJoined, setIsRoomJoined] = useState(false);
|
|
42443
|
+
const [isGloballyUnlocked, setIsGloballyUnlocked] = useState(true); // Global unlock status
|
|
42395
42444
|
const [syncedAllowedUsers, setSyncedAllowedUsers] = useState(allowedUsers); // Synced allowed users from collaboration
|
|
42396
42445
|
const isCollaborativeUpdateRef = useRef(false); // Track if update came from collaboration
|
|
42397
42446
|
const lastClearTimeRef = useRef(0); // Track when last clear happened
|
|
42398
42447
|
const boardRef = useRef(null);
|
|
42448
|
+
useEffect(() => {
|
|
42449
|
+
const unsubscribe = onSocketStatusChange((connected) => {
|
|
42450
|
+
setIsSocketConnected(connected);
|
|
42451
|
+
});
|
|
42452
|
+
return () => {
|
|
42453
|
+
console.log('[WHITEBOARD] Unsubscribing from socket status listener');
|
|
42454
|
+
unsubscribe();
|
|
42455
|
+
};
|
|
42456
|
+
}, []); // Empty dependencies - single listener for component lifecycle
|
|
42399
42457
|
// Set userId in context when component mounts or userId changes
|
|
42400
42458
|
useEffect(() => {
|
|
42401
42459
|
dispatch({ type: 'SET_USER_ID', payload: userId });
|
|
@@ -42476,7 +42534,9 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transp
|
|
|
42476
42534
|
setHasRequestedState(false);
|
|
42477
42535
|
}
|
|
42478
42536
|
callback(data);
|
|
42479
|
-
}, webSocketUrl)
|
|
42537
|
+
}, webSocketUrl, () => {
|
|
42538
|
+
setIsRoomJoined(true);
|
|
42539
|
+
});
|
|
42480
42540
|
}, [roomId, webSocketUrl]);
|
|
42481
42541
|
// Initialize the collaborative whiteboard hook
|
|
42482
42542
|
const collaborativeConfig = useMemo(() => ({
|
|
@@ -42513,48 +42573,42 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transp
|
|
|
42513
42573
|
});
|
|
42514
42574
|
}
|
|
42515
42575
|
}, [allowedUsers, queueAction, userId, syncedAllowedUsers]);
|
|
42516
|
-
// Request state from peers when
|
|
42576
|
+
// Request state from peers when room is successfully joined AND socket is connected
|
|
42577
|
+
// Need both conditions to ensure message can actually be sent
|
|
42517
42578
|
useEffect(() => {
|
|
42518
|
-
if (
|
|
42519
|
-
|
|
42520
|
-
|
|
42521
|
-
|
|
42522
|
-
|
|
42523
|
-
|
|
42524
|
-
if (now - lastStateRequestTime > 5000) {
|
|
42525
|
-
requestStateFromPeers();
|
|
42526
|
-
setHasRequestedState(true);
|
|
42527
|
-
setLastStateRequestTime(now);
|
|
42528
|
-
}
|
|
42529
|
-
}, 500);
|
|
42530
|
-
return () => clearTimeout(requestTimer);
|
|
42579
|
+
if (!isRoomJoined || !isSocketConnected || hasRequestedState || state.shapes.length > 0 || !queueAction) {
|
|
42580
|
+
return;
|
|
42581
|
+
}
|
|
42582
|
+
const now = Date.now();
|
|
42583
|
+
if (now - lastClearTimeRef.current < 10000) {
|
|
42584
|
+
return;
|
|
42531
42585
|
}
|
|
42532
|
-
|
|
42586
|
+
requestStateFromPeers();
|
|
42587
|
+
setHasRequestedState(true);
|
|
42588
|
+
setLastStateRequestTime(Date.now());
|
|
42589
|
+
}, [isRoomJoined, isSocketConnected, state.shapes.length, hasRequestedState, queueAction, requestStateFromPeers, userId, roomId]);
|
|
42533
42590
|
// Reset request flag when room changes
|
|
42534
42591
|
useEffect(() => {
|
|
42535
42592
|
setHasRequestedState(false);
|
|
42536
42593
|
setLastStateRequestTime(0);
|
|
42594
|
+
setIsRoomJoined(false);
|
|
42537
42595
|
}, [roomId]);
|
|
42538
|
-
//
|
|
42596
|
+
// Retry state request if canvas is still empty after 3 seconds
|
|
42539
42597
|
useEffect(() => {
|
|
42540
|
-
if (queueAction
|
|
42541
|
-
|
|
42542
|
-
state.shapes.length === 0 &&
|
|
42543
|
-
!hasRequestedState) {
|
|
42544
|
-
const now = Date.now();
|
|
42545
|
-
if (now - lastClearTimeRef.current < 10000) {
|
|
42546
|
-
return;
|
|
42547
|
-
}
|
|
42548
|
-
if (now - lastStateRequestTime > 10000) {
|
|
42549
|
-
const reconnectTimer = setTimeout(() => {
|
|
42550
|
-
requestStateFromPeers();
|
|
42551
|
-
setHasRequestedState(true);
|
|
42552
|
-
setLastStateRequestTime(now);
|
|
42553
|
-
}, 2000);
|
|
42554
|
-
return () => clearTimeout(reconnectTimer);
|
|
42555
|
-
}
|
|
42598
|
+
if (!isRoomJoined || !isSocketConnected || hasRequestedState || state.shapes.length > 0 || !queueAction) {
|
|
42599
|
+
return;
|
|
42556
42600
|
}
|
|
42557
|
-
|
|
42601
|
+
const now = Date.now();
|
|
42602
|
+
// Don't retry if we recently cleared
|
|
42603
|
+
if (now - lastClearTimeRef.current < 10000) {
|
|
42604
|
+
return;
|
|
42605
|
+
}
|
|
42606
|
+
const retryTimer = setTimeout(() => {
|
|
42607
|
+
requestStateFromPeers();
|
|
42608
|
+
setLastStateRequestTime(Date.now());
|
|
42609
|
+
}, 3000);
|
|
42610
|
+
return () => clearTimeout(retryTimer);
|
|
42611
|
+
}, [isRoomJoined, isSocketConnected, state.shapes.length, queueAction, requestStateFromPeers, lastStateRequestTime, userId]);
|
|
42558
42612
|
// Cleanup stale active drawings periodically
|
|
42559
42613
|
useEffect(() => {
|
|
42560
42614
|
const cleanupInterval = setInterval(() => {
|
|
@@ -42735,6 +42789,28 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transp
|
|
|
42735
42789
|
leaveRoom(roomId);
|
|
42736
42790
|
};
|
|
42737
42791
|
}, [roomId]);
|
|
42792
|
+
// Clear all canvases on component unmount if admin leaves
|
|
42793
|
+
useEffect(() => {
|
|
42794
|
+
return () => {
|
|
42795
|
+
// If admin leaves, clear all users' canvases
|
|
42796
|
+
if (isAdmin && queueAction) {
|
|
42797
|
+
const clearTimestamp = Date.now();
|
|
42798
|
+
// Clear local state immediately
|
|
42799
|
+
dispatch({ type: 'CLEAR_CANVAS' });
|
|
42800
|
+
dispatch({ type: 'CLEAR_ACTIVE_DRAWINGS' });
|
|
42801
|
+
// Send clear action to all users
|
|
42802
|
+
queueAction({
|
|
42803
|
+
type: 'clear',
|
|
42804
|
+
payload: {
|
|
42805
|
+
timestamp: clearTimestamp,
|
|
42806
|
+
adminId: userId,
|
|
42807
|
+
},
|
|
42808
|
+
userId: userId,
|
|
42809
|
+
timestamp: clearTimestamp,
|
|
42810
|
+
});
|
|
42811
|
+
}
|
|
42812
|
+
};
|
|
42813
|
+
}, [isAdmin, userId, dispatch]);
|
|
42738
42814
|
// Global cleanup on app unmount
|
|
42739
42815
|
useEffect(() => {
|
|
42740
42816
|
const handleBeforeUnload = () => {
|
|
@@ -45777,5 +45853,5 @@ function cn(...inputs) {
|
|
|
45777
45853
|
return twMerge(clsx(inputs));
|
|
45778
45854
|
}
|
|
45779
45855
|
|
|
45780
|
-
export { Whiteboard, WhiteboardProvider, cn, useCapture as useWhiteboardStream };
|
|
45856
|
+
export { Whiteboard, WhiteboardProvider, cn, disconnectSocket, getSocket, isSocketConnected, leaveRoom, onReceive, onSend, onSocketStatusChange, useCapture as useWhiteboardStream, waitForSocket };
|
|
45781
45857
|
//# sourceMappingURL=index.esm.js.map
|