Printing a PDF from within a browser to named client printer in Windows 10/11

I currently have an MS Access client app – to generate Purchase Orders – which can print PDF versions of a PO to a named printer on the Windows 10/11 client PC. The Access backend holds the name of the printer and is read by the front-end client mdb app and printed accordingly. The business requires it goes to a printer holding yellow paper – so that the end user doesn’t have to get up to swap the paper each time (yes, we are having to hand-hold our staff). There is also a dedicated printer for PO’s with recharges applied to them. Again, their Pink Copy PO printer names are assigned to each user account within the Access app. Currently, the user just clicks the printer icon for the PO and the app grabs the named printer from the backend and prints to the appropriate printer.

I want to move away from Access and was hoping to recreate it as a browser web app (with JavaScript client-side and PHP/SQL Server for the backend), as we already have this environment in place). Having spoken with the manager, it appears any additional clicks for the end user is an issue.

The initial stumbling block is with the printing, as I need to print POs to named printers on the client side. I believe JavaScript in the browser can only open the printer dialog, but does not allow selecting the relevant stored named printer for a user.

Can someone please suggest any possible solutions to accommodate the above scenario? I wondered if creating a browser extension might allow interacting with Windows printers, or creating and publishing nodejs with the app installed to each client to gain access to their locally installed printers?

What Vue JavaScrip Calendar component can I use to replicate this calendar?

I am trying to replicate a calendar I have seen online. You select a duration from a dropdown, and then it shows all the available dates for that duration along with prices underneath. It also shows 2 months at a time which I like. I am using Vue for my development. The data is obviously going to be coming from the back end, so it needs to use something like JSON to fill in the data. Any help and suggestions would be greatly appreciated.

calendar

How can I modify this Lights Out solver algorithm to account for an arbitrary number of missing tiles?

My code: https://jsfiddle.net/03t2wdrq/

// based on https://www.keithschwarz.com/interesting/code/?dir=lights-out

var puzzle = [];
var toggle = [];
var solution = [];

// initialize toggle matrix where each cell in puzzle has a corresponding row
// each row in toggle is the same length as the total number of cells in the puzzle
// the true values in each row determain which tiles are toggled when a tile is pressed
// in this case, this is the pressed tile and each tile above, below, left, and right of it
function createToggle(p) {
    let t = Array.from({length: area}, () => Array(area).fill(false));
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            for (let yOff = -1; yOff <= 1; yOff++) {
                for (let xOff = -1; xOff <= 1; xOff++) {
                    let [xNew, yNew] = [x + xOff, y + yOff];
                    if (Math.abs(xOff) + Math.abs(yOff) < 2 && 0 <= yNew && yNew < height && 0 <= xNew && xNew < width) {
                        t[x + y * width][xNew + yNew * width] = true;
                    }
                }
            }
        }
    }
    return t
}

// solve the puzzle using its toggle matrix and a bunch of fancy math formulas by people smarter than me
// for a great explanation, check the link at the top
function solve(puzzle, toggle) {

    // initialize s, create new copies of p and t
    let p = puzzle.slice(0);
    let t = toggle.map((arr) => {return arr.slice(0)});
    let s = new Array(area).fill(false);

    // gaussian elimination
    let yNext = 0;
    for (let x = 0; x < area; x++) {

        // find next pivot row
        let pivot = -1;
        for (let y = yNext; y < area; y++) {
            if (t[y][x]) {
                pivot = y;
                break
            }
        }
        if (pivot == -1) continue

        // swap index of pivot row and next row in toggle and puzzle
        [t[pivot], t[yNext]] = [t[yNext], t[pivot]];
        [p[pivot], p[yNext]] = [p[yNext], p[pivot]];
        
        // apply XOR to each row after the pivot with a true value in the same column
        for (let y = pivot + 1; y < area; y++) {
            if (t[y][x]) {
                for (let i = 0; i < area; i++) {
                    t[y][i] = t[y][i] ? !t[yNext][i] : t[yNext][i];

                }
                p[y] = p[y] ? !p[yNext] : p[yNext];
            }
        }

        // increase next row
        yNext++;
    }

    // back substitute
    for (let y = area; y-- > 0;) {

        // find the next pivot column
        let pivot = -1;
        for (let x = 0; x < area; x++) {
            if (t[y][x]) {
                pivot = x;
                break
            }
        }
        if (pivot == -1 && p[y]) {
            return null
        }

        // perform back substitution (more magic)
        s[y] = p[y];
        for (let x = pivot + 1; x < area; x++) {
            s[y] = (s[y] != (t[y][x] && s[x]));
        }
    }

    // end
    return s
}

