Node Postgre Parameterized Query Not Working Properly

I’m building a project in Node Js & Express & TypeScrypt and i’m encountering a weird problem when i try to retrieve the credentials of an user by email. The query cannot find or distinguish if there is any valid credentials only returns an empty array always. When i retrieve the whole collection and filter it with Ts it works as expected. Anyone has any idea why this is not working as intended?

This is how the client makes the query

async query(query: string , values : any[]) {
    if (!this._instance) {
      throw Error("PostGreClient instance is null");
    }
    if (!query) {
      throw Error("Query is empty");
    }
    // Log the query and the values to see what is being executed
    console.log("Executing query:", query);
    console.log("With parameters:", values);
    const obj = {
      text : query,
      values : values
    }
    return await this._instance.query(obj);
  }
async getCredentialsbyEmail(email: string): Promise<UserCredentials | null> {
      try{  
        this._debugListener("Retrieving credentials for email : " , email)
        const result = await this._client.query(`SELECT * from user_credentials WHERE email =$1` ,[email])
    
        const allCredentials = await this.getAllUserCredentials()
        const target = allCredentials.find((c)=> {
          return c.email === email
        })
      
        console.log("Existing Credentials : " +  JSON.stringify(allCredentials))
        this._debugListener("Current Result : " , result.rows)
        console.log("Expected Result = " , target)
        if(!result.rowCount || result.rowCount  === 0 ){
          return null
        }
        else if(result.rowCount >1){
          throw Error("Too many accounts linked to this email : " + email)
        }
        const credentials = result.rows[0].map((obj : any) => {
          return new UserCredentials(
            obj.uid,
            obj.userUid,
            obj.hashed_password,
            obj.email
          )
        })
        return credentials
      }catch(e){
        console.log(e)
        if(e instanceof Error){
          this._errorListener(e.message)
        }
        return null
      }
    }

How to implement a pause menu in my javascript canvas animation?

I’m adding menu states to an interactive canvas animation based on my edits to this MDN exercise. I want to pause the animation when I press esc (not hide it under an opaque overlay) and resume when I click “return”. At this point I feel like I don’t know enough JS to implement it, or whether it’s possible in JS.

(As you can see, I’m a beginner and this is for me to practice simple JS and CSS, not make a proper browser game, so “real world” solutions are not the focus)

The pause menu opens and closes as intended, but I don’t know how to implement the actual pause!

I tried do { } while (clicked === false); and the page froze. I looked it up and learned that waiting and blocking is different, and that you should never do this. I found out about async/await and understood some simple examples. But I didn’t figure out how to apply that to functions in my code. I haven’t gotten to the asynchronous JS part of my coding course yet and it really confuses me. Do I need to learn about asyncronous JS first to solve this? Some replies to similar questions sound like in JS you’re not supposed to pause code execution that way at all… I have no idea how to approach this anymore.

In the MRE snippet I’ve removed unrelated elements and styling, and replaced the canvas with a random number generator.

// pause-test.js

let testArea = document.getElementById("test-area");
let pauseScreen = document.getElementById("pause-menu");
let resumeButton = document.getElementById("resume-button");

function random(min, max) {
  return Math.round(min + ((max - min) * Math.random()));
}

function idleMode() {
  // removed canvas and shapes declarations
  // draw(shapes);

  draw();

  // esc should open pause menu
  document.addEventListener("keydown", (event) => {
    if ((event.key === "Escape")) {
      console.log("esc in animation()");
      pauseMenu();
    }
  })
}

idleMode();

function draw() {
  function loop() {
    // removed frame drawing functions
    // instead display a random number every second 
    let num = random(0, 10000000);
    testArea.textContent = `${num}`;
    setTimeout(() => {
      requestAnimationFrame(loop);
    }, 1000);
    return;
  }
  loop();
}

function pauseMenu() {
  // absolute positioned on top of the canvas
  pauseScreen.classList.remove("hide");
  console.log("paused");

  // QUESTION: How to pause animation until this event fires?
  resumeButton.addEventListener("click", pauseResume);

  return;
}

function pauseResume() {
  console.log("unpaused");
  pauseScreen.classList.add("hide");
  return;
}
<main>
  <div class="display">
    <p id="test-area">test area</p>
    <!-- <canvas tabindex='1'></canvas> -->

    <section id="pause-menu" class="hide">
      <header>Paused</header>
      <button id="resume-button">resume</button>
      <!-- <button id="exit-button">exit</button> -->
    </section>
  </div>
</main>

Check which polygons are bordering each other and register them in a list

So I’m making a bad remake of the game crusader kings 2, but I found an issue that I don’t know how to solve.
I have provinces, and I want to know which ones are bordering each other, then make a new ‘border’ object and push it to a list.
Can anyone help?
Here’s my HTML code:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>replit</title>
  <link href="style.css" rel="stylesheet" type="text/css" />
</head>

<body>
  <h3>Conqueror's Realm</h3>
  <p id='stats'></p>
  <canvas id='thisisacanvas'></canvas>
  <button onclick="Bool()">Background</button>
  <button onclick="Map()">Map</button>
  <button onclick="deSel()">Deselect</button>
  <button onclick="save()">Save</button>
  <button onclick="load()">Load</button>
  <button onclick="newRun()">New Game</button>
  <script src="script.js"></script>
  <p id='selected'></p>
  <p id='loading'></p>
</body>

</html>

And here’s my JS code:

var cvs = document.getElementById('thisisacanvas');
var scale;
cvs.width = (window.innerWidth-17);
scale = cvs.width / 384;
cvs.height = 216*scale;
var ctx = cvs.getContext('2d');


function save() {
  let Save = [
    Stats,
    people,
    provinces,
    countries
  ];
  localStorage.setItem('save', JSON.stringify(Save));
  console.log('saved')
}
function load() {
  let Save = JSON.parse(localStorage.getItem('save'));
  Stats = Save[0];
  people = Save[1];
  provinces = Save[2];
  countries = Save[3];
  update();
  console.log('loaded')
}

var borders = [];
class border {
  constructor(mtn,type,provinces) {
    this.mtn = mtn;   // mountain?
    this.type = type; // l-l, l-w, w-w, land/water
    this.provinces = provinces;
  }
}

const img = new Image();
img.src = "Ref.png";

var bool = true;
function Bool() {
  bool = !bool;
}
var map = true;
function Map() {
  map = !map;
}
function deSel() {
  prev_i = -1;
  pName = NaN;
  overlap = false;
  document.getElementById('selected').innerHTML = '';
}


function newCity(i) {
  if (provinces[i].cities.length < 5) {
  provinces[i].cities.push(prompt('City Name: '))
  } else {
    alert('Max Cities Reached on Province ' + provinces[i].name)
  }
  document.getElementById('selected').innerHTML = provinces[i].name;
  if (provinces[i].capital) {
    document.getElementById('selected').innerHTML += ' (Capital)';
  }
  document.getElementById('selected').innerHTML += '<br>' + `<span style='color: ${provinces[i].country.color};'>${provinces[i].country.name}</span>`;
  for (let j = 0; j < provinces[i].cities.length; j++) {
    document.getElementById('selected').innerHTML += '<br> - ' + provinces[i].cities[j];
  }
  document.getElementById('selected').innerHTML += `<br><button onClick="newCity(${i})">Add City</button>`;
  prev_i = i
  pName = NaN;
}

function InputMousePos(event) {
    let rect = cvs.getBoundingClientRect();
    let x = (event.clientX - rect.left)/scale;
    let y = (event.clientY - rect.top)/scale;
    return [x,y];
}


