Javascript to PHP upload multiple files from a html table

I have created a table that a user can use to input multiple files (photos from a phone).
I generate the table rows using Javascript and x(below) starts at 1 and increments from there. This is the line that generates the javascript

<th class="col-xs-1"> <input class="form-control" type="file" id="myFileInput'+ x + '" accept="image/*;capture=camera"></th>'

When I submit these files for upload I send them via AJAX to a php file. This is what I use to create an array of files to send as POST parameters:

      for (var r = 1, n = table.rows.length; r < n; r++) {
        var myInput = document.getElementById("myFileInput" + r);
        fileArray.push(myInput.files[0]);
      }

I send the files array like this:

form.append('file', fileArray);

The problem is that the files array is POST’ed to the PHP file in the $_POST parameters and not the $_FILES array and I don’t know how to handle that.

Here is a minimum reproduceable example – User interface file (html)

<!DOCTYPE html>
<html>
<head>
<title>LIST of files to php</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>


</head>
<body>

  <!-- Container for padding https://www.w3schools.com/bootstrap5/bootstrap_containers.php -->
<div class="container p-5 my-5 border">

  <!-- Table of files -->

  <div class="container my-5">
    <table id="FileTable" class="table table-bordered table-striped table-hover">
      <thead>
        <tr>
        </tr>
      </thead>
    </table>
    <button onclick="addRow()" type="button" class="btn btn-primary">Add Another Photo Or File</button>    
  </div>

    <!-- Submit request -->
  <div class="container my-5">
    <button onclick="submitFiles()" type="button" class="btn btn-primary">Submit</button>    
  </div>

</div>

<script>
    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);

    function submitFiles() {

      //File(s)
      var table = document.getElementById('FileTable');

      var fileArray = [];
      for (var r = 1, n = table.rows.length; r < n; r++) {
        var myInput = document.getElementById("myFileInput" + r);
        fileArray.push(myInput.files[0]);
      }

      var form = new FormData(),
      xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (xhr.readyState == XMLHttpRequest.DONE) {
            alert('Submited');
        }
      }    

      form.append('file', fileArray);
      xhr.open('post', 'uploadMinReproduceable.php', true);
      xhr.send(form);

}

  function deleteRow(id)
  {
    document.getElementById(id).remove()
  }

  function addRow() {

    var table = document.getElementById("FileTable");
    // GET TOTAL NUMBER OF ROWS 
    var x =table.rows.length;
    var id = "row"+x;
    var row = table.insertRow(x);
    row.id=id;
    var cell1 = row.insertCell(0);
    var cell2 = row.insertCell(1);

    var cell1Inner = '          <th class="col-xs-1">  <input class="form-control" type="file" id="myFileInput'+ x + '" accept="image/*;capture=camera"></th>';
    cell1.innerHTML = cell1Inner;

    cell2.classList.add("text-center");
    cell2.classList.add("py-1");
    cell2.innerHTML = '<button class="btn btn-outline-primary btn-xs" onclick="deleteRow('' + id + '')" >Delete</button>';
  }

  //Initialise the table with a row
  addRow();  

</script>

</body>
</html>

The PHP file:

<?php
 
header('Content-Type: application/json; charset=utf-8');

error_log('post params for upload file ' . $_SERVER['SCRIPT_NAME']);
foreach ($_POST as $key => $value) {
    error_log(' key: '. $key .' value: '. $value);
}

error_log('files params for upload file ' . $_SERVER['SCRIPT_NAME']);
foreach ($_FILES as $key => $value) {
    error_log(' Files key: '. $key .' Files value: '. $value);
}

$dsID = !empty($_POST['id_Job'])?$_POST['id_Job']:'0';
$locationvalue = '';
if (isset($_POST['Location']) && $_POST['Location'] !== '') {
    $locationvalue = $_POST['Location'] ;
}

?>

The result in the error log (note that the $_FILES array is empty):

[26-Feb-2025 17:33:14 Australia/Sydney] post params for upload file /uploadMinReproduceable.php
[26-Feb-2025 17:33:14 Australia/Sydney]  key: file value: [object File]
[26-Feb-2025 17:33:14 Australia/Sydney] files params for upload file /uploadMinReproduceable.php

The expected result in the error log for the $_FILES array:

[26-Feb-2025 16:16:14 Australia/Adelaide] files params for upload file /FileTest/uploadFile.php
[26-Feb-2025 16:16:14 Australia/Adelaide]  Files key: file Files value: Array

FFmpeg WASM writeFile Stalls and Doesn’t Complete in React App with Ant Design

I’m using FFmpeg WebAssembly (WASM) in a React app to process and convert a video file before uploading it. The goal is to resize the video to 720p using FFmpeg before sending it to the backend.

Problem:

Everything works up to fetching the file and confirming it’s loaded into memory, but FFmpeg hangs at ffmpeg.writeFile() and does not proceed further. No errors are thrown.

