Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loadPaths() is laggy for data coming in from onUpdate() via socket-io (Collaborative canvas) #14

Closed
AllanMwirigi opened this issue Dec 31, 2020 · 5 comments

Comments

@AllanMwirigi
Copy link

AllanMwirigi commented Dec 31, 2020

Describe the bug
Hello. Awesome work on the canvas!!
I am trying to sync two whiteboards with each other. But the two keep getting laggy till it's barely usable

To Reproduce
When something is drawn on the first whiteboard, I get the CanvasPath[ ] from onUpdate() and send to the second whiteboard via socketio.
On the second whiteboard, socketio captures the sent CanvasPath[ ] and I use loadPath() to display it on the screen.
It works okay for the first few seconds and what is drawn on one end reflects quickly on the other.
Then gets keeps getting laggy to the point where there is a huge delay. There is even a lag when drawing on the original whiteboard, leave alone reflecting on the other one.
I also noticed that loadPaths() was trigerring onUpdate(), so I disabled socketio sending the paths until data is loaded

Expected behavior
loadPaths() should not cause a lag

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Kubuntu 20.04
  • Browser : Chrome
  • Version 86.0.4240.183

My code
`
import React,{ useState, useEffect } from "react";
import { ReactSketchCanvas } from "react-sketch-canvas";
import ReactTooltip from 'react-tooltip';
import { getsocketIoInstance } from '../utils/socketio-client';

export default class Whiteboard extends React.Component {
constructor(props) {
super(props);
this.styles = {
border: "0.0625rem solid #9c9c9c",
borderRadius: "0.25rem",
};
this.canvas = React.createRef();
this.state = {
eraseMode: false
}
this.pauseSync = false;
this.WhiteBoardMsgType = {
canvas_draw: 1,
canvas_undo: 2,
canvas_redo: 3,
canvas_clear: 4,
}
this.roomName = sessionStorage.getItem('roomName');
this.socketIo = getsocketIoInstance(this.roomName, 'Whiteboard');
}

componentDidMount() {
this.socketIo.on('whiteboard', (changes) => {
const { type, drawUpdates } = changes;
if (type === this.WhiteBoardMsgType.canvas_draw) {
this.pauseSync = true;
this.canvas.current.loadPaths(drawUpdates);
this.pauseSync = false;
// setTimeout(() => {
// this.pauseSync = false;
// }, 50);
}
});
}

whiteBoardUpdated = (drawUpdates) => {
if (!this.pauseSync) {
const changes = { roomName: this.roomName, type: this.WhiteBoardMsgType.canvas_draw, drawUpdates }
this.socketIo.emit('whiteboard', changes);
}
// console.log('pause sync', this.pauseSync);
}
toggleEraseMode = () => {
this.canvas.current.eraseMode(!this.state.eraseMode);
this.setState({ eraseMode: !this.state.eraseMode })
}
undoCanvas = () => {
this.canvas.current.undo();
// no need to send this change as they are already captured in the drawUpdates
// const changes = { roomName: this.roomName, type: this.WhiteBoardMsgType.canvas_undo }
// this.socketIo.emit('whiteboard', changes);
}
redoCanvas = () => {
this.canvas.current.redo();
// no need to send this change as they are already captured in the drawUpdates
// const changes = { roomName: this.roomName, type: this.WhiteBoardMsgType.canvas_redo }
// this.socketIo.emit('whiteboard', changes);
}
clearCanvas = () => {
this.canvas.current.clearCanvas();
// no need to send this change as they are already captured in the drawUpdates
// const changes = { roomName: this.roomName, type: this.WhiteBoardMsgType.canvas_clear }
// this.socketIo.emit('whiteboard', changes);
}

render() {
return (


Whiteboard






<i className="fas fa-eraser" data-tip={this.state.eraseMode ? 'Stop Erase': 'Erase'}
onClick={this.toggleEraseMode}>
<i className="fas fa-broom"data-tip='Clear' onClick={this.clearCanvas}>

<ReactSketchCanvas
ref={this.canvas}
style={this.styles}
// width="600"
// height="400"
strokeWidth={4}
strokeColor="red"
eraserWidth={20}
onUpdate={this.whiteBoardUpdated}
/>

);

}
}
`

