Show and hide a map element in react

I’m creating a game. The user is given 6 letters and they have to make as many words as possible. When each letter is clicked it shows in the display box. I want the clicked letter to disappear from the available letter row so that players cant use the same letter twice. I used .map() to display the 6 letters. How can I make one of them disappear after being clicked?


          <div className="letter_wrapper">
            {slicedLetters.map((l, i) => {
              return (
                <p
                  key={i}
                  className="letter_container">
                  <span
                    onClick={getLetterInput}
                    className="letter">
                    {l}
                  </span>
                </p>
              );
            })}
          
         

Utilizing custom fonts with jspdf (React js)

I have been trying to incorporate the custom fonts which are used in my capstone project website for jspdf. I have tried and experimented for long hours and have exhausted all my options.

Thing I tried:

  • Consulted various stackoverflow questions and various pointers provided
  • Used various base64 encoders suggested on forums
  • Tried a different custom font
  • Tried removing bad/unnecessary characters from base64 encoded font (.ttf to base64)

Custom fonts:

The custom fonts are from google fonts:

https://fonts.googleapis.com/css2?family=Space+Mono&family=Varela+Round&family=Maven+Pro&family=Kalam&display=swap
https://fonts.googleapis.com/css?family=Caprasimo
https://fonts.googleapis.com/css?family=Lilita+One
https://fonts.googleapis.com/css?family=Luckiest+Guy

jspdf version is: 2.5.1

Code that does not work:

const encfontpath = '../../../../../assets/fonts/Caprasimo/Caprasimo-Regular.txt'; // this is base64 encoded font
const cencfontpath = encfontpath.replace(/[=+/\r\n]/g,''); // remove bad characters from base64

pdfstory.setFontSize(20);
pdfstory.addFileToVFS(cencfontpath, 'Caprasimo');
pdfstory.addFont('Caprasimo.ttf', 'Caprasimo', 'normal');
pdfstory.setFont('Caprasimo');

Error:

console.js:20 jsPDF PubSub Error Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('Caprasimo.ttf'). Error: Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('Caprasimo.ttf').
    at Object.<anonymous> (ttfsupport.js:69:1)
    at C.publish (jspdf.js:70:1)
    at Object.Pe (jspdf.js:2446:1)
    at E.y.addFont (jspdf.js:4932:1)
    at handlePrintButtonClick (CreateBook.jsx:149:1)

In addition, I am having this error: the font ‘font-name’ contains a bad BBox. Any help and additional pointers is greatly appreciated.

How to apply filter to entire webpage, without hiding elements?

I’m making a simple Firefox web extension that allows the user to apply a filter to a webpage. When I try applying the CSS filters through document.body.style.filter some of the elements suddenly become hidden (on YouTube clicking full screen hides all of the elements except the title bar, idk why).

I’ve looked at a similar problem and asked ChatGPT (also provided a similar solution) but it didn’t seem to work on in my implemenation. The most important part of my code is here:

// Create a container element
const container = document.createElement("div");
container.style.cssText = `
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
overflow: hidden;
pointer-events: none;
z-index: 9999;`;

// Append the container to the body
document.body.insertAdjacentElement('beforeend', container);

I’m looking for a reason to why my code doesn’t work, or if there was never any hope of it working in the first place, a good solution to applying a filter to an entire page without any weird artifacts (I don’t have to support any old browsers btw)

Vue 3 modifying an element into a list using its index

I´m storing the element´s index in a global variable. In the next code I can add various elements with the same properties, but I can´t edit none of them because it alters the rest. I´m trying to edit each one individually

I´m using vue-draggable-next (npm install vue-draggable-next) for drag and drop

And I´m using Sweetalert2 (npm install sweetalert2)

