Fabric.js how to revert or dismiss scaling applied to an object with transformation matrices

I’m working on a web application which uses fabric.js to make drawings on the web page’s canvas.

I will tell you about some general properties of this tool for context:

  • Canvas is a space that has its own cartesian coordinate system with reverted $x-axis$ and $y-axis$ meaning the valeus of x and y increase as we go towards right and bottom.
  • Each object created in canvas such as rectangle, text etc. has their own 3x3 transformation matrix that tells the position/translation of the object’s center. This transformation matrix tells us where the object is relative to the center of the canvas space.
  • We can use each object’s transformation matrix to calculate where other objects are located relative to that object. To calculate where object B is located relative to object A, we can do the below calculation:
T_A * X = T_B

(T_A)' * T_A * X = (T_A)' * T_B

X = (T_A)' * T_B

Here, X is the unknown transform matrix that tells where the object B is at relative to object A, it also defines a relationship between these objects. T_A is object A‘s transformation matrix and T_B is object B‘s transformation matrix. Prime notation is used to notate that it’s inverse matrix.

initial

Now, I will scale the object A in x-axis by 2, doubling its width. This will cause T_A to change, we can calculate object B‘s new transform matrix using the relationship transform matrix we found in the previous step.

T_{B}=T_{A}*X

scale-x-by-2

When I apply the newly found T_B transformation matrix to the object B it successfully shifts the center of the object B twice as before since we have scaled object A in x-axis by factor of 2. Object B‘s previous translation in x-axis was -61.98 and it’s now -123.97 (roughly doubled). In this process, the relationship matrix X was unchanged and used to calculate new transform matrix of object B. Everything is good so far!

Now, what I’m having trouble is avoiding object B from being also scaled or reverting the scaling applied to object it. As you can see, in the first picture text was smaller but when the object A got scaled by 2 in second picture, it also got scaled. This causes text to be drawn disrupted. I want the text to look as same as it looked in first picture but preserving its relative position to the parent object which is object A. It should remain right below the bottom left corner of rectangle. The scale applied to transformation matrix of object B seem to be also causing its translation properties in x and y-axes to be scaled in some way. Because when I revert the scaling of object B programmatically by setting the text’s scale property back to its original value which is 1, text appears slightly off in the direction of the scale applied. Below is the picture where I revert the scaling applied to object B programmatically:

scale-x-by-2-but-revert-scaling

I need a way to prevent object B from scaling somehow multiplying the T_B we have found with some kind of transform matrix that will revert the effect of scaling applied on it’s translation values in x and y-axes. But I couldn’t figure out what kind of transform matrix I should be using.

NOTE: When we rotate the object, rotation matrix also gets into action in the calculations. Please think throughly in a case where we also rotated the parent object A.

Working demo

const outputEl = document.getElementById('output-table-body');
const rowTemplate = document.getElementById('output-table-row');
const matricesEl = document.getElementById('output-matrices');
const canvasEl = document.getElementById('playground');
const canvas = new fabric.Canvas(canvasEl);

const formatNumber = (number) => {
  return parseFloat(number).toFixed(2);
};

const mouseDownHandler = (opt) => {
  let evt = opt.e;
  if (evt.altKey === true) {
    canvas.isDragging = true;
    canvas.selection = false;
    canvas.lastPosX = evt.clientX;
    canvas.lastPosY = evt.clientY;
  }
};

const mouseMoveHandler = (opt) => {
  if (canvas.isDragging) {
    let e = opt.e;
    let vpt = canvas.viewportTransform;
    vpt[4] += e.clientX - canvas.lastPosX;
    vpt[5] += e.clientY - canvas.lastPosY;
    canvas.requestRenderAll();
    canvas.lastPosX = e.clientX;
    canvas.lastPosY = e.clientY;
  }
};

const mouseUpHandler = (opt) => {
  // on mouse up we want to recalculate new interaction
  // for all objects, so we call setViewportTransform
  canvas.setViewportTransform(canvas.viewportTransform);
  canvas.isDragging = false;
  canvas.selection = true;
};

const zoomHandler = (opt) => {
  let delta = opt.e.deltaY;
  let zoom = canvas.getZoom();
  zoom *= 0.999 ** delta;
  if (zoom > 20) zoom = 20;
  if (zoom < 0.01) zoom = 0.01;
  canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
  opt.e.preventDefault();
  opt.e.stopPropagation();
};