@vinothpandian
Copy link
Owner

Hey @AllanMwirigi,

Thank you for the kind words and detailed description.

I would suggest you take the low-level Canvas class and move the path handling to Socker server instead. You can copy the path handlers from ReactSketchCanvas class and put it in the socket server then sync it up.

Here is a very simple example that I wrote long ago. Hope it still works.

Server

"use strict";

var _immutable = require("immutable");

var express = require("express");
var http = require("http");
var socketIO = require("socket.io");


// our localhost port
var port = 8000;

var app = express();

app.get('/', function (req, res) {
  return res.send('Hello World!');
});

// our server instance
var server = http.createServer(app);

// This creates our socket using the instance of the server
var io = socketIO(server);

var drawMode = true;
var isDrawing = false;
var currentPaths = new _immutable.List();
var message = "";
var canvasColor = "white";
var strokeColor = "black";

var strokeWidth = 4;
var eraserWidth = 8;

// This is what the socket.io syntax is like, we will work this later
io.on("connection", function (socket) {
  console.log("User connected");

  io.emit("receive_message", message);

  io.emit("receive_paths", {
    currentPaths: currentPaths,
    isDrawing: isDrawing
  });

  socket.on("disconnect", function () {
    console.log("user disconnected");
  });

  socket.on("send_message", function (msg) {
    message = msg;
    io.emit("receive_message", message);
  });

  // Pointer down event
  socket.on("sketchPointerDown", function (point) {
    isDrawing = true;

    currentPaths = currentPaths.push(new _immutable.Map({
      drawMode: drawMode,
      strokeColor: drawMode ? strokeColor : canvasColor,
      strokeWidth: drawMode ? strokeWidth : eraserWidth,
      paths: new _immutable.List([point])
    }));

    // console.log(currentPaths.size);

    io.emit("receive_paths", {
      currentPaths: currentPaths,
      isDrawing: isDrawing
    });
  });

  // Pointer move event
  socket.on("sketchPointerMove", function (point) {
    if (!isDrawing) return;

    currentPaths = currentPaths.updateIn([currentPaths.size - 1], function (pathMap) {
      return pathMap.updateIn(["paths"], function (list) {
        return list.push(point);
      });
    });

    io.emit("receive_paths", {
      currentPaths: currentPaths,
      isDrawing: isDrawing
    });
  });

  // Pointer up event
  socket.on("sketchPointerUp", function () {
    isDrawing = false;
  });
});

server.listen(port, '0.0.0.0', function () {
  return console.log("Listening on port " + port);
});

Client

class Sketch extends Component {
  constructor(props) {
    super(props);

    this.socket = socketIOClient("http://127.0.0.1:8000");

    this.state = {
      paths: [],
      isDrawing: false
    };

    this.socket.on("receive_paths", data => {
      this.updatePaths(data);
    });

    this.updatePaths = this.updatePaths.bind(this);

    this.handlePointerDown = this.handlePointerDown.bind(this);
    this.handlePointerMove = this.handlePointerMove.bind(this);
    this.handlePointerUp = this.handlePointerUp.bind(this);
  }

  handlePointerDown = point => {
    this.socket.emit("sketchPointerDown", point);
  };

  handlePointerMove = point => {
    this.socket.emit("sketchPointerMove", point);
  };

  handlePointerUp = () => {
    this.socket.emit("sketchPointerUp");
  };

  updatePaths = ({ currentPaths, isDrawing }) => {
    this.setState({
      paths: fromJS(currentPaths),
      isDrawing
    });
  };

  render() {
    return (
    <Canvas
      allowOnlyPointerType="all"
      paths={this.state.paths}
      isDrawing={this.state.isDrawing}
      onPointerDown={this.handlePointerDown}
      onPointerMove={this.handlePointerMove}
      onPointerUp={this.handlePointerUp}
    />
    );
  }
}