<template>
  <head>
    <!-- Boxicons -->
    <link
      href="https://unpkg.com/[email protected]/css/boxicons.min.css"
      rel="stylesheet"
    />
  </head>

  <div class="container-fluid row mt-2">
    <div class="col-md-3 float-start">
      <div class="panel-default border" style="background-color: #f7f9fa">
        <div class="panel-heading text-center">
          <h3 class="panel-title visible-panel-text">Draggable elements</h3>
        </div>

        <!-- Elements zone -->
        <draggable
          class="row mt-2"
          :list="draggableElements"
          :sort="false"
          :group="{ name: 'cloneableItems', pull: 'clone', put: false }"
          @end="generateJSON"
        >
          <div
            class="col-sm-6 mb-2"
            v-for="element in draggableElements"
            :key="element.id"
          >
            <div class="border drag-element">{{ element.visibleName }}</div>
          </div>
        </draggable>

        <!-- Copy JSON panel -->
        <div
          class="panel-json json-container mt-2 border"
          style="background-color: #f7f9fa"
        >
          <div
            class="panel-heading text-center"
            @click="state.showJSONPanel = !state.showJSONPanel"
          >
            <h3
              class="panel-title"
              style="text-align: center; cursor: pointer; user-select: none"
            >
              JSON
            </h3>
          </div>
          <div class="panel-body" v-show="state.showJSONPanel">
            <textarea
              class="json-textarea form-control"
              v-model="state.generatedJSON"
              style="width: 90%; margin: 0 auto; resize: none"
            ></textarea>
            <button class="btn btn-secondary btn-sm" @click="copyJSON">
              Copy JSON
            </button>
          </div>
        </div>
      </div>
    </div>
    <div class="col-md-6">
      <!-- Show content -->
      <draggable
        class="w-100 h-100 border"
        :empty-insert-threshold="1000"
        :list="state.contentElements"
        @update="generateJSON"
        :group="{ name: 'putItems', pull: 'clone', put: true }"
      >
        <div
          v-for="(element, index) in state.contentElements"
          :key="element.id"
          class="container"
        >
          <template v-if="element.type == 'description'">
            <label>This is the description element</label>
            <div>{{ element.descriptionText }}</div>
          </template>

          <!-- Edit & delete -->
          <div
            class="iterable-icons-container"
            style="display: flex; justify-content: flex-end"
          >
            <!-- Edit icon -->
            <i class="bx bxs-cog edit-icon" @click="editElement(index)"></i>
            <!-- Delete icon -->
            <i class="bx bxs-trash-alt edit-icon" @click="deleteElement()"></i>
          </div>
        </div>
      </draggable>
    </div>

    <!-- Changes panel -->
    <div class="col-md-3 border">
      <div>
        <!-- Description -->
        <template
          v-if="
            state.editedIndex >= 0 &&
            state.contentElements[state.editedIndex].type == 'description'
          "
        >
          <label><strong>Description element</strong></label>
          <input type="text" v-model="edit.descriptionNewText" />

          <button class="btn btn-light" @click="saveChanges(type)">Save changes</button>
        </template>
      </div>
    </div>
  </div>
</template>

<script setup>
import "bootstrap";
import "bootstrap/dist/css/bootstrap.css";

import { reactive, ref } from "vue";

//Drag and drop library
import { VueDraggableNext as draggable } from "vue-draggable-next";

//Sweetalert
import Swal from "sweetalert2";
import "sweetalert2/dist/sweetalert2.min.css";

//Array with the main elements list
const draggableElements = [
  {
    id: crypto.randomUUID(),
    visibleName: "Description",
    descriptionText: "This is a description that you can edit",
    type: "description",
    mandatory: false,
  },
];

//Variable where the data flows
let state = reactive({
  //Store a global index to edit
  editedIndex: ref(-1),
  contentElements: reactive([]),
  showJSONPanel: ref(false),
  generatedJSON: ref(""),
});

//eslint-disable-next-line
let edit = reactive({
  descriptionNewText: ref(""),
});

function copyJSON() {
  const textarea = document.querySelector(".json-textarea");
  textarea.select();
  document.execCommand("copy");
  Swal.fire("Success", "JSON copied", "success");
}