Code Snippet:

  • Loading FFmpeg

     const loadFFmpeg = async () => {
     if (loaded) return; // Avoid reloading if 
     already loaded
    
     const baseURL = 'https://unpkg.com/@ffmpeg/[email protected]/dist/umd';
     const ffmpeg = ffmpegRef.current;
     ffmpeg.on('log', ({ message }) => {
         messageRef.current.innerHTML = message;
         console.log(message);
     });
     await ffmpeg.load({
         coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
         wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
     });
     setLoaded(true);
     };
    
     useEffect(() => {
     loadFFmpeg()
     }, [])
    
  • Fetching and Writing File

      const convertVideoTo720p = async (videoFile) => {
           console.log("Starting video 
         conversion...");
    
    
    
     const { height } = await getVideoMetadata(videoFile);
     console.log(`Video height: ${height}`);
    
     if (height <= 720) {
         console.log("No conversion needed.");
         return videoFile;
     }
    
     const ffmpeg = ffmpegRef.current;
     console.log("FFmpeg instance loaded. Writing file to memory...");
    
     const fetchedFile = await fetchFile(videoFile);
     console.log("File fetched successfully:", fetchedFile);
    
     console.log("Checking FFmpeg memory before writing...");
     console.log(`File size: ${fetchedFile.length} bytes (~${(fetchedFile.length / 1024 / 1024).toFixed(2)} MB)`);
    
     if (!ffmpeg.isLoaded()) {
         console.error("FFmpeg is not fully loaded yet!");
         return;
     }
    
     console.log("Memory seems okay. Writing file to FFmpeg...");
     await ffmpeg.writeFile('input.mp4', fetchedFile);  // ❌ This line hangs, nothing after runs
     console.log("File successfully written to FFmpeg memory.");
          };
    

Debugging Steps I’ve Tried:

  • Ensured FFmpeg is fully loaded before calling writeFile()
    ffmpeg.isLoaded() returns true.
  • Checked file fetch process:
    fetchFile(videoFile) successfully returns a Uint8Array.
  • Tried renaming the file to prevent caching issues
    ✅ Used a unique file name like video_${Date.now()}.mp4, but no change
  • Checked browser console for errors:
    ❌ No errors are displayed.
  • Tried skipping FFmpeg and uploading the raw file instead:
    ✅ Upload works fine without FFmpeg, so the issue is specific to FFmpeg.

Expected Behavior

  • ffmpeg.writeFile('input.mp4', fetchedFile); should complete and allow FFmpeg to process the video.

Actual Behavior

  • Execution stops at writeFile, and no errors are thrown.

Environment:

  • React: 18.x
  • FFmpeg WASM Version: @ffmpeg/[email protected]
  • Browser: Chrome 121, Edge 120
  • Operating System: Windows 11

Question:
Why is FFmpeg’s writeFile() stalling and never completing?
How can I fix or further debug this issue?

Here is my full code:

import { useNavigate } from "react-router-dom";
import { useEffect, useRef, useState } from 'react';
import { Form, Input, Button, Select, Space } from 'antd';
const { Option } = Select;
import { FaAngleLeft } from "react-icons/fa6";
import { message, Upload } from 'antd';
import { CiCamera } from "react-icons/ci";
import { IoVideocamOutline } from "react-icons/io5";
import { useCreateWorkoutVideoMutation } from "../../../redux/features/workoutVideo/workoutVideoApi";
import { convertVideoTo720p } from "../../../utils/ffmpegHelper";
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';


const AddWorkoutVideo = () => {
    const [videoFile, setVideoFile] = useState(null);
    const [imageFile, setImageFile] = useState(null);
    const [loaded, setLoaded] = useState(false);
    const ffmpegRef = useRef(new FFmpeg());
    const videoRef = useRef(null);
    const messageRef = useRef(null);
    const [form] = Form.useForm();
    const [createWorkoutVideo, { isLoading }] = useCreateWorkoutVideoMutation()
    const navigate = useNavigate();

    const videoFileRef = useRef(null); // Use a ref instead of state


    // Handle Video Upload
    const handleVideoChange = ({ file }) => {
        setVideoFile(file.originFileObj);
    };

    // Handle Image Upload
    const handleImageChange = ({ file }) => {
        setImageFile(file.originFileObj);
    };

    // Load FFmpeg core if needed (optional if you want to preload)
    const loadFFmpeg = async () => {
        if (loaded) return; // Avoid reloading if already loaded

        const baseURL = 'https://unpkg.com/@ffmpeg/[email protected]/dist/umd';
        const ffmpeg = ffmpegRef.current;
        ffmpeg.on('log', ({ message }) => {
            messageRef.current.innerHTML = message;
            console.log(message);
        });
        await ffmpeg.load({
            coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
            wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
        });
        setLoaded(true);
    };

    useEffect(() => {
        loadFFmpeg()
    }, [])

    // Helper: Get video metadata (width and height)
    const getVideoMetadata = (file) => {
        return new Promise((resolve, reject) => {
            const video = document.createElement('video');
            video.preload = 'metadata';
            video.onloadedmetadata = () => {
                resolve({ width: video.videoWidth, height: video.videoHeight });
            };
            video.onerror = () => reject(new Error('Could not load video metadata'));
            video.src = URL.createObjectURL(file);
        });
    };

    // Inline conversion helper function
    // const convertVideoTo720p = async (videoFile) => {
    //     // Check the video resolution first
    //     const { height } = await getVideoMetadata(videoFile);
    //     if (height <= 720) {
    //         // No conversion needed
    //         return videoFile;
    //     }
    //     const ffmpeg = ffmpegRef.current;
    //     // Load ffmpeg if not already loaded
    //     // await ffmpeg.load({
    //     //     coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
    //     //     wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
    //     // });
    //     // Write the input file to the ffmpeg virtual FS
    //     await ffmpeg.writeFile('input.mp4', await fetchFile(videoFile));
    //     // Convert video to 720p (scale filter maintains aspect ratio)
    //     await ffmpeg.exec(['-i', 'input.mp4', '-vf', 'scale=-1:720', 'output.mp4']);
    //     // Read the output file
    //     const data = await ffmpeg.readFile('output.mp4');
    //     console.log(data, 'data from convertVideoTo720p');
    //     const videoBlob = new Blob([data.buffer], { type: 'video/mp4' });
    //     return new File([videoBlob], 'output.mp4', { type: 'video/mp4' });
    // };
    const convertVideoTo720p = async (videoFile) => {
        console.log("Starting video conversion...");

        // Check the video resolution first
        const { height } = await getVideoMetadata(videoFile);
        console.log(`Video height: ${height}`);

        if (height <= 720) {
            console.log("No conversion needed. Returning original file.");
            return videoFile;
        }

        const ffmpeg = ffmpegRef.current;
        console.log("FFmpeg instance loaded. Writing file to memory...");

        // await ffmpeg.writeFile('input.mp4', await fetchFile(videoFile));
        // console.log("File written. Starting conversion...");
        console.log("Fetching file for FFmpeg:", videoFile);
        const fetchedFile = await fetchFile(videoFile);
        console.log("File fetched successfully:", fetchedFile);
        console.log("Checking FFmpeg memory before writing...");
        console.log(`File size: ${fetchedFile.length} bytes (~${(fetchedFile.length / 1024 / 1024).toFixed(2)} MB)`);

        if (fetchedFile.length > 50 * 1024 * 1024) { // 50MB limit
            console.error("File is too large for FFmpeg WebAssembly!");
            message.error("File too large. Try a smaller video.");
            return;
        }

        console.log("Memory seems okay. Writing file to FFmpeg...");
        const fileName = `video_${Date.now()}.mp4`; // Generate a unique name
        console.log(`Using filename: ${fileName}`);

        await ffmpeg.writeFile(fileName, fetchedFile);
        console.log(`File successfully written to FFmpeg memory as ${fileName}.`);

        await ffmpeg.exec(['-i', 'input.mp4', '-vf', 'scale=-1:720', 'output.mp4']);
        console.log("Conversion completed. Reading output file...");

        const data = await ffmpeg.readFile('output.mp4');
        console.log("File read successful. Creating new File object.");

        const videoBlob = new Blob([data.buffer], { type: 'video/mp4' });
        const convertedFile = new File([videoBlob], 'output.mp4', { type: 'video/mp4' });

        console.log(convertedFile, "converted video from convertVideoTo720p");

        return convertedFile;
    };


    const onFinish = async (values) => {
        // Ensure a video is selected
        if (!videoFileRef.current) {
            message.error("Please select a video file.");
            return;
        }

        // Create FormData
        const formData = new FormData();
        if (imageFile) {
            formData.append("image", imageFile);
        }

        try {
            message.info("Processing video. Please wait...");

            // Convert the video to 720p only if needed
            const convertedVideo = await convertVideoTo720p(videoFileRef.current);
            console.log(convertedVideo, 'convertedVideo from onFinish');

            formData.append("media", videoFileRef.current);

            formData.append("data", JSON.stringify(values));

            // Upload manually to the backend
            const response = await createWorkoutVideo(formData).unwrap();
            console.log(response, 'response from add video');

            message.success("Video added successfully!");
            form.resetFields(); // Reset form
            setVideoFile(null); // Clear file

        } catch (error) {
            message.error(error.data?.message || "Failed to add video.");
        }

        // if (videoFile) {
        //     message.info("Processing video. Please wait...");
        //     try {
        //         // Convert the video to 720p only if needed
        //         const convertedVideo = await convertVideoTo720p(videoFile);
        //         formData.append("media", convertedVideo);
        //     } catch (conversionError) {
        //         message.error("Video conversion failed.");
        //         return;
        //     }
        // }
        // formData.append("data", JSON.stringify(values)); // Convert text fields to JSON

        // try {
        //     const response = await createWorkoutVideo(formData).unwrap();
        //     console.log(response, 'response from add video');

        //     message.success("Video added successfully!");
        //     form.resetFields(); // Reset form
        //     setFile(null); // Clear file
        // } catch (error) {
        //     message.error(error.data?.message || "Failed to add video.");
        // }
    };

    const handleBackButtonClick = () => {
        navigate(-1); // This takes the user back to the previous page
    };

    const videoUploadProps = {
        name: 'video',
        // action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
        // headers: {
        //     authorization: 'authorization-text',
        // },
        // beforeUpload: (file) => {
        //     const isVideo = file.type.startsWith('video/');
        //     if (!isVideo) {
        //         message.error('You can only upload video files!');
        //     }
        //     return isVideo;
        // },
        // onChange(info) {
        //     if (info.file.status === 'done') {
        //         message.success(`${info.file.name} video uploaded successfully`);
        //     } else if (info.file.status === 'error') {
        //         message.error(`${info.file.name} video upload failed.`);
        //     }
        // },
        beforeUpload: (file) => {
            const isVideo = file.type.startsWith('video/');
            if (!isVideo) {
                message.error('You can only upload video files!');
                return Upload.LIST_IGNORE; // Prevents the file from being added to the list
            }
            videoFileRef.current = file; // Store file in ref
            // setVideoFile(file); // Store the file in state instead of uploading it automatically
            return false; // Prevent auto-upload
        },
    };

    const imageUploadProps = {
        name: 'image',
        action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
        headers: {
            authorization: 'authorization-text',
        },
        beforeUpload: (file) => {
            const isImage = file.type.startsWith('image/');
            if (!isImage) {
                message.error('You can only upload image files!');
            }
            return isImage;
        },
        onChange(info) {
            if (info.file.status === 'done') {
                message.success(`${info.file.name} image uploaded successfully`);
            } else if (info.file.status === 'error') {
                message.error(`${info.file.name} image upload failed.`);
            }
        },
    };
    return (
        <>
            <div className="flex items-center gap-2 text-xl cursor-pointer" onClick={handleBackButtonClick}>
                <FaAngleLeft />
                <h1 className="font-semibold">Add Video</h1>
            </div>
            <div className="rounded-lg py-4 border-[#79CDFF] border-2 shadow-lg mt-8 bg-white">
                <div className="space-y-[24px] min-h-[83vh] bg-light-gray rounded-2xl">
                    <h3 className="text-2xl text-[#174C6B] mb-4 border-b border-[#79CDFF]/50 pb-3 pl-16 font-semibold">
                        Adding Video
                    </h3>
                    <div className="w-full px-16">
                        <Form
                            form={form}
                            layout="vertical"
                            onFinish={onFinish}
                        // style={{ maxWidth: 600, margin: '0 auto' }}
                        >
                            {/* Section 1 */}
                            {/* <Space direction="vertical" style={{ width: '100%' }}> */}
                            {/* <Space size="large" direction="horizontal" className="responsive-space"> */}
                            <div className="grid grid-cols-2 gap-8 mt-8">
                                <div>
                                    <Space size="large" direction="horizontal" className="responsive-space-section-2">

                                        {/* Video */}
                                        <Form.Item
                                            label={<span style={{ fontSize: '18px', fontWeight: '600', color: '#2D2D2D' }}>Upload Video</span>}
                                            name="media"
                                            className="responsive-form-item"
                                        // rules={[{ required: true, message: 'Please enter the package amount!' }]}
                                        >
                                            <Upload {...videoUploadProps} onChange={handleVideoChange} maxCount={1}>
                                                <Button style={{ width: '440px', height: '40px', border: '1px solid #79CDFF', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                                                    <span style={{ color: '#525252', fontSize: '16px', fontWeight: 600 }}>Select a video</span>
                                                    <IoVideocamOutline size={20} color="#174C6B" />
                                                </Button>
                                            </Upload>
                                        </Form.Item>

                                        {/* Thumbnail */}
                                        <Form.Item
                                            label={<span style={{ fontSize: '18px', fontWeight: '600', color: '#2D2D2D' }}>Upload Image</span>}
                                            name="image"
                                            className="responsive-form-item"
                                        // rules={[{ required: true, message: 'Please enter the package amount!' }]}
                                        >
                                            <Upload {...imageUploadProps} onChange={handleImageChange} maxCount={1}>
                                                <Button style={{ width: '440px', height: '40px', border: '1px solid #79CDFF', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                                                    <span style={{ color: '#525252', fontSize: '16px', fontWeight: 600 }}>Select an image</span>
                                                    <CiCamera size={25} color="#174C6B" />
                                                </Button>
                                            </Upload>
                                        </Form.Item>

                                        {/* Title */}
                                        <Form.Item
                                            label={<span style={{ fontSize: '18px', fontWeight: '600', color: '#2D2D2D' }}>Video Title</span>}
                                            name="name"
                                            className="responsive-form-item-section-2"
                                        >
                                            <Input type="text" placeholder="Enter video title" style={{
                                                height: '40px',
                                                border: '1px solid #79CDFF',
                                                fontSize: '16px',
                                                fontWeight: 600,
                                                color: '#525252',
                                                display: 'flex',
                                                alignItems: 'center',
                                                justifyContent: 'space-between',
                                            }} />
                                        </Form.Item>
                                    </Space>
                                </div>
                            </div>

                            {/* </Space> */}
                            {/* </Space> */}


                            {/* Submit Button */}
                            <Form.Item>
                                <div className="p-4 mt-10 text-center mx-auto flex items-center justify-center">
                                    <button
                                        className="w-[500px] bg-[#174C6B] text-white px-10 h-[45px] flex items-center justify-center gap-3 text-lg outline-none rounded-md "
                                    >
                                        <span className="text-white font-semibold">{isLoading ? 'Uploading...' : 'Upload'}</span>
                                    </button>
                                </div>
                            </Form.Item>
                        </Form>
                    </div>
                </div>
            </div>
        </>
    )
}

export default AddWorkoutVideo

Would appreciate any insights or suggestions. Thanks!

Recursive Divide-and-Conquer to Split Array into Valid HH:MM Time Formats

I need to write a recursive function in JavaScript that determines how many ways a given array of digits (0-9) can be split into four contiguous subarrays representing a valid HH:MM time format. The splitting must follow a divide-and-conquer approach:

  1. First Split: Divide the array into two non-empty contiguous parts
    (left and right).
  2. Recursive Splits: Further split each of these parts into two
    non-empty contiguous subparts, resulting in a total of four
    subarrays.
  3. Validation: Each final split must form a valid time where HH is
    between 00-23 and MM is between 00-59

Example:

  • Input: [1, 2, 3, 4]

  • Possible splits:

    First split at index 1: [1], [2, 3, 4] → Split left (no further
    splits possible) and right into [2], [3, 4]. Result: 01:23 (invalid,
    since 23 as MM is valid but 01 as HH is okay. Wait, need to compute
    actual valid combinations).

  • Output: The function should return the total count of valid HH:MM
    combinations from all possible recursive splits.

Constraints:

  • The input array must have at least 4 elements.
  • Splits must be contiguous and non-empty at every step.
  • The recursion must follow the divide-and-conquer methodology (split
    into two parts at each step).

Challenge:

  • How to efficiently track split positions and validate time
    constraints without redundant checks?
  • Ensuring the recursion correctly explores all valid split paths and
    combines results.

Code Attempt:


function countValidTimes(arr) {
  // Base case: arr must allow splitting into 4 parts via two splits
  if (arr.length < 4) return 0;

  // How to structure recursion here?
  // Split into left and right, then recursively split each
  let total = 0;
  for (let i = 1; i < arr.length; i++) {
    const left = arr.slice(0, i);
    const right = arr.slice(i);
    // Now split left and right into two parts each
    total += splitAndCount(left) * splitAndCount(right);
  }
  return total;
}

// Helper function to count splits for a subarray
function splitAndCount(subArr) {
  if (subArr.length < 2) return 0;
  let count = 0;
  for (let j = 1; j < subArr.length; j++) {
    const part1 = subArr.slice(0, j);
    const part2 = subArr.slice(j);
    // Validate if part1 and part2 can form valid HH or MM
    // Need to check which part of the time they represent (HH1, HH2, MM1, MM2)
    // How to track whether we're in the left or right part of the initial split?
  }
  return count;
}


Problem:
The current approach doesn’t track whether a sub-split is part of the HH or MM segments, leading to incorrect validations. How can I recursively manage the context of each split (i.e., whether it contributes to hours or minutes) and aggregate valid combinations correctly?

Specific Issues:

  • How to propagate the position (HH1, HH2, MM1, MM2) through recursive calls.
  • Avoiding counting splits that don’t result in exactly four parts.
  • Efficiently validating the concatenated digits as valid time parts without converting to strings multiple times.

This problem requires a nuanced recursive strategy with context tracking. How can this be implemented effectively in JavaScript?

Puppeteer Error : Could not find Chrome (ver. 131.0.6778.204)

I am using puppeteer to generate pdf. Also i am using selenium for zerodha automation. i need to configure the docker file to work fine for both selenium and puppeteer.

This is docker file

FROM node:16-alpine

# Set the working directory
WORKDIR /app

# Install dependencies
RUN apk update && apk add --no-cache 
    chromium 
    chromium-chromedriver 
    bash 
    curl 
    wget 
    unzip 
    make 
    g++ 
    && npm install -g selenium-webdriver

# Set environment variables
ENV CHROME_BIN=/usr/bin/chromium-browser
ENV CHROMEDRIVER_PATH=/usr/bin/chromedriver
ENV PUPPETEER_SKIP_DOWNLOAD=true

# Copy application code
COPY package*.json ./
RUN npm install
COPY . .

# Expose the port
EXPOSE 8004

# Start the server
CMD ["node", "server.js"]

the code i use to generate pdf

      const browser = await puppeteer.launch({ headless: "new",
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
       });
      const page = await browser.newPage();
      await page.setContent(req.query.html, { waitUntil: 'domcontentloaded' });
      await page.emulateMediaType('screen');
      await page.waitForSelector('body', { visible: true });
      await delay(2000);
      const pdf = await page.pdf({
        path: 'result.pdf',
        margin: { top: '100px', right: '50px', bottom: '100px', left: '50px' },
        printBackground: true,
        format: 'A4',
      });
      await browser.close();

in AWS EC2 i am getting the below error

 message: 'Could not find Chrome (ver. 131.0.6778.204). This can occur if eithern' +
    ' 1. you did not perform an installation before running the script (e.g. npx puppeteer browsers install chrome) orn' +
    ' 2. your cache path is incorrectly configured (which is: /root/.cache/puppeteer).n' +
    'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.',
  data: undefined

Kindly help me to fix this error.

real life question, find the nearest room number

Sample

const roomsByFloor = {
    "5": [
        { "floor": 5, "room": 2, "type": "A" },
        { "floor": 5, "room": 3, "type": "A" },
        { "floor": 5, "room": 4, "type": "A" },
        { "floor": 5, "room": 5, "type": "B" },
        { "floor": 5, "room": 6, "type": "B" },
        { "floor": 5, "room": 43, "type": "B" },
        { "floor": 5, "room": 57, "type": "B" },
        { "floor": 5, "room": 58, "type": "A" },
        { "floor": 5, "room": 70, "type": "C" },
        { "floor": 5, "room": 72, "type": "C" }
    ]
};

given
console.log(getNearestRooms("2A,2B,2C")); // the expect answer is 4 43 57 58 70 72
This means selecting 2 rooms from category A, 2 rooms from category B, and 2 rooms from category C.

restriction:

  1. Selected rooms must be closest to rooms from different categories
  • example, 2A could be room 2 3 4 58 and the shortest distance walking from Room A to B and C is number 4 and 58 (Room 58 previous room is Room 57, and walking 12 additional rooms can reach room 70)
  1. The number of selected rooms must match, “2A,2B,2C” output is 6 rooms
  2. No hardcode. More rooms category can be added for testing. Query can be edited like “2A,1B,2C”
  3. For input “2A,2B”, the result should be 3, 4, 5, 6
  4. For input “2B,2C”, the result should be 43, 57, 70, 72
console.log(getNearestRooms("2A,2B,2C")); // Expected output: [4, 43, 57, 58, 70, 72]
console.log(getNearestRooms("2A,2B"));    // Expected output: [3, 4, 5, 6]
console.log(getNearestRooms("2B,2C"));    // Expected output: [43, 57, 70, 72]
console.log(getNearestRooms("1A,1B,1C")); // Expected output: [58, 57, 70]

Initially, I thought this could be solved using the Sliding Window Technique, but it turned out to be harder than expected, and I failed to solve it. What other technique can be applied?

const roomsByFloor = {
    "5": [
        { "floor": 5, "room": 2, "type": "A" },
        { "floor": 5, "room": 3, "type": "A" },
        { "floor": 5, "room": 4, "type": "A" },
        { "floor": 5, "room": 5, "type": "B" },
        { "floor": 5, "room": 6, "type": "B" },
        { "floor": 5, "room": 43, "type": "B" },
        { "floor": 5, "room": 57, "type": "B" },
        { "floor": 5, "room": 58, "type": "A" },
        { "floor": 5, "room": 70, "type": "C" },
        { "floor": 5, "room": 72, "type": "C" }
    ]
};

function getNearestRooms(request) {
    // Parse request: Extract number & type of each required room
    let roomRequests = request.split(',').map(category => ({
        count: parseInt(category.match(/d+/)[0]),  // "2A" → 2
        type: category.match(/[a-zA-Z]+/)[0].toUpperCase()  // "2A" → "A"
    }));

    let result = [];

    for (let floor in roomsByFloor) {
        let floorRooms = roomsByFloor[floor];  // Get all rooms on this floor
        let counts = {};  // Frequency map for room types in the current window
        let left = 0, right = 0;
        let closestDistance = Infinity;
        let floorResult = [];

        // Expand the window with right pointer
        while (right < floorRooms.length) {
            let rightRoom = floorRooms[right];
            counts[rightRoom.type] = (counts[rightRoom.type] || 0) + 1;
            right++;

            // Try to shrink the window while still satisfying conditions
            while (roomRequests.every(req => counts[req.type] >= req.count)) {
                let currentDistance = floorRooms[right - 1].room - floorRooms[left].room;

                // Update the best result if this subset is smaller
                if (currentDistance < closestDistance) {
                    closestDistance = currentDistance;
                    floorResult = floorRooms.slice(left, right).map(r => r.floor * 100 + r.room);
                }

                // Remove leftmost room from window and shift `left`
                let leftRoom = floorRooms[left];
                counts[leftRoom.type]--;
                if (counts[leftRoom.type] === 0) delete counts[leftRoom.type];
                left++;
            }
        }

        if (floorResult.length) {
            result = floorResult;
        }
    }

    return result;
}

console.log(getNearestRooms("2A,2B,2C"));  

Why do I get a “The parameters (RangeApiAdapter,null,Boolean) don’t match the method signature for SpreadsheetApp.Range.copyTo.” error

I’m trying to copy a range and then paste values to the cells 2 away from it.

What am I doing wrong?

function NewQ2() {
  var spreadsheet = SpreadsheetApp.getActive();
  spreadsheet.getRange('J2').activate();
  spreadsheet.getCurrentCell().setValue('0');
  spreadsheet.getRange('J3').activate();
  spreadsheet.getCurrentCell().setValue('0');
  spreadsheet.getRange('F14:G21').activate();
  spreadsheet.getRange('D14:E21').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
  spreadsheet.getRange('C25:C39').activate();
  spreadsheet.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
  spreadsheet.getRange('C25').activate();
};

I’m not a js user – I use R normally. I’ve only created this by recording it, so I’m somewhat lost.

I’ve tried searching for answers, but they all seem to be from people who haven’t selected a range. I’ve tried doing that, but it still hasn’t worked.

What’s the benefit to using execute over query in mysql2?

When using the NodeJS mysql2 library, I am trying to understand the differences between Connection.execute and Connection.query.

As I understand it, query prepares the statement locally and then makes one call to the database. execute sends the query without parameters to the database to be prepared then sends the parameters separately to be executed with the prepared statement.

Where my understanding really falls apart is how or if the statement gets cached when using Connection.execute. Does calling Connection.execute twice in a row with the same query have any benefit?

In function B1 below, the statement gets re-used, which gives a performance improvement.
However in function B2, does the statement get re-used, or are there four separate network calls being made to the database? In the case there is no re-use of the statement in B2, would B3 be the fastest option?

async function B1(connection) {
  const statement = await connection.prepare("SELECT 1 + ? + ?");
  const result1 = await statement.execute([1, 2]);
  const result2 = await statement.execute([3, 4]);
  await statement.close();
  return [result1, result2];
}

async function B2(connection) {
  const result1 = await connection.execute("SELECT 1 + ? + ?", [1, 2]);
  const result2 = await connection.execute("SELECT 1 + ? + ?", [3, 4]);
  return [result1, result2];
}

async function B3(connection) {
  const result1 = await connection.query("SELECT 1 + ? + ?", [1, 2]);
  const result2 = await connection.query("SELECT 1 + ? + ?", [3, 4]);
  return [result1, result2];
}

Grafana Business Echart change bar color based on value

Trying to build a bar chart in Business Charts. I want this chart to be one color if below RUN and another when above. Below is my code

let AVG_RATE_PER_HOUR = [];
let FullName = [];
let RUN;
let RUN_PLUS;

context.panel.data.series.map((s) => {
  if (s.refId === "AVG_RATE") {
    AVG_RATE_PER_HOUR = s.fields.find((f) => f.name === 'AVG_RATE_PER_HOUR').values;
    FullName = s.fields.find((f) => f.name === 'FullName').values;
  } else if (s.refId === "WO_INFO") {
    RUN = s.fields.find((f) => f.name === 'RUN').values;
    RUN_PLUS = s.fields.find((f) => f.name === 'RUN_PLUS').values;
  }
});

return {
  xAxis: {
    type: 'category',
    data: FullName
  },
  yAxis: {
    type: 'value'
  },
  visualMap: [{
    pieces: [{
      gte: 5,
      label: ">= " + RUN,
      color: "green"
    }, {
      lt: 5,
      gt: 0,
      label: "< " + RUN,
      color: "red"
    }],
  }],
  series: [
    {
      data: AVG_RATE_PER_HOUR,
      label: {
        show: true,
        position: 'top',
        formatter: function (data) {
          return (data.value.toFixed(2))
        }
      },
      type: 'bar'
    }
  ]
};

When I try to use the value of RUN for the gte or the lt I get an error

ECharts Execution Error
e.indexOf is not a function

but it works just fine in the lable:

enter image description here

Do I need to delcare RUN as a number?

how to dynamically create materialize ui slider element

I am importing imgur images and trying to handle cases where there are multiple images in a single post by using materialize UI sliders. However, my implementation resulted in an empty card being displayed. this is the loop I am using:

if(img.images_count >= 1){
                        let slider = document.createElement("div");
                        slider.classList.add("slider");
                        let list = document.createElement("ul");
                        list.classList.add("slides");
                        img.images.forEach(imgList =>
                        {
                            let listElem = document.createElement("li");
                            let imgLink = document.createElement("img");
                            imgLink.classList.add("materialboxed");
                            imgLink.style.width = "100%";
                            imgLink.style.height = "auto";
                            imgLink.style.display = "block";
                            imgLink.style.maxWidth = "100%";
                            imgLink.src = imgList.link;
                            listElem.appendChild(imgLink);
                            list.appendChild(listElem);
                        });
                        let card_content = document.createElement("div");
                        card_content.classList.add("card-content");
                        slider.appendChild(list);
                        card.appendChild(slider);
                        card.appendChild(card_content);
                        col.appendChild(card)
                        imgDiv.appendChild(col);

                    }

similar approach works for 1 image. Thanks!

How does the (Edge) browser’s DevTools console determine the constructor’s name although not present?

I’ve executed the following code in the Microsoft Edge (Version 133.0.3065.69 (Official build) (64-bit)) DevTools console. As you can see, I set the constructor to undefined and construct an object stored in newFoo.
enter image description here
When I inspect the result of newFoo, there is no constructor info anymore, but the console still shows the constructor.name as foo.

How is this possible? Can I also retrieve this information as string from the variable newFoo using JavaScript?

How to use embind with a String class that isn’t std::string?

Emscripten’s embind mechanism seems to have pretty nice support for converting JavaScript’s native strings to and from C++’s std::string automatically; however, the C++ library that I’m trying to create JavaScript bindings for has its own String class that it uses instead of std::string. Short of rewriting the C++ library to use std::string, is there any way to get embind to convert JavaScript strings to this library’s custom String class the same way it handles conversions to/from std::string ?

I think it should be possible by declaring an appropriate BindingType template-struct that contains the necessary conversion code, but I haven’t had any luck getting it to work.

A minimal, self-contained example of what I’ve tried so far is listed below; when I load the index.html page, Safari gives me the error Unhandled Promise Rejection: BindingError: Cannot pass "ABC" as a ContrivedExampleString. The behavior I’d like to see instead is that it prints XXX ABC to the JavaScript console.

#include <cstdlib>
#include <string>
#include <emscripten/bind.h>
#include <emscripten/wire.h>

namespace SomeLibrary {

/** Some library's own String class, oversimplified here for illustration purposes only */
class ContrivedExampleString
{
public:
   ContrivedExampleString()                   : _str(NULL)      {/* empty */}
   ContrivedExampleString(const char * s)     : _str(strdup(s)) {/* empty */}
   ContrivedExampleString(const ContrivedExampleString & rhs) : _str(NULL)      {*this = rhs;}

   ~ContrivedExampleString() {if (_str) free(_str);}

   ContrivedExampleString & operator = (const ContrivedExampleString & rhs)
   {
      if (&rhs != this)
      {
         if (_str) free(_str);
         _str = rhs._str ? strdup(rhs._str) : NULL;
      }
      return *this;
   }

   const char * CStr() const {return _str ? _str : "";}

   size_t Length() const {return _str ? strlen(_str) : 0;}

private:
   char * _str;
};

/** Contrived example class */
class ContrivedExampleClass
{
public:
   ContrivedExampleClass() {/* empty */}

   ContrivedExampleString ContrivedExampleFunction(const ContrivedExampleString & s) const
   {
      return s;
   }
};

};  // end SomeLibrary namespace

using namespace emscripten;
using namespace SomeLibrary;

// Tell embind how to autoconvert ContrivedExampleString <-> JavaScript string
template <> struct emscripten::internal::BindingType<ContrivedExampleString>
{
    typedef struct
    {
        size_t length;
        char data[]; // trailing data
    } * WireType;

    static WireType toWireType(const std::string & v, rvp::default_tag)
    {
        WireType wt = (WireType) malloc(sizeof(size_t) + v.length() +1);
        wt->length = v.length();
        memcpy(wt->data, v.c_str(), v.length()+1);
        return wt;
    }

    static WireType toWireType(const ContrivedExampleString & v, rvp::default_tag)
    {
        WireType wt = (WireType) malloc(sizeof(size_t) + v.Length() +1);
        wt->length = v.Length();
        memcpy(wt->data, v.CStr(), v.Length()+1);
        return wt;
    }

    static ContrivedExampleString fromWireType(WireType v) {
        //return std::string(v->data, v->length);
        return ContrivedExampleString(v->data);
    }
};

EMSCRIPTEN_BINDINGS(ContrivedExampleClass) {
   class_<ContrivedExampleClass>("ContrivedExampleClass")
      .constructor<>()
      .function("ContrivedExampleFunction", &ContrivedExampleClass::ContrivedExampleFunction);
}

FWIW I compile the above code with this command: emcc hello.cpp -o hello.js -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web -lembind -lwebsocket.js

… and here is the corresponding index.html code that I use to test passing and returning a JavaScript string to ContrivedExampleClass::ContrivedExampleFunction(const ContrivedExampleString &):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Emscripten</title>
</head>
<body>
    <h1>Emscripten C++ to JavaScript</h1>
    <script type="module">
        import createModule from "./hello.js";

        createModule().then((Module) => {
            const ContrivedExampleClass = Module.ContrivedExampleClass;
            const cec = new ContrivedExampleClass;

            var s = cec.ContrivedExampleFunction("ABC")
            console.log("XXX ", s);   // should print "XXX ABC"
        });
    </script>
</body>
</html>

Elementor: Fold-up Menu should adjust current width of Navbar (shrinking navbar)

I have a navbar which shrinks on scroll, on mobile (burgermenu) I want that the open menu to be the same size as the navbar in it’s normal or shrinked state, it should always adjust to the curent width of the navbar. I’m working with Elementor Pro btw.

The code I have right now:

Code in Navbar Section:

#main-header {
    width: 100%;
    transition: width 0.3s ease;
    margin: 0 auto;
    padding: 0;
}

#nav-container {
    width: 100%;
    transition: width 0.3s ease, background-color 0.3s ease;
    background-color: transparent; 
    padding: 0;
    border-radius: 50px; 
}

#main-header.shrink-header #nav-container {
    width: 60%;
    background-color: rgba(217, 217, 217, 0.6); 
    padding: 0% 1% 0% 1%;
}

@media screen and (max-width: 767px) {  
#main-header.shrink-header #nav-container {
    width: 86%;
     background-color: rgba(217, 217, 217, 0.6); 
    padding: 0% 1% 0% 1%;
}
}

Code in menu widget:

.elementor-nav-menu--dropdown{
    background-color: blue;
    width: inherit;
    transition: width 0.3s ease;
left: -120% !important;

}

My Script:

<script>
document.addEventListener("DOMContentLoaded", function() {
    var header = document.getElementById("main-header");
    window.addEventListener("scroll", function() {
        if (window.scrollY > 50) {
            header.classList.add("shrink-header"); // Klasse hinzufügen
        } else {
            header.classList.remove("shrink-header"); // Klasse entfernen
        }
    });
});

    </script>

Do you have any ideas how I can do it? I also added a pic how my navbar looks like. enter image description here
Thanks in advance!

Custom React Hook to return memoized value. Async API call, having trouble returning the value and not the promise [duplicate]

So I am trying to build a custom react hook that fetches a value from an API and returns a string. However the issue I am running into is that it is returning a Promise and I am not sure how to resolve.

import { useMemo } from "react";

export const useGetPaymentIntent = (userId: string) => {
  const accesToken = useMemo(async () => {
    const response = await fetch("https://example.com/get-token", {
      method: "POST",
      headers: {
        Authorization: `Bearer process.env.REACT_APP_KEY`,
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams({
        userId: userId
      })
    });

    const data = await response.json();

    // THIS CONSOLE LOG, LOGS THE DESIRED VALUE
    console.log(data.access_token);

    return data.access_token;
  }, [userId]);

  return accesToken
};

For some reason when i import this hook in my application I am getting back Promise{}

How can this hook returned the desired value (The value i was able to log)

How can I apply the same functionality to 3 sets of buttons on 3 sets of slides?

This code works for the first set of slides, but not for the 2nd and 3rd sets (accessed by clicking the 3 dots at the bottom of the page). Specifically the “up” and “down” arrow buttons don’t do anything.

I think it is related to CSS selectors in the JS code. I tries to make it select them all with querySelectorAll but I could not do it.

window.kontext = function(container) {

  // Dispatched when the current layer changes
  var changed = new kontext.Signal();

  // All layers in this instance of kontext
  var layers = Array.prototype.slice.call(container.querySelectorAll('.layer'));

  // Flag if the browser is capable of handling our fancy transition
  var capable = 'WebkitPerspective' in document.body.style ||
    'MozPerspective' in document.body.style ||
    'msPerspective' in document.body.style ||
    'OPerspective' in document.body.style ||
    'perspective' in document.body.style;

  if (capable) {
    container.classList.add('capable');
  }

  // Create dimmer elements to fade out preceding slides
  layers.forEach(function(el, i) {
    if (!el.querySelector('.dimmer')) el.innerHTML += '<div class="dimmer"></div>';
  });

  /**
   * Transitions to and shows the target layer.
   *
   * @param target index of layer or layer DOM element
   */
  function show(target, direction) {

    // Make sure our listing of available layers is up to date
    layers = Array.prototype.slice.call(container.querySelectorAll('.layer'));

    // Flag to CSS that we're ready to animate transitions
    container.classList.add('animate');

    // Flag which direction
    direction = direction || (target > getIndex() ? 'right' : 'left');

    // Accept multiple types of targets
    if (typeof target === 'string') target = parseInt(target);
    if (typeof target !== 'number') target = getIndex(target);

    // Enforce index bounds
    target = Math.max(Math.min(target, layers.length), 0);

    // Only navigate if were able to locate the target
    if (layers[target] && !layers[target].classList.contains('show')) {

      layers.forEach(function(el, i) {
        el.classList.remove('left', 'right');
        el.classList.add(direction);
        if (el.classList.contains('show')) {
          el.classList.remove('show');
          el.classList.add('hide');
        } else {
          el.classList.remove('hide');
        }
      });

      layers[target].classList.add('show');

      changed.dispatch(layers[target], target);

    }

  }

  /**
   * Shows the previous layer.
   */
  function prev() {

    var index = getIndex() - 1;
    show(index >= 0 ? index : layers.length + index, 'left');

  }

  /**
   * Shows the next layer.
   */
  function next() {

    show((getIndex() + 1) % layers.length, 'right');

  }

  /**
   * Retrieves the index of the current slide.
   *
   * @param of [optional] layer DOM element which index is
   * to be returned
   */
  function getIndex(of) {

    var index = 0;

    layers.forEach(function(layer, i) {
      if ((of && of == layer) || (!of && layer.classList.contains('show'))) {
        index = i;
        return;
      }
    });

    return index;

  }

  /**
   * Retrieves the total number of layers.
   */
  function getTotal() {

    return layers.length;

  }

  // API
  return {

    show: show,
    prev: prev,
    next: next,

    getIndex: getIndex,
    getTotal: getTotal,

    changed: changed

  };

};

/**
 * Minimal utility for dispatching signals (events).
 */
kontext.Signal = function() {
  this.listeners = [];
}

kontext.Signal.prototype.add = function(callback) {
  this.listeners.push(callback);
}

kontext.Signal.prototype.remove = function(callback) {
  var i = this.listeners.indexOf(callback);

  if (i >= 0) this.listeners.splice(i, 1);
}

kontext.Signal.prototype.dispatch = function() {
  var args = Array.prototype.slice.call(arguments);
  this.listeners.forEach(function(f, i) {
    f.apply(null, args);
  });
}






// Create a new instance of kontext
var k = kontext(document.querySelector('.kontext'));


// Demo page JS

var bulletsContainer = document.body.querySelector('.bullets');

// Create one bullet per layer
for (var i = 0, len = k.getTotal(); i < len; i++) {
  var bullet = document.createElement('li');
  bullet.className = i === 0 ? 'active' : '';
  bullet.setAttribute('index', i);
  bullet.onclick = function(event) {
    k.show(event.target.getAttribute('index'))
  };
  bullet.ontouchstart = function(event) {
    k.show(event.target.getAttribute('index'))
  };
  bulletsContainer.appendChild(bullet);
}

// Update the bullets when the layer changes
k.changed.add(function(layer, index) {
  var bullets = document.body.querySelectorAll('.bullets li');
  for (var i = 0, len = bullets.length; i < len; i++) {
    bullets[i].className = i === index ? 'active' : '';
  }
});

document.addEventListener('keyup', function(event) {
  if (event.keyCode === 37) k.prev();
  if (event.keyCode === 39) k.next();
}, false);



const sliderContainer = document.querySelector('.slider-container');
const slideRight = document.querySelector('.right-slide');
const slideLeft = document.querySelector('.left-slide');
const upButton = document.querySelector('.up-button');
const downButton = document.querySelector('.down-button');
const slidesLength = slideRight.querySelectorAll('div').length;


console.log(slidesLength);
let activeSlideIndex = 0;

slideLeft.style.top = `-${(slidesLength - 1) * 100}vh`

upButton.addEventListener('click', () => changeSlide('up'))
downButton.addEventListener('click', () => changeSlide('down'))

const changeSlide = (direction) => {
  const sliderHeight = sliderContainer.clientHeight;
  if (direction === 'up') {
    activeSlideIndex++
    if (activeSlideIndex > slidesLength - 1) {
      activeSlideIndex = 0;
    }
  } else {
    activeSlideIndex--
    if (activeSlideIndex < 0) {
      activeSlideIndex = slidesLength - 1;
    }
  }
  slideRight.style.transform = `translateY(-${sliderHeight * activeSlideIndex}px)`
  slideLeft.style.transform = `translateY(${sliderHeight * activeSlideIndex}px)`

}
.kontext {
  width: 100%;
  height: 100%;
}

.kontext .layer {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  visibility: hidden;
  /*box-shadow: 0px 0px 120px rgba( 0, 0, 0, 0.8 );*/
}

.kontext .layer.show {
  visibility: visible;
}

.kontext.capable {
  -webkit-perspective: 1000px;
  -moz-perspective: 1000px;
  perspective: 1000px;

  -webkit-transform-style: preserve-3d;
  -moz-transform-style: preserve-3d;
  transform-style: preserve-3d;
}

.kontext.capable .layer {
  -webkit-transform: translateZ(-100px);
  -moz-transform: translateZ(-100px);
  transform: translateZ(-100px);
}

.kontext.capable .layer.show {
  -webkit-transform: translateZ(0px);
  -moz-transform: translateZ(0px);
  transform: translateZ(0px);
}

.kontext.capable.animate .layer.show.right {
  -webkit-animation: show-right 1s forwards ease;
  -moz-animation: show-right 1s forwards ease;
  animation: show-right 1s forwards ease;
}

.kontext.capable.animate .layer.hide.right {
  -webkit-animation: hide-right 1s forwards ease;
  -moz-animation: hide-right 1s forwards ease;
  animation: hide-right 1s forwards ease;
}

.kontext.capable.animate .layer.show.left {
  -webkit-animation: show-left 1s forwards ease;
  -moz-animation: show-left 1s forwards ease;
  animation: show-left 1s forwards ease;
}

.kontext.capable.animate .layer.hide.left {
  -webkit-animation: hide-left 1s forwards ease;
  -moz-animation: hide-left 1s forwards ease;
  animation: hide-left 1s forwards ease;
}


/* CSS animation keyframes */

@-webkit-keyframes show-right {
  0% {
    -webkit-transform: translateZ(-200px);
  }

  40% {
    -webkit-transform: translate(40%, 0) scale(0.8) rotateY(-20deg);
  }

  100% {
    -webkit-transform: translateZ(0px);
  }
}

@-webkit-keyframes hide-right {
  0% {
    -webkit-transform: translateZ(0px);
    visibility: visible;
  }

  40% {
    -webkit-transform: translate(-40%, 0) scale(0.8) rotateY(20deg);
  }

  100% {
    -webkit-transform: translateZ(-200px);
    visibility: hidden;
  }
}

@-moz-keyframes show-right {
  0% {
    -moz-transform: translateZ(-200px);
  }

  40% {
    -moz-transform: translate(40%, 0) scale(0.8) rotateY(-20deg);
  }

  100% {
    -moz-transform: translateZ(0px);
  }
}

@-moz-keyframes hide-right {
  0% {
    -moz-transform: translateZ(0px);
    visibility: visible;
  }

  40% {
    -moz-transform: translate(-40%, 0) scale(0.8) rotateY(20deg);
  }

  100% {
    -moz-transform: translateZ(-200px);
    visibility: hidden;
  }
}

@keyframes show-right {
  0% {
    transform: translateZ(-200px);
  }

  40% {
    transform: translate(40%, 0) scale(0.8) rotateY(-20deg);
  }

  100% {
    transform: translateZ(0px);
  }
}

@keyframes hide-right {
  0% {
    transform: translateZ(0px);
    visibility: visible;
  }

  40% {
    transform: translate(-40%, 0) scale(0.8) rotateY(20deg);
  }

  100% {
    transform: translateZ(-200px);
    visibility: hidden;
  }
}


@-webkit-keyframes show-left {
  0% {
    -webkit-transform: translateZ(-200px);
  }

  40% {
    -webkit-transform: translate(-40%, 0) scale(0.8) rotateY(20deg);
  }

  100% {
    -webkit-transform: translateZ(0px);
  }
}

@-webkit-keyframes hide-left {
  0% {
    -webkit-transform: translateZ(0px);
    visibility: visible;
  }

  40% {
    -webkit-transform: translate(40%, 0) scale(0.8) rotateY(-20deg);
  }

  100% {
    -webkit-transform: translateZ(-200px);
    visibility: hidden;
  }
}

@-moz-keyframes show-left {
  0% {
    -moz-transform: translateZ(-200px);
  }

  40% {
    -moz-transform: translate(-40%, 0) scale(0.8) rotateY(20deg);
  }

  100% {
    -moz-transform: translateZ(0px);
  }
}

@-moz-keyframes hide-left {
  0% {
    -moz-transform: translateZ(0px);
    visibility: visible;
  }

  40% {
    -moz-transform: translate(40%, 0) scale(0.8) rotateY(-20deg);
  }

  100% {
    -moz-transform: translateZ(-200px);
    visibility: hidden;
  }
}

@keyframes show-left {
  0% {
    transform: translateZ(-200px);
  }

  40% {
    transform: translate(-40%, 0) scale(0.8) rotateY(20deg);
  }

  100% {
    transform: translateZ(0px);
  }
}

@keyframes hide-left {
  0% {
    transform: translateZ(0px);
    visibility: visible;
  }

  40% {
    transform: translate(40%, 0) scale(0.8) rotateY(-20deg);
  }

  100% {
    transform: translateZ(-200px);
    visibility: hidden;
  }
}


/* Dimmer */

.kontext .layer .dimmer {
  display: block;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  visibility: hidden;
  background: transparent;
}

.kontext.capable.animate .layer .dimmer {
  -webkit-transition: background 1s ease;
  -moz-transition: background 1s ease;
  transition: background 1s ease;
}

.kontext.capable.animate .layer.hide .dimmer {
  visibility: visible;
  background: rgba(0, 0, 0, 0.7);
}




/* Styles for the demo */

html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  overflow: hidden;
  background-color: #222;
  background-repeat: repeat;
  color: #fff;
}

.layer {
  text-align: center;
  text-shadow: 1px 1px 0px rgba(0, 0, 0, 0.1);
}

.layer h2 {
  position: relative;
  top: 20%;
  margin: 0;
  font-size: 6.25em;
}



.layer.one {
  background: #dc6c5f;
}

.layer.two {
  background: #95dc84;
}

.layer.three {
  background: #64b9d2;
}

.bullets {
  position: absolute;
  width: 100%;
  bottom: 20px;
  padding: 0;
  margin: 0;
  text-align: center;
}

.bullets li {
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  margin: 0 3px;

  background: rgba(255, 255, 255, 0.5);
  box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
  cursor: pointer;

  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

.bullets li:hover {
  background: rgba(255, 255, 255, 0.8);
}

.bullets li.active {
  cursor: default;
  background: #fff;
}

@media screen and (max-width: 400px) {
  body {
    font-size: 12px;
  }

  .layer h2 {
    font-size: 5em;
  }

  .bullets li {
    margin: 0 6px;
  }
}



/* PART TWO */

.slider-container {
  position: relative;
  overflow: hidden;
  width: 100vw;
  height: 100vh;
}

.left-slide {
  height: 100%;
  width: 35%;
  position: absolute;
  top: 0;
  left: 0;
  transition: transform 0.5s ease-in-out;
}

.left-slide>div {
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

.left-slide h1 {
  font-size: 40px;
  margin-bottom: 10px;
  margin-top: -30px;
}

.right-slide {
  height: 100%;
  position: absolute;
  top: 0;
  left: 35%;
  width: 65%;
  transition: transform 0.5s ease-in-out;
}

.right-slide>div {
  background-repeat: no-repeat;
  height: 100%;
  width: 100%;
  background-size: cover;
  background-position: center center;
}

button {
  background-color: white;
  border: none;
  color: #aaa;
  cursor: pointer;
  padding: 15px;
}

button:hover {
  color: #222;
}

button:focus {
  outline: none;
}

.slider-container .action-buttons button {
  position: absolute;
  left: 35%;
  top: 50%;
  z-index: 100;
}

.slider-container .action-buttons .down-button {
  transform: translateX(-100%);
  border-radius: 5px 0 0 5px;
}

.slider-container .action-buttons .up-button {
  transform: translateY(-100%);
  border-radius: 0 5px 5px 0;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Kontext</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <link rel="stylesheet" href="./stylekontext.css">

  <!-- font awesome -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />

</head>

<body>
  <!-- partial:index.partial.html -->
  <article class="kontext" style="background-color: rgb(7, 5, 9);">
    <div class="layer one show" style="background-color: darkgoldenrod;">
      <div class="slider-container">

        <div class="left-slide">
          <div style="background-color: #fd3555">
            left 1
          </div>
          <div style="background-color: #2a86ba">
            left 2
          </div>
          <div style="background-color: #252e33">
            left 3
          </div>
          <div style="background-color: #ffb866">
            left 4
          </div>
        </div>

        <div class="right-slide">
          <div>right 1</div>

          <div>right 2

          </div>
          <div>right 3</div>
          <div>right 4</div>
        </div>

        <div class="action-buttons">
          <button class="down-button"><i class="fas fa-arrow-down"></i></button>
          <button class="up-button"><i class="fas fa-arrow-up"></i></button>
        </div>
      </div>
    </div>


    <div class="layer two" style="background-color: rgb(184, 11, 11);">
      <div class="slider-container">


        <div class="left-slide">
          <div style="background-color: #fd3555">
            left 1
          </div>
          <div style="background-color: #2a86ba">
            left 2
          </div>
          <div style="background-color: #252e33">
            left 3
          </div>
          <div style="background-color: #ffb866">
            left 4
          </div>
        </div>

        <div class="right-slide">
          <div>right 1</div>
          <div>right 2</div>
          <div>right 3</div>
          <div>right 4</div>
        </div>

        <div class="action-buttons">
          <button class="down-button"><i class="fas fa-arrow-down"></i></button>
          <button class="up-button"><i class="fas fa-arrow-up"></i></button>
        </div>

      </div>
    </div>





    <div class="layer three" style="background-color: rgb(69, 11, 184);">
      <div class="slider-container">


        <div class="left-slide">
          <div style="background-color: #fd3555">
            left 1
          </div>
          <div style="background-color: #2a86ba">
            left 2
          </div>
          <div style="background-color: #252e33">
            left 3
          </div>
          <div style="background-color: #ffb866">
            left 4
          </div>
        </div>

        <div class="right-slide">
          <div>right 1</div>
          <div>right 2</div>
          <div>right 3</div>
          <div>right 4</div>
        </div>

        <div class="action-buttons">
          <button class="down-button"><i class="fas fa-arrow-down"></i></button>
          <button class="up-button"><i class="fas fa-arrow-up"></i></button>
        </div>


      </div>
    </div>
  </article>



  <ul class="bullets"></ul>
  <!-- partial -->
  <script src="scriptkontext.js"></script>


</body>

</html>

I am looking for an angel to revise the JS file.

How to listen to user movement and count their distance in React Expo

I am relatively new to react expo and I am currently working on a personal project where I would like to:
a. Ask the user for permission to grant location access (ok).
b. When a button is clicked then start tracking how far the person has walked/run and it should stop tracking when the stop button is clicked (ok).

For testing I am using react expo go however, when I start it on my phone the distance that I have walked/run is not accurate and when I am stopped it still seems to increment the distance. Please, I would appreciate any help.

I have the following code:

const MIN_MOVEMENT_THRESHOLD = 1; // Ignore movements less than 1 meter

// Haversine formula to calculate distance in meters
const getDistance = (lat1: number, lon1: number, lat2: number, lon2: number) => {
  const R = 6371000; // Radius of Earth in meters
  const toRad = (angle: number) => (angle * Math.PI) / 180;

  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);

  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return R * c;
};

export function RouteMapScreen({ route }: { route: any }) {
  const mapInView = route.params.chosenMap;
//   const pathLength = mapInView.pathLengthRoute;

  //const [dashOffset, setDashOffset] = useState(pathLength * (1 - mapInView.startPoint));
  const [isTracking, setIsTracking] = useState(false);
  const [totalDistance, setTotalDistance] = useState(0);
  const lastLocationRef = useRef<Location.LocationObject | null>(null); // Persist previous location

  useEffect(() => {
    let locationSubscription: Location.LocationSubscription | null = null;
  
    const startTracking = async () => {
      let { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== "granted") {
        console.log("Permission to access location was denied");
        return;
      }
  
      locationSubscription = await Location.watchPositionAsync(
        { 
          accuracy: Location.Accuracy.Highest, // Highest accuracy for better precision
          distanceInterval: 1, // Update more frequently for better accuracy
          timeInterval: 1000  // Update every second
        },
        (location) => {
          console.log("Current location:", location.coords);
  
          // Ignore updates when stationary or moving too slowly
          if (location.coords.speed !== null && location.coords.speed < 0.5) {
            console.log("User is stationary or moving too slowly, ignoring movement.");
            return;
          }

          if (lastLocationRef.current) {
            const distance = getDistance(
              lastLocationRef.current.coords.latitude,
              lastLocationRef.current.coords.longitude,
              location.coords.latitude,
              location.coords.longitude
            );
  
            // Only add the distance if it's greater than the threshold
            if (distance > MIN_MOVEMENT_THRESHOLD) {
              setTotalDistance((prev) => prev + distance);
              console.log("Distance added:", distance);
            }
          }
  
          lastLocationRef.current = location; // Store last location
        //   setDashOffset((prevOffset) => Math.max(prevOffset - 10, 0));
        }
      );
    };
  
    if (isTracking) {
      startTracking();
    }
  
    console.log("Tracking status:", isTracking);
  
    return () => {
      if (locationSubscription) {
        locationSubscription.remove();
        console.log("Tracking stopped.");
      }
    };
  }, [isTracking]);

  return (
    <>
      <View style={styles.mapActions}>
        <View style={[styles.upperButtons, { backgroundColor: mapInView.cardBackgroundColor }]}>
          <Pressable android_ripple={{ color: "darkred" }}>
            <Text style={styles.startText}>INVITE</Text>
          </Pressable>
        </View>
        <Text>Run</Text>
        <View style={[styles.upperButtons, { backgroundColor: "#A82354" }]}>
          <Pressable android_ripple={{ color: "darkred" }} onPress={() => setIsTracking(false)}>
            <Text style={styles.startText}>END</Text>
          </Pressable>
        </View>
      </View>
      <View>
        <Text style={styles.distanceText}>Distance: {totalDistance.toFixed(2)} meters</Text>
      </View>
      <View style={styles.mapArea}></View>
      <View style={styles.mapBottomActions}>
        <View style={[styles.buttonOuterContainer, { backgroundColor: mapInView.cardBackgroundColor }]}>
          <Pressable android_ripple={{ color: "darkred" }} onPress={() => setIsTracking(true)}>
            <Text style={styles.startText}>START</Text>
          </Pressable>
        </View>
      </View>
    </>
  );
}