const outputObjects = () => {
  outputEl.innerHTML = '';
  matricesEl.innerHTML = '';

  [parent, child].forEach((object) => {
    const transformMatrix = object.calcTransformMatrix();
    const opt = fabric.util.qrDecompose(transformMatrix);
    const rowEl = rowTemplate.content.cloneNode(true);
    const tdEls = rowEl.querySelectorAll('td');

    tdEls[0].innerText = object.name;
    tdEls[1].innerText = formatNumber(opt.scaleX);
    tdEls[2].innerText = formatNumber(opt.scaleY);
    tdEls[3].innerText = formatNumber(opt.angle);
    tdEls[4].innerText = formatNumber(opt.translateX);
    tdEls[5].innerText = formatNumber(opt.translateY);

    outputEl.appendChild(rowEl);
  });

  const pM = parent.calcTransformMatrix();
  const cM = child.calcTransformMatrix();
  const rM = child.relationship;
  matricesEl.innerHTML += `
    <p>Parent transform matrix</p>
    <p>=</p>
    <table>
      <tbody>
        <tr>
          <td>${formatNumber(pM[0])}</td>
          <td>${formatNumber(pM[2])}</td>
          <td>${formatNumber(pM[4])}</td>
        </tr>
        <tr>
          <td>${formatNumber(pM[1])}</td>
          <td>${formatNumber(pM[3])}</td>
          <td>${formatNumber(pM[5])}</td>
        </tr>
        <tr>
          <td>0</td>
          <td>0</td>
          <td>1</td>
        </tr>
      </tbody>
    </table>
  `;
  matricesEl.innerHTML += `
    <p>Children transform matrix</p>
    <p>=</p>
    <table>
      <tbody>
        <tr>
          <td>${formatNumber(cM[0])}</td>
          <td>${formatNumber(cM[2])}</td>
          <td>${formatNumber(cM[4])}</td>
        </tr>
        <tr>
          <td>${formatNumber(cM[1])}</td>
          <td>${formatNumber(cM[3])}</td>
          <td>${formatNumber(cM[5])}</td>
        </tr>
        <tr>
          <td>0</td>
          <td>0</td>
          <td>1</td>
        </tr>
      </tbody>
    </table>
  `;
  matricesEl.innerHTML += `
    <p>Relationship transform matrix</p>
    <p>=</p>
    <table>
      <tbody>
        <tr>
          <td>${formatNumber(rM[0])}</td>
          <td>${formatNumber(rM[2])}</td>
          <td>${formatNumber(rM[4])}</td>
        </tr>
        <tr>
          <td>${formatNumber(rM[1])}</td>
          <td>${formatNumber(rM[3])}</td>
          <td>${formatNumber(rM[5])}</td>
        </tr>
        <tr>
          <td>0</td>
          <td>0</td>
          <td>1</td>
        </tr>
      </tbody>
    </table>
  `;
};

const selectionCreatedOrUpdatedHandler = (opt) => {
  outputObjects();
};

const selectionClearedHandler = () => {
  outputObjects();
};

const objectUpdateHandler = () => {
  outputObjects();
};

canvas.on('mouse:wheel', zoomHandler);
canvas.on('mouse:down', mouseDownHandler);
canvas.on('mouse:move', mouseMoveHandler);
canvas.on('mouse:up', mouseUpHandler);

canvas.on('object:moving', objectUpdateHandler);
canvas.on('object:scaling', objectUpdateHandler);
canvas.on('object:rotating', objectUpdateHandler);

canvas.on('selection:created', selectionCreatedOrUpdatedHandler);
canvas.on('selection:updated', selectionCreatedOrUpdatedHandler);
canvas.on('selection:cleared', selectionClearedHandler);

const canvasCenterPoint = new fabric.Circle({
  radius: 5,
  fill: 'red',
  top: -2.5,
  left: -2.5,
  selectable: false,
  evented: false
});

const parent = new fabric.Rect({
  name: 'Parent',
  top: -100,
  left: -100,
  width: 199,
  height: 199,
  fill: 'transparent',
  stroke: 'red',
  strokeWidth: 1,
});
const child = new fabric.IText('Child text', {
  name: 'Child',
  top: 101,
  left: -100,
  fontSize: 18,
  fontFamily: 'Arial',
});
const parentM = parent.calcTransformMatrix();
const parentInvM = fabric.util.invertTransform(parentM);
child.relationship = fabric.util.multiplyTransformMatrices(
  parentInvM,
  child.calcTransformMatrix()
);

const update = () => {
  const parentMatrix = parent.calcTransformMatrix();
  const newM = fabric.util.multiplyTransformMatrices(
    parentMatrix,
    child.relationship
  );
  const opt = fabric.util.qrDecompose(newM);

  child.set({
    flipX: false,
    flipY: false,
  });
  child.setPositionByOrigin(
    {
      x: opt.translateX,
      y: opt.translateY
    },
    'center',
    'center'
  );
  child.set({
    ...opt,
    scaleX: 1,
    scaleY: 1,
  });
  child.setCoords();

  canvas.requestRenderAll();
};

parent.on('moving', update);
parent.on('rotating', update);
parent.on('scaling', update);

canvas.add(parent, child, canvasCenterPoint);

canvas.viewportTransform[4] = 400;
canvas.viewportTransform[5] = 250;

const center = () => {
  parent.setPositionByOrigin({
    x: 0,
    y: 0,
  }, 'center', 'center');
  parent.setCoords();
  update();
}

outputObjects();
html {
    font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
    color: #cccccc;
}

body {
    margin: 50px 0 0 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background-color: #222222;
}

#playground {
    border: 1px solid #666666;
}

.output {
    display: flex;
    flex-direction: column;
    row-gap: 40px;
}

.output__heading {
    font-size: 2rem;
}