function generateJSON() {
  //Actualizar valor del textarea
  let textarea = document.querySelector(".json-textarea");
  textarea.value = state.generatedJSON;

  //Generar el archivo JSON de acuerdo a lo puesto por el usuario
  state.generatedJSON = JSON.stringify(state.contentElements, null, 2);
}

//Edit
function editElement(index) {
  console.log("Button pressed");
  state.editedIndex = index;

  console.log("Index is: ", state.editedIndex)
}

//Delete element
//Eliminar elemento del panel de construcción del formulario
function deleteElement(index) {
  Swal.fire({
    title: "Confirm delete",
    text: "Are you sure?",
    icon: "warning",
    showCancelButton: true,
    confirmButtonText: "Yes, delete",
    cancelButtonText: "Cancel",
  }).then((result) => {
    if (result.isConfirmed) {
      state.contentElements.splice(index, 1);
      state.generatedJSON = JSON.stringify(state.contentElements, null, 2);
    }
  });
}

//Save changes in the element
function saveChanges(type) {
  type = state.contentElements[state.editedIndex].type

  console.log("Type is: ", type)

  switch(type){
    case 'description':
      //Description data
      state.contentElements[state.editedIndex].descriptionText = edit.descriptionNewText;

      state.editedIndex = -1
      edit.descriptionNewText = ""
      break;
  }
}
</script>

<style scoped>
.drag-element {
  cursor: pointer;
}

.edit-icon {
  font-size: 25px;
}
.edit-icon:hover {
  color: blueviolet;
  cursor: pointer;
}
</style>

Cannot use import statement outside a module, react.js [duplicate]

I’m following a youtube tutorial and it was working fine yesterday, however today I boot up the project and get thrown hundreds of deprecated warnings in the terminal. The project also won’t start due to the error in the title. here is the terminal error:

C:Program Filesnodejsnode.exe .React-Beginner-Course-2022-episode7srcApp.js
Process exited with code 1
Uncaught SyntaxError C:UsersdevinOneDriveDesktopReact-Beginner-Course-2022-episode7React-Beginner-Course-2022-episode7srcApp.js:1
import "./App.css";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (internal/vm:73:18)
    at wrapSafe (internal/modules/cjs/loader:1178:20)
    at Module._compile (internal/modules/cjs/loader:1220:27)
    at Module._extensions..js (internal/modules/cjs/loader:1310:10)
    at Module.load (internal/modules/cjs/loader:1119:32)
    at Module._load (internal/modules/cjs/loader:960:12)
    at executeUserEntryPoint (internal/modules/run_main:81:12)
    at <anonymous> (internal/main/run_main_module:23:47)

the github repo i’m following is found at link

I tried running npm audit fix --force countless times because it seemed to do something different each time, but after every attempt there was still countless errors.

Not able to display user input from form under a div

<html>

<head>
  <link rel="stylesheet" href="css/index.css">
  <script type="module" src="src/index.js"></script>
</head>