@AllanMwirigi
Copy link
Author

Thanks for the quick reply.
I will try to implement it soonest and give feedback.

@AllanMwirigi
Copy link
Author

AllanMwirigi commented Jan 3, 2021

Hello
I followed the sample you shared. Unfortunately I'm stuck on an error which I've been unable to fix
I keep getting the error "Cannot read property 'reduce' of undefined at Paths.tsx 11" when I try to use updatePaths() after receiving the paths data via socketio.
Could you kindly help? I've looked at it severally but I can't identify the problem.

Screenshot_20210103_090248
Screenshot_20210103_090429

Here is my code. The only major change I made was to wrap the path data inside an object called workspaces in order to support rooms. In the workspaces, the roomName is the key and the path data is the value. I use this to identify the room and send data to the right room.

Server

"use strict";

const _immutable = require("immutable");
const logger = require('./utils/winston');

const workspaces = {};

exports.initSync = (server) => {
  const socketio = require('socket.io')(server, {
    cors: {
      // NOTE!!!: in case domain is changed, ensure to replace/update these; local and prod domains
      origin: ["http://localhost:3000"],
      // if using socket.io v3, then these two are needed; had to downgrade to v2.3 because ngx-socket-io client in Angular didn't seem to be comaptible, was giving 400 errors
      methods: ["GET", "POST"],
      // credentials: true
    }
  });
  
  // sockets for real time data
  socketio.on('connection', (socket) => {
    socket.on('join-room', ({ roomName, userName }) => {
      // each user in a workspace will join a room identified by the room name
      // create a new entry in workspaces if none exists
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: new _immutable.List(),
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      socket.join(roomName);
      logger.debug(`socket ${socket.id} joined room ${roomName}`);
      socket.to(roomName).emit('join-room', userName);
      socketio.to(roomName).emit('whiteboard-paths', { 
        currentPaths: workspaces[roomName].currentPaths,
        isDrawing: workspaces[roomName].isDrawing
      });
    });
  
    // socket.on('whiteboard-paths', (data) => {
    //   // send drawn changes to the other user in the workspace room
    //   const { roomName, type, drawUpdates } = data;
    //   // socket.to(roomName).emit('whiteboard', { type, drawUpdates });
    //   socket.to(roomName).emit('whiteboard-paths', { currentPaths, isDrawing });
    // });
  
    socket.on('chat-msg', (data) => {
      const { roomName, txt, senderName } = data;
      socket.to(roomName).emit('chat-msg', { txt, senderName });
    });

    // Pointer down event
    socket.on("sketchPointerDown", function ({ roomName, point }) {
      logger.debug(`pointerDown ${JSON.stringify(point)}`)
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: new _immutable.List(),
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      workspaces[roomName].isDrawing = true;
      const { drawMode, strokeColor, canvasColor, strokeWidth, eraserWidth } = workspaces[roomName];
      workspaces[roomName].currentPaths = workspaces[roomName].currentPaths.push(new _immutable.Map({
        drawMode: drawMode,
        strokeColor: drawMode ? strokeColor : canvasColor,
        strokeWidth: drawMode ? strokeWidth : eraserWidth,
        paths: new _immutable.List([point])
      }));

      socketio.to(roomName).emit('whiteboard-paths', { 
        currentPaths: workspaces[roomName].currentPaths,
        isDrawing: workspaces[roomName].isDrawing
      });
    });

    // Pointer move event
    socket.on("sketchPointerMove", function ({ roomName, point }) {
      logger.debug(`pointerMove ${JSON.stringify(point)}`)
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: new _immutable.List(),
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      if (!workspaces[roomName].isDrawing) return;

      workspaces[roomName].currentPaths = workspaces[roomName].currentPaths.updateIn([workspaces[roomName].currentPaths.size - 1], function (pathMap) {
        return pathMap.updateIn(["paths"], function (list) {
          return list.push(point);
        });
      });

      socketio.to(roomName).emit('whiteboard-paths', { 
        currentPaths: workspaces[roomName].currentPaths,
        isDrawing: workspaces[roomName].isDrawing
      });
    });

    // Pointer up event
    socket.on("sketchPointerUp", function ({roomName}) {
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: new _immutable.List(),
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      workspaces[roomName].isDrawing = false;
    });

  
  });
}