.output__matrices {
    display: grid;
    grid-template-rows: auto;
    grid-template-columns: auto auto 1fr;
    grid-column-gap: 5px;
    grid-row-gap: 10px;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Fabric Test</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <canvas id="playground" width="800" height="500"></canvas>
    <div class="output">
      <div class="output__transform-matrices" id="outputs">
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>ScaleX</th>
              <th>ScaleY</th>
              <th>Angle</th>
              <th>TranslateX</th>
              <th>TranslateY</th>
            </tr>
          </thead>
          <tbody id="output-table-body"></tbody>
        </table>
        <div class="output__matrices" id="output-matrices"></div>
      </div>
    </div>
    <template id="output-table-row">
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
    </template>
    <script
      src="https://cdn.jsdelivr.net/npm/[email protected]/dist/fabric.min.js"
    ></script>
  </body>
</html>

Importing module error: module does not provide an export named default/{module name}

I have a script named /src/web/static/js/my_vue_widget.js. It looks like this:

const MyVueWidget = {
    name: 'MyVueWidget',
    ...
};
export default MyVueWidget;

I have an HTML /src/web/static/templates/index.html which tries to import the Vue component from that JavaScript.

import {MyVueWidget} from '/static/js/my_vue_widget.js';

##Also tried these varaitions with no better results:
#import MyVueWidget from '/static/js/my_vue_widget.js';
#import MyVueWidget from './static/js/my_vue_widget.js';
#import {MyVueWidget} from './static/js/my_vue_widget.js';

In my FastAPI app’s main.py I have:

app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/templates", tmplts, name="templates")

When this page is being rendered, in the console I see the following JavaScript error:

Uncaught SyntaxError: The requested module '/static/js/my_vue_widget.js' does not provide an export named 'MyVueWidget' (at (index):142:17)

How should I set the `scale` argument in the `reduceRegion` function if I applied a buffer around the points?

I am using a script to calculate and export EVI2 values derived from Sentinel-2 data to a table. Since the 10-meter spatial resolution of Sentinel-2 is insufficient to represent the area I need, I applied a 30-meter buffer to my points (line 15) to include information from the surrounding area. I have three questions:

  1. Is this approach adequate to achieve my objective?
  2. How should I set the scale argument in the reduceRegion function (line 68)?
  3. Why did I get the error Collection.toList: The value of 'count' must be positive. Got: 0 when running my script in Google Earth Engine?

Edited: I kept it to 5 points to save time, effort, and storage.

How to fix this Cypress chaining problem?

The Cypress command below does the folllowing

  1. waits for an API endpoint to be called that creates a new event
  2. extracts the ID of the new event from the response body
  3. navigates to the new event’s URL
  4. returns a subject that yields the new event’s ID (or in other words “returns a promise that resolves the new event’s ID”)
Cypress.Commands.add('createEvent', () => {
  cy.intercept('POST', '/api/events').as('postEventApi')

  return cy.wait('@postEventApi')
      .then(({ response }) => response.body.data.id)
      .then(eventId => cy.visit(`/events/${eventId}`).then(() => eventId))
})

A caller can use this command to create an event, navigate to it, then do something with the ID

cy.createEvent().then(eventId => console.log('New event ID', eventId)

However, the code .then(() => eventId) generates a warning

It is not safe to chain further commands after visit command. Consider breaking up this command chain.

The Cypress docs confirm that this is unsafe.

Is it possible to fix this command such that the warning no longer applies?

Why my page is shown as a link with NextJS routing system

With React and Next.js, I have a home page as below :

  return (
    <div className="App">
        <Link href="/"><Home email={email} loggedIn={loggedIn} /></Link>
        <Link href="/login"><Login setLoggedIn={setLoggedIn} setEmail={setEmail} /></Link>
    </div>
  )

And my Home() is like that:

  return (
    <div className="mainContainer">
      <div className={'titleContainer'}>
        <div>Welcome!</div>
      </div>

      <div>This is the home page.</div>

      <div className={'buttonContainer'}>
        <input
          type="button"
          onClick={onButtonClick}
          value={loggedIn ? 'Log out' : 'Log in'}
        />

        {loggedIn ? <div>Your email address is {email}</div> : <div />}
      </div>
    </div>
  )

When I go to localhost:3000/login my page login is shown correctly, but when I go to my main page localhost:3000 the full page is shown a link (hand cursor everywhere on the page and I see localhost:3000 at the bottom of my web bowser, why ?

Set Interval Js [duplicate]

I want to make a slide show with JS using the setInterval function, but I don’t know why my code is not working? While there is no error in console, I have a number of pictures that start with the name “bk” followed by number. I call the pictures using an if statement in function, but the slide effect does not work. It stops at bk1.

Here is my code. If you could help me by explaining how setInterval works.

function slides(sf) {
  sf = sf + 1;
  document.getElementById("centerd")
    .style.backgroundImage = "url(pics/bk" + sf + ".png)";

  if (sf > 2) {
    sf = 1;
  }
}
setInterval(slides(0), 3000);

Also I tried with a for loop:

function slides() {
  for (var i = 0; i < 3; i++) {
    document.getElementById("centerd")
      .style.backgroundImage = "url(pics/bk" + i + ".png)";

    if (i == 2) {
      i = 1;
    }
  }
}

setInterval(slides, 3000);

But that did not work either.

How can I match whitespace outside of HTML comments with RegEx?

I would like to replace instances of “n”, “t”, and ” ” (four spaces) in RegEx, but preserve all whitespace inside of an HTML comment block. Unfortunately, the comment can contain anything, including other HTML tags, so I have to match “<!–” and “–>” specifically. Furthermore, there may be multiple instances of comments with whitespace to match in between. I can use multiple RegEx expressions if needed, but I cannot modify the HTML content aside from the replacement.

Here is some sample code to experiment with:

<div>
    <p>Sample text!</p>
    <!--
        <img src="test.jpg" alt="This is an image!" width="500" height="600">
    -->
</div>
<div>
    <p>Sample text!</p>
    <!--
        <img src="test.jpg" alt="This is an image!" width="500" height="600">
    -->
</div>
<div>
    <p>Sample text!</p>
    <!--
        <img src="test.jpg" alt="This is an image!" width="500" height="600">
    -->
</div>

In this instance, all sets of four spaces should be matched except for the ones in each comment (lines 4, 5, 10, 11, 16, 17).

I have already split up my expressions into one for each type of whitespace, and I have been experimenting with spaces. The closest I have gotten is this:

/(?<!<!--.*?(?<!-->.*?))    (?!(?!.*?<!--).*?-->)/gs

which matches instances of tabs not in the first or last comment block, but it does match tabs in the middle comment blocks which is incorrect. However I suspect it could be accomplished by modifying something in the second half:

/    (?!(?!.*?<!--).*?-->)/gs

Any suggestions? Is this even possible?

how could I make my input form change position as the list above grows

I have my HTML as

window.onload = function() {

  /*The initial item number set to 3 */
  var itemNo = 3;

  /* get the date of current day */
  var today = new Date();
  var todaystring = today.toLocaleDateString();

  /* after click the 'add dish' button, the form appears */
  var showformbtn = document.getElementById("btn1");
  showformbtn.onclick = function() {
    var getform = document.getElementById("formdiv");
    getform.style.display = "block";

  }

  /* the button used to add dish after fill the form */
  var addbtn = document.getElementById("btnadd");
  addbtn.onclick = function() {

    var dishname = document.getElementById("dishname").value;
    var storename = document.getElementById("storename").value;
    var rating = document.getElementById("rating").value;
    var feedback = document.getElementById("feedback").value;

    var error1 = document.getElementById("error1");
    var error2 = document.getElementById("error2");
    var error3 = document.getElementById("error3");
    var error4 = document.getElementById("error4");


    /* set a boolean to check and prevent empty input */
    let noError = true;

    /* check empty logic */
    if (dishname === "") {
      error1.innerHTML = "Please input again!";
      error1.style.color = "red";
      document.getElementById("dishname").focus();
      noError = false;
      return false;
    } else {
      error1.innerHTML = "";
    }

    if (storename === "") {
      error2.innerHTML = "Please input again!";
      error2.style.color = "red";
      document.getElementById("storename").focus();
      noError = false;
      return false;
    } else {
      error2.innerHTML = "";
    }

    if (rating == 0) {
      error3.innerHTML = "Please choose a rating";
      error3.style.color = "red";
      document.getElementById("rating").focus();
      noError = false;
      return false;
    } else {
      error3.innerHTML = "";
    }

    if (feedback === "") {
      error4.innerHTML = "Please input again!";
      error4.style.color = "red";
      document.getElementById("feedback").focus();
      noError = false;
      return false;
    } else {
      error4.innerHTML = "";
    }


    /* if no empty input, then add our input into the list */
    if (noError = true) {

      itemNo = itemNo + 1;
      var ele1 = document.createElement("p");
      ele1.innerText = itemNo;
      ele1.classList.add("list");
      ele1.setAttribute("id", itemNo);
      document.getElementById("num").appendChild(ele1);

      var ele2 = document.createElement("p");
      ele2.innerText = dishname;
      ele2.setAttribute("id", itemNo + "dish");
      ele2.classList.add("list");
      document.getElementById("dish").appendChild(ele2);

      var ele3 = document.createElement("p");
      ele3.innerText = storename;
      ele3.setAttribute("id", itemNo + "store");
      ele3.classList.add("list");
      document.getElementById("store").appendChild(ele3);

      var ele4 = document.createElement("p");
      ele4.innerText = rating;
      ele4.setAttribute("id", itemNo + "rating");
      ele4.classList.add("list");
      document.getElementById("rate").appendChild(ele4);

      var ele5 = document.createElement("p");
      ele5.innerText = feedback;
      ele5.setAttribute("id", itemNo + "feedback");
      ele5.classList.add("list");
      document.getElementById("fb").appendChild(ele5);

      var ele6 = document.createElement("p");
      ele6.innerText = todaystring;
      ele6.setAttribute("id", itemNo + "date");
      ele6.classList.add("list");
      document.getElementById("date").appendChild(ele6);


      /* clear the form after add each row */
      document.getElementById("dishname").value = "";
      document.getElementById("storename").value = "";
      document.getElementById("rating").value = 0;
      document.getElementById("feedback").value = "";


      return false;

    }
  }

  /* remove button function */
  var removebtn = document.getElementById("btn3");
  removebtn.onclick = function() {

    var rm1 = document.getElementById(itemNo);
    rm1.remove();

    var rm2 = document.getElementById(itemNo + "dish");
    rm2.remove();

    var rm3 = document.getElementById(itemNo + "store");
    rm3.remove();

    var rm4 = document.getElementById(itemNo + "rating");
    rm4.remove();

    var rm5 = document.getElementById(itemNo + "feedback");
    rm5.remove();

    var rm6 = document.getElementById(itemNo + "date");
    rm6.remove();

    itemNo = itemNo - 1;

  }


  /* clear button function */
  var clearbtn = document.getElementById("btn2");
  clearbtn.onclick = function() {
    location.reload();
  }


  /* end window.onload function */
}
body {
  margin: 0;
  padding: 0;
}

.header {
  display: flex;
  flex-direction: row;
  background-color: #face77;
}

.button {
  padding-top: 10px;
  padding-bottom: 10px;
  padding-left: 18px;
  padding-right: 18px;
  background-color: #face77;
  font-weight: bold;
  font-size: 16px;
  border: transparent;
}

.btnfield {
  position: absolute;
  left: 65%;
}

.hardcoded {
  display: flex;
  flex-direction: row;
  gap: 80px;
  margin-left: 150px;
  margin-top: 25px;
}

.list {
  margin-bottom: 5px;
  margin-top: 10px;
  margin-left: 25px;
}

.inputform {
  position: absolute;
  top: 75%;
  left: 15%;
  display: none;
}

.form {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

footer {
  margin-top: 200px;
  position: absolute;
  top: 95%;
  right: 80%;
}
<div class="header">
  <h2 style="margin-left: 40px">DISH DAIRY</h2>
  <img style="width: 55px; height: 55px;margin-left: 20px;margin-top: 8px" src="./images/Meal.png" alt="icon">
  <p style="margin-left: 50px; font-size: 18px; margin-top: 22px;">Your Personal Food Log</p>
</div>

<h1 style="margin-left: 40px">Welcome!</h1>

<div class="btnfield">
  <button class="button" id="btn1">Add Dish</button>
  <button class="button" id="btn2">Clear All</button>
  <button class="button" id="btn3">Remove Dish</button>
</div>

<div>
  <p style="margin-top:100px; margin-left: 100px"><em>Show My Favourite and Dislike:</em></p>
</div>

<div class="hardcoded">

  <div id="num">
    <p>Item No.</p>
    <p class="list">1</p>
    <p class="list">2</p>
    <p class="list">3</p>
  </div>

  <div id="dish">
    <p style="margin-left:25px">Dish Name</p>
    <p class="list">CoconutStickyRice MiniCake</p>
    <p class="list">TaroUbe MiniCake</p>
    <p class="list">Icy Taro Ball B</p>
  </div>

  <div id="store">
    <p style="margin-left:25px">Store Name</p>
    <p class="list">LA LA Bakeshop</p>
    <p class="list">LA LA Bakeshop</p>
    <p class="list">Meet Fresh</p>
  </div>

  <div id="rate">
    <p style="margin-right:5px;">Rating out of 5</p>
    <p class="list">5</p>
    <p class="list">5</p>
    <p class="list">5</p>
  </div>

  <div id="fb">
    <p style="margin-left: 25px;">Feedback</p>
    <p class="list">so good</p>
    <p class="list">awesome</p>
    <p class="list">amazing</p>
  </div>

  <div id="date">
    <p style="margin-left:24px">Date</p>
    <p class="list">2024/10/18</p>
    <p class="list">2024/11/01</p>
    <p class="list">2024/12/02</p>
  </div>

</div>

<div class="inputform" id="formdiv">
  <form class="form" id="form">
    Dish Name:
    <input type="text" id="dishname">
    <span id="error1"></span> Store Name:
    <input type="text" id="storename">
    <span id="error2"></span> Rating Out Of 5:
    <select name="rating" id="rating">
      <option value="0">-- Choose your rating --</option>
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
    </select>
    <span id="error3"></span> Feedback:
    <input type="text" id="feedback">
    <span id="error4"></span>
    <button type="submit" id="btnadd" style="font-weight: bold; font-size: 16px; margin-left:45px; margin-top: 15px; padding-top: 10px; padding-bottom: 10px; background-color:#face77;max-width: 70px; border:transparent">Add</button>
  </form>
</div>

<footer>
  <p style="font-size: 12px;">Copyright &copy; 2024 All Right Reserved</p>
</footer>

I would like to know how to change my CSS code to make the input form changes its position, as the list above will grow more if I input more rows. How could make the form move closer to bottom as the lists grow??

Thank you so much for help.

Also, I hope any suggestions to help my project better for use.

Thank you for your time 🙂

issue with the java script

<script>
$(document).ready(function() {
    // Initialize DataTable
    $('table').DataTable();

    // Prepare chartData and chartLabels arrays
    let chartData = [];
    let chartLabels = ['Target', 'Proportionate Target', 'Up to Selected Month', 'Up to Same Period Previous Year']; // Only these labels

    // Get the row labeled "Total"
    const totalRow = $('table tbody tr:last'); // Last row is the "Total" row

    // Extract Target and Proportionate Target from the Total row
    const target = parseFloat(totalRow.find('td').eq(1).text().trim()) || 0; // "Target" in column index 1
    const proportionateTarget = parseFloat(totalRow.find('td').eq(2).text().trim()) || 0; // "Proportionate Target" in column index 2

    // Add Target and Proportionate Target to chartData
    chartData.push(target, proportionateTarget);

    // Extract final performance metrics from the Total row
    const upToSelectedMonth = parseFloat(totalRow.find('td').eq(3).text().trim()) || 0; // "Up to Selected Month" in column index 3
    const previousYearData = parseFloat(totalRow.find('td').eq(5).text().trim()) || 0; // "Up to Same Period Previous Year" in column index 5

    // Add final performance metrics to chartData
    chartData.push(upToSelectedMonth, previousYearData);

    // Extract month data starting from index 7
    const months = ['April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', 'January', 'February', 'March'];

    let monthData = [];
    let monthLabels = [];

    // Loop through month columns starting from index 7
    $('table thead th').each(function(index) {
        if (index >= 7 && index <= 18) { // Month columns start from index 7 and end at 18 (March)
            const headerText = $(this).text().trim();
            const monthValue = parseFloat(totalRow.find('td').eq(index).text().trim()) || 0;
            monthLabels.push(headerText); // Add the month name to chart labels
            monthData.push(monthValue); // Add the corresponding month value
        }
    });

    // Add month data to chartData (after Target and Proportionate Target)
    chartData = chartData.concat(monthData);
    chartLabels = chartLabels.concat(monthLabels); // Add month labels after the static labels

    // Create the chart
    const ctx = document.getElementById('performanceChart').getContext('2d');
    const performanceChart = new Chart(ctx, {
        type: 'bar',  // Bar chart
        data: {
            labels: chartLabels,  // X-axis labels (Target, Proportionate Target, months)
            datasets: [{
                label: 'Data Values',
                data: chartData, // All data values including months
                backgroundColor: ['rgba(54, 162, 235, 0.7)', 'rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)', 'rgba(153, 102, 255, 0.7)', 'rgba(255, 159, 64, 0.7)', 'rgba(255, 99, 132, 0.7)', 'rgba(54, 162, 235, 0.7)', 'rgba(75, 192, 192, 0.7)', 'rgba(153, 102, 255, 0.7)', 'rgba(255, 159, 64, 0.7)', 'rgba(255, 99, 132, 0.7)', 'rgba(54, 162, 235, 0.7)'], // Different colors for each bar
                borderColor: 'rgba(54, 162, 235, 1)',
                borderWidth: 1
            }]
        },
        options: {
            responsive: true,
            scales: {
                x: {
                    beginAtZero: true,
                    ticks: {
                        autoSkip: true,
                        maxTicksLimit: 20
                    }
                },
                y: {
                    beginAtZero: true
                }
            },
            plugins: {
                tooltip: {
                    callbacks: {
                        label: function(tooltipItem) {
                            return tooltipItem.dataset.label + ': ' + tooltipItem.raw.toFixed(2);
                        }
                    }
                },
                legend: {
                    display: true,
                    position: 'top'
                }
            }
        }
    });
});
</script>

This is my java script code for generating bar graphs from a table. The issue is I have removed ‘% growth as compared to same period last year’ from chartlebels but it still showing in the chartlebels and ‘Up to Same Period Previous Year’ is showing two times in chartlebels which should show only once. please findout the issue.

Google Maps JavaScript API: InfoWindow closes prematurely when changing map bounds

I’m working with the Google Maps JavaScript API and trying to ensure that only one InfoWindow is open at a time. I have the following code to open and close the InfoWindow when a marker is clicked or when the map is clicked. It works fine for the most part, but I’m running into an issue when changing the map bounds.

let activeInfoWindow = null;

marker.addListener('click', () => {
    if (activeInfoWindow) {
        activeInfoWindow.close();
    }

    infoWindow.open({
        anchor: marker,
        map,
        shouldFocus: false,
    });

    activeInfoWindow = infoWindow;
});

map.addListener('click', () => {
    if (activeInfoWindow) {
        activeInfoWindow.close();
        activeInfoWindow = null;
    }
});

map.addListener('bounds_changed', () => {
    if (activeInfoWindow) {
        activeInfoWindow.close();
        activeInfoWindow = null;
    }
});

The Problem:
The issue I’m encountering is that the InfoWindow closes too quickly when the map bounds are changed (due to zooming or panning), even before the marker has had a chance to move into view. This results in a poor user experience, as the user has to click the marker again to reopen the InfoWindow, which is disruptive.

What I’ve Tried:
I tried using setTimeout() to delay the InfoWindow closure, but that doesn’t solve the issue.
I also looked into idle or bounds_changed events, but couldn’t find a way to avoid the premature closing.

Expected Behavior:
I want the InfoWindow to remain open while the user is zooming or panning the map to get the marker into view, and only close after the user has finished adjusting the map bounds.

Has anyone encountered this problem or can suggest a solution to improve the user experience with InfoWindow closing in response to map bounds changes?

How to detect if tag has a class in child Angular component

I have a dynamic class which is applied on html tag when user presses the button to switch view to dark mode:
<html lang="en" class="app-dark-theme">

Now in child Angular component I need to check if this class exists on html tag to style one particular class in this child component.

How can I achive this? I tried :has() pseudo-selector but it didn’t work for me.

  html:has(.app-dark-theme) {
    .test-class {
      border: 1px solid var(--p-blue-200);
    }
  }

Prefferable solution is pure CSS/SCSS if possible.

Thanks.

Why the following userscripts make google page reload continuously but works on other page?

I have created this userscript to get split the text chinese text and click to popup the words. But it works on every other website but on google page, it keeps reloading the page.

In following code I use jieba to cut the chinese text, then use loop to replace the text with chinese. It works on all pages instead of google search page.

What could be fix for it.

// ==UserScript==
// @name         Annotate Chinese Text with Popup
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Annotate Chinese text and show a popup on click using Jieba WASM
// @author       You
// @match        *://*/*
// @grant        GM_addElement
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(async function () {
    'use strict';

    // Load styles for the popup
    GM_addStyle(`
        .clickable-word {
            cursor: pointer;
        }
    `);

    // Create the popup element
    const popup = GM_addElement(document.body, 'div', {
        id: 'wordPopup',
    });

    // Load Jieba WASM module
    const script = GM_addElement('script', {
        src: 'https://cdn.jsdelivr.net/npm/jieba-wasm@latest/pkg/web/jieba_rs_wasm.js',
        type: 'module',
    });

    script.onload = async () => {
        const { default: init, cut } = await import('https://cdn.jsdelivr.net/npm/jieba-wasm@latest/pkg/web/jieba_rs_wasm.js');
        await init('https://cdn.jsdelivr.net/npm/jieba-wasm@latest/pkg/web/jieba_rs_wasm_bg.wasm');
        console.log('Jieba WASM initialized.');

        // Function to replace words in the text of a web page
        function replaceWordsOnPage(jieba) {
            // Walk through the DOM and replace text in text nodes
            function processNode(node) {
                if (node.nodeType === Node.TEXT_NODE) {
                    // Process only Chinese text
                    const regexChinese = /[u4e00-u9fa5]+/g; // Match Chinese characters
                    let originalText = node.nodeValue;
                    let processedHtml = '';
                    let lastIndex = 0;

                    let match;
                    while ((match = regexChinese.exec(originalText)) !== null) {
                        // Append non-Chinese text before the match
                        processedHtml += originalText.slice(lastIndex, match.index);

                        // Cut the matched Chinese text
                        const cutWords = jieba.cut(match[0]);

                        // Wrap cut words with custom tags and add click handler
                        processedHtml += cutWords
                            .map(word => `<abc class="clickable-word" data-word="${word}">${word}</abc>`)
                            .join('');

                        lastIndex = regexChinese.lastIndex;
                    }

                    // Append remaining non-Chinese text
                    processedHtml += originalText.slice(lastIndex);

                    // Replace the text node with a span containing the processed HTML
                    const span = document.createElement('span');
                    span.innerHTML = processedHtml;
                    node.replaceWith(span);
                } else if (node.nodeType === Node.ELEMENT_NODE) {
                    // Process child nodes recursively
                    Array.from(node.childNodes).forEach(processNode);
                }
            }

            // Start processing from the body element
            processNode(document.body);

            // Attach click event listener for Chinese words
            document.body.addEventListener('click', (event) => {
                if (event.target.classList.contains('clickable-word')) {
                    const word = event.target.dataset.word;
                    showPopup(word, event.pageX, event.pageY);
                }
            });
        }

        // Show popup with the clicked word
        function showPopup(word, x, y) {
            popup.style.left = `${x - 20}px`;
            popup.style.top = `${y + 20}px`;
            popup.style.display = 'block';
            popup.style.position = "absolute";
            popup.style.background = "#f9f9f9";
            popup.style.border = "1px solid #ddd"
            popup.style.padding = "8px";
            popup.style.boxShadow = "0 4px 8px rgba(0,0,0,0.1)";
            popup.style.zIndex = "10000";
            popup.innerHTML = `<strong>${word}</strong>`;
        }

        // Hide popup when clicking elsewhere
        document.addEventListener('click', (event) => {
            if (!event.target.classList.contains('clickable-word')) {
                popup.style.display = 'none';
            }
        });

        // Process the page with Jieba
        replaceWordsOnPage({ cut });
    };
})();

NestJs HMR not working with “type”: “module”

I followed NestJs HMR using webpack using following url.

https://docs.nestjs.com/recipes/hot-reload

It works fine when we use "type": "commonjs" in package.json.

Unfortunately I have monorepo with client app that uses "type": "module"

When I run nest build --webpack --webpackPath webpack-hmr.config.js --watch

I get below error.

 Error  require() of ES Module my-projectwebpack-hmr.config.js from [email protected] not supported.
webpack-hmr.config.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead either rename webpack-hmr.config.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).

That is because I used require and module.exports with "type": "module"

const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');

module.exports = function (options, webpack) {
  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.WatchIgnorePlugin({
        paths: [/.js$/, /.d.ts$/],
      }),
      new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false }),
    ],
  };
};

