@ngenux/ngage-whiteboarding 1.0.3 → 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 +0 -94
- package/dist/index.esm.js +78 -9
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +78 -9
- 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.map +1 -1
- 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 +1 -1
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(() => ({
|
|
@@ -37264,16 +37282,20 @@ const LeftSidebar = ({ queueAction, hasToolAccess = false, shouldBeOpenByDefault
|
|
|
37264
37282
|
width: '250px',
|
|
37265
37283
|
maxHeight: '80vh',
|
|
37266
37284
|
cursor: isDragging ? 'grabbing' : 'grab'
|
|
37267
|
-
}, 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
|
|
37268
|
-
? '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'
|
|
37269
37287
|
: state.color === color
|
|
37270
|
-
? 'border-
|
|
37288
|
+
? 'border-[3px] border-yellow-400 dark:border-yellow-300 scale-125 shadow-lg'
|
|
37271
37289
|
: color === '#FFFFFF'
|
|
37272
|
-
? 'border-gray-
|
|
37273
|
-
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`, style: {
|
|
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: {
|
|
37274
37292
|
backgroundColor: color,
|
|
37275
|
-
boxShadow: color === '#FFFFFF'
|
|
37276
|
-
|
|
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
|
|
37277
37299
|
? 'opacity-50 cursor-not-allowed border-gray-200 dark:border-gray-700'
|
|
37278
37300
|
: state.backgroundColor === color
|
|
37279
37301
|
? 'border-blue-400 dark:border-blue-500 scale-110'
|
|
@@ -42455,10 +42477,57 @@ const Whiteboard = ({ roomId, isAdmin = false, allowedUsers = [], userId, transp
|
|
|
42455
42477
|
}, [state.activeDrawings, state.lastClearTimestamp, dispatch]);
|
|
42456
42478
|
// Export functions to pass to toolbar
|
|
42457
42479
|
const handleExportImage = React.useCallback((format) => {
|
|
42458
|
-
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
|
|
42459
42528
|
boardRef.current.exportAsImage(format);
|
|
42460
42529
|
}
|
|
42461
|
-
}, []);
|
|
42530
|
+
}, [videoStream]);
|
|
42462
42531
|
const handleExportPDF = React.useCallback(() => {
|
|
42463
42532
|
if (boardRef.current) {
|
|
42464
42533
|
boardRef.current.exportAsPDF();
|