Client

import { produce } from "immer";
import React from "react";
import { Canvas, CanvasPath, Point } from "react-sketch-canvas";
import ReactTooltip from "react-tooltip";
import { fromJS } from 'immutable';
import { getsocketIoInstance } from '../utils/socketio-client';

/* Default settings */

const defaultProps = {
  width: "100%",
  height: "100%",
  className: "",
  canvasColor: "white",
  strokeColor: "red",
  background: "",
  strokeWidth: 4,
  eraserWidth: 20,
  allowOnlyPointerType: "all",
  style: {
    border: "0.0625rem solid #9c9c9c",
    borderRadius: "0.25rem",
  },
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  onUpdate: (_: CanvasPath[]): void => { },
  withTimestamp: false,
};

/* Props validation */

export type ReactSketchCanvasProps = {
  width: string;
  height: string;
  className: string;
  strokeColor: string;
  canvasColor: string;
  background: string;
  strokeWidth: number;
  eraserWidth: number;
  allowOnlyPointerType: string;
  onUpdate: (updatedPaths: CanvasPath[]) => void;
  style: React.CSSProperties;
  withTimestamp: boolean;
};

export type ReactSketchCanvasStates = {
  drawMode: boolean;
  isDrawing: boolean;
  resetStack: CanvasPath[];
  undoStack: CanvasPath[];
  currentPaths: CanvasPath[];
  eraseMode: boolean;
};