So I converted them to import and export default as below

import nodeExternals from 'webpack-node-externals';
import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';

export default (options, webpack) => {
  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.WatchIgnorePlugin({
        paths: [/.js$/, /.d.ts$/],
      }),
      new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false }),
    ],
  };
};

Now I get following and for that part I am not sure how to solve it.

 Error  require() of ES Module webpack-hmr.config.js from [email protected] not supported.
Instead change the require of webpack-hmr.config.js in [email protected] to a dynamic import() which is available in all CommonJS modules.

Redirect request to my homepage and keep path React Next.js

I started with React, read some docs but I can’t find a solution, maybe I’m wrong

I created a project using create-next-app

I have a HomePage in src/app/page.tsx

export default function HomePage() {
  const [loggedIn, setLoggedIn] = useState(false)
  const [email, setEmail] = useState('')

  return (
    <div className="App">
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home email={email} loggedIn={loggedIn} />} />
        <Route path="/login" element={<Login setLoggedIn={setLoggedIn} setEmail={setEmail} />} />
      </Routes>
    </BrowserRouter>
    </div>
  )
}

In Home() method, I show a button

const onButtonClick = () => {
  navigate('/login')
}
[...]
<input
  type="button"
  onClick={onButtonClick}
  value={loggedIn ? 'Log out' : 'Log in'}