function inside(point, vs) {
  var x = point[0], y = point[1];
  
  var inside = false;
  for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
    var xi = vs[i][0], yi = vs[i][1];
    var xj = vs[j][0], yj = vs[j][1];
    
    var intersect = ((yi > y) != (yj > y))
      && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
    if (intersect) inside = !inside;
  }
  
  return inside;
};
var pName = NaN;
var prev_i = NaN;
var overlap = false;
cvs.addEventListener("mousedown", function (event) {
  for (let i = 0; i < provinces.length; i++) {
    if (inside(InputMousePos(event), provinces[i].vertexPositions)) {
      if (prev_i != i) {
        overlap = false;
        document.getElementById('selected').innerHTML = provinces[i].name;
        if (provinces[i].capital) {
          document.getElementById('selected').innerHTML += ' (Capital)';
        }
        document.getElementById('selected').innerHTML += '<br>' + `<span style='color: ${provinces[i].country.color};'>${provinces[i].country.name}</span>`;
        for (let j = 0; j < provinces[i].cities.length; j++) {
          document.getElementById('selected').innerHTML += '<br> - ' + provinces[i].cities[j];
        }
        document.getElementById('selected').innerHTML += `<br><button onClick="newCity(${i})">Add City</button>`;
        prev_i = i
        pName = NaN;
      } else {
        pName = provinces[i].country.name;
        overlap = true;
        prev_i = -1;
        document.getElementById('selected').innerHTML = `<span style='color: ${provinces[i].country.color};'>${provinces[i].country.name}</span>`;
        document.getElementById('selected').innerHTML += '<br>'+ provinces[i].country.ruler.title + ' ' + provinces[i].country.ruler.name + ' ' + provinces[i].country.ruler.Title;
      }
    }
  }
});

function findPerson(identifier, object) {
  let found = [];
  if (identifier == 'name') {
    console.log('searching name');
    for (let count = 0; count < people.length; count++) {
       if (people[count].name == object) {
         found.push(people[count]);
       }
    }
  } else if (identifier == 'rule') {
    console.log('searching rule');
    for (let count = 0; count < people.length; count++) {
       if (people[count].rule == object) {
         found.push(people[count]);
       }
    }
  } else if (identifier == 'title') {
    console.log('searching title');
    for (let count = 0; count < people.length; count++) {
       if (people[count].title == object) {
         found.push(people[count]);
       }
    }
  }
  if (found.length == 1) {
    return found[0];
  } else if (found.length == 0) {
    reportError(identifier + ' ' + object + ' not found')
  } else {
    return found;
  }
}


class Trait {
  constructor(name, effectHP, effectXPG, description) {
    this.name = name;
    this.effectHP = effectHP;
    this.effectXPG = effectXPG;
    this.description = description;
  }
}

class Stats {
  country = new Country('rgb(0,0,0 / 100%','None');
  human = new Human(0,0,0,0,'None',0,0,0,[]);
}


class Human {
  constructor(lvl, xp, gold, name, prestiege, religion, nationality, country, traits, title, rule, Title) {
    this.country = country;
    this.title = title;
    if (Title) {
      this.Title = Title;
    } else {
      this.Title = '';
    }
    if (rule) {
      this.rule = rule;
    } else {
      this.rule = '';
    }
    if (religion) {
      this.religion = religion;
    } else {
      this.religion = '';
    }
    this.health = 80;
    this.lvl = lvl;
    this.xp = xp;
    this.xpg = 1.0;
    this.gold = gold;
    this.name = name;
    this.prestiege = prestiege;
    this.nationality = nationality;
    this.traits = traits;
  }
}

class Country {
  constructor(color,name, ruler) {
    this.color = color;
    this.name = name;
    this.provinces = 0;
    this.ruler = ruler;
  }
}

var people = [];
var provinces = [];
var countries = [];

function compare(a,b,len) {
  for (let i = 0; i < len; i++) {
    if (a[i] != b[i]) {
      return false;
    }
  }
  return true;
}

class Province {
  constructor(parent,name,vertexPositions,isCapital,cities) {
    parent.provinces++;
    this.country = parent;
    this.capital = isCapital;
    this.name = name;
    this.vertexPositions = vertexPositions;
    this.buildings = [];
    this.cities = cities;
  }
}

function findCountry(name) {
  for (var i = 0; i < countries.length; i++) {
    if (countries[i].name == name) {
      return countries[i];
    }
  }
  a = new Country(`rgb(${Math.floor(Math.random()*255)} ${Math.floor(Math.random()*255)} ${Math.floor(Math.random()*255)} / 100%)`, name) //75%
  countries.push(a)
  return a;
}

var loading = true;