<body>
  <div id="header" class="header">
    <div class="m_title">
      <h1>OverFlow</h1>
    </div>
    <div class="m_searchbar">
      <input class="m_searching" type="text" placeholder="Search...">
    </div>
  </div>

  <div id="main" class="main">
    <article>
      <div class="m_wrapper" id="button-container">
        <div class="m_content-header">
          <h1> All Questions</h1>
        </div>
        <div class="m_button">
          <button class="m_ask" onclick="toggleQuestionForm()">Ask Question</button>
          <br>
          <div class="m_nested">

            <button class="m_b1">Newest</button>
            <div class="divider"></div>
            <button class="m_b2">Active</button>
            <div class="divider"></div>
            <button class="m_b3">Unanswered</button>
          </div>
        </div>
      </div>
      <div class="form">
        <form id="question-form" style="display: none;" action="#" method="post">

          <div class="box">
            <div class="box1" id="questionTitle"> <label for="questionTitle">Question Title<span
                  class="required">*</span></label></div>
            <div class="box2">
              <p><i>Limit title to 100 characters or less</i></p>
            </div>
            <div class="box3"><textarea name="questionTitle" id="questionTitle" cols="50" rows="2" maxlength="100"
                required></textarea></div>

            <div class="box1" id="questionText"> <label for="questionText">Question Text<span
                  class="required">*</span></label></div>
            <div class="box2">
              <p><i>Add details</i></p>
            </div>
            <div class="box3"><textarea name="questionText" id="questionText" cols="50" rows="10" required></textarea>
            </div>

            <div class="box1" id="questionTag"><label for="questionTag">Tags<span class="required">*</span></label>
            </div>
            <div class="box2">
              <p><i>Add keywords seperated by spaces</i></p>
            </div>

            <div class="box3"> <textarea name="questionTag" id="questionTag" cols="50" rows="2" required></textarea>
            </div>



            <div class="box1" id="userName"> <label for="userName">Username<span class="required">*</span></label></div>
            <div class="box3"> <textarea name="userName" id="userName" cols="50" rows="2" required></textarea></div>

            <div class="box4" id="button">
              <button class="b4" type="submit">Post Question</button>

            </div>
            <p class="mandatory"><span class="required">*</span> indicates mandatory fields</p>

          </div>

        </form>
      </div>

      <div id="submittted-data" style="display: none;">
       <!-- question title goes here -->
      </div>

    </article>
    <aside>
      <div class="m_sidebar">
        <a href="index.html" onclick="toggleQuestionForm()">Questions</a>
        <!-- reload the damn screen!!!!!!-->
        <a href="#m__tags" rel="noreferrer">Tags</a>
      </div>
    </aside>
    <u></u>

  </div>
  <script>

    function toggleQuestionForm() {
      var questionForm = document.getElementById("question-form");
      var buttonContainer = document.getElementById("button-container");

      // Check if the question form is currently hidden
      if (questionForm.style.display === "none") {
        // Show the question form and hide the button container
        questionForm.style.display = "block";
        buttonContainer.style.display = "none";


      } else {
        // Hide the question form and show the button container
        questionForm.style.display = "none";
        buttonContainer.style.display = "block";
      }
    }

    //create function to post content
    document.getElementById("question-form").addEventListener("submit", function (event) {
      event.preventDefault();

      // Get the question title from the form
      const questionTitle = document.getElementById("questionTitle").value;

      // Display the question title in the submitted-data div
      const submittedData = document.getElementById("submitted-data");
      submittedData.innerHTML = questionTitle;
    });


  </script>
</body>

</html>

I have a form in my html file that mimics the asking question process of stack over flow. at the moment it has no action set to it but only the post method. after the user clicks on the post question button it doesn’t reload the home screen and neither does the question title appear in the section “submitted-data”.

I don’t know why but If I get rid of the post method then the function used to display the title wont appear.

warning I am very new to html and JavaScript. Any help as well as criticism is much appreciated `

The console is logging “Not Found 404” with Heroku link. What do I do to fix this?

I am creating a CRUD app of my choice for an assignment; however, they didn’t really explain what a HEROKU app link is.
In the notes, my instructor provided the link.
“https://ancient-taiga-31359.herokuapp.com/api/houses”.

However, the console keeps logging that it isn’t found.
POST https://ancient-taiga-31359.herokuapp.com/ 404 (Not Found)

His CRUD app was using houses, while mine is using rescues instead. Would it be okay to change the end of the link to rescues instead? I know that the sources on my HTML file are correct because it wouldn’t link over to my JavaScript file.

This is my JavaScript code with the link

I’ve tried just logging into Heroku on my browser. I’m not really sure how to get it to work besides that though.
I’m really looking for a basic explanation for Heroku links in JavaScript code with AJAX, HTML, BootStrap, and jQuery. I’m pretty new at servers and apps so an explanation of how VSC and Heroku links work would be great too!

How to make Contact Form 7 work when injected via AJAX in WordPress?

I am working on a WordPress project where I need to use Contact Form 7 as part of an AJAX response to check if certain data is available. However, I am facing an issue where the form is not working as expected.

Here’s the scenario: When an AJAX request succeeds, I return the Contact Form 7 shortcode as part of the JSON response like so:

wp_send_json_success(array(
    // ...
    'form' => apply_shortcodes('[contact-form-7 id="42e8b16" title="Contact form 1"]')
));

In my AJAX response handler, I include the form in the HTML as follows:

let formData = data.form;
let summary =`${formData}`; 
document.getElementById('element').innerHTML = summary;

However, when I attempt to submit the form, I am redirected to a new page that only contains the text “0”. Additionally, the browser console logs a 400 Bad Request error pointing to admin-ajax.php as follows:

domain.localpc/wp-admin/admin-ajax.php 400 (Bad Request), filename : admin-ajax.php#wpcf7-f87-o1:1

I am unsure of what might be causing this issue. Is there a specific way to make Contact Form 7 work in JavaScript, especially when it’s being injected into the page as a response to an initial AJAX request? Any insights or solutions would be greatly appreciated.

Thank you!

Morph transition on slider jQuery

The code works pretty much as I want it to. I have 2 sliders, one currently has 6 images, the other 5. By clicking on the left button, the slider moves to the left, the very first image disappears and appears as the last image. Likewise, when the right button is clicked, the last image disappears and appears as the first image. The problem is the other images in the sequence that I want to move as a morph transition. How can I achieve the above?

Here is my code:

$(document).ready(() => {
  const leftButton = $(".left");
  const rightButton = $(".right");

  const disableButtons = () => {
    leftButton.prop("disabled", true);
    rightButton.prop("disabled", true);
  };

  const enableButtons = () => {
    leftButton.prop("disabled", false);
    rightButton.prop("disabled", false);
  };

  leftButton.on("click", () => {
    disableButtons();
    const currentTopImage = $("#top-row .image:first-child");
    const currentBottomImage = $("#bottom-row .image:first-child");
    currentTopImage
      .appendTo("#top-row")
      .hide()
      .fadeIn(1000, () => {
        enableButtons();
      });
    currentBottomImage.appendTo("#bottom-row").hide().fadeIn(1000);
  });

  rightButton.on("click", () => {
    disableButtons();
    const currentTopImage = $("#top-row .image:last-child");
    const currentBottomImage = $("#bottom-row .image:last-child");
    currentTopImage
      .prependTo("#top-row")
      .hide()
      .fadeIn(1000, () => {
        enableButtons();
      });
    currentBottomImage.prependTo("#bottom-row").hide().fadeIn(1000);
  });
});

Can’t input number zero (0) in react js

From this code, why can’t I input the number 0. So I want when I input a number in front of or behind a 0, the value will be replaced by the number entered, but after that I can input another 0 behind it because it is for calculations. For example, if I input 10, then only the number 1 is entered, the number 0 is not entered

const numberToString = (text) => {
        text = text.toString().replace(/,/g, '');
        let number = parseFloat(Math.round(text * 100) / 100);

        let decimals = number % 1 === 0 ? 0 : 2;
        number = number.toFixed(decimals);

        let parts = number.toString().split(".");
        parts[0] = parts[0].replace(/B(?=(d{3})+(?!d))/g, ",");
        return parts.join(".");
    };


 const onChange = (e) => {
        e.preventDefault();

        let text = e.target.value;
         text = text.toString().replace(/,/g, '');
        text = text.replace(/^0+|0+$/g, '');
        text = text.replace(/[^0-9]/g, '');
        var number = parseFloat(Math.round(text * 100) / 100);
        let details = calculatorDetails;
        if (!isNaN(number)) {
            if (e.target.name === "kursIDR") {
                setFormData({
                    ...formData,
                    [e.target.name]: number,
                    ["v" + e.target.name]: text,
                });
            }
 else {
            setFormData({ ...formData, [e.target.name]: e.target.value })
        }
    };

 <div className="row align-items-center mb-3">
                                    <label className="col-sm-2 col-form-label">Electrical Testing & COMM</label>
                                    <div className="col-sm-10">
                                        <input name="electricalTestingComm" value={velectricalTestingComm} type="text" className="form-control text-right" onChange={isCountReverse ? (e) => onCountReverseChange(e) : (e) => onChange(e)} readOnly={status === 'Submitted' || status === 'User Approved' || status === 'Customer Approved' || status === 'Failed' || status === 'Canceled'} />
                                    </div>
                                </div>

How to deploy node js application on windows server?

I have built a node js application using sockets.io that let’s you interact with a PostgreSQL database and it emits a signal to all the connected users everytime there’s an update. The application is pretty simple, but the complicated part (at least to me) is the deployment of this application. I have a computer running Windows Server 2012 R2, which I need to deploy the application on. I need it to have a static IP address or domain so the application would be accessible from any location at any time. In the past I have only deployed applications to online services like Render and Hostinger which take care of pretty much everything, so I am new to this.

I did some research and I found a couple of ways how I could achieve this.

Firstly I stumbled upon ngrok, localtunnel and services/modules of that kind, which from my understanding are meant for showcasing your project for a short period of time. I need my application to be running for years to come, be stable and do the job it was meant to do. I also want to stay away from third party/external APIs, modules and things like that, I need my application to be as independent as possible. Therefore, I think that these are not the fit for my case.

Then I found the following articles/discussions:
How to deploy node js in windows server 2012
How to deploy node js web server?
https://marbleit.rs/blog/hosting-nodejs-applications-on-windows-server/
Which recommended using IISNODE.

I also looked through these:
https://webdock.io/en/docs/how-guides/app-installation-and-setup/how-to-deploy-your-first-nodejs-application-to-your-ubuntu-web-server
https://medium.com/@engr.mmohsin/how-to-host-nodejs-on-windows-server-e9bd38b6b6d5

The first one is for Ubuntu, and the second one recommends using PM2, what are the differences between IISNODE and PM2, which is better for this occasion. Will any of them help me achieve my goal or is there something better.

The most confusing part for me is the changing the firewall properties and also how would I get the adress/domain that I will later use to access the application.

I will provide any additional information needed inorder to solve this issue, I am sorry if somethig is not clear, feel free to ask me for anything.

Merge data from an object into existing Map()?

How do we merge data from an object to an existing Map?

To create a Map from an object we can use this:

const myMap = new Map(Object.entries(myObject));

Is there a way to do the same to an existing Map() without using for loop?

const myObject = {
  test: "blah",
  ok: 123
}

const myMap = new Map(Object.entries(myObject));

const mySecondObject = {
  test: "new value",
  foo: "bar"
}

for(let i in mySecondObject)
{
  myMap.set(i, mySecondObject[i]);
}

console.log({size: myMap.size, test: myMap.get("test"), myMap});

Minimum size of a subarray that it’s sum is equal to or larger than target

I have to find minimum size of a subarray that it’s sum is equal to or larger than target using sliding window algorithm. Is this good enough in terms of complexity and how can I improve this, especially how could I improve the minLength assignment?

var minSubArrayLen = function(target, nums) {
    let minLength = 100000000000;
    let left = 0;
    let sum = 0;

    for(let i=0; i<nums.length; i++) {
        sum += nums[i];

        while(sum>=target) {
            minLength = Math.min(minLength, i-left+1);
            sum -= nums[left];
            left++;
        }
    }

    if(left===0) {
        minLength = 0;
    }

    return minLength;
};

It should log 2 to the console

How to calculate and display marker on clicked place in image?

I have code written in .cshtml

The div below contains a map in jpg form. I wrote a JS code for this that calculates the click location on this map and then the HTML + C# code places a marker on the clicked location.

Unfortunately it doesn’t work properly. It shows the place I clicked but it is moved to the right. On the phone, it displays the marker in a completely different place (off the map or in the wrong place on the map)

                <div class="image-container">
                    <div class="image-wrapper" id="imageWrapper">
                        <img id="mapImage" src="/assets/map.jpg" alt="Map" style="position: relative !important; width: 100% !important; height: auto !important;">
                        <div id="markers-container">
                            @foreach (var asset in Model.AssetsList)
                            {
                                var ImageWidth = 1840;
                                var ImageHeight = 1130;
                                <div class="marker" style="left: @(asset.PosX)%; top: @(asset.PosY)%; transform: translate(-50%, -50%); position: absolute;">
                                    <img src="@Model.AssetTypesList.Where(a => a.Id == asset.AssetTypeId).FirstOrDefault()?.ImageUrl" alt="@asset.BoatName" style="width: 25%; height: auto;" />
                                </div>
                            }
                        </div>

                    </div>

                    <button id="image-change-button">
                        <i class="fa-solid fa-right-left "></i>
                    </button>
                </div>

JS:

 function selectMarker(assetTypeId) {
        selectedMarkerId = assetTypeId;
        closePopup();

        document.getElementById("imageWrapper").style.cursor = "crosshair";

        var mapImage = document.getElementById("mapImage");
        mapImage.addEventListener("click", function (event) {

            var offsetX = event.offsetX;
            var offsetY = event.offsetY;

            var image = new Image();
            image.src = mapImage.src;
            var imageWidth = mapImagePopup.width;
            var imageHeight = mapImagePopup.height;


            // positions in percents
            posX = (offsetX / imageWidth) * 100;
            posY = (offsetY / imageHeight) * 100;

            openAddAssetPopup();

            mapImage.style.cursor = "default";
        }, { once: true });
    }

Can someone help me what I need to change in the JS or C# / HTML code so that it picks up the exact place where I clicked IN the image. And then let the marker be generated in exactly the same place where you clicked (on the image, of course, not on the page), regardless of how the page is displayed (on mobile it has a different arrangement)

Image Generation using HTML canvas stops after one generation

I have an image generation button set up and working using Nextjs and the HTML canvas element, which (almost) works beautifully. When a user clicks the “Generate Image” button, it generates an image with a bunch of smaller images in it with labels underneath each one.

Code:

const downloadImage = () => {
    if (isGeneratingImage) return
    setIsGeneratingImage(true)

    // Define sizes for canvas components
    const canvasWidth = 1000;
    const logoHeight = 70;
    const logoMargin = 16;
    const symbolsPerRow = 6;
    const symbolCardWidth = 140;
    const symbolCardHeight = 175;
    const symbolCardGap = 8;
    const symbolImageSize = 96;

    // Calculate canvas height based on number of symbols
    // Symbols are arranged like a flexbox row with wrap
    const canvasHeight = Math.ceil(imageList.length / symbolsPerRow) * (symbolCardHeight + symbolCardGap) + symbolCardGap + logoHeight + (logoMargin * 2);
    const canvasMargin = Math.ceil((canvasWidth - (symbolsPerRow * (symbolCardWidth + symbolCardGap)) + symbolCardGap) / 2);

    // Create canvas element in the html document
    const canvas = document.createElement('canvas');
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    // Get 2d drawing context
    const ctx = canvas.getContext('2d')!;

    // Draw background image (same as the one used for the PageSection)
    const background = new Image();
    background.src = backgroundImageSrc;

    const RobotoBold = new FontFace('Roboto-Bold', 'url(/fonts/Roboto-Bold.ttf)')
    
    RobotoBold.load()
    .then(() => (
        new Promise<void>(resolve => {
            document.fonts.add(RobotoBold);

            background.onload = () => {
                // Calculate scaling factors to cover the canvas while maintaining aspect ratio
                const scaleX = canvasWidth / background.width;
                const scaleY = canvasHeight / background.height;
                const scale = Math.max(scaleX, scaleY);

                // Calculate the new width and height of the image
                const newWidth = background.width * scale;
                const newHeight = background.height * scale;

                // Calculate the position to center the image on the canvas
                const x = (canvasWidth - newWidth) / 2;
                const y = (canvasHeight - newHeight) / 2;

                // Draw the background image with the calculated parameters
                ctx.filter = 'brightness(0.4) blur(10px)';
                ctx.drawImage(background, x, y, newWidth, newHeight);

                // Reset filter
                ctx.filter = 'none';

                resolve();
            };
        })
    ))
    .then(() => {
        // List of promises for loading images
        const imagePromises: Promise<void>[] = [];

        // Load the logo image
        const logo = new Image();
        logo.src = FullLogo.src;
        imagePromises.push(new Promise<void>(resolve => {
            logo.onload = () => {
                // Calculate the scaled width to maintain aspect ratio
                const scaledWidth = (logoHeight / logo.naturalHeight) * logo.naturalWidth;

                // Draw logo horizontally centered with a margin at the top
                ctx.drawImage(
                    logo,
                    canvasWidth / 2 - scaledWidth / 2,
                    logoMargin,
                    scaledWidth,
                    logoHeight
                );
                resolve();
            }
        }));
        
        // Calculate values for drawing symbols in the last row
        const symbolsInLastRow = imageList.length % symbolsPerRow;
        const lastRowOffset = (symbolsPerRow - symbolsInLastRow) * (symbolCardWidth + symbolCardGap) / 2

        // Draw symbols with rounded backgrounds
        for (let i = 0; i < imageList.length; i++) {
            const imageReference = imageList[i];

            // If the symbol is in the last row, we need to adjust the x position to center it
            const isLastRow = i >= imageList.length - symbolsInLastRow;
        
            const x = (i % symbolsPerRow) * (symbolCardWidth + symbolCardGap) + symbolCardGap + canvasMargin + (isLastRow ? lastRowOffset : 0);
            const y = Math.floor(i / symbolsPerRow) * (symbolCardHeight + symbolCardGap) + symbolCardGap + logoHeight + (logoMargin * 2);

            // Draw transparent gray background for symbol with rounded borders
            ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
            roundRect(ctx, x, y, symbolCardWidth, symbolCardHeight, 16);

            // Draw symbol image
            const image = new Image();
            image.src = imageReference.url;
            imagePromises.push(new Promise<void>(resolve => {
                image.onload = () => {
                    ctx.drawImage(image, x + (symbolCardWidth - symbolImageSize) / 2, y + (symbolCardHeight - symbolImageSize) / 4, symbolImageSize, symbolImageSize);
                    resolve();
                }
            }));

            // Draw symbol name
            ctx.fillStyle = 'white';
            ctx.font = '20px Roboto-Bold';
            ctx.textAlign = 'center';
            ctx.fillText(customNames[imageReference.id] ?? imageReference.name, x + symbolCardWidth / 2, y + symbolCardHeight - 24, symbolCardWidth - 16);
        }

        // Convert canvas to Blob and trigger download after all images are loaded
        Promise.all(imagePromises)
        .then(() => {
            canvas.toBlob(blob => {
                // Trigger download
                const a = document.createElement('a');
                a.download = `${calloutSet?.name}.png`;
                a.href = URL.createObjectURL(blob!);
                a.click();
                setIsGeneratingImage(false);
            });
        })
    });
}

Notice how I use Promises to move between each step of the image generation process after the font is loaded, then after the background image is loaded, then after all the smaller images have loaded.

However, the issue is that after the image is generated once (or sometimes several times), it will not work the second time because the background.onload callback is never called, thus the following steps are never executed (I have tested this with console logs). Why is this erratic behavior happening, and how can I fix it?