export class Whiteboard extends React.Component<
  ReactSketchCanvasProps,
  ReactSketchCanvasStates
  > {
  static defaultProps = defaultProps;

  svgCanvas: React.RefObject<Canvas>;

  initialState = {
    drawMode: true,
    isDrawing: false,
    // eslint-disable-next-line react/no-unused-state
    resetStack: [],
    undoStack: [],
    currentPaths: [],
    eraseMode: false,
  };
  roomName: string | null;
  socketIo: any;

  constructor(props: ReactSketchCanvasProps) {
    super(props);

    this.state = this.initialState;

    this.handlePointerDown = this.handlePointerDown.bind(this);
    this.handlePointerMove = this.handlePointerMove.bind(this);
    this.handlePointerUp = this.handlePointerUp.bind(this);

    this.eraseMode = this.eraseMode.bind(this);
    this.clearCanvas = this.clearCanvas.bind(this);
    this.undo = this.undo.bind(this);
    this.redo = this.redo.bind(this);
    this.resetCanvas = this.resetCanvas.bind(this);

    this.liftPathsUp = this.liftPathsUp.bind(this);

    this.svgCanvas = React.createRef();

    this.roomName = sessionStorage.getItem('roomName');
    this.socketIo = getsocketIoInstance(this.roomName, 'Whiteboard');
  }

  componentDidMount() {
    this.socketIo.on('whiteboard-paths', ({ currentPaths, isDrawing }: { currentPaths: CanvasPath[]; isDrawing: boolean; }) => {
      console.log('whiteboard-paths', { currentPaths, isDrawing })
      // update paths
      this.setState({
        currentPaths: fromJS(currentPaths),
        isDrawing
      });
    });
  }

  // updatePaths = (changes: { currentPaths: CanvasPath[]; isDrawing: boolean; }) => {
  //   const { currentPaths, isDrawing } = changes;
  //   this.setState({
  //     paths: fromJS(currentPaths),
  //     isDrawing
  //   });
  // };

  resetCanvas(): void {
    this.setState(this.initialState);
  }

  liftPathsUp(): void {
    const { currentPaths } = this.state;
    const { onUpdate } = this.props;

    onUpdate(currentPaths);
  }

  /* Mouse Handlers - Mouse down, move and up */

  handlePointerDown(point: Point): void {
    this.socketIo.emit("sketchPointerDown", { roomName: this.roomName, point });
  }

  handlePointerMove(point: Point): void {
    this.socketIo.emit("sketchPointerMove", { roomName: this.roomName, point });
  }

  handlePointerUp(): void {
    this.socketIo.emit("sketchPointerUp", { roomName: this.roomName });
  }

  /* Mouse Handlers ends */

  /* Canvas operations */

  eraseMode(erase: boolean): void {
    this.setState(
      produce((draft: ReactSketchCanvasStates) => {
        draft.drawMode = !erase;
      }),
      this.liftPathsUp
    );
  }
  toggleEraseMode = () => {
    this.eraseMode(!this.state.eraseMode);
    this.setState({ eraseMode: !this.state.eraseMode })
  }

  clearCanvas(): void {
    this.setState(
      produce((draft: ReactSketchCanvasStates) => {
        draft.resetStack = draft.currentPaths;
        draft.currentPaths = [];
      }),
      this.liftPathsUp
    );
    // this.socketIo.emit("sketch-clear", { roomName: this.roomName });
  }

  undo(): void {
    const { resetStack } = this.state;

    // If there was a last reset then
    if (resetStack.length !== 0) {
      this.setState(
        produce((draft: ReactSketchCanvasStates) => {
          draft.currentPaths = draft.resetStack;
          draft.resetStack = [];
        }),
        () => {
          const { currentPaths } = this.state;
          const { onUpdate } = this.props;

          onUpdate(currentPaths);
        }
      );
      // this.socketIo.emit("sketch-undo", { roomName: this.roomName });
      return;
    }

    this.setState(
      produce((draft: ReactSketchCanvasStates) => {
        const lastSketchPath = draft.currentPaths.pop();

        if (lastSketchPath) {
          draft.undoStack.push(lastSketchPath);
        }
      }),
      this.liftPathsUp
    );
    // this.socketIo.emit("sketch-undo", { roomName: this.roomName });
  }

  redo(): void {
    const { undoStack } = this.state;

    // Nothing to Redo
    if (undoStack.length === 0) return;

    this.setState(
      produce((draft: ReactSketchCanvasStates) => {
        const lastUndoPath = draft.undoStack.pop();

        if (lastUndoPath) {
          draft.currentPaths.push(lastUndoPath);
        }
      }),
      this.liftPathsUp
    );
    // this.socketIo.emit("sketch-redo", { roomName: this.roomName });
  }

  /* Finally!!! Render method */

  render(): JSX.Element {
    const {
      width,
      height,
      className,
      canvasColor,
      background,
      style,
      allowOnlyPointerType,
    } = this.props;

    const { currentPaths, isDrawing } = this.state;

    return (
      <div className="whiteboard">
        <h4>Whiteboard</h4>
        <ReactTooltip id="whtbrd-tltp" place="top" type="info" effect="float" />
        <div className="whiteboard-icons">
          <i className="fas fa-undo" data-tip='Undo' onClick={this.undo} data-for="whtbrd-tltp"></i>
          <i className="fas fa-redo" data-tip='Redo' onClick={this.redo} data-for="whtbrd-tltp"></i>
          <i className="fas fa-eraser" data-tip={this.state.eraseMode ? 'Stop Erase' : 'Erase'}
            onClick={this.toggleEraseMode} data-for="whtbrd-tltp"></i>
          <i className="fas fa-broom" data-tip='Clear' onClick={this.clearCanvas} data-for="whtbrd-tltp"></i>
        </div>
        <Canvas
          ref={this.svgCanvas}
          width={width}
          height={height}
          className={className}
          canvasColor={canvasColor}
          background={background}
          allowOnlyPointerType={allowOnlyPointerType}
          style={style}
          paths={currentPaths}
          isDrawing={isDrawing}
          onPointerDown={this.handlePointerDown}
          onPointerMove={this.handlePointerMove}
          onPointerUp={this.handlePointerUp}
        />
      </div>
    );
  }
}

@AllanMwirigi
Copy link
Author

