@ngenux/ngage-whiteboarding 1.0.2 → 1.0.4

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/dist/index.js CHANGED
@@ -36730,9 +36730,27 @@ const BoardComponent = React.forwardRef(({ roomId = 'default-room', queueAction,
36730
36730
  const activeShapes = Object.values(state.activeDrawings);
36731
36731
  return activeShapes.map(shape => (jsxRuntime.jsx(MemoizedShapeComponent, { shape: shape }, `active-${shape.id}`)));
36732
36732
  }, [state.activeDrawings]);
36733
+ const getStageDataURL = React.useCallback((format = 'png') => {
36734
+ if (!stageRef.current) {
36735
+ return '';
36736
+ }
36737
+ try {
36738
+ const stage = stageRef.current;
36739
+ return stage.toDataURL({
36740
+ mimeType: format === 'png' ? 'image/png' : 'image/jpeg',
36741
+ quality: 1,
36742
+ pixelRatio: 2,
36743
+ });
36744
+ }
36745
+ catch (error) {
36746
+ console.error('Failed to get stage data URL:', error);
36747
+ return '';
36748
+ }
36749
+ }, []);
36733
36750
  React.useImperativeHandle(ref, () => ({
36734
36751
  exportAsImage,
36735
36752
  exportAsPDF,
36753
+ getStageDataURL,
36736
36754
  }));
36737
36755
  // Memoized cursor style for performance
36738
36756
  const cursorStyle = React.useMemo(() => ({
@@ -36740,7 +36758,7 @@ const BoardComponent = React.forwardRef(({ roomId = 'default-room', queueAction,
36740
36758
  state.tool === 'select' ? 'default' :
36741
36759
  state.tool === 'pan' ? 'grab' : 'crosshair'
36742
36760
  }), [hasToolAccess, state.tool]);
36743
- return (jsxRuntime.jsx("div", { ref: containerRef, className: "w-full h-full bg-white relative", children: jsxRuntime.jsx(Stage, { ref: stageRef, width: size.width, height: size.height, onMouseDown: handleMouseDown, onMousemove: handleMouseMove, onMouseup: handleMouseUp, onTouchStart: handleMouseDown, onTouchMove: handleMouseMove, onTouchEnd: handleMouseUp, style: cursorStyle, children: jsxRuntime.jsxs(Layer, { children: [React.useMemo(() => {
36761
+ return (jsxRuntime.jsx("div", { ref: containerRef, className: "w-full h-full relative", style: { backgroundColor: state.backgroundColor }, children: jsxRuntime.jsx(Stage, { ref: stageRef, width: size.width, height: size.height, onMouseDown: handleMouseDown, onMousemove: handleMouseMove, onMouseup: handleMouseUp, onTouchStart: handleMouseDown, onTouchMove: handleMouseMove, onTouchEnd: handleMouseUp, style: cursorStyle, children: jsxRuntime.jsxs(Layer, { children: [React.useMemo(() => {
36744
36762
  if (state.backgroundColor === 'transparent')
36745
36763
  return null;
36746
36764
  return (jsxRuntime.jsx(Rect, { x: 0, y: 0, width: size.width, height: size.height, fill: state.backgroundColor, listening: false }));
@@ -37050,7 +37068,7 @@ const Undo2 = createLucideIcon("Undo2", [
37050
37068
  ]);
37051
37069
 
37052
37070
  // Top Toolbar Component
37053
- const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockToggle, isAdmin = false, hasToolAccess = false, isGloballyUnlocked = false, shouldBeOpenByDefault = true }) => {
37071
+ const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockToggle, isAdmin = false, hasToolAccess = false, isGloballyUnlocked = false, shouldBeOpenByDefault = true, hasVideoBackground = false }) => {
37054
37072
  const { state, dispatch } = useWhiteboard();
37055
37073
  const [isVisible, setIsVisible] = React.useState(shouldBeOpenByDefault);
37056
37074
  const handleToggleVisibility = () => {
@@ -37114,16 +37132,16 @@ const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockTog
37114
37132
  }
37115
37133
  }
37116
37134
  };
37117
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "absolute top-5 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-40", children: [!isVisible && (jsxRuntime.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: jsxRuntime.jsx(ChevronDown, { size: 16, className: "text-current" }) })), isVisible && (jsxRuntime.jsx("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg", children: jsxRuntime.jsxs("div", { className: "flex items-center gap-1 p-1", children: [isAdmin && (jsxRuntime.jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${isGloballyUnlocked
37135
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "absolute top-5 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-10", children: [!isVisible && (jsxRuntime.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: jsxRuntime.jsx(ChevronDown, { size: 16, className: "text-current" }) })), isVisible && (jsxRuntime.jsx("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg", children: jsxRuntime.jsxs("div", { className: "flex items-center gap-1 p-1", children: [isAdmin && (jsxRuntime.jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${isGloballyUnlocked
37118
37136
  ? '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'
37119
37137
  : '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 ? jsxRuntime.jsx(LockOpen, { size: 16, className: "text-current" }) : jsxRuntime.jsx(Lock, { size: 16, className: "text-current" }) })), jsxRuntime.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: jsxRuntime.jsx(Undo2, { size: 16, className: "text-current" }) }), jsxRuntime.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: jsxRuntime.jsx(Redo2, { size: 16, className: "text-current" }) }), jsxRuntime.jsx("div", { className: "w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1" }), tools.map((tool) => (jsxRuntime.jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${!hasToolAccess
37120
37138
  ? 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'
37121
37139
  : state.tool === tool.type
37122
37140
  ? 'bg-purple-100 dark:bg-purple-900/50 text-purple-600 dark:text-purple-300'
37123
- : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`, onClick: () => handleToolSelect(tool.type), disabled: !hasToolAccess, title: hasToolAccess ? tool.label : `${tool.label} - Access restricted`, children: React.cloneElement(tool.icon, { className: 'text-current' }) }, tool.type))), jsxRuntime.jsx("button", { className: "w-10 h-10 flex items-center justify-center rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300", onClick: handleToggleVisibility, title: "Hide Tools", children: jsxRuntime.jsx(ChevronUp, { size: 16, className: "text-current" }) })] }) }))] }), jsxRuntime.jsxs("div", { className: "absolute top-5 right-20 flex items-center gap-2 z-40", children: [isAdmin && (jsxRuntime.jsxs("button", { className: "h-10 px-3 flex items-center gap-2 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg text-sm bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 shadow-lg", onClick: handleClear, children: [jsxRuntime.jsx(Eraser, { size: 16, className: "text-current" }), "Clear"] })), jsxRuntime.jsxs("button", { className: "h-10 px-3 flex items-center gap-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700 text-white rounded-lg text-sm shadow-lg", onClick: () => handleExportImage?.('png'), children: [jsxRuntime.jsx(Save, { size: 16, className: "text-current" }), "Save"] })] })] }));
37141
+ : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}`, onClick: () => handleToolSelect(tool.type), disabled: !hasToolAccess, title: hasToolAccess ? tool.label : `${tool.label} - Access restricted`, children: React.cloneElement(tool.icon, { className: 'text-current' }) }, tool.type))), jsxRuntime.jsx("button", { className: "w-10 h-10 flex items-center justify-center rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300", onClick: handleToggleVisibility, title: "Hide Tools", children: jsxRuntime.jsx(ChevronUp, { size: 16, className: "text-current" }) })] }) }))] }), jsxRuntime.jsxs("div", { className: "absolute top-5 right-20 flex items-center gap-2 z-10", children: [isAdmin && (jsxRuntime.jsxs("button", { className: "h-10 px-3 flex items-center gap-2 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg text-sm bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 shadow-lg", onClick: handleClear, children: [jsxRuntime.jsx(Eraser, { size: 16, className: "text-current" }), "Clear"] })), jsxRuntime.jsxs("button", { className: "h-10 px-3 flex items-center gap-2 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700 text-white rounded-lg text-sm shadow-lg", onClick: () => handleExportImage?.('png'), children: [jsxRuntime.jsx(Save, { size: 16, className: "text-current" }), "Save"] })] })] }));
37124
37142
  };
37125
37143
  // Left Sidebar Component
37126
- const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault = false }) => {
37144
+ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault = false, disableBackgroundChange = false, hasVideoBackground = false }) => {
37127
37145
  const { state, dispatch } = useWhiteboard();
37128
37146
  const [isVisible, setIsVisible] = React.useState(shouldBeOpenByDefault);
37129
37147
  const [isDragging, setIsDragging] = React.useState(false);
@@ -37131,6 +37149,13 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37131
37149
  const [position, setPosition] = React.useState({ x: 20, y: 80 });
37132
37150
  const [hasEverHadAccess, setHasEverHadAccess] = React.useState(hasToolAccess);
37133
37151
  const [wasManuallyClosedAfterAccess, setWasManuallyClosedAfterAccess] = React.useState(false);
37152
+ // Set white as default stroke color when video background is active
37153
+ React.useEffect(() => {
37154
+ if (hasVideoBackground && state.color === '#000000') {
37155
+ // Only change if currently black, to avoid overriding user's choice
37156
+ dispatch({ type: 'SET_COLOR', payload: '#FFFFFF' });
37157
+ }
37158
+ }, [hasVideoBackground, state.color, dispatch]);
37134
37159
  // Track initial access grant
37135
37160
  React.useEffect(() => {
37136
37161
  if (hasToolAccess && !hasEverHadAccess) {
@@ -37159,7 +37184,7 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37159
37184
  setWasManuallyClosedAfterAccess(false);
37160
37185
  };
37161
37186
  const strokeColors = [
37162
- '#000000', '#DC2626', '#059669', '#2563EB', '#EA580C', '#7C3AED'
37187
+ '#FFFFFF', '#000000', '#DC2626', '#059669', '#2563EB', '#EA580C', '#7C3AED'
37163
37188
  ];
37164
37189
  const backgroundColors = [
37165
37190
  '#FFFFFF', '#F8FAFC', '#FEF2F2', '#F0FDF4', '#EFF6FF', '#1F2937', 'transparent'
@@ -37208,7 +37233,7 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37208
37233
  dispatch({ type: 'SET_COLOR', payload: color });
37209
37234
  };
37210
37235
  const handleBackgroundColorChange = (color) => {
37211
- if (!hasToolAccess) {
37236
+ if (!hasToolAccess || disableBackgroundChange) {
37212
37237
  console.warn('[STYLE_ACCESS] User does not have access to change background');
37213
37238
  return;
37214
37239
  }
@@ -37249,19 +37274,28 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37249
37274
  if (!hasToolAccess) {
37250
37275
  return null;
37251
37276
  }
37252
- return (jsxRuntime.jsx("button", { className: "absolute top-20 left-4 w-10 h-10 flex items-center justify-center bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-lg shadow-lg z-50", onClick: handleManualOpen, title: "Show Style Panel", children: jsxRuntime.jsx(ChevronRight, { size: 16, className: "text-current" }) }));
37277
+ return (jsxRuntime.jsx("button", { className: "absolute top-20 left-4 w-10 h-10 flex items-center justify-center bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-lg shadow-lg z-10", onClick: handleManualOpen, title: "Show Style Panel", children: jsxRuntime.jsx(ChevronRight, { size: 16, className: "text-current" }) }));
37253
37278
  }
37254
- return (jsxRuntime.jsxs("div", { className: `absolute bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-40 ${isDragging ? 'shadow-2xl' : ''} ${!hasToolAccess ? 'opacity-75' : ''}`, style: {
37279
+ return (jsxRuntime.jsxs("div", { className: `absolute bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-10 ${isDragging ? 'shadow-2xl' : ''} ${!hasToolAccess ? 'opacity-75' : ''}`, style: {
37255
37280
  left: position.x,
37256
37281
  top: position.y,
37257
37282
  width: '250px',
37258
37283
  maxHeight: '80vh',
37259
37284
  cursor: isDragging ? 'grabbing' : 'grab'
37260
- }, onMouseDown: handleMouseDown, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-2 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 rounded-t-lg", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-200", children: "Style Panel" }), !hasToolAccess && (jsxRuntime.jsx("span", { className: "text-xs bg-orange-100 dark:bg-orange-900 text-orange-600 dark:text-orange-300 px-2 py-1 rounded", children: "Locked" }))] }), jsxRuntime.jsx("button", { className: "close-button p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded", onClick: handleManualClose, onMouseDown: (e) => e.stopPropagation(), title: "Close Panel", children: jsxRuntime.jsx(ChevronLeft, { size: 14, className: "text-gray-600 dark:text-gray-300" }) })] }), jsxRuntime.jsxs("div", { className: "sidebar-content p-3 overflow-y-auto", style: { maxHeight: 'calc(80vh - 40px)' }, children: [!hasToolAccess && (jsxRuntime.jsxs("div", { className: "mb-4 p-3 bg-orange-50 dark:bg-orange-900/30 border border-orange-200 dark:border-orange-800 rounded-lg text-center", children: [jsxRuntime.jsx("div", { className: "text-orange-700 dark:text-orange-300 text-sm font-medium mb-1", children: "\uD83D\uDD12 Tools Locked" }), jsxRuntime.jsx("div", { className: "text-orange-600 dark:text-orange-400 text-xs", children: "Contact admin for access" })] })), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke Color" }), jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-1", children: strokeColors.map((color) => (jsxRuntime.jsx("button", { className: `w-6 h-6 rounded border-2 transition-all ${!hasToolAccess
37261
- ? 'opacity-50 cursor-not-allowed border-gray-200 dark:border-gray-700'
37285
+ }, onMouseDown: handleMouseDown, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-2 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 rounded-t-lg", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-200", children: "Style Panel" }), !hasToolAccess && (jsxRuntime.jsx("span", { className: "text-xs bg-orange-100 dark:bg-orange-900 text-orange-600 dark:text-orange-300 px-2 py-1 rounded", children: "Locked" }))] }), jsxRuntime.jsx("button", { className: "close-button p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded", onClick: handleManualClose, onMouseDown: (e) => e.stopPropagation(), title: "Close Panel", children: jsxRuntime.jsx(ChevronLeft, { size: 14, className: "text-gray-600 dark:text-gray-300" }) })] }), jsxRuntime.jsxs("div", { className: "sidebar-content p-3 overflow-y-auto", style: { maxHeight: 'calc(80vh - 40px)' }, children: [!hasToolAccess && (jsxRuntime.jsxs("div", { className: "mb-4 p-3 bg-orange-50 dark:bg-orange-900/30 border border-orange-200 dark:border-orange-800 rounded-lg text-center", children: [jsxRuntime.jsx("div", { className: "text-orange-700 dark:text-orange-300 text-sm font-medium mb-1", children: "\uD83D\uDD12 Tools Locked" }), jsxRuntime.jsx("div", { className: "text-orange-600 dark:text-orange-400 text-xs", children: "Contact admin for access" })] })), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke Color" }), jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-1", children: strokeColors.map((color) => (jsxRuntime.jsx("button", { className: `w-6 h-6 rounded transition-all relative ${!hasToolAccess
37286
+ ? 'opacity-50 cursor-not-allowed border-2 border-gray-200 dark:border-gray-700'
37262
37287
  : state.color === color
37263
- ? 'border-purple-400 dark:border-purple-500 scale-110'
37264
- : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`, style: { backgroundColor: color }, onClick: () => handleColorChange(color), disabled: !hasToolAccess, title: hasToolAccess ? color : 'Access restricted' }, color))) })] }), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Background" }), jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-1", children: backgroundColors.map((color) => (jsxRuntime.jsx("button", { className: `w-6 h-6 rounded border-2 transition-all relative ${!hasToolAccess
37288
+ ? 'border-[3px] border-yellow-400 dark:border-yellow-300 scale-125 shadow-lg'
37289
+ : color === '#FFFFFF'
37290
+ ? 'border-2 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500'
37291
+ : 'border-2 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`, style: {
37292
+ backgroundColor: color,
37293
+ boxShadow: color === '#FFFFFF'
37294
+ ? 'inset 0 0 0 1px rgba(0,0,0,0.15)'
37295
+ : state.color === color
37296
+ ? '0 4px 12px rgba(234, 179, 8, 0.5), 0 0 0 2px rgba(234, 179, 8, 0.3)'
37297
+ : 'none'
37298
+ }, onClick: () => handleColorChange(color), disabled: !hasToolAccess, title: hasToolAccess ? (color === '#FFFFFF' ? 'White' : color) : 'Access restricted', children: state.color === color && (jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: color === '#FFFFFF' || color === '#EA580C' || color === '#DC2626' ? '#000000' : '#FFFFFF', strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }) })) }, color))) })] }), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Background" }), disableBackgroundChange && (jsxRuntime.jsx("div", { className: "p-3 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-lg text-center", children: jsxRuntime.jsx("div", { className: "text-blue-700 dark:text-blue-300 text-xs", title: "Disabled when video background is active", children: "\uD83D\uDD12 Background selection locked" }) })), !disableBackgroundChange && (jsxRuntime.jsx("div", { className: "grid grid-cols-6 gap-1", children: backgroundColors.map((color) => (jsxRuntime.jsx("button", { className: `w-6 h-6 rounded border-2 transition-all relative ${!hasToolAccess
37265
37299
  ? 'opacity-50 cursor-not-allowed border-gray-200 dark:border-gray-700'
37266
37300
  : state.backgroundColor === color
37267
37301
  ? 'border-blue-400 dark:border-blue-500 scale-110'
@@ -37269,7 +37303,7 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37269
37303
  backgroundColor: color === 'transparent' ? 'white' : color
37270
37304
  }, onClick: () => handleBackgroundColorChange(color), disabled: !hasToolAccess, title: hasToolAccess ? (color === 'transparent' ? 'Transparent' : color) : 'Access restricted', children: color === 'transparent' && (jsxRuntime.jsx("div", { className: "absolute inset-0 bg-gradient-to-br from-red-500 to-red-500 opacity-50 rounded", style: {
37271
37305
  background: 'linear-gradient(45deg, transparent 40%, #ef4444 40%, #ef4444 60%, transparent 60%)'
37272
- } })) }, color))) })] }), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke width" }), jsxRuntime.jsx("div", { className: "flex gap-2", children: strokeWidths.map((width) => (jsxRuntime.jsx("button", { className: `flex-1 p-2 rounded flex items-center justify-center ${!hasToolAccess
37306
+ } })) }, color))) }))] }), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke width" }), jsxRuntime.jsx("div", { className: "flex gap-2", children: strokeWidths.map((width) => (jsxRuntime.jsx("button", { className: `flex-1 p-2 rounded flex items-center justify-center ${!hasToolAccess
37273
37307
  ? 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'