puzzle = [
    [true, false, true, false, true],
    [false, true, true, false, false],
    [true, true, false, true, true],
    [false, true, false, true, true],
    [false, true, true, true, false]
];
let [width, height] = [puzzle[0].length, puzzle.length];
let area = width * height;
puzzle = puzzle.flat(Infinity);

toggle = createToggle(puzzle);

solution = solve(puzzle, toggle);

console.log("puzzle:n");
console.log(puzzle);
console.log("ntoggle:n");
console.log(toggle);
console.log("nsolution:n");
console.log(solution);

I explain what it does, but a better explanation is from where I based my program off of:
https://www.keithschwarz.com/interesting/code/?dir=lights-out

My goal is to modify it so that it can return a solution to a puzzle where an arbitrary amount of the tiles in the n*n puzzle matrix are null or some other value other than true or false. These null tiles have no Boolean value, do not affect the solution, and do not detect inputs.

A more polished version of this program I created demonstrates the idea I have when you choose the “walls” option in the input selection: https://jsfiddle.net/5x2as7yq/

// inRange function: Alexander (via Stack Overflow)
// Lesson: Keith Schwarz (via keithschwarz.com)

// Walls do not work, otherwise perfect!

// Shorthand Key:
// p = puzzle
// t = toggle
// x = horizontal index / column number
// y = vertical index / row number
// w = temporary width
// h = temporary height
// i = index
// j = jindex
// next = next free row


//
// Variables
//

// initial matrices
var puzzle = [];
var toggle = [];
var solution = [];
// initial variables
var width = 0;
var height = 0;
var area = 0;
var inputMode = 0;
// elements
const table = document.querySelector("tbody");
const form = document.getElementById("options");
const submit = document.querySelector("caption");
const select = document.querySelector("select");

// 
// Matrix Functions
// 

// check if coords (x, y) are in bounds
function inBounds(x, y) {
    return 0 <= y && y < height && 0 <= x && x < width
}

//
// Create the puzzle and its corresponding arrays
//

// update width, height, and area
function update() {
    let formData = new FormData(form);
    for (const entry of formData.entries()) {
        if (entry[0] == "width") width = Math.round(entry[1]);
        if (entry[0] == "height") height = Math.round(entry[1]);
        if (entry[0] == "mode") inputMode = entry[1];
    }
    area = width * height;
}

// update dimensions, populate display
function populate() {
    select.value = "lights";
    update();

    // populate
    table.replaceChildren();
    puzzle = Array(area).fill(true);
    toggle = Array.from({ length: area }, () => Array(area).fill(false));
    for (let y = 0; y < height; y++) {
        table.insertRow();
        for (let x = 0; x < width; x++) {
            let i = x + y * width;

            // table, puzzle
            table.rows[y].insertCell().addEventListener("click", function () {
                update();
                let cells = table.querySelectorAll("td")
                this.classList.remove("solution");
                if (inputMode == "lights") {
                    this.classList.toggle("off");
                    puzzle[i] = !cells[i].classList.contains("off");
                } else if (inputMode == "walls") {
                    this.classList.toggle("wall");
                    puzzle[i] = cells[i].classList.contains("wall") ? null : cells[i].classList.contains("off");
                } else if (inputMode == "play") {
                    for (let j = 0; j < toggle[y].length; j++) {
                        if (toggle[i][j]) {
                            cells[j].classList.toggle("off");
                            puzzle[j] = !cells[j].classList.contains("off");
                        }
                    }
                }
            });

            // toggle
            for (let yOff = -1; yOff <= 1; yOff++) {
                for (let xOff = -1; xOff <= 1; xOff++) {
                    let [xNew, yNew] = [x + xOff, y + yOff];
                    if (Math.abs(xOff) + Math.abs(yOff) < 2 && inBounds(xNew, yNew)) {
                        toggle[x + y * width][xNew + yNew * width] = true;
                    }
                }
            }
        }
    };
}

//
// Find Solution
// 

