Obtener valor de celda en tabla html

Quiero obtener el valor de la celda seleccionada de una tabla la cual envia el ID seleccionado a una la funcion editar();

` <table class="table table-responsive">
                <thead>
                    <tr>   
                        <th>ID</th> 
                        <th>Area</th>
                        <th>Falla</th>
                        <th>Reporta</th>
                        <th>Recive</th>
                        <th>Comentarios</th>
                    </tr>
                </thead>
                <tbody id="PN">
                </tbody> 
                <tbody id="PT">
                    <?php
                    if ($resultado->num_rows > 0) {
                        while ($row = $resultado->fetch_assoc()) {
                            echo "<tr>";
                            echo "<td>" . $row['ID'] . "</td>";
                            echo "<td>" . $row['Area'] . "</td>";
                            echo "<td>" . $row['Falla'] . "</td>";
                            echo "<td>" . $row['Reporta'] . "</td>";
                            echo "<td>" . $row['Recibe'] . "</td>";
                            echo "<td>" . $row['Comentario'] . "</td>";
                            echo "<td><button class='btn btn-success 
                            btn-sm' onclick='Editar(.$row['ID'] . ")'Editar>
                            Editar</button></td>";
                            echo "</tr>";
                        }
                    } else {
                        echo "<tr><td colspan='6'>No hay datos disponibles</td></tr>";
                    }
                    ?>
                </tbody>
            </table>`

La funcion en cuestion tiene que limpiar el Div actual y agregar un textArea en el cual quiero que se agregue el texto rescatado de la celda

`<script>
function Editar(ID){
// Guardar el contenido original del contenedor pendientes
const contenedorP = document.getElementById('pendientes');
const contenidoOriginal = contenedorP.innerHTML;
document.getElementById('pendientes').innerHTML = '';
document.getElementById('pendientes')
    
    contenedorP.innerHTML = `
        <h2>Editar comentarios</h2>
        <textarea name="comentarios" rows="10" cols="80"></textarea><br><br>
        <button class="btn btn-success">Aceptar</button>
    `;
}
</script>`

Hasta ahora cuando intento rescatar el valor del ID e imprimirlo en consola me sale como undefined

loading animation after submiting contact form google sheet

Here is whats not working:

  • i tryed to show loading animation during timedelay between after submiting form and showing success message(i am using swal.fire()to show success message). but didn’t worked.

here is what i successfully wrote code for loading animation

its working in standalone, i unable to add this loading animation feature in appscript.

$(document).ready(function(){
  $("#myform").on("submit", function(){
    $("#preloder").fadeIn();
  });//submit
});//document ready
/* Preloder */

#preloder {
    display: none;
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 999999;
    /* background: #000; */
    background: #ffffff;
}

.loader {
    width: 50px;
    height: 50px;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -20px;
    margin-left: -20px;
    border-radius: 60px;
    animation: loader 0.8s linear infinite;
    -webkit-animation: loader 0.8s linear infinite;
}

@keyframes loader {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
        border: 4px solid #056d4d;      
        /* border: 4px solid #f44336; */
        border-left-color: transparent;
    }
    50% {
        -webkit-transform: rotate(180deg);
        transform: rotate(180deg);
        border: 4px solid #056d4d;
        /* border: 4px solid #673ab7; */
        border-left-color: transparent;
    }
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
        border: 4px solid #056d4d;
        border-left-color: transparent;
    }
}