function newRun() {
  loading = true;
  
  people.push(new Human(14, 52, 108, 'Drest IX', 72, 'Catholic', 'Scottish', 'Scotland', [], 'King', 'Scotland'));
  
  countries.push(new Country('rgb(50 50 150 / 100%)','Scotland', findPerson('rule', 'Scotland'))); //75%
  
  provinces.push(new Province(findCountry("Scotland"),"Sutherland",[[77,30],[68,28],[72,24],[64,24],[60,35],[62,35]],false,['Durness']));
  provinces.push(new Province(findCountry("Scotland"),"Perth",[[77,33],[77,30],[62,35],[60,42],[65,38],[74,40]],true,['Perth']));
  provinces.push(new Province(findCountry("Scotland"),"Lanark",[[65,38],[74,40],[70,45],[63,46]],false,['Edinburough','Glasgow']));
  
  people.push(new Human(15,51,86,'Aethelred I',71,'Catholic','English','England',[],'King','England'));
  
  countries.push(new Country('rgb(150 50 50 / 100%)','England', findPerson('rule', 'England'))); //75%
  
  provinces.push(new Province(findCountry("England"),"Cumberland",[[70,45],[74,40],[79,42],[80,46],[72,50],[70,49],[71,45]],false,['Newcastle']));
  provinces.push(new Province(findCountry("England"),"Yorkshire",[[80,46],[72,50],[72,55],[80,57],[89,58],[87,51]],false,['York','Lincoln']));
  provinces.push(new Province(findCountry("England"),"Essex",[[89,58],[80,57],[73,65],[82,62],[90,65],[95,63],[94,58]],false,['Bury St.Edmonds']));
  provinces.push(new Province(findCountry("England"),"London",[[73,65],[82,62],[90,65],[90,66],[72,67]],true,['London']));
  provinces.push(new Province(findCountry("England"),"Sussex",[[90,66],[72,67],[75,71],[91,70],[94,67]],false,['Canterbury']));
  
  people.push(new Human(12, 76, 24, 'Merfyn Frych', 107, 'Catholic', 'Welsh', 'Wales', [], 'King', 'Wales','the Great'));
  
  countries.push(new Country('rgb(170 135 50 / 100%)','Wales', findPerson('rule', 'Wales'))); // 75%
  
  provinces.push(new Province(findCountry("Wales"),"Wales",[[72,55],[80,57],[73,65],[64,64],[70,62],[65,56]],true,['Shrewsbury']));
  provinces.push(new Province(findCountry("Wales"),"Cornwall",[[72,67],[75,71],[65,74],[62,74],[67,69]],false,['Bristol']));
  
  people.push(new Human(18,105,67,'Lóegaire mac Néill',104,'Catholic','Irish','Ireland',[],'King','Ireland'));
  
  countries.push(new Country('rgb(50 150 50 / 100%)','Ireland', findPerson('rule', 'Ireland')));
  
  provinces.push(new Province(findCountry("Ireland"),"Ulster",[[58,44],[61,50],[58,52],[54,49],[52,50],[49,48],[52,45]], true,['Belfast']));
  
  provinces.push(new Province(findCountry("Ireland"),"Connacht",[[52,45],[49,48],[47,56],[44,56],[40,55],[39,50],[45,50],[45,48],[48,44]],true,['Galway']));
  
  provinces.push(new Province(findCountry("Ireland"),"Dublin",[[49,48],[47,56],[53,62],[57,60],[58,52],[54,49],[52,50]],true,['Dublin']));
  
  provinces.push(new Province(findCountry("Ireland"),"Munster",[[47,56],[53,62],[50,63],[41,66],[38,61],[42,60],[44,56]],true,['Cork','Limerick']));
  
  people.push(new Human(22,86,103,'Alan I',92,'Catholic','Breton','Brittany',[],'King','Brittany', 'the Great'));
  
  countries.push(new Country('rgb(170 90 130 / 100%)','Brittany', findPerson('rule', 'Brittany')));
  
  provinces.push(new Province(findCountry("Brittany"),"Brittany",[[65,84],[66,87],[77,90],[79,87],[80,84],[75,84],[73,82]],false,[]));
  
  people.push(new Human(22,86,103,'Charles',92,'catholic','French','West Francia',[],'King','West Francia', 'the Simple'));
  
  countries.push(new Country('rgb(70 90 130 / 100%)','West Francia', findPerson('rule', 'West Francia')));
  
  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Normandy",[[79,87],[80,84],[79,77],[81,77],[88,79],[88,77],[91,76],[96,74],[95,77],[91,80],[87,81],[83,83]],false,['Caen']));
  
  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Anjou",[[91,80],[87,81],[83,83],[79,87],[84,91],[88,92],[94,86]],false,[]));
  
  provinces.push(new Province(findCountry("West Francia"),"Île de France",[[95,77],[91,80],[94,86],[100,85],[104,83],[103,77]],true,['Paris']));
  
  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Poitou",[[77,90],[79,87],[84,91],[88,92],[91,97],[86,101],[83,100],[84,98],[79,96]],false,[]));
  
  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Aquitaine",[[91,97],[86,101],[83,100],[81,107],[87,109],[92,105]],false,[]));
  
  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Gascogne",[[81,107],[87,109],[89,112],[89,118],[81,116],[80,115]],false,[]));
  
  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Toulouse",[[92,105],[87,109],[89,112],[89,118],[92,117],[95,120],[102,120],[102,116],[97,108]],false,[]));

  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Auvergne",[[102,116],[97,108],[101,101],[106,103],[108,110],[108,113],[106,113]],false,[]));

  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Bourbon",[[97,108],[101,101],[97,98],[91,97],[92,105]],false,[]))

  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Maine",[[97,98],[91,97],[88,92],[94,86],[100,85],[101,88],[97,90],[98,94]],false,[]));

  provinces.push(new Province(findCountry("West Francia"),"Dutchy of Burgundy",[[100,85],[101,88],[97,90],[98,94],[97,98],[101,101],[106,103],[108,98],[111,96],[112,90],[109,86],[104,83]],false,[]));

  provinces.push(new Province(findCountry("West Francia"),"Prince-Archbishopric of Champagne",[[109,86],[104,83],[103,77],[110,75],[116,78],[115,82]],false,[]));
  
  people.push(new Human(22,86,103,'Dirk I',92,'Catholic','Frisian', 'Frisia',[],'King','Frisia'));
  
  countries.push(new Country('rgb(250 170 50 / 100%)','Frisia', findPerson('rule', 'Frisia')));
  
  provinces.push(new Province(findCountry("Frisia"),"Picardy",[[96,74],[95,77],[103,77],[110,75],[101,69],[96,70]],false,[]));
  
  provinces.push(new Province(findCountry("Frisia"),"Flanders",[[110,75],[101,69],[104,67],[112,66],[117,69],[112,72]],false,[]));
  
  provinces.push(new Province(findCountry("Frisia"),"Wallonia",[[117,69],[112,72],[110,75],[116,78],[119,76],[117,75],[119,73]],false,[]));
  
  provinces.push(new Province(findCountry("Frisia"),"Netherlands",[[104,67],[112,66],[117,69],[118,66],[117,64],[121,64],[123,61],[121,59],[122,59],[123,56],[121,55],[116,55],[114,56],[114,58],[115,58],[116,61],[112,62],[114,58],[111,58],[110,62]],true,['Utrecht']));

  people.push(new Human(22,86,103,'Louis',92,'Catholic','Burgundian','Burgundy',[],'King','Burgundy','the Blind'));

  countries.push(new Country('rgb(130 70 70 / 100%)','Burgundy',findPerson('rule', 'Burgundy')));

  provinces.push(new Province(findCountry("Burgundy"),"Dutchy of Provence",[[108,110],[108,113],[112,114],[115,116],[118,116],[125,111],[121,109],[122,105],[114,107]],true,['Aix']));

  provinces.push(new Province(findCountry("Burgundy"),"Dutchy of Dauphine",[[106,103],[108,110],[114,107],[113,101],[111,96],[108,98]],false,[]));

  provinces.push(new Province(findCountry("Burgundy"),"Prince-Archbishopric of Savoy",[[122,105],[114,107],[113,101],[111,96],[114,95],[117,97],[120,96],[121,100]],false,[]));
  
  provinces.push(new Province(findCountry("Burgundy"),"Dutchy of Franche Comte",[[115,82],[109,86],[112,90],[111,96],[114,95],[116,90]],false,[]));

  provinces.push(new Province(findCountry("Burgundy"),"Dutchy of Franche Comte",[[114,95],[116,90],[115,82],[119,86],[122,89],[117,97]],false,[]));

  try {deSel();} catch {}
}

function update() {
  cvs.width = (window.innerWidth-17);
  scale = cvs.width / 384;
  cvs.height = 216*scale;
  ctx.fillStyle = 'rgb(200 230 255)';
  ctx.fillRect(0, 0, cvs.width, cvs.height);
  if (bool) { ctx.drawImage(img, 0, 0, 400*scale, 300*scale); }
  if (map) {
    for (let i = 0; i < provinces.length; i++) {
      ctx.beginPath()
      ctx.fillStyle = provinces[i].country.color;
      if (provinces[i].country.name == pName) {
        ctx.fillStyle = 'cyan';
      }
      for (let j = 0; j < provinces[i].vertexPositions.length; j++) {
        ctx.lineTo(provinces[i].vertexPositions[j][0]*scale,provinces[i].vertexPositions[j][1]*scale);
      }
      ctx.lineTo(provinces[i].vertexPositions[0][0]*scale,provinces[i].vertexPositions[0][1]*scale);
      ctx.stroke();
      ctx.fill();
    }
    if (!overlap && prev_i >= 0) {
      ctx.beginPath();
      ctx.fillStyle = 'cyan';
      for (let j = 0; j < provinces[prev_i].vertexPositions.length; j++) {
        ctx.lineTo(provinces[prev_i].vertexPositions[j][0]*scale,provinces[prev_i].vertexPositions[j][1]*scale);
      }
      ctx.lineTo(provinces[prev_i].vertexPositions[0][0]*scale,provinces[prev_i].vertexPositions[0][1]*scale);
      ctx.stroke();
      ctx.fill();
    }
  }
}

newRun();
setInterval(update, 250);

I tried a method, but I don’t think it works and it took way too long.

Jest Test in Next.js Fails with “ReferenceError: document is not defined”

I’m working on a Next.js app and trying to write a Jest test for a simple button component. However, when I run the test, I get this error:

ReferenceError: document is not defined
The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string.    
Consider using the "jsdom" test environment.

Here’s my setup:
package.json

{
  "name": "help-center-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "test": "jest"
  },
  "dependencies": {
    "next": "14.2.18",
    "react": "^18",
    "react-dom": "^18",
    "ts-node": "^10.9.2"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.0.1",
    "@testing-library/user-event": "^14.5.2",
    "@types/jest": "^29.5.14",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.18",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "ts-jest": "^29.2.5",
    "typescript": "^5"
  }
}

jest.config.ts

import type { Config } from 'jest';
import nextJest from 'next/jest';

const createJestConfig = nextJest({ dir: './' });

const config: Config = {
  clearMocks: true,
  coverageProvider: 'v8',
  testEnvironment: 'jsdom',
};

export default createJestConfig(config);

Test file

import Button from '@/app/components/Button';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';

describe('Button component', () => {
  it('renders a button with the btn classname', () => {
    render(<Button />);

    const buttonElement = screen.getByRole('button', { name: /Click me/i });

    expect(buttonElement).toBeInTheDocument();
    expect(buttonElement).toHaveClass('btn');
  });
});

I suspect there’s an issue with my Jest configuration or the environment setup, but I can’t pinpoint it.

How can I resolve this error and ensure my tests run correctly?

Img tag with php script including JS code as src

I am facing a problem that makes my brain bug…

I have an image to display in an HTML page, I plan to put a PHP script in the SRC of the IMG tag.
This PHP script renders a scene by reading a database, with DIV elements positioned on a rectangle including 3D transformations via CSS.
Still in the PHP script, I use dom-to-image, a JS library allowing to generate an image corresponding to a DOM node, I use it on my scene.