// return the solution to a puzzle array using a toggle matrix
function solve(puzzle, toggle) {

    // initialize s, create new copies of p and t
    let p = puzzle.slice(0);
    let t = toggle.map((arr) => {return arr.slice(0)});
    let s = new Array(area).fill(false);

    // gaussian elimination
    let yNext = 0;
    for (let x = 0; x < area; x++) {

        // find next pivot row
        let pivot = -1;
        for (let y = yNext; y < area; y++) {
            if (t[y][x]) {
                pivot = y;
                break
            }
        }
        if (pivot == -1) continue

        // swap index of pivot row and next row in toggle and puzzle
        [t[pivot], t[yNext]] = [t[yNext], t[pivot]];
        [p[pivot], p[yNext]] = [p[yNext], p[pivot]];
        
        // apply XOR to each row after the pivot with a true value in the same column
        for (let y = pivot + 1; y < area; y++) {
            if (t[y][x]) {
                for (let i = 0; i < area; i++) {
                    t[y][i] = t[y][i] ? !t[yNext][i] : t[yNext][i];

                }
                p[y] = p[y] ? !p[yNext] : p[yNext];
            }
        }

        // increase next row
        yNext++;
    }

    // back substitute
    for (let y = area; y-- > 0;) {

        // find the next pivot column
        let pivot = -1;
        for (let x = 0; x < area; x++) {
            if (t[y][x]) {
                pivot = x;
                break
            }
        }
        if (pivot == -1 && p[y]) {
            return null
        }

        // perform back substitution (more magic)
        s[y] = p[y];
        for (let x = pivot + 1; x < area; x++) {
            s[y] = (s[y] != (t[y][x] && s[x]));
        }
    }

    // end
    return s
}

//
// Controls
//

form.addEventListener("submit", function (e) {
    e.preventDefault();
    populate();
});

submit.addEventListener("click", function () {
    solution = solve(puzzle, toggle);

    // show solution
    if (solution == null) {
        submit.innerHTML = "No Solution";
    } else {
        submit.innerHTML = "Done";
        select.value = "play";
        for (let i = 0; i < area; i++) {
            if (solution[i]) {
                table.querySelectorAll("td")[i].classList.add("solution");
            }
        }
    }
    update();
    setTimeout(() => submit.innerHTML = "Solve", 1000);
});

populate();
body {
    /* base size */
    --mult: 30px;
    /* margins */
    --margin: calc(var(--mult) * 0.1);
    --margin2: calc(var(--margin) * 0.5);
    /* space between elements */
    --gap: calc(var(--mult) * 0.3);
    /* input heights */
    --input: calc(var(--mult) * 1.5);
    --select: calc(var(--input) * 0.5);
    /* colors */
    --main-bg: rgb(245, 245, 245);
    --ui-bg-on: rgb(235, 235, 235);
    --ui-bg-off: rgb(195, 195, 195);
    /* default border */
    --border: var(--margin) solid rgb(155, 155, 155);
    --solution: var(--margin) solid rgb(255, 0, 0);
    /* font style */
    font-size: 30px;
    font-family: Arial, Helvetica, sans-serif;

    margin: 0;

    #lightsOut {
        width: fit-content;
        background: var(--main-bg);
        border: var(--border);
        display: flex;
        line-height: var(--input);

        #display {
            margin: var(--margin);
            border-collapse: collapse;

            tbody {
                tr {
                    z-index: 0;

                    td {
                        cursor: pointer;
                        position: relative;
                        box-sizing: border-box;
                        z-index: 0;
                        width: var(--input);
                        height: var(--input);
                        background: var(--ui-bg-on);
                        border: var(--border);

                        &.off {
                            background: var(--ui-bg-off);
                        }

                        &.solution::after {
                            width: var(--gap);
                            height: var(--gap);
                            content: "";
                            display: flex;
                            position: relative;
                            margin: auto;
                            border-radius: 100%;
                            background-color: black;
                        }

                        &.wall {
                            background: none;
                            border: none;
                        }
                    }
                }
            }

            caption {
                cursor: pointer;
                box-sizing: border-box;
                caption-side: bottom;
                width: 100%;
                height: var(--input);
                font-size: var(--mult);
                background: var(--ui-bg-on);
                margin-top: var(--margin);
                border: var(--border);
                user-select: none;
            }
        }

        #options {
            margin: var(--margin);
            margin-left: 0;
            margin-top: calc(var(--margin) + var(--margin2));

            label {
                box-sizing: border-box;
                height: var(--input);
                font-size: var(--mult);
                margin: var(--margin2), 0;
                display: flex;
                user-select: none;
                gap: var(--gap);

                input[type=number],
                select {
                    flex: right;
                    width: var(--input);
                    height: var(--mult);
                    font-size: var(--mult);
                    text-align: center;
                    margin-left: auto;
                    padding: 0;
                    margin-top: auto;
                    margin-bottom: auto;
                }

                select {
                    font-size: var(--select);
                    text-align: left;
                    width: auto;
                }
            }

            button {
                cursor: pointer;
                height: var(--input);
                font-size: var(--mult);
                background: var(--ui-bg-on);
                border: var(--border);
                margin: var(--margin2), 0;
                user-select: none;
                width: 100%;
                display: block;
            }
        }
    }
}

input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
    opacity: 1;
}
<!DOCTYPE html>

<html>