@-webkit-keyframes loader {
    0% {
        -webkit-transform: rotate(0deg);
        border: 4px solid #056d4d;
        border-left-color: transparent;
    }
    50% {
        -webkit-transform: rotate(180deg);
        border: 4px solid #056d4d;
        border-left-color: transparent;
    }
    100% {
        -webkit-transform: rotate(360deg);
        border: 4px solid #056d4d;
        border-left-color: transparent;
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Page Preloder -->
<div id="preloder">
    <div class="loader"></div>
</div>

<form id="myform">
  <input type="text" name="fname" id="fname" value="" />
  <input type="submit" value="Submit" />
</form>

Here is appscript i tryed to add timedelay loading animation feature. {files made in appscript: (code.gs and index.html and library id)}.

library id
1CcBYkrGSeBRgphHUE92vWInyULOcJ1Ub6eFUR0_gI1h9I6whLjXtDA-P

code.gs

function doGet() {
  return HtmlService.createTemplateFromFile('index').evaluate()
  .addMetaTag('viewport', 'width=device-width, initial-scale=1')
  .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
}

var url = '#'
var sh = 'file1'
var folderId = '#'

function processForm(formdata) {
  var superscript = SuperScript.initSuper(url, sh);
  var formObject = {};
  formdata.forEach(element => formObject[element.name] = element.value);
  formdata.forEach(element => formObject[element.message] = element.value);
  formdata.forEach(element => formObject[element.email] = element.value);
  formdata.forEach(element => formObject[element.phone] = element.value);

  var f = formdata.find(({ name }) => name == "myfile");
  var file = (f && f.value?.name && f.value?.data) ? superscript.uploadFile(folderId, formObject.myfile.data, formObject.myfilename) : "";
  var ss = SpreadsheetApp.openByUrl(url);
  var ws = ss.getSheets()[0];
  ws.appendRow([
    new Date(),
    formObject.name,
    "'" + formObject.message,
    formObject.email,
    formObject.phone,
    file && file.getUrl()
  ]);

}


index.html

<!DOCTYPE html>
<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
        integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
        integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
  
  
</head>
<body>
    <div class="container py-5">
        <div class="row">
            <div class="col-lg-5 col-md-8 mx-auto shadow border bg-white p-4 rounded">
                <nav class="navbar navbar-dark bg-primary">
                    <a class="navbar-brand" href="#" fw-bold mb-3>WRITE YOUR QUERY / MESSAGE</a>
                </nav>
                <br>
                <form id="myForm" onsubmit="handleFormSubmit()">
                    <div id="form_alerts"></div>
                    <div class="form-group mb-3">
                        <label for="name">Name</label>
                        <input type="text" class="form-control" id="name" placeholder="Enter your Name here|" name="name"required>
            </div>

            <div class="form-group mb-3">
            <label for="email" >Email</label>
                        <input type="email"  class="form-control" id="email"
            placeholder="Enter your email address here|" name="email" required>
            </div>

            <div class="form-group mb-3">
           <label for="phone">phone number</label>
  <input type="tel" id="phone" class="form-control" name="phone" placeholder="Enter your phone number here with country code|" pattern="[+]{1}[0-9]{2}[0-9]{10}" required>
  <small>requested example Format: +919263943858</small>
            </div>

            <div class="form-group mb-3">
              <label for="message">Message</label>
    <textarea class="form-control" id="message" placeholder="Write your message here|" name="message" required ></textarea>
              </div>

                         <div class="form-group mb-3">
                            <label for="uploadFile">Upload File</label>
                            <input type="file" class="form-control" File="file" id="file">
              <small>For multiple/bigger file size please attach/share via google drive link in message section.</small>
                            </div>

                            <button type="submit" class= "btn btn-primary btn-block">SEND</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
  <script>
        function preventFormSubmit() {
                var forms = document.querySelectorAll('form');
                for (var i = 0; i < forms.length; i++) {
                    forms[i].addEventListener('submit',
                     function (event) {
                        event.preventDefault();
                    });
                }
            }
            window.addEventListener('load', preventFormSubmit);
            function handleFormSubmit() {
                var formdata = $('#myForm').serializeArray()
                formdata.push({
                  name: 'myfile',
                  value: myfile
                })
                google.script.run.withSuccessHandler(success).processForm(formdata);
            }
            function success(){
                 document.getElementById("myForm").reset()
                 Swal.fire({
                  position: 'center',
                  icon: 'success',
                  title: 'Sended successfully! Be ready we will contact you shortly',
                  showConfirmButton: true,
      
                })
            }
        var myfile ={},file, reader = new FileReader();
      reader.onloadend = function(e) {
          myfile.data = e.target.result
          myfile.name = file.name
          console.log(myfile)
      };
     $('#file').change(function(){
       file = $('#file')[0].files[0]
        reader.readAsDataURL(file);
     })
    </script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
    <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  <script src="sweetalert2.all.min.js"></script>
</body>
</html>

How do I add a keyboard listener to a compose window in a Thunderbird extension?

I’m writing an extension for Thunderbird email client.

One part of the extension is a character counter when composing new messages. For this I’ll ideally need to add a keyboard listener to perform a counting function on keyup.

The alternative is to use a setInterval and just continually perform the counting function every half second or so, but that’s obviously not as ideal as just updating the counter on each keypress.

The API documentation says that I can add listeners using .addListener but it doesn’t actually explain how to do that, I can’t seem to find a way to make it work.

messenger.addEventListener() is not a function.

window.addEventListener() doesn’t seem to actually add the listener to the compose window like it would to a browser window.

Also tried document.body but no joy.

Is this even possible?

navigator.mediaDevices mobile multi-back cameras choosing camera without focus

In general, this implementation is working, but on some devices like the iPhone 15 Plus and Samsung S21 FE, even when choosing the correct camera, it doesn’t focus properly unless zoom is applied.

What is the best approach for these cases?
sandbox: https://codesandbox.io/p/devbox/stupefied-cherry-xn67qf?file=%2Fapp%2Fpage.tsx%3A54%2C43

"use client";
import { useEffect, useRef, useState } from "react";

export default function Home() {
  const videoRef = useRef<any>(null);
  const [selectedCameraId, setSelectedCameraId] = useState<any>();

  const requestCameraPermission = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
      const tracks = stream.getTracks();

      for (const track of tracks) {
        track.stop();
      }

      selectDefaultCamera();
    } catch (error) {
      console.log("Usuário não deu permissão na câmera");
    }
  };

  const selectDefaultCamera = async () => {
    try {
      const devices = await navigator.mediaDevices.enumerateDevices();

      const videoDevices = devices.filter(
        (device) => device.kind === "videoinput"
      );

      const devicesCompletedInfo: any = [];

      for (const device of videoDevices) {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: { deviceId: { exact: device.deviceId } },
        });

        const track = stream.getVideoTracks()[0];
        const capabilities: any = track.getCapabilities();

        devicesCompletedInfo.push({ capabilities, basicInfo: device });

        track.stop();
      }

      const findCameraWithTorch = devicesCompletedInfo.find(
        (device: any) =>
          device.capabilities.torch &&
          device.capabilities.facingMode.includes("environment") &&
          !device.basicInfo.label.toLowerCase().includes("angular") &&
          !device.basicInfo.label.toLowerCase().includes("wide")
      );

      const cameraId = findCameraWithTorch
        ? findCameraWithTorch.basicInfo.deviceId
        : undefined;
      startVideo(cameraId);
    } catch (error) {
      console.log(error);
      alert("Não foi possivel listar todas cameras");
    }
  };

  const startVideo = async (cameraId: string) => {
    try {
      const constraints: any = {
        video: {
          facingMode: "environment",
          audio: false,
          width: { min: 640, ideal: 1280, max: 1920 },
          height: { min: 480, ideal: 720, max: 1080 },
          deviceId: cameraId ? { exact: cameraId } : undefined,
        },
      };

      if (videoRef.current) {
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        const track = stream.getVideoTracks()[0];
        const capabilities: any = track.getCapabilities();

        setSelectedCameraId(capabilities.deviceId);

        videoRef.current.srcObject = stream;
      }
    } catch (err) {
      console.error("Error accessing webcam: ", err);
    }
  };

  useEffect(() => {
    requestCameraPermission();
  }, []);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline />
    </div>
  );
}