Here is my code for my main page:

<img src="genscene.php" alt="Scene" />

Here is my code in simplified form of the genscene.php:

<html>
<head>
<link rel="stylesheet" type="text/css" href="/css/scene.css" />
</head>
<body>

<?php
// $scene variable contains html code generated by reading database
$scene = '<div id="scene"><div id="elt1"></div><div id="elt2"></div></div>';
echo $scene;
?>

<!-- javascript with dom-to-image -->
<script type="text/javascript">
  domtoimage.toJpeg(document.getElementById('scene')).then(function(dataUrl) {
    // Dunno what to do from here...
  });
</script>
</body>
</html>

How to use the dataUrl retrieved from dom-to-image as the source of the image of the main page?

Maybe there is a shorter / simplier way to do what I want to do… but my brain’s dead for now D:

Cursor keeps reset from `pointer` to `default` upon changing `document.title` on MacOS Firefox & Safari

I recently created a webpage, where there is a counter and a button.
The counter will +1 when the button is clicked, and the page title will also change according to the counter number.

I have a CSS that setting all buttons to have pointer as cursor on hovering, which working great:

button:hover {
  cursor: pointer;
}

However, when I change the page title accordingly:

function changeTitle() {
  document.title = count;
}

The cursor change from pointer to default, until it leave the button and re-enter it.


Let’s organize a bit:

On MacOS Firefox & Safari, when I:

Click a button -> Increment Counter -> Change document.title
The cursor change from pointer to default


Sample Code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    button:hover {
      cursor: pointer;
    }

    #plus-one {
      width: 10rem;
      height: 8rem;
    }
  </style>
  <title>Document</title>
</head>
<body>
  <div id="counter">0</div>
  <button type="button" id="plus-one">+1</button>
  
  <script>
    var count = 0;

    const counter = document.getElementById("counter");
    const btn = document.getElementById("plus-one");

    btn.addEventListener("click", () => {
      count++;
      counter.innerHTML = count;
      document.title = `Now is ${count}`;
    })
  </script>
</body>
</html>

Only on MacOS Firefox and Safari
It is good on MacOS Chrome, Windows Chrome and Windows Firefox.

What’s going on here?

How to properly render Multi Polygon / Polygon from react-native-maps with a huge amount of coordinates?

I’m utilizing react-native-maps in my project. There’s a functionality on desktop where a user can draw “regions” on the map that have a corresponding color. They should display on mobile.

I’m having an issue with rendering multi-polygons – polygons that are themselves comprised of multiple polygons.

This is Redux project. So to give an example when fetched from the store, simpler regions/polygons exist at

geom:
  coordinates: Array(1)
    0: Array(1)
      0: Array(4606)
        0: (2) [-118.997304971, 45.990188871]
           // remaining coordinates
        ....

More complex regions/Multi-polygons exist at

geom:
 coordinates: Array(63)
  0: Array(1)
    0: Array(17)
      0: (2) [-124.686066, 47.932503]
       ....