<body>
    <div id="lightsOut">
        <table id="display">
            <tbody>
            </tbody>
            <caption>
                Solve
            </caption>
        </table>
        <form id="options">
            <label>
                Width:
                <input name="width" type="number" value="3" min="3" max="9" required>
            </label>
            <label>
                Height:
                <input name="height" type="number" value="3" min="3" max="9" required>
            </label>
            <button>
                Reset
            </button>
            <label>
                Input:
                <select name="mode">
                    <option value="lights">Lights</option>
                    <option value="walls">Walls</option>
                    <option value="play">Play</option>
                </select>
            </label>
        </form>
    </div>
</body>

<head>
    <link rel="stylesheet" href="LO.css">
    <script src="LO.js"></script>
</head>

</html>

Obviously, introducing walls into the mix would make the number of solvable puzzles much lower, but my goal is to be able to generate solutions for the ones that are.

Please let me know if you need any further info about the programs or question.

Progress line between steps in a stepper app

I have a created a dummy stepper application, functionality wise the app works fine . I am trying to add a progress line right below the circle and between all circles, but unfortunately it is not showing up. The circles indicating the steps have three states

  1. Green, when the step is completed
  2. blue, when on the current step
  3. White, when its untouched.

In the similar lines, I want green line when the current step is completed and the line directed towards the next step should be grey. Can someone help?

sandbox: https://codesandbox.io/p/sandbox/stepper-ppnqgc

[![enter image description here][1]][1]

import React from "react";
import "./styles.css";

interface StepsProps {
  currentStep: number;
  setStep: (step: number) => void;
  submitted: boolean;
  stepsData: { title: string; content: string }[];
}

const Steps: React.FC<StepsProps> = ({
  currentStep,
  setStep,
  submitted,
  stepsData,
}) => (
  <nav className="steps">
    <ul>
      {stepsData.map((step, index) => {
        const isComplete =
          index < currentStep || (index === stepsData.length - 1 && submitted);
        const isActive = index === currentStep;

        return (
          <li
            key={index}
            className={`${isActive ? "active" : ""} ${
              isComplete ? "complete" : ""
            }`}
          >
            <span className="circle" />
            {index < stepsData.length - 1 && <span className="progress-line" />}
            <span className="step-label" onClick={() => setStep(index)}>
              {step.title}
            </span>
          </li>
        );
      })}
    </ul>
  </nav>
);

export default Steps;


body {
  margin: 0;
  font-family: "Arial", sans-serif;
}

.header {
  text-align: center;
  background-color: #4caf50;
  color: white;
  padding: 10px 0;
}

.footer {
  text-align: center;
  background-color: #f5f5f5;
  padding: 10px 20px;
  display: flex;
  justify-content: center;
  gap: 15px;
  border-top: 1px solid #ccc;
}

.footer-button {
  padding: 10px 20px;
  font-size: 16px;
  color: #fff;
  background-color: #007bff;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.footer-button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.footer-button.submit {
  background-color: #28a745;
}

.footer-button:hover:not(:disabled) {
  background-color: #0056b3;
}

