@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.d.ts CHANGED
@@ -4,7 +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 type { WhiteboardProps } from './src/components/Whiteboard';
7
+ export type { WhiteboardProps } from './src/components/Whiteboard/index';
8
8
  export type { ShapeProps, ToolType, StrokeStyle, WhiteboardState, DrawingAction, CompressedData } from './src/types';
9
9
  export interface WhiteboardProviderProps {
10
10
  children: React.ReactNode;
@@ -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,6BAA6B,CAAC;AACnE,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,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
@@ -36710,9 +36710,27 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
36710
36710
  const activeShapes = Object.values(state.activeDrawings);
36711
36711
  return activeShapes.map(shape => (jsx(MemoizedShapeComponent, { shape: shape }, `active-${shape.id}`)));
36712
36712
  }, [state.activeDrawings]);
36713
+ const getStageDataURL = useCallback((format = 'png') => {
36714
+ if (!stageRef.current) {
36715
+ return '';
36716
+ }
36717
+ try {
36718
+ const stage = stageRef.current;
36719
+ return stage.toDataURL({
36720
+ mimeType: format === 'png' ? 'image/png' : 'image/jpeg',
36721
+ quality: 1,
36722
+ pixelRatio: 2,
36723
+ });
36724
+ }
36725
+ catch (error) {
36726
+ console.error('Failed to get stage data URL:', error);
36727
+ return '';
36728
+ }
36729
+ }, []);
36713
36730
  useImperativeHandle(ref, () => ({
36714
36731
  exportAsImage,
36715
36732
  exportAsPDF,
36733
+ getStageDataURL,
36716
36734
  }));
36717
36735
  // Memoized cursor style for performance
