@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/README.md +344 -342
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +125 -18
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +125 -18
- package/dist/index.js.map +1 -1
- package/dist/src/components/Whiteboard/Board.d.ts +1 -0
- package/dist/src/components/Whiteboard/Board.d.ts.map +1 -1
- package/dist/src/components/Whiteboard/Toolbar.d.ts +2 -0
- package/dist/src/components/Whiteboard/Toolbar.d.ts.map +1 -1
- package/dist/src/components/Whiteboard/index.d.ts +2 -0
- package/dist/src/components/Whiteboard/index.d.ts.map +1 -1
- package/dist/src/utils/video-coordinates.d.ts +36 -0
- package/dist/src/utils/video-coordinates.d.ts.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +3 -3
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;
|
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,
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
37244
|
-
:
|
|
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-
|
|
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 = () => {
|