/>

When I click on the button, I can see my Login form.

I saw that my url is now localhost:3000/login

If I do a refresh (F5) I have a 404 http error

I edited my next.config.js to redirect all requests to my homepage

const nextConfig: NextConfig = {
  async redirects() {
    return [{
        source: "/:path*",
        destination: "/",
        permanent: true,
      },
    ];
  },
};

but I have a too many redirects error

I edited as below and now when I navigate to /login I can see my homepage

const nextConfig: NextConfig = {
  async redirects() {
    return [{
        source: "/login",
        destination: "/",
        permanent: true,
      },
    ];
  },
};

How to redirect requests to my homepage and go to my <Route path="/login" ... ?

Code-inject a switch’s webview login (Alcatel-Lucent)

Main information:

  • The school’s network switches are by Alcatel-Lucent
    • They have WebView enabled.
  • I am trying to get through the sign-in layer.
  • External extensions ARE blocked.
  • Chrome policies are set to mandatory, all of them.
  • I am on chromeos, no hope of getting developer mode. (I’ve tried everything)

what I am trying to accomplish

What I am doing is trying to get through the WebView login screen through any means.
It is accessible via HTTP and HTTPS if of any help.
I have tried stuff like simple injection scripts, but I do not really understand the languages behind them except HTML. I can’t use developer tools, but I’m trying to find the data that is posted to _self and record it to a text file.