36718
36736
  const cursorStyle = useMemo(() => ({
@@ -36720,7 +36738,7 @@ const BoardComponent = forwardRef(({ roomId = 'default-room', queueAction, hasTo
36720
36738
  state.tool === 'select' ? 'default' :
36721
36739
  state.tool === 'pan' ? 'grab' : 'crosshair'
36722
36740
  }), [hasToolAccess, state.tool]);
36723
- return (jsx("div", { ref: containerRef, className: "w-full h-full bg-white relative", children: 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: jsxs(Layer, { children: [useMemo(() => {
36741
+ return (jsx("div", { ref: containerRef, className: "w-full h-full relative", style: { backgroundColor: state.backgroundColor }, children: 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: jsxs(Layer, { children: [useMemo(() => {
36724
36742
  if (state.backgroundColor === 'transparent')
36725
36743
  return null;
36726
36744
  return (jsx(Rect, { x: 0, y: 0, width: size.width, height: size.height, fill: state.backgroundColor, listening: false }));
@@ -37030,7 +37048,7 @@ const Undo2 = createLucideIcon("Undo2", [
37030
37048
  ]);
37031
37049
 
37032
37050
  // Top Toolbar Component
37033
- const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockToggle, isAdmin = false, hasToolAccess = false, isGloballyUnlocked = false, shouldBeOpenByDefault = true }) => {
37051
+ const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockToggle, isAdmin = false, hasToolAccess = false, isGloballyUnlocked = false, shouldBeOpenByDefault = true, hasVideoBackground = false }) => {
37034
37052
  const { state, dispatch } = useWhiteboard();
37035
37053
  const [isVisible, setIsVisible] = useState(shouldBeOpenByDefault);
37036
37054
  const handleToggleVisibility = () => {
@@ -37094,16 +37112,16 @@ const TopToolbar = ({ queueAction, handleExportImage, handleClear, handleLockTog
37094
37112
  }
37095
37113
  }
37096
37114
  };
37097
- return (jsxs(Fragment, { children: [jsxs("div", { className: "absolute top-5 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-40", 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: [isAdmin && (jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${isGloballyUnlocked
37115
+ 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: [isAdmin && (jsx("button", { className: `w-10 h-10 flex items-center justify-center rounded transition-colors ${isGloballyUnlocked
37098
37116
  ? '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'
37099
37117
  : '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
37100
37118
  ? 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'
37101
37119
  : state.tool === tool.type
37102
37120
  ? 'bg-purple-100 dark:bg-purple-900/50 text-purple-600 dark:text-purple-300'
37103
- : '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__default.cloneElement(tool.icon, { className: 'text-current' }) }, tool.type))), 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: jsx(ChevronUp, { size: 16, className: "text-current" }) })] }) }))] }), jsxs("div", { className: "absolute top-5 right-20 flex items-center gap-2 z-40", children: [isAdmin && (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: [jsx(Eraser, { size: 16, className: "text-current" }), "Clear"] })), 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: [jsx(Save, { size: 16, className: "text-current" }), "Save"] })] })] }));
37121
+ : '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__default.cloneElement(tool.icon, { className: 'text-current' }) }, tool.type))), 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: jsx(ChevronUp, { size: 16, className: "text-current" }) })] }) }))] }), jsxs("div", { className: "absolute top-5 right-20 flex items-center gap-2 z-10", children: [isAdmin && (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: [jsx(Eraser, { size: 16, className: "text-current" }), "Clear"] })), 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: [jsx(Save, { size: 16, className: "text-current" }), "Save"] })] })] }));
37104
37122
  };
37105
37123
  // Left Sidebar Component
37106
- const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault = false }) => {
37124
+ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault = false, disableBackgroundChange = false, hasVideoBackground = false }) => {
37107
37125
  const { state, dispatch } = useWhiteboard();
37108
37126
  const [isVisible, setIsVisible] = useState(shouldBeOpenByDefault);
37109
37127
  const [isDragging, setIsDragging] = useState(false);
@@ -37111,6 +37129,13 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37111
37129
  const [position, setPosition] = useState({ x: 20, y: 80 });
37112
37130
  const [hasEverHadAccess, setHasEverHadAccess] = useState(hasToolAccess);
37113
37131
  const [wasManuallyClosedAfterAccess, setWasManuallyClosedAfterAccess] = useState(false);
37132
+ // Set white as default stroke color when video background is active
37133
+ useEffect(() => {
37134
+ if (hasVideoBackground && state.color === '#000000') {
37135
+ // Only change if currently black, to avoid overriding user's choice
37136
+ dispatch({ type: 'SET_COLOR', payload: '#FFFFFF' });
37137
+ }
37138
+ }, [hasVideoBackground, state.color, dispatch]);
37114
37139
  // Track initial access grant
37115
37140
  useEffect(() => {
37116
37141
  if (hasToolAccess && !hasEverHadAccess) {
@@ -37139,7 +37164,7 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37139
37164
  setWasManuallyClosedAfterAccess(false);
37140
37165
  };
37141
37166
  const strokeColors = [
37142
- '#000000', '#DC2626', '#059669', '#2563EB', '#EA580C', '#7C3AED'
37167
+ '#FFFFFF', '#000000', '#DC2626', '#059669', '#2563EB', '#EA580C', '#7C3AED'
37143
37168
  ];
37144
37169
  const backgroundColors = [
37145
37170
  '#FFFFFF', '#F8FAFC', '#FEF2F2', '#F0FDF4', '#EFF6FF', '#1F2937', 'transparent'
@@ -37188,7 +37213,7 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37188
37213
  dispatch({ type: 'SET_COLOR', payload: color });
37189
37214
  };
37190
37215
  const handleBackgroundColorChange = (color) => {
37191
- if (!hasToolAccess) {
37216
+ if (!hasToolAccess || disableBackgroundChange) {
37192
37217
  console.warn('[STYLE_ACCESS] User does not have access to change background');
37193
37218
  return;
37194
37219
  }
@@ -37229,19 +37254,28 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37229
37254
  if (!hasToolAccess) {
37230
37255
  return null;
37231
37256
  }
37232
- return (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: jsx(ChevronRight, { size: 16, className: "text-current" }) }));
37257
+ return (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: jsx(ChevronRight, { size: 16, className: "text-current" }) }));
37233
37258
  }
37234
- return (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: {
37259
+ return (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: {
37235
37260
  left: position.x,
37236
37261
  top: position.y,
37237
37262
  width: '250px',
37238
37263
  maxHeight: '80vh',
37239
37264
  cursor: isDragging ? 'grabbing' : 'grab'
37240
- }, onMouseDown: handleMouseDown, children: [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: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-200", children: "Style Panel" }), !hasToolAccess && (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" }))] }), 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: jsx(ChevronLeft, { size: 14, className: "text-gray-600 dark:text-gray-300" }) })] }), jsxs("div", { className: "sidebar-content p-3 overflow-y-auto", style: { maxHeight: 'calc(80vh - 40px)' }, children: [!hasToolAccess && (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: [jsx("div", { className: "text-orange-700 dark:text-orange-300 text-sm font-medium mb-1", children: "\uD83D\uDD12 Tools Locked" }), jsx("div", { className: "text-orange-600 dark:text-orange-400 text-xs", children: "Contact admin for access" })] })), jsxs("div", { className: "mb-4", children: [jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke Color" }), jsx("div", { className: "grid grid-cols-6 gap-1", children: strokeColors.map((color) => (jsx("button", { className: `w-6 h-6 rounded border-2 transition-all ${!hasToolAccess
37241
- ? 'opacity-50 cursor-not-allowed border-gray-200 dark:border-gray-700'
37265
+ }, onMouseDown: handleMouseDown, children: [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: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-200", children: "Style Panel" }), !hasToolAccess && (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" }))] }), 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: jsx(ChevronLeft, { size: 14, className: "text-gray-600 dark:text-gray-300" }) })] }), jsxs("div", { className: "sidebar-content p-3 overflow-y-auto", style: { maxHeight: 'calc(80vh - 40px)' }, children: [!hasToolAccess && (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: [jsx("div", { className: "text-orange-700 dark:text-orange-300 text-sm font-medium mb-1", children: "\uD83D\uDD12 Tools Locked" }), jsx("div", { className: "text-orange-600 dark:text-orange-400 text-xs", children: "Contact admin for access" })] })), jsxs("div", { className: "mb-4", children: [jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke Color" }), jsx("div", { className: "grid grid-cols-6 gap-1", children: strokeColors.map((color) => (jsx("button", { className: `w-6 h-6 rounded transition-all relative ${!hasToolAccess
37266
+ ? 'opacity-50 cursor-not-allowed border-2 border-gray-200 dark:border-gray-700'
37242
37267
  : state.color === color
37243
- ? 'border-purple-400 dark:border-purple-500 scale-110'
37244
- : '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))) })] }), jsxs("div", { className: "mb-4", children: [jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Background" }), jsx("div", { className: "grid grid-cols-6 gap-1", children: backgroundColors.map((color) => (jsx("button", { className: `w-6 h-6 rounded border-2 transition-all relative ${!hasToolAccess
37268
+ ? 'border-[3px] border-yellow-400 dark:border-yellow-300 scale-125 shadow-lg'
37269
+ : color === '#FFFFFF'
37270
+ ? 'border-2 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500'
37271
+ : 'border-2 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`, style: {
37272
+ backgroundColor: color,
37273
+ boxShadow: color === '#FFFFFF'
37274
+ ? 'inset 0 0 0 1px rgba(0,0,0,0.15)'
37275
+ : state.color === color
37276
+ ? '0 4px 12px rgba(234, 179, 8, 0.5), 0 0 0 2px rgba(234, 179, 8, 0.3)'
37277
+ : 'none'
37278
+ }, onClick: () => handleColorChange(color), disabled: !hasToolAccess, title: hasToolAccess ? (color === '#FFFFFF' ? 'White' : color) : 'Access restricted', children: state.color === color && (jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: 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: jsx("polyline", { points: "20 6 9 17 4 12" }) }) })) }, color))) })] }), jsxs("div", { className: "mb-4", children: [jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Background" }), disableBackgroundChange && (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: 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 && (jsx("div", { className: "grid grid-cols-6 gap-1", children: backgroundColors.map((color) => (jsx("button", { className: `w-6 h-6 rounded border-2 transition-all relative ${!hasToolAccess
37245
37279
  ? 'opacity-50 cursor-not-allowed border-gray-200 dark:border-gray-700'
37246
37280
  : state.backgroundColor === color
37247
37281
  ? 'border-blue-400 dark:border-blue-500 scale-110'
@@ -37249,7 +37283,7 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
37249
37283
  backgroundColor: color === 'transparent' ? 'white' : color
37250
37284
  }, onClick: () => handleBackgroundColorChange(color), disabled: !hasToolAccess, title: hasToolAccess ? (color === 'transparent' ? 'Transparent' : color) : 'Access restricted', children: color === 'transparent' && (jsx("div", { className: "absolute inset-0 bg-gradient-to-br from-red-500 to-red-500 opacity-50 rounded", style: {
37251
37285
  background: 'linear-gradient(45deg, transparent 40%, #ef4444 40%, #ef4444 60%, transparent 60%)'
37252
- } })) }, color))) })] }), jsxs("div", { className: "mb-4", children: [jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke width" }), jsx("div", { className: "flex gap-2", children: strokeWidths.map((width) => (jsx("button", { className: `flex-1 p-2 rounded flex items-center justify-center ${!hasToolAccess
37286
+ } })) }, color))) }))] }), jsxs("div", { className: "mb-4", children: [jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Stroke width" }), jsx("div", { className: "flex gap-2", children: strokeWidths.map((width) => (jsx("button", { className: `flex-1 p-2 rounded flex items-center justify-center ${!hasToolAccess
37253
37287
  ? 'opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600'
37254
37288
  : state.strokeWidth === width
37255
37289
  ? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300'
@@ -42234,7 +42268,7 @@ const disconnectSocket = () => {
42234
42268
  }
42235
42269
  };
42236
42270
 
42237
- const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42271
+ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transparentBackground = false, videoStream }) => {
42238
42272
  const { state, dispatch, setQueueAction, requestStateFromPeers, webSocketUrl } = useWhiteboard();
42239
42273
  const [lastCollaborativeAction, setLastCollaborativeAction] = useState(null);
42240
42274
  const [hasRequestedState, setHasRequestedState] = useState(false);
@@ -42248,6 +42282,25 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42248
42282
  useEffect(() => {
42249
42283
  dispatch({ type: 'SET_USER_ID', payload: userId });
42250
42284
  }, [userId, dispatch]);
42285
+ // Set background color based on transparentBackground prop or videoStream presence
42286
+ useEffect(() => {
42287
+ if (transparentBackground || videoStream) {
42288
+ // Force transparent background when transparentBackground flag is set or video is active
42289
+ dispatch({ type: 'SET_BACKGROUND_COLOR', payload: 'transparent' });
42290
+ }
42291
+ else if (!transparentBackground && !videoStream) {
42292
+ // Reset to white when screen share stops and transparentBackground is false
42293
+ dispatch({ type: 'SET_BACKGROUND_COLOR', payload: '#FFFFFF' });
42294
+ }
42295
+ }, [transparentBackground, videoStream, dispatch]);
42296
+ // Video ref for setting srcObject
42297
+ const videoRef = useRef(null);
42298
+ // Set video srcObject when videoStream changes
42299
+ useEffect(() => {
42300
+ if (videoRef.current && videoStream) {
42301
+ videoRef.current.srcObject = videoStream;
42302
+ }
42303
+ }, [videoStream]);
42251
42304
  // Determine if current user has tool access (using synced allowed users for cross-computer sync)
42252
42305
  const hasToolAccess = useMemo(() => {
42253
42306
  return isAdmin || syncedAllowedUsers.includes(userId) || isGloballyUnlocked;
@@ -42404,10 +42457,57 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42404
42457
  }, [state.activeDrawings, state.lastClearTimestamp, dispatch]);
42405
42458
  // Export functions to pass to toolbar
42406
42459
  const handleExportImage = useCallback((format) => {
42407
- if (boardRef.current) {
42460
+ if (!boardRef.current) {
42461
+ return;
42462
+ }
42463
+ // If video is active, capture both video and canvas
42464
+ if (videoStream && videoRef.current) {
42465
+ const video = videoRef.current;
42466
+ const canvas = document.createElement('canvas');
42467
+ const ctx = canvas.getContext('2d');
42468
+ if (!ctx)
42469
+ return;
42470
+ // Set canvas size to match the whiteboard
42471
+ canvas.width = video.videoWidth || video.offsetWidth;
42472
+ canvas.height = video.videoHeight || video.offsetHeight;
42473
+ try {
42474
+ // Draw the video frame
42475
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
42476
+ // Get the Konva stage data URL
42477
+ const stageDataURL = boardRef.current.getStageDataURL(format);
42478
+ // Create an image from the stage
42479
+ const stageImage = new Image();
42480
+ stageImage.onload = () => {
42481
+ // Draw the annotations on top of the video
42482
+ ctx.drawImage(stageImage, 0, 0, canvas.width, canvas.height);
42483
+ // Convert to blob and download
42484
+ canvas.toBlob((blob) => {
42485
+ if (blob) {
42486
+ const timestamp = new Date().toISOString().slice(0, 16).replace('T', '_').replace(/:/g, '-');
42487
+ const filename = `whiteboard_video_annotation_${timestamp}.${format}`;
42488
+ const link = document.createElement('a');
42489
+ link.download = filename;
42490
+ link.href = URL.createObjectURL(blob);
42491
+ document.body.appendChild(link);
42492
+ link.click();
42493
+ document.body.removeChild(link);
42494
+ URL.revokeObjectURL(link.href);
42495
+ }
42496
+ }, format === 'png' ? 'image/png' : 'image/jpeg', 1.0);
42497
+ };
42498
+ stageImage.src = stageDataURL;
42499
+ }
42500
+ catch (error) {
42501
+ console.error('Failed to export video with annotations:', error);
42502
+ // Fallback to regular export
42503
+ boardRef.current.exportAsImage(format);
42504
+ }
42505
+ }
42506
+ else {
42507
+ // No video - regular export
42408
42508
  boardRef.current.exportAsImage(format);
42409
42509
  }
42410
- }, []);
42510
+ }, [videoStream]);
42411
42511
  const handleExportPDF = useCallback(() => {
42412
42512
  if (boardRef.current) {
42413
42513
  boardRef.current.exportAsPDF();
@@ -42528,7 +42628,14 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId }) => {
42528
42628
  disconnectSocket();
42529
42629
  };
42530
42630
  }, []);