So you can see that the nesting of the coordinates is different between the two (the coordinates existing at coordinates[0][0] vs having to map over coordinates to get them all. I’m not totally privy to the workings of the backend currently, but basically, the organization of the coordinates is generated from geometries, so this difference is probably something PostGIS did.

THE ISSUE is that I cannot get the multi polygons to render the color correctly. I’ve tried chunking, simplifying the shapes by sampling Nth points or by using the Ramer Douglas Pecker (RDP) algorithm. Using RDP has gotten me some progress, but the issue I’ve run into when trying a simplify the shape approach is that while some parts of the region’s colors appear, they disappear depending on the zoom level of the map, and some polygons in the multi polygon do not show the color regardless of zoom.

How can I handle rendering multi polygons? Or does react-native-maps just struggle to render multi polygons/ polygons with 20,000, 30,000+ coordinates?

Here’s my current renderPolygon function:

const renderPolygon = (region) => {
    // Helper to calculate point-to-line distance
    const pointLineDistance = (point, start, end) => {
      const [x, y] = point;
      const [x1, y1] = start;
      const [x2, y2] = end;
      
      const numerator = Math.abs((y2-y1)*x - (x2-x1)*y + x2*y1 - y2*x1);
      const denominator = Math.sqrt((y2-y1)**2 + (x2-x1)**2);
      
      return numerator/denominator;
    };
  
    // RDP implementation
    const rdpSimplify = (points, epsilon) => {
      if (points.length <= 2) return points;
      
      let maxDistance = 0;
      let maxIndex = 0;
      
      // Find point with max distance
      for (let i = 1; i < points.length - 1; i++) {
        const distance = pointLineDistance(
          points[i], 
          points[0], 
          points[points.length-1]
        );
        
        if (distance > maxDistance) {
          maxDistance = distance;
          maxIndex = i;
        }
      }
      
      // Recursively simplify if max distance > epsilon
      if (maxDistance > epsilon) {
        const first = rdpSimplify(points.slice(0, maxIndex + 1), epsilon);
        const second = rdpSimplify(points.slice(maxIndex), epsilon);
        return [...first.slice(0, -1), ...second];
      }
      
      return [points[0], points[points.length-1]];
    };
  
    const simplifyCoords = (coords) => {
      // Only simplify if more than 1000 points
      if (coords.length <= 1000) {
        return [...coords];
      }
  
      // Dynamic epsilon based on coordinate count
      const epsilon = coords.length > 10000 ? 0.001 : 0.0001;
      
      // Apply RDP while ensuring closure
      const simplified = rdpSimplify(coords.slice(0, -1), epsilon);
      simplified.push(simplified[0]); // Close polygon
      
      return simplified;
    };
  
    const toLatLng = (coords) =>
      coords.map((coord) => ({
        latitude: Number(coord[1]),
        longitude: Number(coord[0]),
      }));
  
    const { coordinates } = region.geom;
  
    // Rest of the render logic remains the same...
    if (coordinates.length > 1) {
      return coordinates.map((polyCoords, index) => {
        const coords = polyCoords[0];
        const simplified = simplifyCoords(coords);
  
        return (
          <Polygon
            key={`${region.id}-${index}`}
            coordinates={toLatLng(simplified)}
            fillColor={hexToRgba(region.color, 0.5)}
            strokeColor={"#FFFFFF"}
            strokeWidth={2}
            tappable={true}
            geodesic={true}
            zIndex={100}
          />
        );
      });
    }
  
    const coords = coordinates[0][0];
    return (
      <Polygon
        key={region.id}
        coordinates={toLatLng(coords)}
        fillColor={hexToRgba(region.color, 0.5)}
        strokeColor={"#FFFFFF"}
        strokeWidth={2}
        tappable={true}
        geodesic={true}
        zIndex={100}
      />
    );
  };

Mongo server error: invalid namespace specified while uploading data using postman

{MongoServerError: Invalid namespace specified: /ytDB.users} getting this error whenever i try to pass data from the postman

app.js file

import express from "express";
import cookieParser from "cookie-parser";
import cors from "cors";


const app = express();
// app.use is used when we want to use a middleware in our applications or when we need to do a configuration settings in our application.
app.use(cors({
    origin:process.env.CORS_ORIGIN,
    credentials:true

}))


// CORS (Cross-Origin Resource Sharing) is a security feature implemented by web browsers to control how web pages can request resources from a different domain than the one that served the web page. It is a mechanism that allows servers to specify who can access their resources and how those resources can be accessed.

 app.use(express.json({limit:"32kb"}))
 // limiting the size of the json data that can be sent to the server - Prevents server from crashing
 app.use(express.urlencoded({extended:true,limit:"32kb"}))
 //above line is used to limit the size of the data that can be sent to the server through the url encoding
 app.use(express.static("public"))
 //public is the name of the folder where all the static files are stored like images, videos, etc.
 app.use(cookieParser())
 // above line is used to add cookies and modify it from my server to users browser and perform crud  operations on it.


 //routes import

 import userRouter from './routes/user.routes.js';

 // routes declaration

 app.use('/api/v1/users',userRouter)


export default app;

dotenv config file

import dotenv from 'dotenv';
import connect_DB from './db/index.js';
import app from './app.js'
// import express from 'express';

// You used the dotenv module to load environment variables from a .env file into process.env. This allows you to manage configuration settings outside of your code, making it easier to change settings without modifying the source code.
// const app = express();
dotenv.config({
    path: "./.env"
})
 

connect_DB()
.then((result) => {
    app.listen(process.env.PORT ||8000,()=>{
        console.log(`server is running at port ${process.env.PORT}`)
    })
    
}).catch((err) => {
    console.log("Mongo db conncetion failed",err)
});

constants.js file

export const DB_NAME = "youtube-clone";

database conncetion file

import mongoose from "mongoose";
import { DB_NAME } from "../constants.js";


const connect_DB = async () => {


    try {
        const uri = `${process.env.MONGODB_URI}/{DB_NAME}`;
        console.log(`Connection to mongo db is in progress at ${uri}`)
        const connectionInstance = await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`);
        console.log(`connection successfull to ${connectionInstance.connection.host}`);

    } catch (error) {
        console.log("Error aya hai", error)
        process.exit(1)
    }
}



export default connect_DB;

user.controller.js

// import asyncHandler from '../utils/asyncHandler.js';
import ApiError from "../utils/ApiError.js";
import asyncHandler from "../utils/asyncHandler.js";
// import { User } from "../models/user.models.js";
import { uploadOnCloudinary } from "../services/cloudinary.js";
import { ApiResponse } from "../utils/ApiResponse.js";
import {User} from "../models/user.models.js";

// below function is used to register a user
// The registerUser function is created in the src/controllers/user.controller.js file. The registerUser function is an asynchronous function that takes the request and response objects as arguments. The registerUser function sends a response with a status code of 200 and a JSON object with a message property set to "ok".

// this is a controller function to register a user
const registerUser = asyncHandler(async (req, res) => {
  // res.status(200).json({
  //   message: "ok",
  // });
  //getting user details from frontend
  const { fullname, email, password, username } = req.body;
  console.log("email:", email);

  // validating the user details that are not empty
  if (
    [fullname, email, password, username].some((field) => field?.trim() === "")
  ) {
    throw new ApiError(400, "Please fill in all fields");
  }
  // checking if the user already exists in the database

  existedUser = await User.findOne({
    // the $or operator performs a logical OR operation on an array of two or more expressions and selects the documents that satisfy at least one of the expressions. this is specially used to check if the user already exists in the database
    $or: [{ username }, { email }]
  });

  if (existedUser) {
    throw new ApiError(400, "User already exists");
  }
  // By the below code we are checking for images and avatars and getting the local path of the images and avatars that are uploaded by user and storing them in the variables avatarLocalPath and coverImageLocalPath

  // this is necessary to access the files that are uploaded by the user
  const avatarLocalPath = req.files?.avatar[0]?.path;
  // const coverImageLocalPath = req.files?.coverImage[0]?.path;

  // checking avatar properly that we are getting it ao not

  if (!avatarLocalPath) {
    throw new ApiError(400, " Please upload an avatar");
  }
  // uploading the avatar and cover image to cloudinary by calling the uploadOnCloudinary function
  const avatar = await uploadOnCloudinary(avatarLocalPath);
  const coverImage = await uploadOnCloudinary(coverImageLocalPath);

  // checking if the avatar is uploaded successfully or not because it is a mandatory field and if it is not uploaded successfully then the database will crash
  if (!avatar) {
    throw new ApiError(500, "Avatar not uploded please check and reupload");
  }
  // creating a user object with all the check data and saving it to the database
  const user = await User.create({
    fullname,
    avatar: avatar.url,
    coverImage: coverImage?.url || "",
    email,
    password,
    username: username.toLowerCase(),
  });

  //removing the password and refresh token field from the response
  const createdUser = await user.findById(user._id).select("-password -refreshToken");
  // checking if the user is created successfully or not
  if (!createdUser) {
    throw new ApiError(500, "Something went wrong while registering the user");
  }

  // returning the response with status code 200 and user object
  return res.status(201).json(new ApiResponse(200, createdUser, "User registered successfully"));
});

export default registerUser;

user.models.js file

import mongoose, { Schema } from "mongoose";
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";

const userSchema = new mongoose.Schema(
    {
        username: {
            type: String,
            required: true,
            lowercase: true,
            trim: true,
            unique: true
        },
        email:{
            type:String,
            required:true,
            lowercase:true,
            unique:true,
            trim:true
        },
        fullname:{
            type:String,
            required:true,
            trim:true
        },
        avatar:{
            type:String,
            required:true,
        },
        coverImage:{
            type:String,
        },
        password:{
            type:String,
            required:[true,"Password is required"],
            unique:true,
            trim:true,
            lowercase:true
        },
        watchHistory:[
            {
                type:mongoose.Schema.Types.ObjectId,
                ref:"Video"
            }
        ],
        refreshToken:{
            type:String
        }

    },
    {
        timestamps:true
    }
)
// this is a middleware that will run before saving the user to the database
userSchema.pre("save",async function(next){

    if(!this.isModified("password")){
      return next();
    }
    this.password = await bcrypt.hash(this.password,10);
    next()
})
// this method  compares as=nd return true or false if the password is correct
userSchema.methods.isPasswordCorrect =async function(password){
    return await bcrypt.compare(password,this.password)
}

// this method generates an access token
userSchema.methods.generateAccessToken = function(){
    return jwt .sign(
        { //payload
            _id:this._id,
            email:this.email,
            username:this.username,
            fullname:this.fullname
        },//secret key
        process.env.ACCESS_TOKEN_SECRET,
        {//options
            expiresIn:process.env.ACCESS_TOKEN_EXPIRY
        }
    )
}
// this method generates a refresh token
 userSchema.methods.generaterefreshToken  =function (){
    return jwt.sign(
        {
            _id:this._id,
           
        },
        process.env.REFRESH_TOKEN_SECRET,
        {
            expiresIn:process.env.REFRESH_TOKEN_EXPIRY
        }
    )
 }
export const User = mongoose.model("User",userSchema);

user.routes.js

import { Router } from "express";
import  registerUser  from "../controllers/user.controller.js";
import { upload } from "../middlewares/multer.middleware.js";

// adding the multers middleware to the route
const router = Router();
router.route("/register").post(
    //injecting the middleware before the .post method above is executed
upload.fields([
    {name:"avatar",
    maxcount:1},
    {
        name:"coverImage",
        maxCount:1
    }
    //the middleware is injected here to handel the image uploads and avatar uploads it ensures the files are processed and available in the request object before the controller function is executed
]) ,   
    registerUser
)


export default router;

please help me about this error i have tried everything to best of my knowledge

Babel Error: .plugins is not a valid Plugin property When Upgrading to Expo SDK 52

I’m attempting to upgrade my Expo project to SDK version 52, but I’m encountering a persistent Babel error that I can’t resolve. Below are the details of my setup, configurations, and the specific errors I’m facing.

Project Configuration

package.json:

{
  "name": "app-mobile",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "npx expo start --web"
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "1.17.11",
    "@react-native-picker/picker": "2.4.8",
    "@react-navigation/bottom-tabs": "^6.5.7",
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/native-stack": "^6.9.12",
    "axios": "^1.7.2",
    "expo": "~52.0.0",
    "expo-dev-client": "~2.2.1",
    "expo-linear-gradient": "^12.1.2",
    "expo-splash-screen": "~0.18.2",
    "expo-status-bar": "~1.4.4",
    "nativewind": "^2.0.11",
    "papaparse": "^5.4.1",
    "react": "18.2.0",
    "react-native": "0.72.0",
    "react-native-dropdown-picker": "^5.4.6",
    "react-native-fingerprint-scanner": "^6.0.0",
    "react-native-fs": "^2.20.0",
    "react-native-gesture-handler": "~2.9.0",
    "react-native-heroicons": "^3.2.0",
    "react-native-modal": "^13.0.1",
    "react-native-ratings": "^8.1.0",
    "react-native-reanimated": "~2.14.4",
    "react-native-safe-area-context": "4.5.0",
    "react-native-screens": "~3.20.0",
    "react-native-star-rating": "^1.1.0",
    "react-native-svg": "13.4.0",
    "react-native-vector-icons": "^9.2.0",
    "tailwindcss": "^3.2.7"
  },
  "devDependencies": {
    "@babel/core": "^7.22.17",
    "@types/react": "~18.2.14",
    "babel-preset-expo": "^9.2.0",
    "typescript": "^4.0.0"
  },
  "private": true
}

babel.config.js:

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['nativewind/babel'],
  };
};

tailwind.config.js:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./App.{js,jsx,ts,tsx}", 
    "./screens/**/*.{js,jsx,ts,tsx}",
    "./components/**/*.{js,jsx,ts,tsx}"
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

postcss.config.js:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

index.js:

import { registerRootComponent } from 'expo';
import App from './App';

registerRootComponent(App);

App.js:

import { StatusBar } from 'expo-status-bar';
import { SafeAreaView, StyleSheet, Text, View } from 'react-native';
import AppNavigation from './navigation/appNavigation';
import { AuthProvider } from './hooks/AuthProvider';

export default function App() {
    return (
        <AuthProvider>
            <AppNavigation />
            <StatusBar style="auto" />
        </AuthProvider>
    );
}

metro.config.js:

// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');

module.exports = getDefaultConfig(__dirname);

Errors Encountered

  1. Babel Error:

    ERROR index.js: [BABEL] C:UserssalvaDesktopEstia Codeapp-mobileindex.js: .plugins is not a valid Plugin property
    
  2. iOS Bundling Error:

    iOS Bundling failed 455ms C:UserssalvaDesktopEstia Codeapp-mobileindex.js (1 module)
    ERROR index.js: [BABEL] C:UserssalvaDesktopEstia Codeapp-mobileindex.js: .plugins is not a valid Plugin property
    

Request for Help

Has anyone encountered the .plugins is not a valid Plugin property error when upgrading to Expo SDK 52? If so, how did you resolve it? Any guidance on what might be causing this issue or additional steps I should take would be greatly appreciated.

Thank you in advance for your assistance!

How to get correct neighbors of empty tile and swap it, javascript sliding puzzle

The problem is that sometimes a wrong tile moves in a diagonal direction. This is a sliding puzzle. So tiles can move only horizontally or vertically.

Same tile cannot move in a row. For example if tile moves left, the next tile move cannot be to right. This is checked before swapping.

The goal is to move tile one time when a shuffle-button is clicked. First I check the neighbors next to the empty tile. Then I pick a random neighbor and swap it. I am not sure if I am swapping correctly or if there is an error in getNeighbors-function. Any advice?

let movesEl = document.getElementById("moves");
let emptyIndEl = document.querySelector(".empty-index");
let neighborEl = document.querySelector(".neighbor");
let dirEl = document.querySelector(".direction");
let neighborsEl = document.querySelector(".neighbors");
let curEmptyIndEl = document.querySelector(".new-empty-index");
let mapEl = document.querySelector(".new-map");

class Game {
  constructor() {
    this.parentEl = document.querySelector('#app');
    this.puzzleRows = 3;
    this.puzzleCols = 4;
    this.width = 300;
    this.height = 300;
    this.cells = [];
    this.map = [0,1,2,3,4,5,6,7,8,9,10,11];
    this.shuffling = false;
    this.moves = 0;
    this.prevDir = null;
    // events
    this.onFinished = () => {};
    this.onSwap = () => {};
  }

  init() {
    this.el = this.createWrapper();
    this.parentEl.appendChild(this.el);
    this.parentEl.style.height = this.height + 'px';
    this.setMap();
  }

  createWrapper() {
    const div = document.createElement('div');
    div.classList.add('puzzle-cells');
    div.style.position = 'relative';
    div.style.margin = ' 0 auto';
    return div;
  }

  restart(){
    this.stateMoves(0)
    this.map = [0,1,2,3,4,5,6,7,8,9,10,11];
    for(let i = 0; i < this.map.length; i++){
      if(this.cells[i].index != this.map[i]){
        this.swapCells(i, this.findPosition(this.map[i]))
      }
    }
  }

  stateMoves(num){
    this.moves = num ;
    movesEl.innerText = num;
  }
  
  setMap() {
    for (let i = 0; i < this.puzzleRows * this.puzzleCols; i++) {
      this.cells.push(new Cell(this, i));
    }

    for(let i = 0; i < this.map.length; i++){
      if(this.cells[i].index != this.map[i]){
        this.swapCells(i, this.findPosition(this.map[i]))
      }
    }

    this.map = this.getMap();
  }

  shuffleTiles() {
    // Find the index of the empty tile
    const emptyIndex = this.findEmpty();
    emptyIndEl.innerText = emptyIndex;
    console.log('emptyIndex ', emptyIndex)
   
    // Get the neighbors of the empty tile 
    // contains index and directions [10, 'left']
    const neighbors = this.getNeighbors(emptyIndex);
    // Select random neighbor data
    const rand = Math.floor(Math.random() * neighbors.length);
    let neighbor = neighbors[rand];
    
    // Prevent moving same tile twice in a row 
    // Get new neighbor if directions matches
    if(this.prevDir == neighbor[1]){
      neighbors.splice(rand, 1);
      const r = Math.floor(Math.random() * neighbors.length);
      neighbor = neighbors[r];
    }
    neighborEl.innerText = neighbor[0];
    console.log('selected neigbor ind', neighbor[0])

    // Store direction
    this.statePrevDir(neighbor[1]);
    dirEl.innerText = this.prevDir;
    console.log('prevDir ', this.prevDir)
    neighborsEl.innerText = JSON.stringify(this.getNeighbors(neighbor[0]));
    console.log('current neighbors', JSON.stringify(this.getNeighbors(neighbor[0])))

    // Swap the empty tile with the selected neighbor
    this.map[emptyIndex] = this.map[neighbor[0]];
    this.map[neighbor[0]] = 11; // The empty tile now occupies the neighbor's position
    curEmptyIndEl.innerText = this.map[emptyIndex];
    console.log('swapp ', this.map[emptyIndex])
    console.log('current emptyIndex ', this.map[emptyIndex])
    // Update html 
    this.swapCells(this.map[emptyIndex], this.findPosition(this.map[neighbor[0]]))

    // Update moves html
    this.moves++;
    this.stateMoves(this.moves);
    mapEl.innerText = JSON.stringify(this.map);
    console.log('updated map', JSON.stringify(this.map))
  }

  statePrevDir(dir){
    // Store direction that cannot be used for the next move.
    switch(dir){
      case 'left':
        this.prevDir = 'right';
        break;
      case 'right':
        this.prevDir = 'left';
        break;
      case 'top':
        this.prevDir = 'bottom';
        break;
      case 'bottom':
        this.prevDir = 'top';
        break;
    }
  }

  getNeighbors(emptyIndex) {
    // The grid
    // 0 1 2
    // 3 4 5
    // 6 7 8
    // 9 10 11

    const adjacentPieces = [];
    const row = Math.floor(emptyIndex / 3); // 0-3
    const col = emptyIndex % 3; // 0-2
   
    // Left neighbor
    if (col > 0) 
      adjacentPieces.push([emptyIndex - 1, 'left']); 
    // Right neighbor
    if (col < 2) 
      adjacentPieces.push([emptyIndex + 1, 'right']);
   
    // Top neighbor
    if (row > 0) 
      adjacentPieces.push([emptyIndex - 3, 'top']);
     
    // Bottom neighbor
    if (row < 3) 
      adjacentPieces.push([emptyIndex + 3, 'bottom']);
      
    return adjacentPieces;
  }

  swapCells(i, j, animate) {
    this.cells[i].setPosition(j, animate, i);
    this.cells[j].setPosition(i);
    [this.cells[i], this.cells[j]] = [this.cells[j], this.cells[i]];
  }
    
  getMap(){
    const arr = [];
      const list = [];
      for(let i = 0; i < this.cells.length; i++){
          const j = this.cells[i].index;
          let obj = {i: i, j: j}
          list.push(j);
              arr.push(obj)
      }
      return list;
  }

  findEmptyIndexFromBottom(arr){
    // Find empty index position
    let index = '';
    for(let i = this.dimmension - 1; i >= 0; i--){
      for (let j = this.dimmension - 1; j >= 0; j--){
        if(arr[i][j] == 0){
          index = this.dimmension - i;
        }
      }
    }
    return index;
  }

  findPosition(ind) {
    return this.cells.findIndex(cell => cell.index === ind);
  }

  findEmpty() {
    return this.cells.findIndex(cell => cell.isEmpty);
  }
}

// ============================
// CELL
//============================
class Cell {
  constructor(puzzle, ind) {
    this.isEmpty = false;
    this.index = ind;
    this.puzzle = puzzle;
    this.width = this.puzzle.width / this.puzzle.puzzleRows;
    this.height = this.puzzle.height / this.puzzle.puzzleCols;

    this.el = this.createTile();
    puzzle.el.appendChild(this.el);

    if (this.index === this.puzzle.puzzleRows * this.puzzle.puzzleCols - 1) {
      this.isEmpty = true;
      return;
    }
    this.tileNum(this.index);
    this.setPosition(this.index);
  }

  createTile() {
    const div = document.createElement('div');
    div.style.backgroundSize = `${Math.floor(this.puzzle.width)}px ${Math.floor(this.puzzle.height)}px`;
    div.style.position = 'absolute';
    div.classList.add('puzzle-block');
  
    div.onclick = () => {
      const currentCellIndex = this.puzzle.findPosition(this.index);
      const emptyCellIndex = this.puzzle.findEmpty();
      const {x, y} = this.getXY(currentCellIndex);
      const {x: emptyX, y: emptyY} = this.getXY(emptyCellIndex);
    
      if ((x === emptyX || y === emptyY) && (Math.abs(x - emptyX) === 1 || Math.abs(y - emptyY) === 1)) {
        if (this.puzzle.onSwap && typeof this.puzzle.onSwap === 'function') {
          this.puzzle.onSwap.call(this)
          this.puzzle.moves++
          this.puzzle.stateMoves(this.puzzle.moves);
        }

        this.puzzle.swapCells(currentCellIndex, emptyCellIndex, true);
      }
    };

    return div;
  }

  tileNum(ind){
    const {x, y} = this.getXY(this.index);
    const left = Math.floor(this.width * x);
    const top = Math.floor(this.height * y);
    this.el.style.width = `${Math.floor(this.width - 3)}px`;
    this.el.style.height = `${Math.floor(this.height - 3)}px`;
    this.el.style.backgroundPosition = `-${left}px -${top}px`;

    // number
    const div = document.createElement("div");
    div.classList.add("num");
    div.style.width = `${Math.floor(this.width - 3)}px`;
    div.style.height = `${Math.floor(this.height - 3)}px`;
    div.innerText = ind;
    this.el.appendChild(div);
  }

  setPosition(destinationIndex, animate, currentIndex) {
    const {left, top} = this.getPositionFromIndex(destinationIndex);
    const {left: currentLeft, top: currentTop} = this.getPositionFromIndex(currentIndex);
    if (animate) {
      if (left !== currentLeft) {
        this.animate('left', currentLeft, left);
      } else if (top !== currentTop) {
        this.animate('top', currentTop, top);
      }
    } else {
      this.el.style.left = `${Math.floor(left)}px`;
      this.el.style.top = `${Math.floor(top)}px`;
    }
  }

  animate(position, currentPosition, destination) {
    const animationDuration = 50;
    const frameRate = 5;
    let step = frameRate * Math.abs((destination - currentPosition)) / animationDuration;

    let id = setInterval(() => {
      if (currentPosition < destination) {
        currentPosition = Math.min(destination, currentPosition + step);
        if (currentPosition >= destination) {
          clearInterval(id)
        }
      } else {
        currentPosition = Math.max(destination, currentPosition - step);
        if (currentPosition <= destination) {
          clearInterval(id)
        }
      }

      this.el.style[position] = currentPosition + 'px';
    }, frameRate)
  }

  getPositionFromIndex(index) {
    const {x, y} = this.getXY(index);
    return {
      left: this.width * x,
      top: this.height * y
    }
  }

  getXY(index) {
    return {
      x: index % this.puzzle.puzzleRows,
      y: Math.floor(index / this.puzzle.puzzleRows)
    }
  }
}

// Puzzle object
const game = new Game(); 
game.init();

document.querySelector('.restart').addEventListener('click', function(){
  game.restart();
})

document.querySelector('.shuffle').onclick =()=>{
  game.shuffleTiles();
}
body{
        padding: 0;
        margin: 0;
        overflow: hidden;
    }
    .container{
        padding-top: 10px;
        display: flex;
        justify-content: center;
        gap: 15px;
    }
    .app{
        width: 300px;
    }
    .num{
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #f2f2f2;
    }
    .footer{
        display: flex;
        justify-content: center;
        flex-direction: column;
        gap: 10px;
        width: fit-content;
        margin: 0 auto;
        padding-top: 15px;
    }
    .values{
        padding-top: 15px;
        display: flex;
        flex-direction: column;
        gap: 5px;
        width: fit-content;
        margin: 0 auto;
        justify-content: center;
    }
    .values span{
        font-weight: bold;
        text-transform: uppercase;
    }
    .values div{
        display: flex;
        flex-direction: column;
        gap: 3px;
    }
<div class="container">
    <div id="app" class="app"></div>

</div>
<div class="footer">
    <button class="shuffle">shuffle</button>
    <button class="restart">restart</button>
    <span id="moves"></span>
</div>
<div class="values">
    <div>Empty index: <span class="empty-index"></span></div>
    <div>Selected neighbor: <span class="neighbor"></span></div>
    <div>Prevented direction: <span class="direction"></span></div>
    <div>Current neighbors: <span class="neighbors"></span></div>
    <div>Empty index after swap: <span class="new-empty-index"></span></div>
    <div>Map after swap: <span class="new-map"></span></div>
   </div>

Why is the new_chat_members or chat_member event not triggered in a Telegram supergroup?

I am developing a Telegram bot using Telegraf to listen for the new_chat_members event when a user joins a group. However, the event does not trigger in a supergroup, even though the bot has the required permissions.

Here is the bot’s information in the group:

{
    "result": {
        "user": {
            "id": "ID_BOT",
            "is_bot": true,
            "first_name": "NAME_BOT",
            "username": "USER_NAME_BOT"
        },
        "status": "administrator",
        "can_be_edited": false,
        "can_manage_chat": true,
        "can_change_info": true,
        "can_delete_messages": true,
        "can_invite_users": true,
        "can_restrict_members": true,
        "can_pin_messages": true,
        "can_manage_topics": false,
        "can_promote_members": false,
        "can_manage_video_chats": true,
        "can_post_stories": true,
        "can_edit_stories": true,
        "can_delete_stories": true,
        "is_anonymous": false,
        "can_manage_voice_chats": true
    }
}

Here is the supergroup’s information:

{
    "result": {
        "id": "ID",
        "title": "TITLE",
        "username": "USERNAME",
        "type": "supergroup",
        "active_usernames": ["USERNAME"],
        "description": "This is description",
        "invite_link": "https://t.me/",
        "can_set_sticker_set": true,
        "has_visible_history": true,
        "permissions": {
            "can_send_messages": true,
            "can_send_media_messages": true,
            "can_send_audios": false,
            "can_send_documents": false,
            "can_send_photos": true,
            "can_send_videos": false,
            "can_send_video_notes": false,
            "can_send_voice_notes": false,
            "can_send_polls": false,
            "can_send_other_messages": true,
            "can_add_web_page_previews": true,
            "can_change_info": false,
            "can_invite_users": false,
            "can_pin_messages": false,
            "can_manage_topics": false
        },
        "join_to_send_messages": true,
        "has_hidden_members": true,
        "max_reaction_count": 11,
        "accent_color_id": 6
    }
}

My code :

require('dotenv').config();
const { Telegraf } = require('telegraf');
const axios = require('axios');

// Constant & data
const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN, { handlerTimeout: 9_000_000 });

// Start & Help
bot.on('new_chat_members', async (ctx) => {
    try {
        for (const user of ctx.message.new_chat_members) {
            if (user.is_bot) continue;
            console.log(user);
            await axios.post(`${process.env.APP_URL_API}/request/join/telegram`, { id: user.id }, {
                headers: {
                    "Content-Type": "application/json",
                }
            });
            console.log('New join', user.id);
        }
    } catch (e) {
        console.error('Error', e);
    }
});

bot.launch();

// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));

I already tried using chat_member, but the event still doesn’t work.

Want wrapped content to expand only downwards with CSS

I have a searchfield that autofills with different quippy lines that may or may not need to wrap to fit all the words. This is anchored to a centered parent that makes sure that the searchfield appears in the middle of the screen. I suspect that it’s not exactly what I need, since when the input text needs to wrap to two (or more) rows to fit in the search field that then expands horizontally to envelop the text, it expands both “up” and “down” relative its original “one line” appearance.

What I want is to have the search field expand ONLY down from its original position, and since I’m new to web and especially CSS, I’m unsure as how to go about doing this.
I have attached both relevant .css and relevant html-tags bellow.

.center-container {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.input-field {
  position: relative;
  background-color: var(--inputFieldBGDark);
  color: var(--textDark);
  border-radius: 30px;
  padding: 10px;
  width: 400px;
  height: fit-content;
  display: flex;
  margin: 5px;
  overflow: hidden;
}

.input-field-text {
  font-family: "Courier New", Courier, monospace;
  flex: 1;
  font-size: 28px;
  font-weight: bold;
  user-select: none;
  white-space: wrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
<div className="center-container input-field">
  <img id="search-icon" src="../public/magnifying-glass-dark.svg" alt="Search Icon" className="input-field-icon" />
  <div className="input-field-text">
    <Typewriter options={{ strings: texts, autoStart: true, loop: true, delay: "natural", pauseFor: 2000, cursor: "_", }} />
  </div>
</div>

I’ve tried to swap around who’s parent to whom, looked around for different ways to style boxes to expand reactively to childs content etc. with the hopes of solving the above stated issue, without success…

gulp-uglify expects an object, non-object provided

I have an old project and have to add a filter to remove an item from the array of objects if the condition is true. I am getting the below error when trying to run gulp build.

[14:44:03] Using gulpfile /runner/_work/MyLegacyProject/gulpfile.js
[14:44:03] Starting 'build'...
[14:44:03] Finished 'build' after 9.03 ms
[14:44:06] gulp-uglify expects an object, non-object provided

events.js:174
      throw er; // Unhandled 'error' event
      ^
Error
    at new JS_Parse_Error (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:1534:18)
    at js_error (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:1542:11)
    at croak (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2089:9)
    at token_error (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2097:9)
    at unexpected (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2103:9)
    at expr_atom (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2630:9)
    at maybe_unary (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2792:19)
    at expr_ops (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2827:24)
    at maybe_conditional (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2832:20)
    at maybe_assign (eval at <anonymous> (/runner/_work/MyLegacyProject/node_modules/uglify-js/tools/node.js:28:1), <anonymous>:2856:20)
Emitted 'error' event at:
    at Duplexer.onerror (/runner/_work/MyLegacyProject/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:640:52)
    at Duplexer.emit (events.js:198:13)
    at DestroyableTransform.<anonymous> (/runner/_work/MyLegacyProject/node_modules/gulp-streamify/src/index.js:20:12)
    at DestroyableTransform.emit (events.js:203:15)
    at onwriteError (/runner/_work/MyLegacyProject/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:443:12)
    at onwrite (/runner/_work/MyLegacyProject/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:470:11)
    at WritableState.onwrite (/runner/_work/MyLegacyProject/node_modules/through2/node_modules/readable-stream/lib/_stream_writable.js:180:5)
    at DestroyableTransform.afterTransform (/runner/_work/MyLegacyProject/node_modules/through2/node_modules/readable-stream/lib/_stream_transform.js:93:3)
    at DestroyableTransform.minify [as _transform] (/runner/_work/MyLegacyProject/node_modules/gulp-uglify/minifier.js:71:14)
    at DestroyableTransform.Transform._read (/runner/_work/MyLegacyProject/node_modules/through2/node_modules/readable-stream/lib/_stream_transform.js:184:10)

Below is the code segment where I am making the changes. It is pretty obvious that, array.filter is causing the issue. Any suggestions?

const statusTypes = [
            {
                "Id": 1,
                "Name": "Status 1"
            },
            {
                "Id": 2,
                "Name": "Status 2"
            },
            {
                "Id": 3,
                "Name": "Status 3"
            }
        ];

console.log(statusTypes);

// throws error when gulp build is run
// const myStatuses = (condition === true) ? statusTypes.filter((i) => i.Id !== 3) : statusTypes;

// works with gulp build
const myStatuses = (condition === true) ? statusTypes : statusTypes;


console.log(myStatuses);

Below are the versions I see in package.json

    "browserify": "^10.2.4",
    "gulp-react": "^3.0.1",
    "gulp-streamify": "^1.0.2",
    "gulp-uglify": "^1.4.2"

Infinite scrolling marquee on a 3d/dynamic perspective path? Can it be done?

I’ve been trying to create what I’d describe as a “dynamic perspective infinite marquee”. Specifically I’m using this video as a reference.

Screenshot of it attached.

Any help would be massively appreciated.

I’ve been pulling my hair out trying to adapt this codepen, which has a similar outcome, but all my attempts to turn it into an infinite scroll instead of working from the mouse’s x-axis have failed.

The transform in the previous link does give a similar effect (added so I can link the codepen):

.left-3d {
  position: absolute;
  transform-origin: right center;
  transform: rotateY(100deg);
  top: 0;
  bottom: 0;
  right: 100%;
}

#left {
  transform: translateX(0%);
}

#center {
  transform: translateX(-100%);
}

.right-3d {
  position: absolute;
  transform-origin: left center;
  transform: rotateY(-100deg);
  top: 0;
  bottom: 0;
  left: 100%;
}

#right {
  transform: translateX(-200%);
}

So far I’ve been using vanilla html/css/js. Someone mentioned to me this effect would be easy to replicate in Three.js/R3F but I don’t know where to start on that.

Any help would be massively appreciated!

Sort array with click

I want to sort my new array “posts2” when I click the buttons. But nothings happens when I click the buttons. The sort buttons work if I change “post2” to the original array “posts” in the methods function “sort3”. But how should I change my code to be able to sort only the new array “post2”? I guess I have tried to call “posts2” in the wrong way because it’s in a computed property.


<button @click="sort3('id')">Sort the posts by id</button>
  <button @click="sort3('field')">Sort the posts by field</button>
  <button @click="sort3('author')">Sort the posts by author</button>
  <p v-for="item in newPosts" :key="item.id">{{ item.id + " " + item.type + " " + item.titles + " <b> " + item.meta['string_binding'] + " </b> " + item.author }}</p>


export default {
    data() { 
      return {
        posts: [],
        posts2: [],
      };
    },
    mounted() {
        fetch('http://localhost/Wordpress/wp-json/wp/v2/posts')
        .then(res => res.json())
        .then(data => this.posts = data)
        .then(err => console.log(err)) 
    },
    methods: {
        sort3(msg){
            if (msg == 'field') {
                this.posts2 = this.posts2.sort((a, b) => a.meta['string_binding'] - b.meta['string_binding']);
            } else if (msg == 'id') {
                this.posts2 = this.posts2.sort((a, b) => a.id - b.id);
            } else if (msg == 'author') {
              this.posts2 = this.posts2.sort((a, b) => a.author - b.author);
          } 
        }
},
    computed: {
      newPosts() {
        const titleMap = {
        post: 'SinglePost',
        page: 'SinglePage',
        movies: 'SingleMovie'
      };
        const posts2 = this.posts.map(item => ({...item, titles: titleMap[item.type]})).slice(0, 5);
        return posts2;
  }
},
}