Here is the site’s code, as provided by the view-source: protocol:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>

<head>
<title>Webview Logon Page</title>
<style type="text/css">
body    {
    margin:0px;
    background:#5C5C5C;
    color:#FFFFFF;
    font-family:Verdana, Arial, Helvetica, Fixed;
    font-weight:bold;
    font-size:10pt;
}
table   {   
    border-collapse:collapse;
    border-style:none;
  margin:0px; 
  width:100%;
  padding:0px;
}
table.formTable { margin:35px; width:auto; }
td  {
    background:#6850A2;
    color:#FFFFFF;
    font-family:Verdana, Arial, Helvetica, Fixed;
    font-weight:bold;
    font-size:10pt;
    text-align:left;
    vertical-align:top;
    white-space:nowrap;
}
td.spacer {
    background:#000000;
  font-size:20pt;
    text-align:right;
    padding:10px;
    width:25%;
}
td.label {
    text-align:right;
    height:30px;
    padding:0px 10px 25px 10px;
}
td.input { height:30px; padding:0px 10px 5px 10px; }
td.data { 
    font-size:11pt;
    height:30px;
    padding:30px 10px;
}
</style>
<script type="text/javascript">
if (self != top) 
{
    if ( location.protocol == "http:" )
      top.location = "http://192.168.0.1/web/content/login.html"
    if ( location.protocol == "https:" )
      top.location = "https://192.168.0.1/web/content/login.html"
}