Correct way to Middleware with NodeJS, ExpressJS, Tokens (Login & Dashboard)?

Hope your’ll fine. We’ll I have a problem using NodeJS, especifically with a Middleware, in my case it supposed to have a Dashboard with two cases:

  • If user try to go /dashboard without login -> send to /login
  • If user go to /dashboard with correct login -> show /dashboard

But I have error with the correct way,

const verificarToken = (req, res, next) => {
  // Excluir recursos estáticos como favicon, imágenes, archivos JS, etc.
  if (req.path.match(/.(ico|jpg|jpeg|png|js|css)$/)) {
    return next();  // Continuar sin requerir token
  }

  const authHeader = req.headers['authorization'];
  console.log("Encabezado Authorization recibido:", authHeader);  // Depuración

  if (!authHeader) {
    console.log("No se proporcionó un token. Redirigiendo a /login.");
    return res.status(403).json({ mensaje: 'No se proporcionó un token' });
  }

  const token = authHeader.split(' ')[1];  // Extraer el token después de 'Bearer'
  jwt.verify(token, 'secreto_jwt', (err, decoded) => {
    if (err) {
      console.log("Token no válido. Redirigiendo a /login.");
      return res.status(401).json({ mensaje: 'Token no válido' });
    }
    req.usuario = decoded;
    next();  // Continuar si el token es válido
  });
};

verificarToken is my Middleware, and the section of login and dashboard:

// Ruta para el Login (GET)
app.get('/login', (req, res) => {
  res.sendFile(__dirname + '/public/login.html');
});

// Ruta protegida para el Dashboard (GET)
app.get('/dashboard', verificarToken, (req, res) => {
  res.sendFile(__dirname + '/public/dashboard.html');
});

// Ruta de login (POST)
app.post('/login', (req, res) => {
  const { nombre_usuario, password } = req.body;

  // Buscar al usuario en la base de datos
  conexion.query('SELECT * FROM usuarios WHERE nombre_usuario = ?', [nombre_usuario], (err, results) => {
    if (err) {
      return res.status(500).json({ mensaje: 'Error en la consulta' });
    }
    if (results.length === 0) {
      return res.status(401).json({ mensaje: 'Usuario no encontrado' });
    }

    const usuario = results[0];

    // Comparar la contraseña ingresada con la almacenada en la base de datos
    bcrypt.compare(password, usuario.password, (err, esValido) => {
      if (err) {
        return res.status(500).json({ mensaje: 'Error al comparar contraseñas' });
      }
      if (esValido) {
        // Generamos el token de seguridad
        const token = jwt.sign({ nombre_usuario: usuario.nombre_usuario, rol: usuario.rol }, 'secreto_jwt', { expiresIn: '1h' });
        res.json({ mensaje: `Bienvenido ${usuario.nombre_usuario}`, token });  // Devolver el token al cliente
      } else {
        res.status(401).json({ mensaje: 'Contraseña incorrecta' });
      }
    });
  });
});

showing the way of security using Token, Bcrypt etc…

login.html script:

  <script>
// Lógica para manejar el login
document.getElementById('loginForm').addEventListener('submit', function(e) {
  e.preventDefault(); // Prevenir que el formulario se recargue

  // Capturar los valores de los campos
  const nombre_usuario = document.getElementById('nombre_usuario').value;
  const password = document.getElementById('password').value;

  // Enviar la solicitud POST al servidor
  fetch('/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ nombre_usuario, password })
  })
  .then(response => {
    // Verificar si la respuesta fue exitosa (status 200-299)
    if (!response.ok) {
      throw new Error('Error en la solicitud de login');
    }
    return response.json();
  })
  .then(data => {
    console.log('Respuesta del servidor:', data);  // Mostrar la respuesta en la consola
    if (data.token) {
      // Guardar el token en localStorage
      localStorage.setItem('token', data.token);
      console.log('Token guardado en localStorage:', data.token);

      // Verificar acceso al dashboard con el token
      verificarAccesoDashboard(); 
    } else {
      // Mostrar un mensaje de error si las credenciales no son válidas
      document.getElementById('errorMsg').style.display = 'block';
    }
  })
  .catch(error => {
    console.error('Error en la solicitud:', error);
    document.getElementById('errorMsg').style.display = 'block';
  });
});