Hello, again
So after doing some digging, I realized that using Immutable.Js as per the example you had given was the problem
I can't believe I never realized that using ImmutableJs was resulting in different types of the data in curentPaths, than what was expected on the Canvas and Paths.tsx
I first attempted to rework Paths.tsx to fit the types from ImmutableJs (mostly Maps). I was able to get it all done without any errors but still nothing would display on the Canvas.
Next, I attempted to remove the ImmutableJs stuff from the server. I'm glad to say that this is working well !!
I tried to replace the ImmutableJs code as best as I could. You can let me know if there is any serious downside of not using ImmutableJs.
Here is my code below. Thanks a lot for all the help!!!

Server

"use strict";

const logger = require('./utils/winston');

const workspaces = {};

exports.initSync = (server) => {
  const socketio = require('socket.io')(server, {
    cors: {
      // NOTE!!!: in case domain is changed, ensure to replace/update these; local and prod domains
      origin: ["http://localhost:3000"],
      // if using socket.io v3, then these two are needed; had to downgrade to v2.3 because ngx-socket-io client in Angular didn't seem to be comaptible, was giving 400 errors
      methods: ["GET", "POST"],
      // credentials: true
    }
  });
  
  // sockets for real time data
  socketio.on('connection', (socket) => {
    socket.on('join-room', ({ roomName, userName }) => {
      // each user in a workspace will join a room identified by the room name
      // create a new entry in workspaces if none exists
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          // currentPaths: new _immutable.List(),
          currentPaths: [],
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      socket.join(roomName);
      logger.debug(`socket ${socket.id} joined room ${roomName}`);
      socket.to(roomName).emit('join-room', userName);
      socketio.to(roomName).emit('whiteboard-paths', { 
        currentPaths: workspaces[roomName].currentPaths,
        isDrawing: workspaces[roomName].isDrawing
      });
    });
  
    socket.on('chat-msg', (data) => {
      const { roomName, txt, senderName } = data;
      socket.to(roomName).emit('chat-msg', { txt, senderName });
    });

    // Pointer down event
    socket.on("sketchPointerDown", function ({ roomName, point }) {
      // logger.debug(`pointerDown ${JSON.stringify(point)}`)
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: [],
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      workspaces[roomName].isDrawing = true;
      const { drawMode, strokeColor, canvasColor, strokeWidth, eraserWidth } = workspaces[roomName];
      const cp = workspaces[roomName].currentPaths.slice();
      cp.push({
        drawMode: drawMode,
        strokeColor: drawMode ? strokeColor : canvasColor,
        strokeWidth: drawMode ? strokeWidth : eraserWidth,
        paths: [point]
      });
      workspaces[roomName].currentPaths = cp;

      socketio.to(roomName).emit('whiteboard-paths', { 
        currentPaths: workspaces[roomName].currentPaths,
        isDrawing: workspaces[roomName].isDrawing
      });
    });

    // Pointer move event
    socket.on("sketchPointerMove", function ({ roomName, point }) {
      // logger.debug(`pointerMove ${JSON.stringify(point)}`)
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: [],
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      if (!workspaces[roomName].isDrawing) return;

      const cp = workspaces[roomName].currentPaths.slice();
      cp[workspaces[roomName].currentPaths.length - 1].paths.push(point);
      workspaces[roomName].currentPaths = cp;

      socketio.to(roomName).emit('whiteboard-paths', { 
        currentPaths: workspaces[roomName].currentPaths,
        isDrawing: workspaces[roomName].isDrawing
      });
    });

    // Pointer up event
    socket.on("sketchPointerUp", function ({roomName}) {
      if (workspaces[roomName] == null) {
        workspaces[roomName] = {
          drawMode: true,
          isDrawing: false,
          currentPaths: [],
          canvasColor: "white",
          strokeColor: "red",
          strokeWidth: 4,
          eraserWidth: 20,
        }
      }
      workspaces[roomName].isDrawing = false;
    });

  
  });
}

@vinothpandian
Copy link
Owner

You don't have to use Immutable. You can do by writing pure functions in your code or try immer library.

Repository owner locked and limited conversation to collaborators Sep 18, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants