React useEffect not running after all DOM has loaded

I have a react Application where I am trying to add the AWS Chat Widget.

It is working but I want to add some additional styling to the launch icon which doesnt seem to be their out of the box – https://docs.aws.amazon.com/connect/latest/adminguide/customize-widget-launch.html

I want too add a tool tip as I have mocked up in the image:

enter image description here

This is the code I have added to useEffect method:

  useEffect(() => {
    const widgetEle =  document.getElementById('amazon-connect-chat-widget');
    debugger;
    if (widgetEle) {
      widgetEle.innerHTML = '<span class="tooltip-text" id="left">Chat with us!<button id="chatToolTipClose" type="button" class="close">x</button></span>';
    }
 }, []);

I thought this should only run after the DOM has fully rendered – but on my debugger the widgetEle is always null. But when I run the exit the debugger the default google chat icon is displayed – and when I inspect the element I can see there is no typo in the id – it is exactly what I specify.

Is there something I am missing?

Can’t Log into Strapi after Accidentally Setting User to ‘Active False’

On the Strapi application, I accidentally checked the ‘active false’ box for the user when it should have been ‘true.’ Since then, I can’t log in to Strapi. I’m convinced that to fix this issue, I need to go into the database, but I can’t find which file I should modify to change my user’s status back to ‘active true.’

Database: SQLite

Language: JavaScript

I tried to back up an old version on GitHub, but no success…

I also tried to filter all active elements in VS Code, but I can’t figure out which one I should fix.

Put a for loop iteration in a Bootstrap group list button click

I have a lab about addEventListener, where in a click of a button shows your for loop with office locations.

const grupList = document.querySelector("ul");
console.log(grupList);