// Función para verificar el acceso al dashboard usando el token
function verificarAccesoDashboard() {
  const token = localStorage.getItem('token');
  if (!token) {
    console.log("No se encontró token. Redirigiendo a /login.");
    window.location.href = '/login';
    return;
  }

  // Hacer la solicitud GET a /dashboard con el token en el encabezado Authorization
  fetch('/dashboard', {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    credentials: 'include'
  })
  .then(response => {
    if (response.status === 401 || response.status === 403) {
      console.log("Token inválido o acceso denegado. Redirigiendo a /login.");
      window.location.href = '/login';
    } else {
      console.log("Token válido. Mostrando dashboard.");
      window.location.href = '/dashboard'; // Redirigir ahora al dashboard
    }
  })
  .catch(error => {
    console.error('Error en la solicitud al dashboard:', error);
    window.location.href = '/login';  // Redirigir al login si ocurre un error
  });
}

  </script>

in this scenario I delete every script on dashboard.html, what I was expecting is a successfull login, but after making the “fetch dashboard with GET method” the node server show
“Encabezado Authorization recibido: Bearer token
and just after that shows
“Encabezado Authorization recibido: undefined”
“No se proporcionó un token. Redirigiendo a /login”
and is in this point that I need help, I understand that the first log of node server is with the Fetch Method GET to Dashboard, but the next one is the “window.location.href” that is not giving the header “Authorization …”.

So how can make a Middleware successfull or in other words how can I make the correct way?

Deleting script on Dashboard.html
Using FETCH METHOD in login.html before the redirect to /dashboard

Dealing with Bootstrap style in JavaScript

I was needed to override some Bootstrap classes, and now I can’t get an access to them in JS.

