@@ -33,6 +33,7 @@ import {
3333 getDashArrayDotted ,
3434 RECT_CORNER_RADIUS_FACTOR ,
3535 ROUND_RADIUS_FACTOR ,
36+ TEXT_ADJUSTED_HEIGHT ,
3637 WS_URL ,
3738} from "@/config/constants" ;
3839import { MessageQueue } from "./MessageQueue" ;
@@ -129,6 +130,9 @@ export class CanvasEngine {
129130 private currentTheme : "light" | "dark" | null = null ;
130131 private onLiveUpdateFromSelection ?: ( shape : Shape ) => void ;
131132
133+ private activeTextarea : HTMLTextAreaElement | null = null ;
134+ private activeTextPosition : { x : number ; y : number } | null = null ;
135+
132136 constructor (
133137 canvas : HTMLCanvasElement ,
134138 roomId : string | null ,
@@ -227,8 +231,6 @@ export class CanvasEngine {
227231 console . log ( `Assigned connection ID: ${ this . connectionId } ` ) ;
228232 }
229233
230- // console.log("engine this.connectionId = ", this.connectionId);
231- // console.log("engine this.userId = ", this.userId);
232234 switch ( data . type ) {
233235 case WsDataType . USER_JOINED :
234236 if (
@@ -262,36 +264,15 @@ export class CanvasEngine {
262264 break ;
263265
264266 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- // );
275267 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);
281268 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- // );
287269 const key = `${ data . userId } -${ data . connectionId } ` ;
288270 this . remoteCursors . set ( key , {
289271 x : coords . x ,
290272 y : coords . y ,
291273 userId : data . userId ,
292274 userName : data . userName ?? data . userId ,
293275 } ) ;
294- // console.log("remoteCursors map size:", this.remoteCursors.size);
295276 this . clearCanvas ( ) ;
296277 }
297278 break ;
@@ -357,23 +338,9 @@ export class CanvasEngine {
357338 connectionId : data . connectionId ,
358339 shapeId : streamedShape . id ,
359340 } ) ;
360- // console.log("STREAM_SHAPE streamKey2 = ", streamKey);
361- // console.log(
362- // "STREAM_SHAPE this.remoteStreamingShapes before = ",
363- // this.remoteStreamingShapes
364- // );
365341 this . remoteStreamingShapes . set ( streamKey , streamedShape ) ;
366- // console.log(
367- // "STREAM_SHAPE this.remoteStreamingShapes after = ",
368- // this.remoteStreamingShapes
369- // );
370342 const userConnKey = `${ data . userId } -${ data . connectionId } ` ;
371343 this . remoteClickIndicators . set ( userConnKey , Date . now ( ) ) ;
372- // console.log(
373- // "this.remoteClickIndicators = ",
374- // this.remoteClickIndicators
375- // );
376-
377344 this . clearCanvas ( ) ;
378345 } catch ( err ) {
379346 console . error ( "Error handling streamed shape:" , err ) ;
@@ -425,21 +392,12 @@ export class CanvasEngine {
425392 this . encryptionKey !
426393 ) ;
427394 const shape = JSON . parse ( decrypted ) ;
428- // console.log(
429- // "DRAW this.remoteStreamingShapes before = ",
430- // this.remoteStreamingShapes
431- // );
432395 const streamKey = getStreamKey ( {
433396 userId : data . userId ,
434397 connectionId : data . connectionId ,
435398 shapeId : shape . id ,
436399 } ) ;
437- // console.log("streamKey = ", streamKey);
438400 this . remoteStreamingShapes . delete ( streamKey ) ;
439- // console.log(
440- // "DRAW this.remoteStreamingShapes after = ",
441- // this.remoteStreamingShapes
442- // );
443401 this . updateShapes ( [ shape ] ) ;
444402 this . notifyShapeCountChange ( ) ;
445403 }
@@ -860,6 +818,7 @@ export class CanvasEngine {
860818 shape . x ,
861819 shape . y ,
862820 shape . width ,
821+ shape . height ,
863822 shape . text ,
864823 shape . strokeFill ,
865824 shape . fontStyle ,
@@ -870,6 +829,11 @@ export class CanvasEngine {
870829 }
871830 } ) ;
872831
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+
873837 this . remoteStreamingShapes . forEach ( ( shape ) => {
874838 if ( shape . type === "rectangle" ) {
875839 this . drawRect (
@@ -938,6 +902,7 @@ export class CanvasEngine {
938902 shape . x ,
939903 shape . y ,
940904 shape . width ,
905+ shape . height ,
941906 shape . text ,
942907 shape . strokeFill ,
943908 shape . fontStyle ,
@@ -960,28 +925,21 @@ export class CanvasEngine {
960925 }
961926
962927 this . remoteCursors . forEach ( ( cursor , userConnKey ) => {
963- // console.log("cursor = ", cursor);
964- // console.log("userConnKey = ", userConnKey);
965928 const { x, y, userId, userName } = cursor ;
966929 const screenX = x * this . scale + this . panX ;
967930 const screenY = y * this . scale + this . panY ;
968931
969932 const cursorColor : string = getClientColor ( { userId, userName } ) ;
970- // const labelStrokeColor = this.currentTheme === "dark" ? "#2f6330" : COLOR_DRAG_CALL;
971933 const boxBackground = cursorColor ;
972934 const boxTextColor = COLOR_CHARCOAL_BLACK ;
973935 const pointerWidth = 12 ;
974936 const pointerHeight = 15 ;
975937
976938 const lastClickTime = this . remoteClickIndicators . get ( userConnKey ) ;
977- if ( lastClickTime ) {
978- // console.log("lastClickTime = ", lastClickTime);
979- }
980939 const showClickCircle =
981940 ! ! lastClickTime && Date . now ( ) - lastClickTime < 800 ;
982941
983942 if ( showClickCircle ) {
984- // console.log("showClickCircle = ", showClickCircle);
985943 this . ctx . beginPath ( ) ;
986944 this . ctx . arc ( x , y , 14 , 0 , Math . PI * 2 , false ) ;
987945 this . ctx . lineWidth = 3 ;
@@ -1050,7 +1008,7 @@ export class CanvasEngine {
10501008 this . ctx . strokeStyle = COLOR_WHITE ;
10511009 this . ctx . stroke ( ) ;
10521010
1053- // Optional highlight stroke for speaker // Option 2 for showing active indicator
1011+ // Highlight stroke for speaker // Option 2 for showing active indicator
10541012 // this.ctx.beginPath();
10551013 // this.ctx.roundRect(boxX - 2, boxY - 2, boxWidth + 4, boxHeight + 4, 8);
10561014 // this.ctx.strokeStyle = labelStrokeColor;
@@ -1079,7 +1037,6 @@ export class CanvasEngine {
10791037
10801038 mouseDownHandler = ( e : MouseEvent ) => {
10811039 const { x, y } = this . transformPanScale ( e . clientX , e . clientY ) ;
1082- // console.log("x, y: ", x, y);
10831040 if ( this . activeTool === "selection" ) {
10841041 const selectedShape = this . SelectionController . getSelectedShape ( ) ;
10851042 if ( selectedShape ) {
@@ -1128,7 +1085,7 @@ export class CanvasEngine {
11281085 strokeStyle : this . strokeStyle ,
11291086 fillStyle : this . fillStyle ,
11301087 } ) ;
1131- } else if ( this . activeTool == "text" ) {
1088+ } else if ( this . activeTool === "text" ) {
11321089 this . clicked = false ;
11331090 this . handleTexty ( e ) ;
11341091 } else if ( this . activeTool === "eraser" ) {
@@ -1212,7 +1169,6 @@ export class CanvasEngine {
12121169 const height = y - this . startY ;
12131170
12141171 let shape : Shape | null = null ;
1215- // console.log("this.streamingShapeId in ");
12161172 switch ( this . activeTool ) {
12171173 case "rectangle" :
12181174 shape = {
@@ -1640,8 +1596,6 @@ export class CanvasEngine {
16401596 clientY : touch . clientY ,
16411597 } ) ;
16421598
1643- // console.log("Touch started at", touch.clientX, touch.clientY);
1644-
16451599 this . mouseDownHandler ( simulatedMouse ) ;
16461600 } ;
16471601
@@ -1674,40 +1628,52 @@ export class CanvasEngine {
16741628 const { x, y } = this . transformPanScale ( e . clientX , e . clientY ) ;
16751629
16761630 const textarea = document . createElement ( "textarea" ) ;
1631+ this . activeTextarea = textarea ;
1632+ this . activeTextPosition = { x, y } ;
16771633 Object . assign ( textarea . style , {
16781634 position : "absolute" ,
16791635 display : "inline-block" ,
16801636 backfaceVisibility : "hidden" ,
16811637 margin : "0" ,
16821638 padding : "0" ,
1683- border : "0" ,
1639+ border : `1px dotted ${ this . strokeFill } ` ,
16841640 outline : "0" ,
16851641 resize : "none" ,
16861642 background : "transparent" ,
1687- overflow : "hidden" ,
1688- overflowWrap : "break-word" ,
1643+ overflowX : "hidden" ,
1644+ overflowY : "hidden" ,
1645+ overflowWrap : "normal" ,
16891646 boxSizing : "content-box" ,
16901647 wordBreak : "normal" ,
1691- whiteSpace : "pre-wrap " ,
1648+ whiteSpace : "pre" ,
16921649 transform : `translate(${ x * this . scale + this . panX } px, ${ y * this . scale + this . panY } px)` ,
16931650 verticalAlign : "top" ,
16941651 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
16961657 width : "auto" ,
1697- minHeight : "2rem " ,
1658+ minHeight : "auto " ,
16981659 } ) ;
1699- // console.log(
1700- // `this.fontSize= ${this.fontSize}, this.fontFamily= ${this.fontFamily}, this.textAlign=${this.textAlign}, this.scale=${this.scale}`
1701- // );
17021660 const calFont = getFontSize ( this . fontSize , this . scale ) ;
17031661 textarea . classList . add ( "collabydraw-texty" ) ;
1704- textarea . dir = "auto" ;
1705- textarea . tabIndex = 0 ;
1706- textarea . wrap = "off" ;
17071662 textarea . style . color = this . strokeFill ;
17081663 const fontString = `${ calFont } px/1.2 ${ this . fontFamily === "normal" ? "Arial" : this . fontFamily === "hand-drawn" ? "Collabyfont, Xiaolai" : "Assistant" } ` ;
17091664 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` ;
17111677
17121678 const collabydrawContainer = document . querySelector (
17131679 ".collabydraw-textEditorContainer"
@@ -1731,23 +1697,24 @@ export class CanvasEngine {
17311697 }
17321698
17331699 span = document . createElement ( "span" ) ;
1734-
17351700 Object . assign ( span . style , {
17361701 visibility : "hidden" ,
17371702 position : "absolute" ,
17381703 whiteSpace : "pre-wrap" ,
17391704 wordBreak : "break-word" ,
17401705 font : textarea . style . font ,
1741- width : "auto" ,
1742- height : "auto" ,
1706+ padding : "0" ,
1707+ margin : "0" ,
1708+ lineHeight : "1.2" ,
17431709 } ) ;
17441710
17451711 span . textContent = textarea . value || " " ;
17461712 document . body . appendChild ( span ) ;
17471713
17481714 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" ;
17511718 } ) ;
17521719 } ;
17531720
@@ -1762,7 +1729,10 @@ export class CanvasEngine {
17621729 }
17631730 } ) ;
17641731
1732+ let saveCalled = false ;
17651733 const save = ( ) => {
1734+ if ( saveCalled ) return ;
1735+ saveCalled = true ;
17661736 const text = textarea . value . trim ( ) ;
17671737 if ( ! text ) {
17681738 textarea . remove ( ) ;
@@ -1774,12 +1744,15 @@ export class CanvasEngine {
17741744 if ( ! span ) {
17751745 throw new Error ( "Span is null" ) ;
17761746 }
1747+ this . activeTextarea = null ;
1748+ this . activeTextPosition = null ;
17771749 const newShape : Shape = {
17781750 id : uuidv4 ( ) ,
17791751 type : "text" ,
17801752 x : x ,
17811753 y : y ,
1782- width : span . offsetWidth ,
1754+ width : textarea . offsetWidth ,
1755+ height : textarea . offsetHeight - TEXT_ADJUSTED_HEIGHT ,
17831756 text,
17841757 fontSize : this . fontSize ,
17851758 fontFamily : this . fontFamily ,
@@ -1831,6 +1804,7 @@ export class CanvasEngine {
18311804
18321805 const handleClickOutside = ( e : MouseEvent ) => {
18331806 if ( ! textarea . contains ( e . target as Node ) ) {
1807+ document . removeEventListener ( "mousedown" , handleClickOutside ) ;
18341808 save ( ) ;
18351809 }
18361810 } ;
@@ -2363,6 +2337,7 @@ export class CanvasEngine {
23632337 x : number ,
23642338 y : number ,
23652339 width : number ,
2340+ height : number ,
23662341 text : string ,
23672342 fillStyle : string ,
23682343 fontStyle : FontStyle ,
0 commit comments