const listItems = document.querySelector("li");
console.log(listItems);`

const offices = document.getElementById("ofLocations");


let officeLocations = ["New York", "London", "Paris", "Tokyo", "Sydney"];



```function getOfficeLocations (arr){
 ``` for (let i = 0; i < arr.length; i++) {
    `console.log(arr[i]);
```offices.addEventListener("click",  getOfficeLocation {

})
  }
}
`getOfficeLocation(officeLocations);`

This is what I have done so far.

Multiple different textures/materials on 3D heightmap

over the weeks I have created a chunked infinite heightmap terrain. The chunk system is very simple and is not the topic of this question. The topic of this question is how to create a terrain heightmap with the possibility of blending between different textures/materials (grass, dirt, sand, etc.) smoothly.

A chunk is basically a grid of height values and texture values (so 0 is dirt, 1 is grass, 2 is something else). Then I generate the chunk mesh by taking all those values and turning them into vertex attributes (height is a buffer object and so is the texture/material; I only pass the height because I calculate the X and Z position in the shader).

Now, the vertex shader looks like this:

  #version 300 es
  layout (location = 0) in float height;
  layout (location = 1) in float material;

  out float h;
  out float m;
  out vec2 uv;
  
  uniform vec4 chunkPosition;
  uniform int chunkSize;
  uniform mat4 view, projection;

  void main () {
    int col = gl_VertexID % (chunkSize + 1);
    int row = gl_VertexID / (chunkSize + 1);
    vec4 position = vec4 (col, height, row, 1.0);
  
    gl_Position = projection * view * (chunkPosition + position);
    uv = vec2 (float (col) / float (chunkSize), float (row) / float (chunkSize));
    h = height;
    m = material;
  }

And the fragment shader looks like this:

  #version 300 es
  precision lowp float;

  in vec2 uv;
  in float h;
  in float m;
  out vec4 color;

  uniform sampler2D grass, drygrass, dirt;
  
  void main () {
    vec4 grs = texture (grass, uv);
    vec4 drt = texture (dirt, uv);
  
    color = mix (grs, drt, m);
  }

With the single value m representing the material at a given vertex I seem to be able to sucessfuly blend between two textures and it looks great. However, I can’t wrap my head around how to implement more textures here (in this example, how to make it so drygrass is also on the terrain).

I understand that the way it works here is it does a weighed average and interpolated between the grass and dirt materials based on the value m (which for now I have not made it go beyond 1).

I already tried making it so the value does go to 2 (so in this example it could be e.g. drygrass) but I find it hard to figure out how to mix it all.

Is the way to go to have a different “m” value for every single texture type in the terrain?

autofill html form w/ multiple different fields, including: Checkbox and Dropdown selections

Part of the html form I want to autofill:

<div h2 class="display-6 text-uppercase mb-3">
   Service</h2>
   <span class="question">Select Service(s):</span><br>
   <input type="checkbox" id="car_wash" name="car_wash" value="car_wash"/>
   <label for="car_wash"> Car Wash</label><br>
   <input type="checkbox" id="window_cleaning" name="window_cleaning" value="Window Cleaning" autocomplete="name">
   <label for="window_cleaning"> Window Cleaning</label><br>
   <input type="checkbox" id="power_wash" name="power_wash" value="Power Wash" autocomplete="name"
   <label for="power_wash"> Powerwashing</label><br>
   <input type="checkbox" id="curb_address" name="curb_address" value="Curb Address Painting" autocomplete="name"
   <label for="curb_address"> Curb Address Painting</label><br>
</div>
<div id="dropdownHolder" class="col-lg-12 mb-3">
   <select id="carwash_type" name="carwash_type" class="form-select form-control" aria-label="Default select example" value="ON">
      <option selected> Car Type</option>
      <option value="1">Coupe / Sedan / Hatchback</option>
      <option value="2">Suv / Crossover</option>
      <option value="3">Mini Van</option>
      <option value="4">Pickup Truck</option>
      <option value="5">I'm not sure</option>
   </select>
</div>
<div id="dropdownHolder" class="col-lg-12 mb-3">
   <select id="carwash_type" name="carwash_type" class="form-select form-control" aria-label="Default select example" value="ON">
      <option selected> Car Wash Package</option>
      <option value="1">Express Interior</option>
      <option value="2">Express Exterior</option>
      <option value="3">Full Service Wash</option>
   </select>
</div>

There are several pages linking to this form, each of which have a service selected and if it’s a car wash, the extras as well, including car type and wash package.

Here is the html of the car wash selection – before the form page, trying to link to it w/autofilled selections.

<div class="services-content text-center border py-3 py-md-5 px-3">
   <iconify-icon class="service-icon"
      icon="fa6-solid:car-on"></iconify-icon>
   <h4 class="my-3">Auto Detailing</h4>
   <a href="booking.html" class="icon-link my-3">
      Book Now 
      <iconify-icon
         icon="tabler:arrow-right" class="arrow-icon"></iconify-icon>
   </a>
</div>

From what I’ve found out, there are several ways to do this. I’m still very new and I’m just not sure how to go about this the most reliable way. If needed maybe with js?

Failed loading pdf, getting error failed loading worker using React-PDF, Mantine Dropzone

My goal is to have a user upload a PDF file and preview it using React-PDF. I am using Mantine’s Dropzone for file upload and loading the file into preview using React-pdf.

The issue is that I am failing to load the pdf file and getting error

Error: Setting up fake worker failed: "Cannot load script at: http://localhost:5173/pdf.worker.js".

My code:

import { useState, useRef, useEffect } from 'react';
import { Document, Page } from 'react-pdf';
import { Button, Group } from '@mantine/core';
import { Dropzone, PDF_MIME_TYPE } from '@mantine/dropzone';

type PDFFile = string | File | null;

const FileDropzone = () => {
  const dropzoneOpenRef = useRef<() => void>(null);
  const [file, setFile] = useState<PDFFile>(null);

  const onFileChange = (files: File[]) => {
    if (files && files[0]) {
      setFile(files[0]);
    }
  };

  useEffect(() => {
    console.log(file);
  }, [file]);

  return (
    <>
      <h1>React Drop File</h1>
      <p>Click on the Vite and React logos to learn more</p>
      <Dropzone
        openRef={dropzoneOpenRef}
        onDrop={onFileChange}
        onReject={(file) => console.log('Rejected files', file)}
        maxSize={5 * 1024 ** 2}
        accept={PDF_MIME_TYPE}>
        <Group justify='center' mt='md'>
          <Button onClick={() => dropzoneOpenRef.current?.()}>
            Select Files
          </Button>
        </Group>
      </Dropzone>

      <div>
        <Document file={file} onLoadError={console.error}>
          <Page pageNumber={1} />
        </Document>
      </div>
    </>
  );
};

export default FileDropzone;

Can you automatically extract React components from JSX?

I’m working on a React translation of a website exported from a design tool called Webflow. The export contains the html and css I need to create the page. I’ve found a tool to translate the HTML into React, but now I want to turn segments of the translated code into components automatically in a similar way.

There are two tools I’ve found to do this:

  1. Glean,
  2. VScode Refactor

They both allow you to turn a snippet of JSX into a component within the same module, but they don’t automatically use the <Component></Component> syntax, they can’t be named on creation, and they don’t allow you to spin out that snippet into a new file. All of which are features I would really like.

Are these the only two options for automatically generated react Components? Or is there a better way?

Capacitor doesn’t work for Ionic with vanilla JS

When creating Ionic app following documentation https://ionicframework.com/docs/vue/quickstart and converting typescript to javascript (10 steps on this doc page), then there is a problem creating android version by running this command: ionic cap add android

This command should create android folder, but i get this error message:

enter image description here

This error doesn’t exist if application is set for typescript, it appears only after converting to javascript.

Seems this is an internal issue with capacitor. Is there a way to fix this via vite.config or something similar?

By the way it’s Ionic version 7

React-Native Expo-Go app: ‘Not a valid React component’ error

I am trying to develop my first proper React-Native app and I’m having trouble straight out the gate. I am trying to use a few different imported Navigation libraries (BottomTabNavigator, NativeStackNavigator, Stack.Screen, etc.).

I am getting an error when I run the app on Expo-Go saying something like “Error: Got an invalid value for ‘component’ prop for the screen ‘ManageExpense’. It must be a valid React Component.”
So far a I have App.js, and 3 screens, AllExpenses.js, ManageExpense.js, and RecentExpenses.js.
I’ll post them in that order below.

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

//importing custom components
import ManageExpense from './screens/ManageExpense';
import RecentExpenses from './screens/RecentExpenses';
import AllExpenses from './screens/AllExpenses';

const Stack = createNativeStackNavigator();// this stack will create an object that gives access to 2 components (nav, register screens): https://reactnavigation.org/docs/native-stack-navigator
const BottomTabs = createBottomTabNavigator(); // allows bottom tab navigation: npm install @react-navigation/bottom-tabs

//this function handles the bottom tab navigator
function ExpensesOverview() {
  return (
    <BottomTabs.Navigator>
      <BottomTabs.Screen name="RecentExpenses" component={RecentExpenses} />
      <BottomTabs.Screen name="AllExpenses" component={AllExpenses} />
    </BottomTabs.Navigator>
  );
}

export default function App() {
  return (
    <>
      <StatusBar style="auto" />
      {/* setting up navigation */}
      <NavigationContainer>
        {/* StackNavigator allows me to dynamically load the tabs options based on which screen is selected*/}
        <Stack.Navigator>
          {/* below uses the custom function above to display both options in one stack.screen */}
          <Stack.Screen name="ExpensesOverview" component={ExpensesOverview} />
          <Stack.Screen name="ManageExpense" component={ManageExpense} />
        </Stack.Navigator>
      </NavigationContainer>
    </>
  );
}


import React from 'react';
import { Text } from 'react-native';

export default function AllExpenses() {

    return <Text>AllExpenses Screen</Text>

}
import React from 'react';
import { Text } from 'react-native';

export default function ManageExpense() {

    return <Text>ManageExpense Screen</Text>

}


import React from 'react';
import { Text } from 'react-native';

export default function RecentExpenses() {

    return <Text>RecentExpenses Screen</Text>

}

I’m trying to get the imported navigation components to work properly. I really can’t spot where I’m going wrong I’ve been looking at it for a while and even ChatGPT can’t seem to spot it… Any help would be appreciated. Thanks.

Why doesn’t my mobile menu open anymore ever since I’ve added some new script?

Ever since I’ve added the color-choice script, my hamburger menu isn’t working anymore. I don’t know what is in conflict but the only thing that happens when I click on the hamburger menu is that I get a notification ‘Javascript:void(0)’. Can anyone help me figure this problem out? Of course I need the mobile menu to work before I can launch my website, but I also really want to keep my color choice script because I think that’s a cool feature. Hope someone can help me out.

$(window).on("load", function () {

    "use strict";

/* ===================================
        Loading Timeout
 ====================================== */
$('.side-menu').removeClass('hidden');

    setTimeout(function(){
        $('.preloader').fadeOut();
    }, 1000);
});

jQuery(function ($) {

    "use strict";

/* ===================================
    Side Menu
====================================== */

if ($("#sidemenu_toggle").length) {
    $("#sidemenu_toggle").on("click", function () {
        $(".side-menu").removeClass("side-menu-opacity");
        $(".pushwrap").toggleClass("active");
        $(".side-menu").addClass("side-menu-active"), $("#close_side_menu").fadeIn(700)
    }), $("#close_side_menu").on("click", function () {
        $(".side-menu").removeClass("side-menu-active"), $(this).fadeOut(200), $(".pushwrap").removeClass("active");
        setTimeout(function(){
            $(".side-menu").addClass("side-menu-opacity");
        }, 500);
    }), $(".side-menu .navbar-nav .nav-link").on("click", function () {
        $(".side-menu").removeClass("side-menu-active"), $("#close_side_menu").fadeOut(200), $(".pushwrap").removeClass("active");
        setTimeout(function(){
            $(".side-menu").addClass("side-menu-opacity");
        }, 500);
    }), $("#btn_sideNavClose").on("click", function () {
        $(".side-menu").removeClass("side-menu-active"), $("#close_side_menu").fadeOut(200), $(".pushwrap").removeClass("active");
        setTimeout(function(){
            $(".side-menu").addClass("side-menu-opacity");
        }, 500);
    });
}

/* ===================================
    Bottom Nav Appear
====================================== */

$(function () {
    var $win = $(window);
    jQuery(function($) {
        $(window).scroll(function() {
            if($(window).scrollTop() + $(window).height() > $(document).height() - 200) {
                // alert("near bottom!");
                $('.sidenav-bottom').css('opacity','1');
                $('.sidenav-bottom').addClass('sidenav-bottom-fixed');
            }
            if($(window).scrollTop() + $(window).height() < $(document).height() - 300 && $(window).scrollTop() + $(window).height() > $(document).height() - 400 )  {
                $('.sidenav-bottom').css('opacity','0');
                $('.sidenav-bottom').removeClass('sidenav-bottom-fixed');
            }
        });
    });
});

if ($(window).width() <= 991) {
    $(".scroll").on("click", function (event) {
        event.preventDefault();
        $('html,body').animate({
            scrollTop: $(this.hash).offset().top - 40
        }, 200);
    });
}

/* ===================================
    Header Appear
====================================== */

$(window).on('scroll', function () {
    if ($(this).scrollTop() > 260) { // Set position from top to add class
        $('.sidenav-bottom').css('opacity','0');
        $('header .inner-header').addClass('header-appear');
    }
    if($(this).scrollTop() < 260) {
        $('.sidenav-bottom').css('opacity','1');
        $('header .inner-header').removeClass('header-appear');
    }
});

/*===================================
    Go Top Scroll
====================================== */
$(function(){
    // Scroll Event
    $(window).on('scroll', function(){
        var scrolled = $(window).scrollTop();
        if (scrolled > 260) $('.go-top').addClass('active');
        if (scrolled < 260) $('.go-top').removeClass('active');
    });
    // Click Event
    $('.go-top').on('click', function() {
        $("html, body").animate({ scrollTop: "0" },  100);
    });
});

/* ===================================
    Portfolio Carousel
====================================== */

$('.portfolio-carousel').owlCarousel({
    loop:true,
    margin:10,
    slideSpeed: 5000,
    slideTransition:'linear',
    nav:false,
    dots:false,
    autoplay:false,
    autoplayTimeout:8000,
    autoplayHoverPause:false,
    responsive:{
        0:{
            items:1
        },
        600:{
            items:1
        },
        1000:{
            items:1
        },
    }
});

$('.portfolio-right-arr').click(function() {
    var owl = $('.portfolio-carousel');
    owl.owlCarousel();
    owl.trigger('next.owl.carousel');
});
$('.portfolio-left-arr').click(function() {
    var owl = $('.portfolio-carousel');
    owl.owlCarousel();
    owl.trigger('prev.owl.carousel');
});

/* ===================================
    Banner Carousel
====================================== */

$('.slider-carousel').owlCarousel({
    loop: true,
    margin: 10,
    slideSpeed: 9999,
    slideTransition: 'linear',
    nav: false,
    dots: false,
    autoplay: true,
    smartSpeed: 450,
    autoplayTimeout: 3000,
    autoplayHoverPause: false,
    responsive:{
        0:{
            items:1
        },
        600:{
            items:1
        },
        1000:{
            items:1
        },
    }
});

/* ===================================
    Testimonial Carousel
====================================== */

$('.review-carousel').owlCarousel({
    loop:true,
    margin:10,
    slideSpeed: 5000,
    slideTransition:'linear',
    nav:false,
    dots:false,
    autoplay:false,
    autoplayTimeout:8000,
    autoplayHoverPause:true,
    responsive:{
        0:{
            items:1
        },
        600:{
            items:1
        },
        1000:{
            items:1
        },
    }
});

/* ===================================
     Wow Effects
======================================*/

var wow = new WOW(
    {
        boxClass:'wow',      // default
        animateClass:'animated', // default
        offset:0,          // default
        mobile:false,       // default
        live:true        // default
    }
);
    wow.init();
});

$(document).ready(function() {
    // Toon de kleurkeuze pop-up bij het laden van de pagina
    setTimeout(function() {
        $("#color-choice-popup").fadeIn();
        $("#color-choice-overlay").fadeIn(); // Laat ook de overlay zien
        $("body").addClass("no-scroll"); // Voeg een klasse toe om scrollen te voorkomen
    }, 1500);

    // Functie om de themaklasse van de website aan te passen
    function updateThemeClass(themeClass) {
        document.documentElement.className = themeClass; // Stel de klasse van het root element in
    }

    // Event listener voor kleurkeuze
    $(".color-choice").on("click", function() {
        var themeClass = $(this).data("theme");
        updateThemeClass(themeClass); // Pas de themaklasse aan met de gekozen klasse
        $("#color-choice-popup").fadeOut(); // Verberg de pop-up
        $("#color-choice-overlay").fadeOut(); // Verberg ook de overlay
        $("body").removeClass("no-scroll"); // Verwijder de klasse om scrollen mogelijk te maken
    });

    // Controleer of de kleurindicator al bestaat om te voorkomen dat deze meerdere keren wordt toegevoegd
    if($("#color-indicator-menubar").length === 0) {
        // Zorg ervoor dat dit geplaatst wordt in het juiste header-element, naast de Get a Quote knop
        $("<div id='color-indicator-menubar'></div>")
            .insertBefore(".inner-header .row .col-lg-4.d-flex.justify-content-end .btn-main");
    }

    // Laat de kleurkeuzepopup opnieuw zien wanneer op de kleurindicator wordt geklikt
    $("#color-indicator-menubar").on("click", function() {
        $("#color-choice-popup").fadeIn();
        $("#color-choice-overlay").fadeIn(); // Laat ook de overlay zien
        $("body").addClass("no-scroll"); // Voeg een klasse toe om scrollen te voorkomen
    });
});

Drad&Drop in JS: detect a file

I’m implementing a drag&drop in JS, it works but the detection of my file is quite bad (when the file is drag over the dropzone it isn’t always detected).
Also i would like to click anywhere in the dropzone to activate my input it doesn’t work either.

Here is my code and a link of my codePen to test it: https://codepen.io/ChloeMarieDom/pen/QWPLqNz?editors=0010)

<div class="frame">
  <div class="container">
        <div class="title">Drop file to upload</div>
        <div class="line"></div>
        <div class="dropzone">
            <svg class="upload" viewBox="0 0 640 512" width="100" title="cloud-upload-alt">
        <path d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zM393.4 288H328v112c0 8.8-7.2 16-16 16h-48c-8.8 0-16-7.2-16-16V288h-65.4c-14.3 0-21.4-17.2-11.3-27.3l105.4-105.4c6.2-6.2 16.4-6.2 22.6 0l105.4 105.4c10.1 10.1 2.9 27.3-11.3 27.3z" />
            </svg>
            <span class="filename"></span>
            <input type="file" class="input">
        </div>
        <div class="upload-btn">Upload file</div>
        <svg class="syncing" viewBox="0 0 512 512" width="100" title="circle-notch">
    <path d="M288 39.056v16.659c0 10.804 7.281 20.159 17.686 23.066C383.204 100.434 440 171.518 440 256c0 101.689-82.295 184-184 184-101.689 0-184-82.295-184-184 0-84.47 56.786-155.564 134.312-177.219C216.719 75.874 224 66.517 224 55.712V39.064c0-15.709-14.834-27.153-30.046-23.234C86.603 43.482 7.394 141.206 8.003 257.332c.72 137.052 111.477 246.956 248.531 246.667C393.255 503.711 504 392.788 504 256c0-115.633-79.14-212.779-186.211-240.236C302.678 11.889 288 23.456 288 39.056z" />
    </svg>
        <svg class="done" viewBox="0 0 512 512" width="100" title="check-circle">
    <path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" />
    </svg>
        
        
  </div>
</div>
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400);

.frame {
  position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
  top: 50%;
  left: 50%;
  width: 400px;
  height: 400px;
  margin-top: -200px;
  margin-left: -200px;
  border-radius: 2px;
    box-shadow: 4px 8px 16px 0 rgba(0,0,0,0.1);
  background:linear-gradient(to top right, #3A92AF 0%, #5CA05A 100%);
  color: #676767;
    font-family: 'Open Sans', Helvetica, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.container {
  position: absolute;
    width: 300px;
    height: 260px;
    background: #fff;
    border-radius: 3px;
    box-shadow: 8px 10px 15px 0 rgba(0,0,0,0.2);
}

.title {
    position: absolute;
    top: 0;
    width: 100%;
    font-size: 16px;
    text-align: center;
    border-bottom: 1px solid #676767;
    line-height: 50px;
}

.line {
    position: relative;
    width: 0px;
    height: 3px;
    top: 49px;
    left: 0;
    background: #6ECE3B;
    &.active {
        animation: progressFill 5s ease-out forwards;
    }
}
@keyframes progressFill {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}

.dropzone {
    visibility: show;
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
    width: 100px;
    height: 80px;
    border: 1px dashed #676767;
    border-radius: 3px;
}
.dropzone.dragover {
  background-color: rgba(0, 0, 0, 0.1); 
}

.upload {
    position: absolute;
    width: 60px;
    opacity: 0.3;
}

.input {
    position: absolute;
    inset: 0;
    opacity: 0;
}

.filename {
    overflow: hidden;
}

.syncing {
    opacity: 0;
    position: absolute;
  top: calc(50% - 25px);
    left: calc(50% - 25px);
    width: 50px;
    height: 50px;
    animation: rotate 2s linear infinite;
}
@keyframes rotate {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

.done {
    opacity: 0;
    position: absolute;
  top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    width: 50px;
    height: 50px;
}

.upload-btn {
    position: relative;
    top: 180px;
    left: 80px;
    width: 140px;
    height: 40px;
    line-height: 40px;
    text-align: center;
    background: #6ECE3B;
    border-radius: 3px;
    cursor: pointer;
    color: #fff;
    font-size: 14px;
    box-shadow: 0 2px 0 0 #498C25;
    transition: all .2s ease-in-out;
    &:hover {
        box-shadow: 0 2px 0 0 #498C25, 0 2px 10px 0 #6ECE3B;
    }
    
    
}
// Variables
const dropzone = document.querySelector('.dropzone');
const filenameDisplay = document.querySelector('.filename');
const input = document.querySelector('.input');
const uploadBtn = document.querySelector('.upload-btn');
const syncing = document.querySelector('.syncing');
const done = document.querySelector('.done');
const upload = document.querySelector('.upload');
const line = document.querySelector('.line');


dropzone.addEventListener("dragover", (e) => {
  e.preventDefault();
    e.stopPropagation();
    dropzone.classList.add("dragover");
});

dropzone.addEventListener("dragleave", () => {
  dropzone.classList.remove("dragover");
});

dropzone.addEventListener("drop", (e) => {
    e.preventDefault();
  e.stopPropagation();
  dropzone.classList.remove("dragover");
  handleFiles(e.dataTransfer.files);
});

dropzone.addEventListener("dragenter", (e) => {
  e.preventDefault();
  e.stopPropagation();
  dropzone.classList.add("dragover");
});

dropzone.addEventListener("click", () => {
  input.click(); 
});

input.addEventListener('change', (event) => {
  const files = event.target.files;
  if (files.length > 0) {
    const fileName = files[0].name;
    document.querySelector('.filename').textContent = fileName;
    upload.style.display = 'none';
  }
});

dropzone.addEventListener('click', () => {
    input.click();
})

uploadBtn.addEventListener('click', () => {
  const files = document.querySelector('.input').files;
  if (files.length > 0) {  
    dropzone.style.transition = 'opacity 0.5s ease';
    syncing.style.transition = 'opacity 1s ease';
    dropzone.style.opacity = '0';
    syncing.style.opacity = '0.3';
    line.classList.add('active');
    uploadBtn.textContent = 'Uploading...';

    setTimeout(() => {
            done.style.transition = 'opacity 1s ease';
            syncing.style.transition = 'opacity 0.5s ease';
      syncing.style.opacity = '0';
        done.style.opacity = '0.3';
            uploadBtn.textContent = 'Done!';
            input.value = ''; 
    }, 5000); 
  }
});
  

this is what i tried to click everywhere in the dropzone :
dropzone.addEventListener("click", () => { input.click(); });

2d HTML5 Game: How to add collision shapes to loaded tiles that might be rotated and flipped?

I have map data like this, each element separated by space is a different tile with own properties:

static get TILEMAP() {
    return "0C0003 0W1003 0W0003 0W2003 0W3003 0W4003".split(" "); // example tiles, its much longer for real maps
  }

I load tile images like this:

let tiles = {};
const tilemap = Constants.TILEMAP;

function loadTiles() {
  for (const tileIndex in tilemap) {
    const rawTile = tilemap[tileIndex].substring(0, 3); // 0W1.png for example
    const tileImg = new Image();
    tileImg.src = `sprites/maps/tiles/${rawTile}.png`;
    tiles[rawTile] = tileImg;
  }
}

And I draw them to canvas like this:

static rotationKeys = {0: 0, 1: 90, 2: 180, 3: 270};
static tileSize = 50;

function drawMap() {
  const tileSize = Constants.tileSize;
  gameSettings.mapWidth = 35;
  gameSettings.mapHeight = 24;

  const startX = Math.max(0, Math.floor(camera.x / (tileSize * scaleFactor)));
  const startY = Math.max(0, Math.floor(camera.y / (tileSize * scaleFactor)));

  const endX = Math.min(gameSettings.mapWidth, startX + Math.ceil(canvas.width / (tileSize * scaleFactor)) + 1);
  const endY = Math.min(gameSettings.mapHeight, startY + Math.ceil(canvas.height / (tileSize * scaleFactor)) + 1);

  for (let y = startY; y < endY; y++) {
    for (let x = startX; x < endX; x++) {
      let index = y * gameSettings.mapWidth + x;
      const tile = tilemap[index];
      const rotation = parseInt(tile[3]); // Get rotation value for this tile
      const flip = parseInt(tile[4]);

      // Apply rotation transformation
      ctx.save(); // Save the current canvas state
      ctx.translate((x + 0.5) * tileSize * scaleFactor, (y + 0.5) * tileSize * scaleFactor); // Translate to the center of the tile
      ctx.rotate(Constants.rotationKeys[rotation] * Math.PI / 180); // Rotate by the specified angle (converted to radians)

      // Apply flip transformation
      var xScale = 1;
      var yScale = 1;
      if (flip == 1 || flip == 3) {
        xScale = -1;
      }
      if (flip == 2 || flip == 3) {
        yScale = -1;
      }
      ctx.scale(xScale, yScale);

      try {
          ctx.drawImage(tiles[tile.substring(0, 3)], -0.5 * tileSize * scaleFactor, -0.5 * tileSize * scaleFactor, tileSize * scaleFactor, tileSize * scaleFactor); // Draw the rotated image
      } catch (error) {
          console.error(`Failed to draw tile at (${x}, ${y}):`, error);
          console.log("Responsible tile:", tile);
      }
      ctx.restore();
    }
  }
}

As you can see, the tiles can go through quite a lot. Rotations or flips or combined.

I’ve tried defining all collisions like this:

 static colTransforms = {
    "5": {
      rotations: {
        "0": { x: -1, y: -1, width: 52, height: 38,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 38 },
          "2": { x: -1, y: 13, width: 52, height: 38 },
          "3": { x: -1, y: -1, width: 52, height: 38 }
        }},

        "1": { x: -1, y: -1, width: 38, height: 52,
        flips: {
          "1": { x: 13, y: -1, width: 38, height: 52 },
          "2": { x: -1, y: -1, width: 38, height: 52 },
          "3": { x: 13, y: -1, width: 38, height: 52 }
        }},

        "2": { x: -1, y: 13, width: 52, height: 38,
        flips: {
          "1": { x: -1, y: 13, width: 52, height: 38 },
          "2": { x: -1, y: -1, width: 52, height: 38 },
          "3": { x: -1, y: 13, width: 52, height: 38 }
        }},

        "3": { x: -1, y: -1, width: 38, height: 52,
        flips: {
          "1": { x: 13, y: -1, width: 38, height: 52 },
          "2": { x: -1, y: -1, width: 38, height: 52 },
          "3": { x: 13, y: -1, width: 38, height: 52 }
        }}
      },
    },

    "6": {
      rotations: {
        "0": { x: -1, y: -1, width: 52, height: 26,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 26 },
          "2": { x: -1, y: 25, width: 52, height: 26 },
          "3": { x: -1, y: -1, width: 52, height: 26 }
        }},

        "1": { x: -1, y: -1, width: 26, height: 52,
        flips: {
          "1": { x: 25, y: -1, width: 26, height: 52 },
          "2": { x: -1, y: -1, width: 26, height: 52 },
          "3": { x: 25, y: -1, width: 26, height: 52 }
        }},

        "2": { x: -1, y: 25, width: 52, height: 26,
        flips: {
          "1": { x: -1, y: 25, width: 52, height: 26 },
          "2": { x: -1, y: -1, width: 52, height: 26 },
          "3": { x: -1, y: 25, width: 52, height: 26 }
        }},

        "3": { x: -1, y: -1, width: 52, height: 26,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 26 },
          "2": { x: -1, y: 25, width: 52, height: 26 },
          "3": { x: -1, y: -1, width: 52, height: 26 }
        }}
      },
    },

    "7": {
      rotations: {
        "0": { x: -1, y: -1, width: 52, height: 13,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 13 },
          "2": { x: -1, y: 38, width: 52, height: 13 },
          "3": { x: -1, y: -1, width: 52, height: 13 }
        }},

        "1": { x: -1, y: -1, width: 13, height: 52,
        flips: {
          "1": { x: 38, y: -1, width: 13, height: 52 },
          "2": { x: -1, y: -1, width: 13, height: 52 },
          "3": { x: 38, y: -1, width: 13, height: 52 }
        }},

        "2": { x: -1, y: 38, width: 52, height: 13,
        flips: {
          "1": { x: -1, y: 38, width: 52, height: 13 },
          "2": { x: -1, y: -1, width: 52, height: 13 },
          "3": { x: -1, y: 38, width: 52, height: 13 }
        }},

        "3": { x: -1, y: -1, width: 52, height: 13, 
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 13 },
          "2": { x: -1, y: 38, width: 52, height: 13 },
          "3": { x: -1, y: -1, width: 52, height: 13 }
        }}
      },
    },

    "8": {
      rotations: {
        "0": { x: -1, y: -1, width: 26, height: 26,
        flips: {
          "1": { x: 25, y: -1, width: 26, height: 26 },
          "2": { x: -1, y: 25, width: 26, height: 26 },
          "3": { x: 25, y: -1, width: 26, height: 26 }
        }},

        "1": { x: 25, y: -1, width: 26, height: 26,
        flips: {
          "1": { x: -1, y: -1, width: 26, height: 26 },
          "2": { x: 25, y: 25, width: 26, height: 26 },
          "3": { x: -1, y: -1, width: 26, height: 26 }
        }},

        "2": { x: 25, y: 25, width: 26, height: 26,
        flips: {
          "1": { x: -1, y: 25, width: 26, height: 26 },
          "2": { x: 25, y: -1, width: 26, height: 26 },
          "3": { x: -1, y: 25, width: 26, height: 26 }
        }},

        "3": { x: -1, y: 25, width: 26, height: 26,
        flips: {
          "1": { x: 25, y: 25, width: 26, height: 26 },
          "2": { x: -1, y: -1, width: 26, height: 26 },
          "3": { x: 25, y: 25, width: 26, height: 26 }
        }}
      },
    },
    "9": {
      Lfirst: {
        rotations: {
          "0": { x: -1, y: -1, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: -1, width: 52, height: 26 },
            "2": { x: -1, y: 25, width: 52, height: 26 },
            "3": { x: -1, y: -1, width: 52, height: 26 }
          }},

          "1": { x: 25, y: 25, width: 26, height: 52,
          flips: {
            "1": { x: 25, y: -1, width: 26, height: 52 },
            "2": { x: -1, y: -1, width: 26, height: 52 },
            "3": { x: 25, y: -1, width: 26, height: 52 }
          }},

          "2": { x: -1, y: 25, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: 25, width: 52, height: 26 },
            "2": { x: -1, y: -1, width: 52, height: 26 },
            "3": { x: -1, y: 25, width: 52, height: 26 }
          }},

          "3": { x: -1, y: 25, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: -1, width: 52, height: 26 },
            "2": { x: -1, y: 25, width: 52, height: 26 },
            "3": { x: -1, y: -1, width: 52, height: 26 }
          }}
        },
      },
      Lsecond: {
        rotations: {
          "0": { x: -1, y: -1, width: 26, height: 52,
          flips: {
            "1": { x: 25, y: -1, width: 26, height: 52 },
            "2": { x: -1, y: -1, width: 26, height: 52 },
            "3": { x: 25, y: -1, width: 26, height: 52 }
          }},

          "1": { x: -1, y: -1, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: -1, width: 52, height: 26 },
            "2": { x: -1, y: 25, width: 52, height: 26 },
            "3": { x: -1, y: -1, width: 52, height: 26 }
          }},

          "2": { x: 25, y: -1, width: 26, height: 52,
          flips: {
            "1": { x: -1, y: -1, width: 26, height: 26 },
            "2": { x: 25, y: 25, width: 26, height: 26 },
            "3": { x: -1, y: -1, width: 26, height: 26 }
          }},
          "3": { x: -1, y: -1, width: 26, height: 52,
          flips: {
            "1": { x: 25, y: -1, width: 26, height: 52 },
            "2": { x: -1, y: -1, width: 26, height: 52 },
            "3": { x: 25, y: -1, width: 26, height: 52 }
          }}
        },
      }
    }
  } // transformations for collisions

So that we could later check for collision like this when the player is walking into the tile:

let newTileX;
let newTileY;

let newTileX;
    let newTileY;

    this.x = this.body.x;
    this.y = this.body.y;

    if (speedX != null) {
      newTileX = Math.floor((this.x + speedX) / 50);
    } else {
      newTileX = Math.floor(this.x / 50);
    }

    if (speedY != null) {
      newTileY = Math.floor((this.y + speedY) / 50);
    } else {
      newTileY = Math.floor(this.y / 50);
    }

    const tileName = Constants.TILEMAP[newTileY * 35 + newTileX];

    const rotation = tileName[3]; // Get rotation value for this tile
    const flip = tileName[4]; // Get flip value
    const collision = tileName[5]; // Get collision type

    let collisionShape = null;

    switch (parseInt(collision)) {
      case 0:
      case 1:
      case 2:
        // The entire tile is walkable
        collisionShape = null; // No need to define a collision shape for walkable tiles
        break;
      case 3:
      case 4:
        // The entire tile is an obstacle
        collisionShape = { x: -1, y: -1, width: 52, height: 52 };
        break;
      case 5:
      case 6:
      case 7:
      case 8:
        if (parseInt(flip) > 0) {
          collisionShape = Constants.colTransforms[collision].rotations[rotation].flips[flip];
          
        } else {
          collisionShape = Constants.colTransforms[collision].rotations[rotation];
        }
        break;
      case 9:
        if (parseInt(flip) > 0) {
          collisionShape = [];
          console.log(collision)
          collisionShape.push(Constants.colTransforms[collision].Lfirst.rotations[rotation].flips[flip]);
          collisionShape.push(Constants.colTransforms[collision].Lsecond.rotations[rotation].flips[flip]);
        } else {
          console.log(collision)
          collisionShape = [];
          collisionShape.push(Constants.colTransforms[collision].Lfirst.rotations[rotation]);
          collisionShape.push(Constants.colTransforms[collision].Lsecond.rotations[rotation]);
        }
        break
    }

    // Check for collision only if collision shape is defined
    if (collisionShape != null && !Array.isArray(collisionShape)) {
      // Calculate the player's local position within the tile
      const localPlayerX = (this.x + (speedX || 0)) % 50;
      const localPlayerY = (this.y + (speedY || 0)) % 50;

      // Calculate the center of the circular area (head radius)
      const playerCenterX = localPlayerX + Constants.playerCircle;
      const playerCenterY = localPlayerY + Constants.playerCircle;

      // Calculate the closest point on the rectangle to the circle
      const closestX = this.clamp(playerCenterX, collisionShape.x, collisionShape.x + collisionShape.width);
      const closestY = this.clamp(playerCenterY, collisionShape.y, collisionShape.y + collisionShape.height);

      // Calculate the distance between the circle's center and the closest point
      const distanceX = playerCenterX - closestX;
      const distanceY = playerCenterY - closestY;

      // Check if the distance is less than or equal to the circle's radius
      if ((distanceX * distanceX + distanceY * distanceY) <= (Constants.playerCircle * Constants.playerCircle)) {
          // Collision detected, player cannot move
          speedX = 0;
          speedY = 0;
      }
    } else if (collisionShape != null && Array.isArray(collisionShape)) {
      for (let shape of collisionShape) {
        // Calculate the player's local position within the tile
        const localPlayerX = (this.x + (speedX || 0)) % 50;
        const localPlayerY = (this.y + (speedY || 0)) % 50;

        // Calculate the center of the circular area (head radius)
        const playerCenterX = localPlayerX + Constants.playerCircle;
        const playerCenterY = localPlayerY + Constants.playerCircle;

        // Calculate the closest point on the rectangle to the circle
        const closestX = this.clamp(playerCenterX, shape.x, shape.x + shape.width);
        const closestY = this.clamp(playerCenterY, shape.y, shape.y + shape.height);

        // Calculate the distance between the circle's center and the closest point
        const distanceX = playerCenterX - closestX;
        const distanceY = playerCenterY - closestY;

        // Check if the distance is less than or equal to the circle's radius
        if ((distanceX * distanceX + distanceY * distanceY) <= (Constants.STICK_FIGURE_HEAD_RADIUS * Constants.STICK_FIGURE_HEAD_RADIUS)) {
            // Collision detected, player cannot move
            speedX = 0;
            speedY = 0;
        }
      }
    }

Its not working as expected though, and honestly the whole approach seems messy to say the least. Some shapes arent flipped or rotated properly (could be my calculations, but it also could be that the code is just doing wrong math). How to actually properly add collision shapes to the individual tiles and make sure they go through the same rotations and flips when necessary?

Linear time increase per promise when resolving concurrently using Promise.all?

I have a method that steps through a JSON payload to run computation on some data within that payload. When I create 100 promises of that method and then pass them into Promise.all(), it seems that for every promise that is resolved concurrently, the next one takes a bit longer to resolve.

This is not specific to my method, I have tried a similar thing with a public API that returns simple JSON data. In calling that API concurrently using Promise.all I observed the same thing… every consecutive promise would take longer and longer to resolve.

Here is a simple code snippet to illustrate what I’m doing:

const axios = require('axios');

const getSomeData = async () => {
  try {
    await axios.get('https://some.api.com');
  } catch (error) {
    console.log('Failed to get data...');
  }
};

const promiseList = [];

for (let i = 0; i < 100; i += 1) {
  promiseList.push(new Promise(async (res) => {
    const startTime = performance.now();
    await getSomeData();
    const stopTime = performance.now();
    console.log(`time taken by promise: ${stopTime - startTime}`);
    res();
  }));
}

const main = async () => {
  const startTime = performance.now();
  await Promise.all(promiseList);
  const stopTime = performance.now();
  console.log(`total time taken: ${stopTime - startTime}`);
};

main();

This of course is a pretty pointless method but I created it strictly to observe how promises behave when running concurrently and how much performance benefit there is versus running sequentially (it’s obviously a significant improvement).

My question is, what would cause every consecutive promise to take longer and longer until all the promises are resolved? In which case if I were to run this again, it would reset the time taken back to around 200ms for the first promise, before it started increasing again until the last promise would take around 600ms to resolve.

Is this just the nature of single threaded Javascript execution and concurrency? I am really interested in hearing a more in depth explanation of this phenomenon.

Recurring events in Fullcalendar version 6 using rrule

I’m trying to figure out recurring events in fullcalendar version 6 using rrule but it is not working. I need to do recurring events every week on specific days, every month, and yearly. I can’t figure out what is wrong neither is shows me any error in console.

  calendar = new Calendar(calendarEl, {
        // Event handlers
        eventClick: function(info) {
            var eventObj = info.event;
            populateAndShowModal(eventObj, eventObj.extendedProps.procedure);
        },
        eventReceive: function(info) {
            var eventObj = info.event;
            populateAndShowModal(eventObj, eventObj.title);
        },

        schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
        droppable: true,
        themeSystem: 'bootstrap5',
        displayEventTime: false,
        slotLabelInterval: {
            minutes: 30
        },
        slotMinTime: '08:00:00',
        slotMaxTime: '19:00:00',
        eventTimeFormat: {
            hour: '2-digit',
            minute: '2-digit',
            hour12: false,
        },
        height: "auto",
        initialView: 'resourceTimelineDay',
        aspectRatio: 1.5,
        headerToolbar: {
            left: 'prev,next,custom2',
            center: 'title',
            right: 'custom1,resourceTimelineDay,resourceTimelineWeek,resourceTimelineMonth'
        },
        editable: true,
        resourceAreaHeaderContent: 'Staff',
        resources: [{
            id: 6,
            title: "New",
        }, {
            id: 211,
            title: "Test",
        }],
        events: [{
            title: "SiH|97155|Processed|208|97155",
            duration: '39:00',
            resourceId: 211,
            assignedToName: "Matt Randall",
            procedure: "97155",
            color: "#00ffca",
            rrule: {
                freq: rrule.RRule.WEEKLY,
                interval: 2,
                byweekday: [rrule.RRule.WED],
                dtstart: '2024-03-13T08:00:00'
            }
        }]
    });
    calendar.render();

picture of calendar

CSP works with Next.js prod build but not on dev server

I’ve been trying to implement CSP on my Next.js application, and I’ve managed to get it working when running next build and next start, but for some reason when running next dev it doesn’t render at all, and the following error is logged on the console:

Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
    at ./node_modules/next/dist/compiled/@next/react-refresh-utils/dist/runtime.js (react-refresh.js?ts=1710261451116:30:26)

I tried following the suggestions in the Next.js docs as well as in this GitHub discussion of using a nonce, but I was only able to get it working by adding the header to next.config.js. I’m also using the page router, if that info is helpful. This is what the relevant parts of my next.config.js file looks like:

const cspHeader = `
  default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline' fonts.googleapis.com;
  font-src 'self' https:;
  img-src 'self' blob: data:;
  object-src 'none';
  connect-src 'self' https:;
  upgrade-insecure-requests;
`

module.exports = {
  ...
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: cspHeader.replace(/n/g, ''),
          },
        ],
      },
    ]
  },
}

Any idea on how to resolve this issue (preferably without modifying the CSP header just for local development)?