.body {
  display: flex;
  height: calc(100vh - 140px); 

.left {
  width: 30%;
  background-color: #f9f9f9;
  padding: 20px;
  border-right: 1px solid #ccc;
}

.right {
  flex: 1;
  padding: 20px;
  background-color: #fff;
}

/* Steps Component */
.steps ul {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

.steps li {
  display: flex;
  align-items: center;
  position: relative;
  margin-bottom: 20px;
  cursor: pointer;
}

.steps li:last-child {
  margin-bottom: 0;
}

.circle {
  width: 25px;
  height: 25px;
  border-radius: 50%;
  border: 2px solid #ccc; 
  background-color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  transition: background-color 0.3s, border-color 0.3s;
}

.complete .circle {
  background-color: #4caf50;
  border-color: #4caf50;
}

.active .circle {
  background-color: #cccccc;
  border-color: #cccccc;
}

.progress-line {
  position: absolute;
  top: 25px;
  left: 12px; 
  width: 2px;
  height: calc(100% - 25px); 
  background-color: #ccc; 
  z-index: 0;
}

.steps li.complete .progress-line {
  background-color: #4caf50; 
}

.step-label {
  margin-left: 15px;
  font-size: 16px;
  color: #333;
  flex: 1;
  transition: color 0.3s;
}

.steps li.active .step-label {
  font-weight: bold;
  color: #4caf50;
}

.steps li:hover .step-label {
  color: #007bff;
}

.step-content {
  padding: 20px;
  font-size: 16px;
  color: #333;
  line-height: 1.5;
}


  [1]: https://i.sstatic.net/TMrKotjJ.png

“Find in Page” feature with React.js

So basically the thing i need is same to the default one that you have when you try to search through the website using Ctrl + F. The only thing i need also is to have possibility to search through virtualized data as well. But im not sure is it even possible. Any thoughts on this?

Have everything to build a copy of default “Search in Page” but need to add functionality to look for virtualized data

Javascript added image in the shopping cart item isn’t showing

I believe that the problem is in the img tag. When I open my shopping cart with all the items added on my site, the images are not present. When you open the shopping cart, there is only the item name and price but the image of the item isn’t showing.

My website is also on github if anyone needs. Or you can search for it online and see the problem. Try adding a product and then open the shopping cart – mimiluxm.github.io

const cartItemsContainer = document.querySelector('.cart-items');
const totalPriceElement = document.querySelector('.total-price');
const cartSection = document.querySelector('.cart');
const checkoutForm = document.querySelector('.checkout-form');

let cartItems = JSON.parse(localStorage.getItem('cartItems')) || [];

function updateCart() {
  cartItemsContainer.innerHTML = ''; 
  let total = 0;

  cartItems.forEach((item, index) => {
    console.log(item); 
    const cartItem = document.createElement('div');
    cartItem.classList.add('cart-item');
    cartItem.innerHTML = `
      <div class="item-info">
        <img src="${item.image}" alt="${item.name}">
        <span class="item-name">${item.name}</span>
      </div>
      <span class="item-price">${item.price} MDL</span>
      <button class="delete" onclick="removeItem(${index})">-</button>
    `;
    cartItemsContainer.appendChild(cartItem);
    total += item.price;
  });

  totalPriceElement.textContent = `Total: ${total} MDL`;
}

function removeItem(index) {
  cartItems.splice(index, 1);
  updateCart(); 
  localStorage.setItem('cartItems', JSON.stringify(cartItems)); 
}

function openCheckout() {
  if (cartItems.length === 0) {
    alert('Coșul este gol! Adăugați produse înainte de a finaliza comanda.');
    return;
  }

  cartSection.style.display = 'none';
  checkoutForm.style.display = 'block';
}

function submitOrder() {
  const firstName = document.getElementById('first-name').value;
  const lastName = document.getElementById('last-name').value;
  const phone = document.getElementById('phone').value;
  const email = document.getElementById('email').value;
  const address = document.getElementById('address').value;

  if (!firstName || !lastName || !phone || !email || !address) {
    alert('Vă rugăm să completați toate câmpurile!');
    return;
  }

  const total = cartItems.reduce((sum, item) => sum + item.price, 0);

  alert(`Vă mulțumim pentru comandă, ${firstName} ${lastName}!nTotal: ${total} MDL.nVă vom contacta la ${phone} sau prin email la ${email}.nAdresa de livrare: ${address}`);

  cartItems.length = 0;
  updateCart();
  localStorage.setItem('cartItems', JSON.stringify(cartItems));

  checkoutForm.style.display = 'none';
  cartSection.style.display = 'block';
}

# updateCart();

I tried many changes but it seems nothing works.

innerHTML value updation problem ripple effect button

I want to know, why the value is not updating, i mean the listener itself of click event is not executing, See the following code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            font-family: sans-serif;
            font-size: 1rem;
        }

        .ripple_effect {
            position: absolute;
            width: 1px;
            height: 1px;
            border-radius: 50%;
            background-color: rgba(0, 92, 187, 0.3);
            transform: scale(0);
            transition: all 0.4s ease-in-out;
        }

        .ripple_targetEl {
            display: inline-flex;
            align-items: center;
            background-color: transparent;
            position: relative;
            z-index: 10;
            overflow: hidden;
            text-decoration: none;
            cursor: pointer;
            color: rgb(0, 92, 187);
            transition: all 0.1s ease-in-out;
        }

        .ripple_targetEl:hover {
            background-color: rgba(0, 92, 187, 0.15);
        }

        a {
            padding: 0.3rem 0.6rem;
            border-radius: 10px;
        }
    </style>
</head>

<body>
    <span id="value"></span><br>
    <a href="#" id="link" class="ripple_targetEl">Click</a>
    <script>
        const ripple_span = document.createElement('span');
        const link = document.getElementById('link');
        const valueSpan = document.getElementById('value');
        let value = 0
        link.appendChild(ripple_span);
        link.addEventListener('mousedown', (e) => {
            createRipple(e);
        });


        function createRipple(e) {
            const prevRipple = ripple_span.querySelector('.ripple_effect');
            if(prevRipple) prevRipple.remove();
            const rippleInnerSpan = document.createElement('span');
            const targetElRect = link.getBoundingClientRect();
            const diameter = Math.max(targetElRect.width, targetElRect.height);
            const x = e.clientX - targetElRect.left;
            const y = e.clientY - targetElRect.top;
            rippleInnerSpan.style.top = `${y}px`;
            rippleInnerSpan.style.left = `${x}px`;
            rippleInnerSpan.classList.add('ripple_effect');
            ripple_span.appendChild(rippleInnerSpan);
            setTimeout(() => {
                rippleInnerSpan.style.transform = `scale(${diameter * 2.5})`;
            }, 0);

            link.addEventListener('mouseup', () => {
                rippleInnerSpan.style.opacity = '0';
            });

        }

        link.addEventListener('click', () => {
            value++;
            valueSpan.innerHTML = value;
        });
    </script>
</body>

</html>

Now, if i remove the prevRippleEl.remove() line then it works fine, ripple effect works as well as value is also increased, so, I want to know why and how prevRippleEl.remove() is causing the problem.
Keep in mind, I don’t want the solution, I’ve already the solution.

js array contains at least of item from array 1 and 1 item array 2 and

I have this array as main array.

features= [
               '6776c2ea484f9fcf986bc607',
               '6776c2fc484f9fcf986bc61b',
               '6776c2f3484f9fcf986bc611',
               '6776c2e0484f9fcf986bc5fd',
               '6776c36e484f9fcf986bc663',
               '6776c378484f9fcf986bc66d',
               '6776c4d7484f9fcf986bc77b'
            ],

and I have this array as sub array.

 const sub_features = [
         ['6776c2e0484f9fcf6bc5fd', '6776c2d5484f9fcf9865f3'],
         ['84sa6d8f46s8d4f6sd6f84', '80as6df468as408a46df48', '70as4df468as401a46df48'],
         ['6776c4e6484f9fcf986bc785'],
      ];
  • this is important sub_features array maybe has 1,2,3, 4 or more items.

how can I check features array has at least one item of sub_features[0] and one item of sub_features[1] and one item of sub_features[2] ?

  • this is very important that number of sub_features array is changeable like this
const sub_features = [
         ['84sa6d8f46s8d4f6sd6f84', ],
         ['6776c4e6484f9fcf986bc785','80as6df468as408a46df48',],
      ];

thanks for your responses

dash app custom JS cannot find the dcc.Store for callback

I am creating a webapp that should provide the python code with a callback whenever user rightclicks in the webapp.

I tried to do this with a dcc.Store and to write to the store on a contextmenu event. So far so good. I put this custom code that catches the contextmenu event in assets/custom.js, however, when I try to get the store with var store = document.getElementById("contextmenu_data");, it returns null, thus never actually writing something to the store. What could be wrong?

Here is an MRE for the python and JS code:

Python

import dash
from dash import dcc, html
from dash.dependencies import Input, Output

import threading

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div(id="output"),
    dcc.Store(id='contextmenu_data'),
])