<style>
  .accordion-button:not(.collapsed):enabled { background: #0000FF; }
  .accordion-button.collapsed:enabled       { background: #FF0000; }
  <!-- One color for expanded item another for collapsed one -->            

  #red.form-range::-webkit-slider-runnable-track { background: #FF0000; }
  #red.form-range::-moz-range-track  
  <!-- Change slider color -->
</style>

<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#y" aria-expanded="true" aria-controls="y"></button>

<input class="form-range" type="range" min="0" max="255" step="1" id="red">

I’d like to change these attributes dynamicly in JS.
But using the same selectors in JS doesn’t work as I expected.

let selExt = '.accordion-button:not(.collapsed):enabled';
let selCol = '.accordion-button.collapsed:enabled';
document.querySelector(selExt).style.background = '#F0F0F0';
document.querySelector(selCol).style.background = '#0F0F0F';

It just assigns new color for entire item (not for each state separately like in HTML).

As to ::-webkit selector I can’t find it in style list at all.

let sel = '-webkit-slider-runnable-track';
document.querySelector('#red').style[sel]; // null

How ta mange all these conplicated attributes in JavaScript?
Thanks.

React Native app can’t access HTTP URL on Android – what’s wrong?

I’m trying to access this data source from a React Native app. The URL works great in a browser and in the RN app on iOS but not on Android where I just get a “fetch network error” with no HTTP response codes etc.

Here’s a demo project
https://snack.expo.dev/PyhI0-ifGaoh0SygD_xIV?platform=android

What can be wrong since Android can’t access this endpoint which seems to be a regular HTTPS GET operation that returns some JSON? Can anyone see what’s going wrong?

I tried to inspect the network traffic with Charles Proxy, which didn’t give me any clues but at least I can see that the requests are being sent from the device.

But I also noticed, that when running trough Charles Proxy with SSL enabled and a certificate installed, the above data source does not respond properly, not even on iOS or in a browser on MacOS. It seems like the response is being delivered, but the connection is not closed, and the client keeps waiting for it.

I’m not sure if this issue is related to the issue of RN apps on Android which cannot access the URL, but this is the only data source I’ve ever experienced that was not accessible through Charles Proxy and also not trough RN apps on Android?

response hanging forever when Charles inspects the SSL traffic

Scraping a web element requested after load

I’m trying to scrape a web and I need to get the link of a video. The problem is those videos are loaded afterward, so I can’t get the url from the HMTL using Jsoup. I can see the request information when viewing the chrome’s inspector > Network > Media > Headers of the video element (I think this is what I’m looking for, because the resquet url that appears is what I need). I don’t know how to get that url. I think I could mimic the request or something like that, but I don’t know to do that. Is there a way to get it?

I tried loading the page on an Android Studios’ WebView and getting the elements by js and didn’t work. Also catching every response after loaded and I can get the ad videos that way, but not the video url that I need.

Retrieving authentication feature flags from backend to use in client side? Bad idea?

currently working on an application that has security implementations on the backend of an Express application. For general testing purposes, I am thinking of implementing a feature flag on the backend side, to disable all security. Because the client-side app also has related security features (tokens), I am thinking about having an endpoint that retrieves whether the feature flag is set in the backend to see whether or not to disable security features in the client application.

This would eliminate the need for creating a separate feature flag on the front-end side, since I would only want security on both sides to be disabled altogether, or not at all. However, am worried about potential security concerns with this approach. Can anyone offer a second opinion on this? Thanks.

My code has random lag spike that freeze the program [closed]

I am making a Javascript 2d platformer and when adding scrolling based on player position it started to have random lag spikes I don’t know why but I would like to stop it. Any help would be appreciated. Attached is the code:

const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const Player = new Image();
const gameTiles = new Image();
//this is the source to the tile map's original image
gameTiles.src = 'gameTiles.png';
//this is the source to the character's animation map
Player.src = 'Character.png';
let keys = {
  right: false,
  left: false,
  up: false,
  down: false,
  c: false,
  e: false,
  w: false,
  a: false,
  s: false,
  d: false
};
let gravity = 1.2;
let drag = 1.5;
let levelMax = 50;
//this is the tile map it specifies what to draw and where
let mapx = [
  6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
let mapy = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
]
let camera = {
  x: 0,
  y: 0,
  px: 0,
  py: 0
};
//controls camera movement
function cameraControl() {
  if (camera.x > ((levelMax * 32) - 1280)) {
    camera.x = ((levelMax * 32)) - 1280;
  }
  if (camera.x < 0) {
    camera.x = 0
  }
  if (camera.y < 0) {
    camera.y = 0;
  }
  if (camera.y > ((levelMax * 32) - 640)) {
    camera.y = ((levelMax * 32) - 640)
  }
}
let lastBox = 0;
let box = [];

function level() {
  for (j = 0; j < levelMax; j++) {
    for (i = 0; i < levelMax; i++) {
      ctx.fillStyle = "lime";
      box.push({
        X: (32 * i),
        Y: 608 - (j * 32),
        Width: 32,
        Height: 32
      });
      arrayIndex = (j * levelMax) + i;
      bobX = box[arrayIndex].X;
      bobY = box[arrayIndex].Y;
      tilex = 32 * mapx[arrayIndex];
      tiley = 32 * mapy[arrayIndex];
      if ((bobX - camera.x) >= -32 && (bobX - camera.x) <= 1280 && (bobY + camera.y) >= -32 && (bobY + camera.y) <= 640) {
        ctx.drawImage(gameTiles, tilex, tiley, 32, 32, (bobX - camera.x), (bobY + camera.y), 32, 32);
      }
    }
  }
  //this is what I came up with when trying to make sure player doesn't move offscreen while
  //also making sure the level still scrolls. I think its the source of the lag, any ideas on
  //how to optimize?
  if (camera.x >= ((levelMax * 32) - 1280)) {
    camera.px = 0;
    if (camera.x <= 0) {
      camera.px = 0;
    } else {
      if (player.x < 224) {
        camera.x -= 5;
        camera.px = 5;
      } else {
        camera.px = 0;
      }
    }
  } else {
    if (player.x >= 1024) {
      camera.x += 5;
      camera.px = -5;
    } else {
      if (camera.x <= 0) {
        camera.px = 0;
      } else {
        if (player.x < 224) {
          camera.x -= 5;
          camera.px = 5;
        } else {
          camera.px = 0;
        }
      }
    }
  }
  player.x += camera.px;
  player.y += camera.py;
  if (player.x < 0) {
    player.x = 0;
  }
  if (player.x > 1248) {
    player.x = 1248;
  }
}
//recognizes key movements
function keydown(e) {
  //w
  if (e.keyCode == 87) {
    keys.w = true;
  }
  //a
  if (e.keyCode == 65) {
    keys.a = true;
  }
  //s
  if (e.keyCode == 83) {
    keys.s = true;
  }
  //d
  if (e.keyCode == 68) {
    keys.d = true;
  }
  // 37 is the code for thr left arrow key
  if (e.keyCode == 37) {
    keys.left = true;
  }
  // 39 is the code for the right arrow key
  if (e.keyCode == 39) {
    keys.right = true;
  }
  //up key
  if (e.keyCode == 38) {
    keys.up = true;
  }
  //down key
  if (e.keyCode == 40) {
    keys.down = true;
  }
  if (e.keyCode == 67) {
    keys.c = true;
  }
  if (e.keyCode == 69) {
    keys.e = true;
  }
}

function keyup(e) {
  //w
  if (e.keyCode == 87) {
    keys.w = false;
  }
  //a
  if (e.keyCode == 65) {
    keys.a = false;
  }
  //s
  if (e.keyCode == 83) {
    keys.s = false;
  }
  //d
  if (e.keyCode == 68) {
    keys.d = false;
  }
  if (e.keyCode == 37) {
    keys.left = false;
  }
  if (e.keyCode == 39) {
    keys.right = false;
  }
  if (e.keyCode == 38) {
    keys.up = false;
  }
  if (e.keyCode == 40) {
    keys.down = false;
  }
  if (e.keyCode == 67) {
    keys.c = false;
  }
  if (e.keyCode == 69) {
    keys.e = false;
  }
}
//defines player
let player = {
  height: 224,
  width: 139,
  x: 0,
  y: 0,
  stateX: 0,
  stateY: 0,
  jumping: false,
  velocityX: 0,
  velocityY: 0,
  row: 0,
  collumn: 0,
  Idx: 0
};
let gameFrame = 0;
const staggerFrame = 5;
//controls player animations
function animatePlayer() {
  if (keys.d) {
    if (gameFrame % staggerFrame == 0) {
      player.stateY = 0;
      if (player.stateX > 4) {
        player.stateX = 4;
      }
      player.stateX--;
      if (player.stateX < 2) {
        player.stateX = 4;
      }
    }
    if (keys.w) {
      if (gameFrame % staggerFrame == 0) {
        player.stateX = 7;
      }
    }
    if (keys.a) {
      if (gameFrame % 5 == 0) {
        player.stateX = 0;
      }
    }
  } else {
    if (keys.a) {
      if (gameFrame % staggerFrame == 0) {
        player.stateY = 0;
        if (player.stateX > 13) {
          player.stateX = 13;
        }
        player.stateX--;
        if (player.stateX < 11) {
          player.stateX = 13;
        }
      }
      if (keys.w) {
        if (gameFrame % staggerFrame == 0) {
          player.stateX = 8;
        }
      }
      if (keys.d) {
        if (gameFrame % staggerFrame == 0) {
          player.stateX = 0;
        }
      }
    } else {
      if (keys.w) {
        if (gameFrame % staggerFrame == 0) {
          player.stateY = 1;
          player.stateX = 2;
        }
      } else {
        if (gameFrame % 5 == 0) {
          player.stateX = player.stateX + 0.03125;
          if (player.stateX > 1.3125) {
            player.stateX = 0;
          }
          player.stateY = 0;
        }
      }
    }
  }
  if (player.jumping) {
    if (keys.d) {
      player.stateY = 0;
      player.stateX = 7;
    } else {
      if (keys.a) {
        player.stateY = 0;
        player.stateX = 8;
      } else {
        player.stateY = 1;
        player.stateX = 2;
      }
    }
  }
}
//controls player's movements
function playerControls() {
  if (keys.a) {
    player.x += -5;
  }
  if (keys.d) {
    player.x += 5;
  }
  if (keys.w) {
    if (player.jumping == false) {
      player.velocityY = -20;
      player.jumping = true;
    }
  }
  //jump
  if (player.jumping == true) {
    player.y += player.velocityY;
    player.velocityY += gravity;
  }
}
//checks player's array index compared to blocks to check for collision (currently only supports
//top collision
function playerCollision() {
  player.row = Math.floor((player.x + (player.width / 2)) / 32);
  player.collumn = Math.floor((640 - (player.y + 48)) / 32);
  player.Idx = player.row + (player.collumn * levelMax);
  if (mapy[player.Idx] < 5) {
    player.y -= 10;
    player.jumping = false;
  }
}

function loop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  level();
  if (mapx[arrayIndex] == undefined) {
    mapx[arrayIndex] = 0;
  }
  if (mapy[arrayIndex] == null) {
    mapy[arrayIndex] = 0;
  }
  animatePlayer();
  playerControls();
  cameraControl();
  playerCollision();
  ctx.drawImage(Player, (Math.floor(player.stateX) * player.width), (player.stateY * player.height), player.width, player.height, player.x, player.y, (player.width / 4), (player.height / 4));
  gameFrame++;
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
document.addEventListener("keydown", keydown);
document.addEventListener("keyup", keyup);

I’ve tried to remove the player’s camera control but it didn’t fix it, (though it did reduce the lag). is it just to many if statements? If so how can I optimize it?

Swiper.js have offset looped slides prepended sooner?

I have a looping image slider built with Swiper. I also have an offset on the slides so that you can see the slides that come before and after the active slide with the opacity dialed back. Everything is working great, except for the timing of the final slide being prepended to the slider wrapper when navigating backwards.

$(function() {
  var swiper = new Swiper(".mySwiper", {
    direction: 'horizontal',
    slidesOffsetBefore: 100,
    slidesPerView: 'auto',
    spaceBetween: 30,
    initialSlide: 1,
    centeredSlides: false,
    loop: true,
    grabCursor: true,
    observer: true,
    observeParents: true,
    navigation: {
        nextEl: '.swiper-next',
        prevEl: '.swiper-prev'
    }
  });
});
html,
body {
  position: relative;
  height: 100%;
}

body {
  display:flex;
  align-items:center;
  background: #eee;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 14px;
  color: #000;
  margin: 0;
  padding: 0;
}

.container {
  height:300px;
}

.swiper {
  width: 100%;
  height: 100%;
  overflow:visible!important;
}

.swiper-slide {
  text-align: center;
  font-size: 18px;
  background: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
  max-width:50%;
  opacity:.5;
}

.swiper-slide-active {
  opacity:1;
}
<link href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>

<div class="container">
  <div class="swiper mySwiper">
    <div class="swiper-wrapper">
      <div class="swiper-slide">Slide 1</div>
      <div class="swiper-slide">Slide 2</div>
      <div class="swiper-slide">Slide 3</div>
      <div class="swiper-slide">Slide 4</div>
      <div class="swiper-slide">Slide 5</div>
      <div class="swiper-slide">Slide 6</div>
      <div class="swiper-slide">Slide 7</div>
      <div class="swiper-slide">Slide 8</div>
      <div class="swiper-slide">Slide 9</div>
    </div>
    <div class="swiper-nav">
        <button class="swiper-prev">Previous</button>
        <button class="swiper-next">Next</button>
    </div>
  </div>
</div>

I am using initialSlide: 1 to start with the 2nd slide being active. This works for the initial load, but if you hit “previous” it gets back to a point where you cannot see a slide to the left of the active one, because it hasn’t yet been prepended to the swiper-wrapper element. Is there a way around this?

Initial desired look:
enter image description here

And once you click previous making the first slide active:
enter image description here

Duplicating request when trying again – React Native

I have a function that, if it fails to send the request to the backend, retries it for a period of “N” times.

example:

async function handleNonMesaPayment(paymentResponse: PaymentResponse, attempt = 1) {
  await router.setParams({ res: null });

  const { data: user, orderId } = await fetchUserData();
  const id = ${user.cpfCnpj}-${user.serial};
  const maxAttempts = 15;

  const sendRequest = (id, orderId, paymentResponse) => {
    return Promise.race([
      new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.addEventListener('readystatechange', () => {
          if (xhr.readyState !== 4) {
            return;
          }

          if (xhr.status === 201) {
            resolve(xhr.responseText);
          } else {
            reject(new Error(Request failed with status ${xhr.status}));
          }
        });

        xhr.open('POST', ${user.connection}/test/sse/send);
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.setRequestHeader('Authorization', Bearer token);

        xhr.send(JSON.stringify({ id, order_id: orderId, data: paymentResponse }));
      }),
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Tempo excedido alcançado')), 7500)
      ),
    ]);
  };

  const handleResponse = async () => {
    try {
      await sendRequest(id, orderId, paymentResponse);
      if (responsePaymentValid(paymentResponse)) {
        toast('Pagamento realizado com sucesso!', 'success', 3000);
      } else {
        toast('Não foi possível realizar a transação, verifique o sistema para mais detalhes!', 'destructive', 6000);
      }
    } catch (error) {
      // Tenta novamente se a requisição falhar
      if (attempt < maxAttempts) {
        toast(Não foi possível enviar pedido ao servidor. Tentando novamente em 2 segundos... (${attempt}), 'destructive', 4000);
        await new Promise((resolve) => setTimeout(resolve, 2000));
        await handleNonMesaPayment(paymentResponse, attempt + 1);
      } else {
        toast('Todas as tentativas falharam. Por favor, verifique a conexão.', 'destructive', 6000);
      }
    }
  };

  await handleResponse();
}