37274
37308
  : state.strokeWidth === width
37275
37309
  ? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300'
@@ -42254,7 +42288,7 @@ const disconnectSocket = () => {
42254
42288
  }
42255
42289
  };
42256
42290
 
42257
- const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42291
+ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transparentBackground = false, videoStream }) => {
42258
42292
  const { state, dispatch, setQueueAction, requestStateFromPeers, webSocketUrl } = useWhiteboard();
42259
42293
  const [lastCollaborativeAction, setLastCollaborativeAction] = React.useState(null);
42260
42294
  const [hasRequestedState, setHasRequestedState] = React.useState(false);
@@ -42268,6 +42302,25 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42268
42302
  React.useEffect(() => {
42269
42303
  dispatch({ type: 'SET_USER_ID', payload: userId });
42270
42304
  }, [userId, dispatch]);
42305
+ // Set background color based on transparentBackground prop or videoStream presence
42306
+ React.useEffect(() => {
42307
+ if (transparentBackground || videoStream) {
42308
+ // Force transparent background when transparentBackground flag is set or video is active
42309
+ dispatch({ type: 'SET_BACKGROUND_COLOR', payload: 'transparent' });
42310
+ }
42311
+ else if (!transparentBackground && !videoStream) {
42312
+ // Reset to white when screen share stops and transparentBackground is false
42313
+ dispatch({ type: 'SET_BACKGROUND_COLOR', payload: '#FFFFFF' });
42314
+ }
42315
+ }, [transparentBackground, videoStream, dispatch]);
42316
+ // Video ref for setting srcObject
42317
+ const videoRef = React.useRef(null);
42318
+ // Set video srcObject when videoStream changes
42319
+ React.useEffect(() => {
42320
+ if (videoRef.current && videoStream) {
42321
+ videoRef.current.srcObject = videoStream;
42322
+ }
42323
+ }, [videoStream]);
42271
42324
  // Determine if current user has tool access (using synced allowed users for cross-computer sync)
42272
42325
  const hasToolAccess = React.useMemo(() => {
42273
42326
  return isAdmin || syncedAllowedUsers.includes(userId) || isGloballyUnlocked;
@@ -42424,10 +42477,57 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42424
42477
  }, [state.activeDrawings, state.lastClearTimestamp, dispatch]);
42425
42478
  // Export functions to pass to toolbar
42426
42479
  const handleExportImage = React.useCallback((format) => {
42427
- if (boardRef.current) {
42480
+ if (!boardRef.current) {
42481
+ return;
42482
+ }
42483
+ // If video is active, capture both video and canvas
42484
+ if (videoStream && videoRef.current) {
42485
+ const video = videoRef.current;
42486
+ const canvas = document.createElement('canvas');
42487
+ const ctx = canvas.getContext('2d');
42488
+ if (!ctx)
42489
+ return;
42490
+ // Set canvas size to match the whiteboard
42491
+ canvas.width = video.videoWidth || video.offsetWidth;
42492
+ canvas.height = video.videoHeight || video.offsetHeight;
42493
+ try {
42494
+ // Draw the video frame
42495
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
42496
+ // Get the Konva stage data URL
42497
+ const stageDataURL = boardRef.current.getStageDataURL(format);
42498
+ // Create an image from the stage
42499
+ const stageImage = new Image();
42500
+ stageImage.onload = () => {
42501
+ // Draw the annotations on top of the video
42502
+ ctx.drawImage(stageImage, 0, 0, canvas.width, canvas.height);
42503
+ // Convert to blob and download
42504
+ canvas.toBlob((blob) => {
42505
+ if (blob) {
42506
+ const timestamp = new Date().toISOString().slice(0, 16).replace('T', '_').replace(/:/g, '-');
42507
+ const filename = `whiteboard_video_annotation_${timestamp}.${format}`;
42508
+ const link = document.createElement('a');
42509
+ link.download = filename;
42510
+ link.href = URL.createObjectURL(blob);
42511
+ document.body.appendChild(link);
42512
+ link.click();
42513
+ document.body.removeChild(link);
42514
+ URL.revokeObjectURL(link.href);
42515
+ }
42516
+ }, format === 'png' ? 'image/png' : 'image/jpeg', 1.0);
42517
+ };
42518
+ stageImage.src = stageDataURL;
42519
+ }
42520
+ catch (error) {
42521
+ console.error('Failed to export video with annotations:', error);
42522
+ // Fallback to regular export
42523
+ boardRef.current.exportAsImage(format);
42524
+ }
42525
+ }
42526
+ else {
42527
+ // No video - regular export
42428
42528
  boardRef.current.exportAsImage(format);
42429
42529
  }
42430
- }, []);
42530
+ }, [videoStream]);
42431
42531
  const handleExportPDF = React.useCallback(() => {
42432
42532
  if (boardRef.current) {
42433
42533
  boardRef.current.exportAsPDF();
@@ -42548,7 +42648,14 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42548
42648
  disconnectSocket();
42549
42649
  };
42550
42650
  }, []);
42551
- return (jsxRuntime.jsxs("div", { className: "relative h-screen", children: [jsxRuntime.jsx(TopToolbar, { queueAction: queueAction, handleExportImage: handleExportImage, handleExportPDF: handleExportPDF, handleClear: handleClear, handleLockToggle: handleLockToggle, isAdmin: isAdmin, hasToolAccess: hasToolAccess, isGloballyUnlocked: isGloballyUnlocked }), jsxRuntime.jsxs("div", { className: "w-full h-full relative overflow-hidden", children: [jsxRuntime.jsx(Board, { roomId: roomId, queueAction: queueAction, hasToolAccess: hasToolAccess, ref: boardRef }), jsxRuntime.jsx(LeftSidebar, { queueAction: queueAction, hasToolAccess: hasToolAccess, shouldBeOpenByDefault: shouldSidebarBeOpen })] })] }));
42651
+ return (jsxRuntime.jsxs("div", { className: "relative w-full h-full overflow-hidden", style: { isolation: 'isolate' }, children: [jsxRuntime.jsx(TopToolbar, { queueAction: queueAction, handleExportImage: handleExportImage, handleExportPDF: handleExportPDF, handleClear: handleClear, handleLockToggle: handleLockToggle, isAdmin: isAdmin, hasToolAccess: hasToolAccess, isGloballyUnlocked: isGloballyUnlocked, hasVideoBackground: !!videoStream }), jsxRuntime.jsxs("div", { className: "w-full h-full relative overflow-hidden", style: { isolation: 'isolate' }, children: [videoStream && (jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black", style: { zIndex: 0, pointerEvents: 'none' }, children: jsxRuntime.jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, style: {
42652
+ position: 'absolute',
42653
+ inset: 0,
42654
+ width: '100%',
42655
+ height: '100%',
42656
+ objectFit: 'contain',
42657
+ pointerEvents: 'none',
42658
+ } }) })), jsxRuntime.jsx("div", { className: "relative w-full h-full", style: { zIndex: 1 }, children: jsxRuntime.jsx(Board, { roomId: roomId, queueAction: queueAction, hasToolAccess: hasToolAccess, ref: boardRef }) }), jsxRuntime.jsx(LeftSidebar, { queueAction: queueAction, hasToolAccess: hasToolAccess, shouldBeOpenByDefault: shouldSidebarBeOpen, disableBackgroundChange: transparentBackground || !!videoStream, hasVideoBackground: !!videoStream })] })] }));
42552
42659
  };
42553
42660
 
42554
42661
  const useCapture = () => {