@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/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 border-2 transition-all ${!hasToolAccess
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-purple-400 dark:border-purple-500 scale-110'
37288
+ ? 'border-[3px] border-yellow-400 dark:border-yellow-300 scale-125 shadow-lg'
37271
37289
  : color === '#FFFFFF'
37272
- ? 'border-gray-400 dark:border-gray-500 hover:border-gray-500 dark:hover:border-gray-400'
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' ? 'inset 0 0 0 1px rgba(0,0,0,0.1)' : 'none'
37276
- }, onClick: () => handleColorChange(color), disabled: !hasToolAccess, title: hasToolAccess ? (color === '#FFFFFF' ? 'White' : color) : 'Access restricted' }, color))) })] }), jsxRuntime.jsxs("div", { className: "mb-4", children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200 mb-2", children: "Background" }), 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
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();