However, I noticed that it is creating somewhat of a queue, because when the connection is restored, it sends all the attempts to the backend. For example, if it tried to send 3 times, when the connection comes back, the backend receives 3 requests. What could be the issue? I had to use XMLHttpRequest because fetch and axios did not satisfy my scenario.

Only the last attempt should be sent to the backend.

Why can’t I save notes in a multi-entry journal setup using jQuery and AJAX?

I’m working on a web application where users can maintain multiple journal entries on a single page. Each entry allows users to add notes through a Markdown editor. The problem is

const notesContent = notesDisplay.find('.markItUp').val();

is returning null when it should be getting the content inside the textarea when clicking save

full code:

@foreach($notes as $note)
    <div class="notes-display">
        <div class="notes-text">{!! $note->Daily_Notes_HTML !!}</div>
        <button class="add-notes-button" onclick="showEditor(this)">Add Notes</button>
        
        <div class="editor-container" style="display: none;">
            Notes:
            <textarea class="markItUp" style="padding: 5px; width: 100%; height: 225px; box-sizing: border-box;"></textarea>
            <button class="save-notes-button">Save</button>
            <button onclick="cancelEditor(this)">Cancel</button>
        </div>
        
        <input type="hidden" class="notes-markdown" value="{{ $note->Daily_Notes }}">
        <input type="hidden" class="journal-date" value="{{ $note->Date_of_Day }}">
    </div>
