skip to Main Content

Trying to write the flow logic for a drawing tool where user can enter a combination of keyboard inputs to draw complicated shapes on a canvas, to achieve this I’m trying to chain event listeners in a way that they remove themselves once done keeping global state clean. I would rather do this than keeping track hundreds of global state variabls that manages which listeners are active etc. I’m making progress but I’m stuck on recusive calls to Promise.race not sure if there’s a better way to achieve this.

Using Promise.race in a recusive loop means that alot of the promises do not get resolved.
Is there a better way for me to do this?

function startCount(node) {
  const counter = Object.create(counterMethods);
  counter.node = node;
  counter.number = 0;
  counter.exitloopflag = false;
  return counter;
}

const counterMethods = {
  doCounter: function () {
    return new Promise((resolve) => {
      let handler = () => {
        this.node.removeEventListener("click", handler);
        console.log("lip");
        resolve("click");
      };
      this.node.addEventListener("click", handler);
    });
  },
  quit: function () {
    return new Promise((resolve) => {
      let handler = (e) => {
        if (e.key === "Escape") {
          this.node.removeEventListener("keydown", handler);
          console.log("lap");
          resolve("exit");
        }
      };
      this.node.addEventListener("keydown", handler);
    });
  },
  counterFlow: async function () {
    //if (this.exit) return;
    let result = await Promise.race([this.doCounter(), this.quit()]);

    if (result === "exit") {
      console.log(`You have pressed exit`);
      //  this.exit = true;
    } else if (result === "click") {
      this.number += 1;
      console.log(`You have clicked ${this.number} of times`);
      this.counterFlow();
    } else {
      console.log("code should not have run here");
    }
  },
};

2

Answers


  1. Chosen as BEST ANSWER

    I don't think there's an easy way to do this after all, I've resorted to using signals to abort the listeners not sure if this is in efficient but it does seem to work. Also in the mean time I learn how classes work so I've may have updated the code a little. Previously when I press "esc" I get "clack" console.logged out for as many times as I have clicked i.e. build up of exit event listerners from Promise.race however i've used a abort signal to turn those off every time now.

    class StartCount {
      constructor(node) {
        this.node = node;
        this.number = 0;
        this.exit = false;
        this.abortController = new AbortController();
      }
    
      addOne() {
        return new Promise((resolve) => {
          let handler = (e) => {
            console.log(e.clientX, e.clientY);
            resolve("click");
          };
          this.node.addEventListener("click", handler, {
            once: true,
            signal: this.abortController.signal,
          });
        });
      }
    
      quit() {
        return new Promise((resolve) => {
          let handler = (e) => {
            if (e.key === "Escape") {
              console.log("clack");
              this.node.removeEventListener("keydown", handler);
              resolve("exit");
            }
          };
          this.node.addEventListener("keydown", handler, {
            signal: this.abortController.signal,
          });
        });
      }
    
      abortListener() {
        if (this.abortController) {
          this.abortController.abort();
          console.log("All event listeners aborted");
        }
      }
    
      resetController() {
        this.abortController = new AbortController();
      }
    
      async flowLogic() {
        let result = await Promise.race([this.addOne(), this.quit()]);
        this.abortListener();
        this.resetController();
        if (result === "exit") {
          this.exit = true;
          console.log("exit");
          document.dispatchEvent(window.return2Normal);
        } else if (result === "click") {
          this.number += 1;
          console.log(`You have clicked ${this.number} of times`);
          this.flowLogic();
        } else {
          console.log("code should not have run here");
        }
      }
    }
    
    

  2. I do not think that a recursive approach is necessary at all, but this is hard to say for certain because it is unclear what you want to achieve with it. Since you speak of a drawing tool, I assume that you want to prevent the handling of certain events depending on which event came before.

    For example, an event that starts a drawing must be followed by another that finishes the drawing (or by the "Escape" button), but not by another "start drawing" event. This can be achieved with event handlers that are added only once, but whose behavior depends on a state variable.

    The following snippet illustrates this: Clicking, dragging and releasing the mouse draws a rectangle with the start and end points as diagonally opposite corners. But pressing "Escape" during the dragging cancels the drawing.

    var x, y;
    var state = "idle";
    
    document.addEventListener("keydown", function(event) {
      if (event.key === "Escape") state = "inactive";
    });
    document.addEventListener("mousedown", function(event) {
      if (state === "idle") {
        x = event.x;
        y = event.y;
        state = "drawing";
      }
    });
    document.addEventListener("mouseup", function(event) {
      if (state === "drawing") {
        if (y <= event.y) {
          box.style.top = y + "px";
          box.style.height = (event.y - y) + "px";
        } else {
          box.style.top = event.y + "px";
          box.style.height = (y - event.y) + "px";
        }
        if (x <= event.x) {
          box.style.left = x + "px";
          box.style.width = (event.x - x) + "px";
        } else {
          box.style.left = event.x + "px";
          box.style.width = (x - event.x) + "px";
        }
        state = "idle";
      }
    });
    body {
      position: relative;
    }
    
    #box {
      border: solid 1px black;
      position: absolute;
    }
    <div id="box"></div>

    The number of possible states can be much bigger, of course. From the example you gave for drawing a rectangle, I can imagine the following:

    State before User interaction State after Effect
    idle escape inactive drawing finished
    idle press "R" rectangle drawing rectangles
    line press "R" rectangle drawing rectangles
    rectangle escape inactive drawing finished
    rectangle click drawing-rectangle
    drawing-rectangle escape rectangle rectangle abandoned
    drawing-rectangle click rotating-rectangle rectangle shown without rotation
    rotating-rectangle escape rectangle rectangle finished w/o rotation
    rotating-rectangle click rectangle rectangle finished with rotation
    idle press "L" line drawing lines
    rectangle press "L" line drawing lines
    line escape inactive drawing finished
    line click drawing-line
    drawing-line click drawing-line line finished, next line started
    drawing-line escape line last line abandoned

    A polygon can then be drawn as follows: The first line is established by the first two clicks, subsequent lines by one more click each, until escape is pressed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search