42531
- return (jsxs("div", { className: "relative h-screen", children: [jsx(TopToolbar, { queueAction: queueAction, handleExportImage: handleExportImage, handleExportPDF: handleExportPDF, handleClear: handleClear, handleLockToggle: handleLockToggle, isAdmin: isAdmin, hasToolAccess: hasToolAccess, isGloballyUnlocked: isGloballyUnlocked }), jsxs("div", { className: "w-full h-full relative overflow-hidden", children: [jsx(Board, { roomId: roomId, queueAction: queueAction, hasToolAccess: hasToolAccess, ref: boardRef }), jsx(LeftSidebar, { queueAction: queueAction, hasToolAccess: hasToolAccess, shouldBeOpenByDefault: shouldSidebarBeOpen })] })] }));
42631
+ return (jsxs("div", { className: "relative w-full h-full overflow-hidden", style: { isolation: 'isolate' }, children: [jsx(TopToolbar, { queueAction: queueAction, handleExportImage: handleExportImage, handleExportPDF: handleExportPDF, handleClear: handleClear, handleLockToggle: handleLockToggle, isAdmin: isAdmin, hasToolAccess: hasToolAccess, isGloballyUnlocked: isGloballyUnlocked, hasVideoBackground: !!videoStream }), jsxs("div", { className: "w-full h-full relative overflow-hidden", style: { isolation: 'isolate' }, children: [videoStream && (jsx("div", { className: "absolute inset-0 bg-black", style: { zIndex: 0, pointerEvents: 'none' }, children: jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, style: {
42632
+ position: 'absolute',
42633
+ inset: 0,
42634
+ width: '100%',
42635
+ height: '100%',
42636
+ objectFit: 'contain',
42637
+ pointerEvents: 'none',
42638
+ } }) })), jsx("div", { className: "relative w-full h-full", style: { zIndex: 1 }, children: jsx(Board, { roomId: roomId, queueAction: queueAction, hasToolAccess: hasToolAccess, ref: boardRef }) }), jsx(LeftSidebar, { queueAction: queueAction, hasToolAccess: hasToolAccess, shouldBeOpenByDefault: shouldSidebarBeOpen, disableBackgroundChange: transparentBackground || !!videoStream, hasVideoBackground: !!videoStream })] })] }));
42532
42639
  };
42533
42640
 
42534
42641
  const useCapture = () => {