@app.callback(Output("output", 'children'),
              Input("contextmenu_data", 'data'),
              )
def right_click_action(data):
    print(f"right_click_action called with data: {data}")
    if data:
        return f"coordinates are {data}"
    return "no rightclicks have happened"

def run_dash():
    app.run_server(debug=False)

if __name__ == '__main__':

    server = threading.Thread(target=run_dash, daemon=True)
    server.start()

js

window.onload = function() {
    document.addEventListener('contextmenu', function(event) {
        event.preventDefault();
        var store = document.getElementById("contextmenu_data");
        console.log(store);
        if (store) {
            var data = {x: event.clientX, y: event.clientY};
            store.setAttribute('data', JSON.stringify(data));
            store.dispatchEvent(new CustomEvent('data'));
        }
    });
};


Creating npm package from client library/wrapper

I’ve looked around at creating npm packages, and while I understand the basics most tutorials only cover creating a package from a single simple file with a single function.

In my case I’ve created a folder (lib/) that contains a variety of folders and classes (client classes, utility methods, etc.). These all are used to create a client wrapper library for our internal API (for testing in Playwright, in this case). Essentially I have a bunch of XYZ.ts classes that contain multiple methods for accessing/testing our company’s internal API.

I feel like this is a prime opportunity to create an (internal) npm package that can be used in other projects (to set up data/seed data/etc.) or just for testing.

Here is sort of how the folder structure looks:

