@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.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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
37264
|
-
:
|
|
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-
|
|
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 = () => {
|