function TopLoader()
{
     if ( (window.name == "configWin") || (window.name == "helpWin") || (window.name == "addWin") )
   {
        window.opener.top.location = "login.html";
        window.self.close();
   }

     /* Display pre-banner message if file is present
      * Make sure to use single quotes. Double quotes might
      * break JavaScripts.
      */
    var message = '';

    /* Using vi to remove text from pre_banner.txt can leave some sort of whitespace */
    if  (message != '')
          alert(message);
}
</script>
<script type="text/javascript" src="/web/content/scripts/browsercheck.js"></script>
</head>

<body onLoad="checkBrowser(); TopLoader(); document.forms[0].elements[0].focus();">

<FORM method="POST" target="_self" ACTION="/web/content/login.html">
<table>
  <tr>
      <td class="spacer" rowspan="10">WebView</td>
      <td><img border="0" src="/oem/content/banner.jpg" nosave height="54"></td>
  </tr>
  <tr>
    <td class="data"><!--webbot bot="HTMLMarkup" startspan -->
    <!--webbot bot="HTMLMarkup" endspan -->&nbsp;</td>
  </tr>
  <tr>
    <td>
        <table class="formTable">
        <tr>
          <td class="label">User Name</td>
          <td class="input"><INPUT type="text" name="userName" SIZE="30" MAXLENGTH="63" VALUE=""></td>
        </tr>
        <tr>
          <td class="label">Password</td>
          <td class="input"><INPUT type="password" name="password" SIZE="30" MAXLENGTH="40" VALUE=""></td>
        </tr>
        <tr>
          <td style="text-align:center;" colspan="2"><INPUT type="submit" name="B1" VALUE="Login"></td>
        </tr>
      </table>
      </td>
  </tr>
  <tr>
    <td class="data" style="color:#800000;">
      <script type="text/javascript">
        var errMsg="";
        document.write(errMsg==""?"&nbsp;":("<u>Error</u>&nbsp;-&nbsp;" + errMsg));
      </script>
    </td>
 </tr>
</table>
</FORM>

</body>
</html>

You may be wondering, “Why are you doing this, chrom?”, simply for fun. I just want something to learn and do during class in my free time.

If you have any methods to try and get through the security layer, I will try them; except if I already have tried it.

Same thing with my last few posts – I’m not that fluent with this category of stuff, so if you need more information, feel free to ask.