@endforeach

<script type="text/javascript">
function showEditor(button) {
    const notesDisplay = $(button).closest('.notes-display');
    notesDisplay.find('.add-notes-button').hide();
    notesDisplay.find('.notes-text').hide();
    notesDisplay.find('.editor-container').show();

    notesDisplay.find('.markItUp').val(notesDisplay.find('.notes-markdown').val());
    // Initialize editor...

    notesDisplay.find('.save-notes-button').off('click').on('click', function() {
        const notesContent = notesDisplay.find('.markItUp').val(); // Current notes content
        const journalDate = notesDisplay.find('.journal-date').val(); // Journal date

        $.ajax({
            url: "{{ route('journal.saveNotes', ':date') }}".replace(':date', journalDate),
            type: 'POST',
            data: {
                notes: notesContent,
                _token: '{{ csrf_token() }}'
            },
            success: function(response) {
                if (response.html) {
                    notesDisplay.find('.notes-text').html(response.html);
                }
                alert(response.message); 
                cancelEditor(button);
            },
            error: function(xhr) {
                console.log(xhr.responseText);
                alert('An error occurred while saving the notes. Please try again.');
            }
        });
    });
}
</script>

Issues with WebRTC Transceivers: Unexpected Creation of recvonly Transceiver

I am trying to establish a WebRTC connection between two peers using transceivers to manage audio streams. I have a scenario where I expect each peer to have a single transceiver with the sendrecv direction. However, when I run the following code, I observe that the remoteConnection ends up with two transceivers: one sendrecv transceiver with a mid of null and an additional recvonly transceiver.

