@@ -33,6 +33,7 @@ import {
33
33
getDashArrayDotted ,
34
34
RECT_CORNER_RADIUS_FACTOR ,
35
35
ROUND_RADIUS_FACTOR ,
36
+ TEXT_ADJUSTED_HEIGHT ,
36
37
WS_URL ,
37
38
} from "@/config/constants" ;
38
39
import { MessageQueue } from "./MessageQueue" ;
@@ -129,6 +130,9 @@ export class CanvasEngine {
129
130
private currentTheme : "light" | "dark" | null = null ;
130
131
private onLiveUpdateFromSelection ?: ( shape : Shape ) => void ;
131
132
133
+ private activeTextarea : HTMLTextAreaElement | null = null ;
134
+ private activeTextPosition : { x : number ; y : number } | null = null ;
135
+
132
136
constructor (
133
137
canvas : HTMLCanvasElement ,
134
138
roomId : string | null ,
@@ -227,8 +231,6 @@ export class CanvasEngine {
227
231
console . log ( `Assigned connection ID: ${ this . connectionId } ` ) ;
228
232
}
229
233
230
- // console.log("engine this.connectionId = ", this.connectionId);
231
- // console.log("engine this.userId = ", this.userId);
232
234
switch ( data . type ) {
233
235
case WsDataType . USER_JOINED :
234
236
if (
@@ -262,36 +264,15 @@ export class CanvasEngine {
262
264
break ;
263
265
264
266
case WsDataType . CURSOR_MOVE :
265
- // console.log("=== case WsDataType.CURSOR_MOVE ===");
266
- // console.log(
267
- // `this.userId=${this.userId} & data.userId=${data.userId}`
268
- // );
269
- // console.log(
270
- // `this.userName=${this.userName} & data.userName=${data.userName}`
271
- // );
272
- // console.log(
273
- // `this.connectionId=${this.connectionId} & data.connectionId=${data.connectionId}`
274
- // );
275
267
if ( data . userId !== this . userId && data . message ) {
276
- // const decrypted = await decryptData(
277
- // data.message,
278
- // this.encryptionKey!
279
- // );
280
- // console.log("coords/data.message: ", data.message);
281
268
const coords = JSON . parse ( data . message ) ;
282
- // const coords:{x:number,y:number} = data.message;
283
- // console.log("coords: ", coords);
284
- // console.log(
285
- // `x: ${coords.x}, y: ${coords.y}, user: ${data.userName}`
286
- // );
287
269
const key = `${ data . userId } -${ data . connectionId } ` ;
288
270
this . remoteCursors . set ( key , {
289
271
x : coords . x ,
290
272
y : coords . y ,
291
273
userId : data . userId ,
292
274
userName : data . userName ?? data . userId ,
293
275
} ) ;
294
- // console.log("remoteCursors map size:", this.remoteCursors.size);
295
276
this . clearCanvas ( ) ;
296
277
}
297
278
break ;
@@ -357,23 +338,9 @@ export class CanvasEngine {
357
338
connectionId : data . connectionId ,
358
339
shapeId : streamedShape . id ,
359
340
} ) ;
360
- // console.log("STREAM_SHAPE streamKey2 = ", streamKey);
361
- // console.log(
362
- // "STREAM_SHAPE this.remoteStreamingShapes before = ",
363
- // this.remoteStreamingShapes
364
- // );
365
341
this . remoteStreamingShapes . set ( streamKey , streamedShape ) ;
366
- // console.log(
367
- // "STREAM_SHAPE this.remoteStreamingShapes after = ",
368
- // this.remoteStreamingShapes
369
- // );
370
342
const userConnKey = `${ data . userId } -${ data . connectionId } ` ;
371
343
this . remoteClickIndicators . set ( userConnKey , Date . now ( ) ) ;
372
- // console.log(
373
- // "this.remoteClickIndicators = ",
374
- // this.remoteClickIndicators
375
- // );
376
-
377
344
this . clearCanvas ( ) ;
378
345
} catch ( err ) {
379
346
console . error ( "Error handling streamed shape:" , err ) ;
@@ -425,21 +392,12 @@ export class CanvasEngine {
425
392
this . encryptionKey !
426
393
) ;
427
394
const shape = JSON . parse ( decrypted ) ;
428
- // console.log(
429
- // "DRAW this.remoteStreamingShapes before = ",
430
- // this.remoteStreamingShapes
431
- // );
432
395
const streamKey = getStreamKey ( {
433
396
userId : data . userId ,
434
397
connectionId : data . connectionId ,
435
398
shapeId : shape . id ,
436
399
} ) ;
437
- // console.log("streamKey = ", streamKey);
438
400
this . remoteStreamingShapes . delete ( streamKey ) ;
439
- // console.log(
440
- // "DRAW this.remoteStreamingShapes after = ",
441
- // this.remoteStreamingShapes
442
- // );
443
401
this . updateShapes ( [ shape ] ) ;
444
402
this . notifyShapeCountChange ( ) ;
445
403
}
@@ -860,6 +818,7 @@ export class CanvasEngine {
860
818
shape . x ,
861
819
shape . y ,
862
820
shape . width ,
821
+ shape . height ,
863
822
shape . text ,
864
823
shape . strokeFill ,
865
824
shape . fontStyle ,
@@ -870,6 +829,11 @@ export class CanvasEngine {
870
829
}
871
830
} ) ;
872
831
832
+ if ( this . activeTextarea && this . activeTextPosition ) {
833
+ const { x, y } = this . activeTextPosition ;
834
+ this . activeTextarea . style . transform = `translate(${ x * this . scale + this . panX } px, ${ y * this . scale + this . panY } px)` ;
835
+ }
836
+
873
837
this . remoteStreamingShapes . forEach ( ( shape ) => {
874
838
if ( shape . type === "rectangle" ) {
875
839
this . drawRect (
@@ -938,6 +902,7 @@ export class CanvasEngine {
938
902
shape . x ,
939
903
shape . y ,
940
904
shape . width ,
905
+ shape . height ,
941
906
shape . text ,
942
907
shape . strokeFill ,
943
908
shape . fontStyle ,
@@ -960,28 +925,21 @@ export class CanvasEngine {
960
925
}
961
926
962
927
this . remoteCursors . forEach ( ( cursor , userConnKey ) => {
963
- // console.log("cursor = ", cursor);
964
- // console.log("userConnKey = ", userConnKey);
965
928
const { x, y, userId, userName } = cursor ;
966
929
const screenX = x * this . scale + this . panX ;
967
930
const screenY = y * this . scale + this . panY ;
968
931
969
932
const cursorColor : string = getClientColor ( { userId, userName } ) ;
970
- // const labelStrokeColor = this.currentTheme === "dark" ? "#2f6330" : COLOR_DRAG_CALL;
971
933
const boxBackground = cursorColor ;
972
934
const boxTextColor = COLOR_CHARCOAL_BLACK ;
973
935
const pointerWidth = 12 ;
974
936
const pointerHeight = 15 ;
975
937
976
938
const lastClickTime = this . remoteClickIndicators . get ( userConnKey ) ;
977
- if ( lastClickTime ) {
978
- // console.log("lastClickTime = ", lastClickTime);
979
- }
980
939
const showClickCircle =
981
940
! ! lastClickTime && Date . now ( ) - lastClickTime < 800 ;
982
941
983
942
if ( showClickCircle ) {
984
- // console.log("showClickCircle = ", showClickCircle);
985
943
this . ctx . beginPath ( ) ;
986
944
this . ctx . arc ( x , y , 14 , 0 , Math . PI * 2 , false ) ;
987
945
this . ctx . lineWidth = 3 ;
@@ -1050,7 +1008,7 @@ export class CanvasEngine {
1050
1008
this . ctx . strokeStyle = COLOR_WHITE ;
1051
1009
this . ctx . stroke ( ) ;
1052
1010
1053
- // Optional highlight stroke for speaker // Option 2 for showing active indicator
1011
+ // Highlight stroke for speaker // Option 2 for showing active indicator
1054
1012
// this.ctx.beginPath();
1055
1013
// this.ctx.roundRect(boxX - 2, boxY - 2, boxWidth + 4, boxHeight + 4, 8);
1056
1014
// this.ctx.strokeStyle = labelStrokeColor;
@@ -1079,7 +1037,6 @@ export class CanvasEngine {
1079
1037
1080
1038
mouseDownHandler = ( e : MouseEvent ) => {
1081
1039
const { x, y } = this . transformPanScale ( e . clientX , e . clientY ) ;
1082
- // console.log("x, y: ", x, y);
1083
1040
if ( this . activeTool === "selection" ) {
1084
1041
const selectedShape = this . SelectionController . getSelectedShape ( ) ;
1085
1042
if ( selectedShape ) {
@@ -1128,7 +1085,7 @@ export class CanvasEngine {
1128
1085
strokeStyle : this . strokeStyle ,
1129
1086
fillStyle : this . fillStyle ,
1130
1087
} ) ;
1131
- } else if ( this . activeTool == "text" ) {
1088
+ } else if ( this . activeTool === "text" ) {
1132
1089
this . clicked = false ;
1133
1090
this . handleTexty ( e ) ;
1134
1091
} else if ( this . activeTool === "eraser" ) {
@@ -1212,7 +1169,6 @@ export class CanvasEngine {
1212
1169
const height = y - this . startY ;
1213
1170
1214
1171
let shape : Shape | null = null ;
1215
- // console.log("this.streamingShapeId in ");
1216
1172
switch ( this . activeTool ) {
1217
1173
case "rectangle" :
1218
1174
shape = {
@@ -1640,8 +1596,6 @@ export class CanvasEngine {
1640
1596
clientY : touch . clientY ,
1641
1597
} ) ;
1642
1598
1643
- // console.log("Touch started at", touch.clientX, touch.clientY);
1644
-
1645
1599
this . mouseDownHandler ( simulatedMouse ) ;
1646
1600
} ;
1647
1601
@@ -1674,40 +1628,52 @@ export class CanvasEngine {
1674
1628
const { x, y } = this . transformPanScale ( e . clientX , e . clientY ) ;
1675
1629
1676
1630
const textarea = document . createElement ( "textarea" ) ;
1631
+ this . activeTextarea = textarea ;
1632
+ this . activeTextPosition = { x, y } ;
1677
1633
Object . assign ( textarea . style , {
1678
1634
position : "absolute" ,
1679
1635
display : "inline-block" ,
1680
1636
backfaceVisibility : "hidden" ,
1681
1637
margin : "0" ,
1682
1638
padding : "0" ,
1683
- border : "0" ,
1639
+ border : `1px dotted ${ this . strokeFill } ` ,
1684
1640
outline : "0" ,
1685
1641
resize : "none" ,
1686
1642
background : "transparent" ,
1687
- overflow : "hidden" ,
1688
- overflowWrap : "break-word" ,
1643
+ overflowX : "hidden" ,
1644
+ overflowY : "hidden" ,
1645
+ overflowWrap : "normal" ,
1689
1646
boxSizing : "content-box" ,
1690
1647
wordBreak : "normal" ,
1691
- whiteSpace : "pre-wrap " ,
1648
+ whiteSpace : "pre" ,
1692
1649
transform : `translate(${ x * this . scale + this . panX } px, ${ y * this . scale + this . panY } px)` ,
1693
1650
verticalAlign : "top" ,
1694
1651
opacity : "1" ,
1695
- filter : "var(--theme-filter)" ,
1652
+ wrap : "off" ,
1653
+ tabIndex : 0 ,
1654
+ dir : "auto" ,
1655
+ scrollbarWidth : "none" , // Firefox
1656
+ msOverflowStyle : "none" , // IE/Edge
1696
1657
width : "auto" ,
1697
- minHeight : "2rem " ,
1658
+ minHeight : "auto " ,
1698
1659
} ) ;
1699
- // console.log(
1700
- // `this.fontSize= ${this.fontSize}, this.fontFamily= ${this.fontFamily}, this.textAlign=${this.textAlign}, this.scale=${this.scale}`
1701
- // );
1702
1660
const calFont = getFontSize ( this . fontSize , this . scale ) ;
1703
1661
textarea . classList . add ( "collabydraw-texty" ) ;
1704
- textarea . dir = "auto" ;
1705
- textarea . tabIndex = 0 ;
1706
- textarea . wrap = "off" ;
1707
1662
textarea . style . color = this . strokeFill ;
1708
1663
const fontString = `${ calFont } px/1.2 ${ this . fontFamily === "normal" ? "Arial" : this . fontFamily === "hand-drawn" ? "Collabyfont, Xiaolai" : "Assistant" } ` ;
1709
1664
textarea . style . font = fontString ;
1710
- textarea . style . zIndex = "1" ;
1665
+ textarea . style . zIndex = "100" ;
1666
+
1667
+ const rawMaxWidth =
1668
+ window . innerWidth || document . documentElement . clientWidth ;
1669
+ const rawMaxHeight =
1670
+ window . innerHeight || document . documentElement . clientHeight ;
1671
+
1672
+ const calMaxWidth = rawMaxWidth - x - TEXT_ADJUSTED_HEIGHT ;
1673
+ const calMaxHeight = rawMaxHeight - y - TEXT_ADJUSTED_HEIGHT ;
1674
+
1675
+ textarea . style . maxWidth = `${ calMaxWidth } px` ;
1676
+ textarea . style . maxHeight = `${ calMaxHeight } px` ;
1711
1677
1712
1678
const collabydrawContainer = document . querySelector (
1713
1679
".collabydraw-textEditorContainer"
@@ -1731,23 +1697,24 @@ export class CanvasEngine {
1731
1697
}
1732
1698
1733
1699
span = document . createElement ( "span" ) ;
1734
-
1735
1700
Object . assign ( span . style , {
1736
1701
visibility : "hidden" ,
1737
1702
position : "absolute" ,
1738
1703
whiteSpace : "pre-wrap" ,
1739
1704
wordBreak : "break-word" ,
1740
1705
font : textarea . style . font ,
1741
- width : "auto" ,
1742
- height : "auto" ,
1706
+ padding : "0" ,
1707
+ margin : "0" ,
1708
+ lineHeight : "1.2" ,
1743
1709
} ) ;
1744
1710
1745
1711
span . textContent = textarea . value || " " ;
1746
1712
document . body . appendChild ( span ) ;
1747
1713
1748
1714
requestAnimationFrame ( ( ) => {
1749
- textarea . style . width = `${ Math . max ( span ! . offsetWidth + 10 , 50 ) } px` ;
1750
- textarea . style . height = `${ Math . max ( span ! . offsetHeight , 20 ) } px` ;
1715
+ textarea . style . width = `${ Math . max ( span ! . offsetWidth + TEXT_ADJUSTED_HEIGHT , TEXT_ADJUSTED_HEIGHT ) } px` ;
1716
+ textarea . style . height = `${ Math . max ( span ! . offsetHeight + TEXT_ADJUSTED_HEIGHT , TEXT_ADJUSTED_HEIGHT ) } px` ;
1717
+ textarea . style . overflow = "scroll" ;
1751
1718
} ) ;
1752
1719
} ;
1753
1720
@@ -1762,7 +1729,10 @@ export class CanvasEngine {
1762
1729
}
1763
1730
} ) ;
1764
1731
1732
+ let saveCalled = false ;
1765
1733
const save = ( ) => {
1734
+ if ( saveCalled ) return ;
1735
+ saveCalled = true ;
1766
1736
const text = textarea . value . trim ( ) ;
1767
1737
if ( ! text ) {
1768
1738
textarea . remove ( ) ;
@@ -1774,12 +1744,15 @@ export class CanvasEngine {
1774
1744
if ( ! span ) {
1775
1745
throw new Error ( "Span is null" ) ;
1776
1746
}
1747
+ this . activeTextarea = null ;
1748
+ this . activeTextPosition = null ;
1777
1749
const newShape : Shape = {
1778
1750
id : uuidv4 ( ) ,
1779
1751
type : "text" ,
1780
1752
x : x ,
1781
1753
y : y ,
1782
- width : span . offsetWidth ,
1754
+ width : textarea . offsetWidth ,
1755
+ height : textarea . offsetHeight - TEXT_ADJUSTED_HEIGHT ,
1783
1756
text,
1784
1757
fontSize : this . fontSize ,
1785
1758
fontFamily : this . fontFamily ,
@@ -1831,6 +1804,7 @@ export class CanvasEngine {
1831
1804
1832
1805
const handleClickOutside = ( e : MouseEvent ) => {
1833
1806
if ( ! textarea . contains ( e . target as Node ) ) {
1807
+ document . removeEventListener ( "mousedown" , handleClickOutside ) ;
1834
1808
save ( ) ;
1835
1809
}
1836
1810
} ;
@@ -2363,6 +2337,7 @@ export class CanvasEngine {
2363
2337
x : number ,
2364
2338
y : number ,
2365
2339
width : number ,
2340
+ height : number ,
2366
2341
text : string ,
2367
2342
fillStyle : string ,
2368
2343
fontStyle : FontStyle ,
0 commit comments