lib/
 helpers/
  uti.ts
 fixtures/
  bunch-of-json-files.json
 contants/
  index.ts
 api/
  baseclient.ts (Essentially wraps around Playwright's request library to handle headers, etc.)
  client/
   widget.ts
   foo.ts
   some-other-class.ts
  

Essentially I want to be able to package up this entire folder and be able to import the methods like I would normally.

So is my best case probably creating a new project with just this lib/ folder and trying to package that up? Should I leave out the lib/ folder in this case?

Unable to run the test for Node.js application

I created a very simple form-based Node.js Application, which is working fine, including running the code as well as building the code. However, when I run the command npm run test it throws the below error:

> [email protected] test
> jest


  ●  Cannot log after tests are done. Did you forget to wait for something async in your test?
    Attempted to log "Server running on http://localhost:3000".

      14 |
      15 | require('./formData')(app);  // Handle form submission logic
    > 16 |
         | ^
      17 | app.listen(PORT, () => {
      18 |     console.log(`Server running on http://localhost:${PORT}`);
      19 | });

      at console.log (node_modules/@jest/console/build/CustomConsole.js:141:10)
      at Server.<anonymous> (src/server.js:16:11)

 FAIL  tests/form.test.js   POST /submit-form
    ✕ should submit form data successfully (3 ms)

  ● POST /submit-form › should submit form data successfully

    TypeError: app.address is not a function

       5 |     it('should submit form data successfully', async () => {
       6 |         const res = await request(app)
    >  7 |             .post('/submit-form')
         |              ^
       8 |             .send({
       9 |                 name: 'John Doe',
      10 |                 place: 'New York',

      at Test.serverAddress (node_modules/supertest/lib/test.js:46:22)
      at new Test (node_modules/supertest/lib/test.js:34:14)
      at Object.obj.<computed> [as post] (node_modules/supertest/index.js:43:18)
      at Object.post (tests/form.test.js:7:14)

Test Suites: 1 failed, 1 total Tests:       1 failed, 1 total Snapshots:   0 total Time:        0.413 s, estimated 1 s Ran all test suites.

ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From tests/form.test.js.

      at Object.getCodec (node_modules/mysql2/node_modules/iconv-lite/lib/index.js:63:27)
      at Object.getDecoder (node_modules/mysql2/node_modules/iconv-lite/lib/index.js:125:23)
      at Object.<anonymous>.exports.decode (node_modules/mysql2/lib/parsers/string.js:20:23)
      at Packet.readNullTerminatedString (node_modules/mysql2/lib/packets/packet.js:414:25)
      at Function.fromPacket (node_modules/mysql2/lib/packets/handshake.js:62:33)
      at ClientHandshake.handshakeInit (node_modules/mysql2/lib/commands/client_handshake.js:112:40)
      at ClientHandshake.execute (node_modules/mysql2/lib/commands/command.js:45:22)
      at Connection.handlePacket (node_modules/mysql2/lib/base/connection.js:475:34)
      at PacketParser.onPacket (node_modules/mysql2/lib/base/connection.js:93:12)
      at PacketParser.executeStart (node_modules/mysql2/lib/packet_parser.js:75:16)
      at Socket.<anonymous> (node_modules/mysql2/lib/base/connection.js:100:25) node:events:515
    throw err; // Unhandled 'error' event
    ^

Error: Unhandled error. (Error: Encoding not recognized: 'cesu8' (searched as: 'cesu8')
    at Object.getCodec (/home/node/nodejs-app/node_modules/mysql2/node_modules/iconv-lite/lib/index.js:104:23)
    at Object.getDecoder (/home/node/nodejs-app/node_modules/mysql2/node_modules/iconv-lite/lib/index.js:125:23)
    at Object.<anonymous>.exports.decode (/home/node/nodejs-app/node_modules/mysql2/lib/parsers/string.js:20:23)
    at Packet.readNullTerminatedString (/home/node/nodejs-app/node_modules/mysql2/lib/packets/packet.js:414:25)
    at Function.fromPacket (/home/node/nodejs-app/node_modules/mysql2/lib/packets/handshake.js:62:33)
    at ClientHandshake.handshakeInit (/home/node/nodejs-app/node_modules/mysql2/lib/commands/client_handshake.js:112:40)
    at ClientHandshake.execute (/home/node/nodejs-app/node_modules/mysql2/lib/commands/command.js:45:22)
    at Connection.handlePacket (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:475:34)
    at PacketParser.onPacket (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:93:12)
    at PacketParser.executeStart (/home/node/nodejs-app/node_modules/mysql2/lib/packet_parser.js:75:16)
    at Socket.<anonymous> (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:100:25)
    at Socket.emit (node:events:524:28)
    at addChunk (node:internal/streams/readable:561:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
    at Socket.Readable.push (node:internal/streams/readable:392:5)
    at TCP.onStreamRead (node:internal/stream_base_commons:189:23) {   fatal: true })
    at Connection.emit (node:events:513:17)
    at Connection._notifyError (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:247:12)
    at Connection._handleFatalError (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:178:10)
    at Connection.handlePacket (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:485:12)
    at PacketParser.onPacket (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:93:12)
    at PacketParser.executeStart (/home/node/nodejs-app/node_modules/mysql2/lib/packet_parser.js:75:16)
    at Socket.<anonymous> (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:100:25)
    at Socket.emit (node:events:524:28)
    at addChunk (node:internal/streams/readable:561:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
    at Socket.Readable.push (node:internal/streams/readable:392:5)
    at TCP.onStreamRead (node:internal/stream_base_commons:189:23) {   code: 'ERR_UNHANDLED_ERROR',   context: Error: Encoding not recognized: 'cesu8' (searched as: 'cesu8')
      at Object.getCodec (/home/node/nodejs-app/node_modules/mysql2/node_modules/iconv-lite/lib/index.js:104:23)
      at Object.getDecoder (/home/node/nodejs-app/node_modules/mysql2/node_modules/iconv-lite/lib/index.js:125:23)
      at Object.<anonymous>.exports.decode (/home/node/nodejs-app/node_modules/mysql2/lib/parsers/string.js:20:23)
      at Packet.readNullTerminatedString (/home/node/nodejs-app/node_modules/mysql2/lib/packets/packet.js:414:25)
      at Function.fromPacket (/home/node/nodejs-app/node_modules/mysql2/lib/packets/handshake.js:62:33)
      at ClientHandshake.handshakeInit (/home/node/nodejs-app/node_modules/mysql2/lib/commands/client_handshake.js:112:40)
      at ClientHandshake.execute (/home/node/nodejs-app/node_modules/mysql2/lib/commands/command.js:45:22)
      at Connection.handlePacket (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:475:34)
      at PacketParser.onPacket (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:93:12)
      at PacketParser.executeStart (/home/node/nodejs-app/node_modules/mysql2/lib/packet_parser.js:75:16)
      at Socket.<anonymous> (/home/node/nodejs-app/node_modules/mysql2/lib/base/connection.js:100:25)
      at Socket.emit (node:events:524:28)
      at addChunk (node:internal/streams/readable:561:12)
      at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
      at Socket.Readable.push (node:internal/streams/readable:392:5)
      at TCP.onStreamRead (node:internal/stream_base_commons:189:23) {
    fatal: true   } }

Node.js v22.13.0

The test code that I have created is:

const request = require('supertest'); const app = require('../src/server');

describe('POST /submit-form', () => {
    it('should submit form data successfully', async () => {
        const res = await request(app)
            .post('/submit-form')
            .send({
                name: 'John Doe',
                place: 'New York',
                email: '[email protected]',
                phone: '1234567890',
                age: 30,
                technology: 'Node.js',
            });

        expect(res.text).toBe('Form data submitted successfully!');
    }); });

Error: ERESOLVE unable to resolve dependency tree when creating a new React app [duplicate]

I am facing an issue when trying to create a new React app using npx create-react-app. During the installation, I encounter an error indicating a dependency conflict. Below is the error log:

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error While resolving: [email protected]
npm error Found: [email protected]
npm error node_modules/react
npm error   react@"^19.0.0" from the root project
npm error Could not resolve dependency:
npm error peer react@"^18.0.0" from @testing-library/[email protected]
npm error node_modules/@testing-library/react
npm error   @testing-library/react@"^13.0.0" from the root project

How to ignore specific folder and it’s sub folders in Playwright

In my Playwright project, I need to ignore a directory and all of it’s sub directories.

For example if I have the following directories tree:

tests -> fooFolder --> foo2Folder ---> fooFile2.spec.ts
                       fooFile1.spec.ts

         barFolder --> barFile.spec.ts

In this example I need to ignore fooFolder and all tests under it.

I tried ignore it like this:

projects: [
    {
      name: "bar only",
      testIgnore: ["/tests/fooFolder/*.spec.ts"], // ignores only test files that directly under fooFolder
    },
]

I’ve also tried with /tests/fooFolder//*.spec.ts but this causes no ignore at all.

I can put an array of the sub-directories, but this is impractical.

$validatePoint: anchor point.offset > node.getTextContentSize() in lexical editor with iframe

After updating Lexical to the latest stable version (0.23.0), I am encountering an error when typing more than one character in the editor. The problem appears when loading Lexical inside an iframe, as demonstrated in the following code. If I load it without the iframe, the editor works as expected.

Debugging the Issue

The issue seems to stem from a change introduced in this merge request when the $validatePoint function was added. Specifically, this part of the code:

 if (__DEV__) {
    $validatePoint(editor, 'focus', resolvedAnchorPoint);
  }

The problem occurs in Chrome and Firefox, but works fine in Safari. The offset value is always higher than the textContentSize after typing the second character.

Steps To Reproduce

  1. open the codesandbox and type anything in the editor
  2. open the dev console and you will see the error

Code Example

Full Working Example