Here’s the code I’m using:

<!DOCTYPE html>
<html>
  <body>
    <h2>WebRTC with Transceivers on Both Peers</h2>
    <button onclick="startConnection()">Start Connection</button>

    <script>
      let localConnection;
      let remoteConnection;
      let localStream;
      let remoteStream;

      async function startConnection() {
        // Step 1: Get audio from user's microphone for the local peer
        localStream = await navigator.mediaDevices.getUserMedia({ audio: true });

        // Step 2: Create local and remote peer connections
        localConnection = new RTCPeerConnection();
        remoteConnection = new RTCPeerConnection();

        // Step 3: Handle ICE candidates for both peers
        localConnection.onicecandidate = (e) => {
          if (e.candidate) remoteConnection.addIceCandidate(e.candidate);
        };
        remoteConnection.onicecandidate = (e) => {
          if (e.candidate) localConnection.addIceCandidate(e.candidate);
        };

        // Step 4: Create transceivers on both peers
        // Local peer adds audio transceiver with 'sendrecv' direction
        const localTransceiver = localConnection.addTransceiver('audio', {
          direction: 'sendrecv',
        });
        localTransceiver.sender.replaceTrack(localStream.getAudioTracks()[0]);

        // Remote peer also adds a transceiver for symmetry
        const remoteTransceiver = remoteConnection.addTransceiver('audio', { direction: 'sendrecv' });
        
        remoteTransceiver.sender.replaceTrack(localStream.getAudioTracks()[0]);

        // Step 5: Handle remote stream received on the remote peer
        remoteStream = new MediaStream();
        remoteConnection.ontrack = (event) => {
          remoteStream.addTrack(event.track);
          console.log('Received remote audio track');
          // Attach remoteStream to an audio element to play the received audio
          const remoteAudio = document.createElement('audio');
          remoteAudio.srcObject = remoteStream;
          remoteAudio.autoplay = true;
          document.body.appendChild(remoteAudio);
        };
        
        const offerj = await remoteConnection.createOffer();
        await remoteConnection.setLocalDescription(offerj);

        // Step 6: Start the offer/answer negotiation process
        const offer = await localConnection.createOffer();
        await localConnection.setLocalDescription(offer);
        await remoteConnection.setRemoteDescription(offer);

        const answer = await remoteConnection.createAnswer();
        await remoteConnection.setLocalDescription(answer);
        await localConnection.setRemoteDescription(answer);
         
        console.log(localConnection.getTransceivers())
        console.log(remoteConnection.getTransceivers())

        console.log('WebRTC connection established with transceivers on both sides.');
      }
    </script>
  </body>
</html>

Observations:
After running the code, I expected to see one sendrecv transceiver on both localConnection and remoteConnection.
Instead, remoteConnection ends up with two transceivers:

  • One sendrecv transceiver with a mid that is null.
  • One recvonly transceiver that appears to be automatically created during the negotiation process.

Questions:

  • What causes the additional recvonly transceiver to be created in remoteConnection?
  • How can I ensure that there is only one sendrecv transceiver on both peers as intended?
    Expected Behavior:
    I expect both peers to have one sendrecv transceiver, allowing them to send and receive audio seamlessly.

Any insights or suggestions for resolving this issue would be greatly appreciated!

Rewriting GET calls from JS script from an other JS script

I’m trying to convert some (old) dynamic sites to static pages. So far it works fine but I have some included JS scripts that build URL to load some other JS files, and the URL building is complicated (so just searching and replacing URL don’t work, and I want a generic method).

I want to “rewrite” the GET calls performed from the JS in order to map them to my own (local) URL.

I tried using this script (added at the begin of the HTML source)

console.log("Script loaded");
// Override the open method of XMLHttpRequest to intercept all requests
var originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
  console.log("Intercepted HTTP request: " + method + " " + url);
  originalOpen.apply(this, arguments);
};
console.log("Script end");

in a script that is loaded at first in the HTML source. In the console (from webtools in Firefox) I can see the “Script loaded” at the begin of logs (and the “Script end”), but the “GET <url>” in the network logs associated later to the JS script are not intercepted.

I also tried

console.log("Script loaded");
const nativeFetch = window.fetch
window.fetch = async (input, init) => {
  const request = new Request(input, init);
  console.log("request happened!", request.method, request.url);
  return nativeFetch(input, init)
}
console.log("Script end");

with the same (lack) of result.
Note: both scripts come from StackOverflow posts.

In the network console these GET look like:

(method) GET | (domain) <hostname> | (file) <URL path> | (initiator) <the infamous JS script> 

Both scripts don’t log the console.log() directives, so it seems that nothing is intercepted.

Did I miss something? What is the right way to perform this? (if it is possible)

What I expect: [edit] I want to get a call to my JS function each time a script starts a GET call in order to intercept – and modify – the call to my own URL (that I still know).