TypeError: Cannot destructure property ‘user’ of ‘(0 , i.a)(…)’ as it is undefined

Im a trying to create a nextjs app and works perfectly fine in dev mode but when i try to build it it gives me the error the code related to the user is as follows

"use client"

import React, { createContext, useContext, useState, useEffect } from 'react';
import { auth } from '@/libs/firebase';  
import { onAuthStateChanged } from 'firebase/auth';  

// Create the User Context
const UserContext = createContext();

// Provider component
export const UserProvider = ({ children }) => {
    const [user, setUser] = useState(null);

    useEffect(() => {
        // Set up an observer to track the user's authentication state
        const unsubscribe = onAuthStateChanged(auth, (user) => {
            setUser(user);
        });

        return () => unsubscribe(); // Cleanup the observer on unmount
    }, []);


    return (
        <UserContext.Provider value={{ user, setUser }}>
            {children}
        </UserContext.Provider>
    );
};

// Custom hook to use the UserContext
export const useUser = () => useContext(UserContext);

this a UserContext.js

'use client';
import Home_page from "@/pages/Home";
import Landing_page from "@/pages/Front";
import Loader from "@/components/Loader";
import {useUser} from "@/context/UserContext";

export default function Home() {
    const { user } = useUser() || {};

    if (!user) {
        return <Loader />;
    }

    return user ? <Home_page /> : <Landing_page />;
}

this is app/page.jsx

'use client';
import Home_page from "@/pages/Home";
import Landing_page from "@/pages/Front";
import Loader from "@/components/Loader";
import {useUser} from "@/context/UserContext";

export default function Home() {
    const { user } = useUser() || {};

    if (!user) {
        return <Loader />;
    }

    return user ? <Home_page /> : <Landing_page />;
}

this is app/layout.tsx

all the other components where useUser is used

"use client";
import {auth} from "@/libs/firebase";
import {useRouter} from "next/navigation";
import {useUser} from "@/context/UserContext";
import {useState} from "react";
import userIcon from "@/public/userIcon.svg"
import logo from "@/public/logo.png"
import { IconFileInvoice } from '@tabler/icons-react'
import { Sidebar, SidebarBody, SidebarLink } from "@/components/AceternityUi/sidebar";
import { IconArrowLeft, IconBrandTabler} from "@tabler/icons-react";
import Link from "next/link";
import { motion } from "framer-motion";
import Image from "next/image";
import { cn } from "@/libs/utils";
import { signOut } from "firebase/auth";


export function SideNav({setPanel}) {

    const { user } = useUser();

    const router = useRouter();

    const links = [
        {
            label: "Dashboard",
            icon: (
                <IconBrandTabler className="text-neutral-700 dark:text-neutral-200 h-5 w-5 flex-shrink-0" />
            ),
            onClick: () => setPanel("Dashboard"),
        },
        // {
        //     label: "Clients",
        //     icon: (
        //         <IconUserBolt className="text-neutral-700 dark:text-neutral-200 h-5 w-5 flex-shrink-0" />
        //     ),
        //     onClick: () => setPanel("Clients"),
        // },
        {
            label: "Cases",
            icon: (
                <IconFileInvoice className="text-neutral-700 dark:text-neutral-200 h-5 w-5 flex-shrink-0" />
            ),
            onClick: () => setPanel("Cases"),
        },
        {
            label: "Logout",
            icon: (
                <IconArrowLeft className="text-neutral-700 dark:text-neutral-200 h-5 w-5 flex-shrink-0" />
            ),
            onClick: async () => {
                try {
                    await signOut(auth);
                    router.push("/");
                } catch (error) {
                    console.error("Logout failed: ", error.message);
                }
            },
        },
    ];


    const [open, setOpen] = useState(false);


    return (
        <div
            className={cn(
                " flex flex-col md:flex-row   ",
                "h-full " // for your use case, use `h-screen` instead of `h-[60vh]`
            )}
        >
            <Sidebar open={open} setOpen={setOpen}>
                <SidebarBody className="justify-between gap-10 w-full">
                    <div className="flex flex-col  ">
                        {open ? <Logo /> : <LogoIcon />}
                        <div className="mt-8 flex flex-col gap-2 ">
                            {links.map((link, idx) => (
                                <SidebarLink key={idx} link={link} onClick={()=>link.onClick()} />
                            ))}
                        </div>
                    </div>
                    <div>
                        <SidebarLink
                            link={{
                                label: user
                                    ? user.displayName || user.username || "user"
                                    : "Guest",
                                href: "#",
                                icon: (
                                    <Image
                                        src={user?.photoURL || userIcon}
                                        className="h-7 w-7 flex-shrink-0 rounded-full"
                                        width={50}
                                        height={50}
                                        alt="Avatar"
                                    />
                                ),

                            }}
                        />
                    </div>
                </SidebarBody>
            </Sidebar>
        </div>
    );
}
export const Logo = () => {
    return (
        <Link
            href="#"
            className="font-normal hidden space-x-2 gap-x-6 items-center text-sm text-black py-1 relative z-20 md:flex"
        >
            <Image src={ logo } alt={"logo"} className={"w-[40px]"} />
            <motion.span
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                className="font-medium text-black dark:text-white whitespace-pre"
            >
                Lawyers Dairy
            </motion.span>
        </Link>
    );
};
export const LogoIcon = () => {
    return <>
        <Image src={logo} alt={"logo"} className={"w-[40px] md:block hidden"} />
    </>
};


components/Sidebar.jsx

"use client";
import { useState } from "react";
import { useUser } from "../../context/UserContext";
import { db } from "../../libs/firebase";
import { collection, query, where, getDocs, getDoc, deleteDoc, updateDoc, arrayRemove, doc } from "firebase/firestore";
import { Message } from "rsuite";

const DeleteCase = () => {
    const { user } = useUser() || {};
    .....

this is DeleteCase/page.js

"use client";
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { FaArrowLeft } from "react-icons/fa";
import { useForm } from "react-hook-form";
import { useUser } from "@/context/UserContext";
import { db } from "@/libs/firebase";
import { collection, doc, setDoc, query, where, getDocs, updateDoc, arrayUnion } from "firebase/firestore";
import Defaultloader from "../../components/Loader";
import { Loader, Message } from "rsuite"; // Import Loader and Message components from rsuite

export default function AddCaseForm() {
    const router = useRouter();
    const { user } = useUser() || {};
    const { register, handleSubmit, reset, formState: { errors } } = useForm();
    const [caseExists, setCaseExists] = useState(false);
    const [PageLoading, setPageLoading] = useState(true);
    const [loading, setLoading] = useState(false);
    const [message, setMessage] = useState("");
    const [messageType, setMessageType] = useState("error");

    // Redirect to log in if no user is logged in
    useEffect(() => {
        if (!user) {
            router.push("/"); // Redirect to login if user is not authenticated
        } else {
            setPageLoading(false); // Set PageLoading to false once the user is authenticated
        }
    }, [user, router]);

this is AddCase/page.js

Backend API calls not working in production build

I have a project I have been working on with ReactJS for the front end and Flask for the backend. It was working all fine in dev mode, but then I decided to test in prod mode using npm run build and serve -s build. That’s when I noticed that none of my backend APIs in flask are getting called. The way I am calling them is like so:

const response = await fetch("http://localhost:3000/signup", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(userData),
});

and I thought the error may be because of the localhost 3000 port I am using in the code. However, when I start the production server it is also on localhost port 3000 so it should still be able to call those APIs? I’m confused as to why these APIs are no longer getting called from the backend in prod mode and would appreciate any help. The specific problem I am trying to solve is understanding why api calls are not working from my flask backend when running my web app in production mode. As far as stack trace and errors, I see in the Chrome dev tools console I get this error: Uncaught (in promise) SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON and then looking in the networks tab it shows a “request initiator chain” with my localhost route: http://localhost:3000/``signup. As far as the stack in the console, I see the HTTP GET calls being returned with a response of 304.

What I tried so far is to try to see if it could be with the localhost 3000 port after doing some research, but I dont know if this is it because of the fact that the prod server also opens up in port 3000. I also added this to my flask file:
CORS(app, resources={r"/*": {"origins": "*"}}) but that did not seem to help either.

Pentaho – Trim Spaces Dynamically

Bit of a pentaho rookie so let me know if this does not make sense / is not possible:

I am trying to export the contents of a table from my database to a .csv where the columns can change overtime.

Currently, I am using a ‘Table input’ step followed by a ‘Text file output step’. This works to export the file, however it exports the entire field of each column in the select statement:

What I expect in the ID column (10): ‘ABCD’

What I am getting in my current setup for ID column (10): ‘ABCD______’

How can I trim the fields dynamically based on the output of the select statement?

I have tried using a ‘Modified JavaScript value’ step so far but I’m not 100% sure how this interacts with it all and hasn’t seemed to work as it cannot recognise the rows.

var fieldNames = getInputRowMeta().getFieldNames(); // Get field names dynamically

for (var i = 0; i < fieldNames.length; i++) {
    var fieldName = fieldNames[i];  // Get the field name
    var fieldValue = get(fieldName); // Get the value of the field

    if (typeof fieldValue === 'string') {
        // Apply transformation (e.g., trimming)
        set(fieldName, fieldValue.trim());  // Set the modified value back
    }
}

Config

How do I make a working search bar function that connects to Mongoose Database and then it displays the info on a new page?

I am struggling right now with a personal project of mine. As of the moment, I am currently self learning Mongoose and Mongo DB, alongside Javascript and Ejs after finishing up a college course on mobile website design a couple months ago.

This is what my Website looks like so far:

Searchbar

I am trying to make my search bar feature work, but I am completely lost on how to do it. I have searched online resources such as Youtube for help, but they gave tutorials with complicated code that I don’t know how to apply to my own code.

Here’s my js file for the main page I am showing:

<%- include('./partials/header.ejs')%>

<main class = "gridMain"> 

    <div class = "movDescription item1"> 

        <h1 style = "color: red"> TCGCards.com </h1>
        <p> <b> Power to the People! </b> </p>
        <p>  
            
            <b>A marketplace for trading card collectors, and players. Here, we have a great and varied selection of 
            trading card goods from all over the world! You will never find any better deals then this!
            </b>
        </p>

     <div class = "movSearchBar"> 

        <div class = "fixhyperlink li a">
          
          <!-- /cards means that you want to see something specific. -->
          <!--/cards/ means you want to see whole section.  -->

        <div class = "movText">
         <a href = "/cards">  Browse Our Collection </a>
        </div>
          


        </div>

    </div>

    </div>

    <div class = "movIndexImages">
    
    <img src = "/images/pokemon.jpg" alt = "pokemon" class = "item2">

    <img src = "/images/yugioh.jpeg" alt = "yugioh" class = "item3" > 

    <img src = "/images/magic-the-gathering-2-558x331(resized).jpg"  alt = "MGT" class = "item4">

</div>

</main>

<%- include('./partials/footer.ejs')%>

Here’s my app.js file:

const multer = require('multer');

const express = require('express');
const morgan = require('morgan');

const methodOverride = require('method-override');
const {upload} = require('./middleware/fileUpload');
const cardRoutes = require('./routes/cardRoutes');
const userRoutes = require('./routes/userRoutes');
const flash = require('connect-flash');
const session = require('express-session');
const MongoStore = require('connect-mongo');
const mongoose = require('mongoose');
const { MongoClient } = require('mongodb');
const User = require('./models/user');
const Card = require('./models/card');

// Allows user to define routes and mount middleware to manage the 'store'.
const app = express();

let port = 4004;
let host = 'localhost';
let url = 'mongodb+srv://sz2:[email protected]/Project5?retryWrites=true&w=majority&appName=Cluster0'

// Makes this the default folder.
app.set('view engine' , 'ejs');

const client = new MongoClient(url);


mongoose.connect(url)
  .then(() => {
    app.listen(port, host, () => {
      console.log('Server is running on port', port);
   });
  })

  .catch(err => console.log(err.message))


// Allows clients to see static content 
// ex: stylesheets, images, scripts
// When using /public in a route your doing /public/public/images. This is wrong.
app.use(express.static('public'));

// Makes submitted data accessible from form.
app.use(express.urlencoded({extended: true}));

// Logs info about requests to console.
app.use(morgan('tiny'));

app.use(methodOverride('_method'));



// app.get("/search/:key", async (req, res) => {

//    let data = await Card.find({
    
//     "$or": [
//         {name: {$regex: req.params.key}}
//     ]

//    })
// })

// When user navigates to home page, index.ejs will be shown
app.get('/' , (req, res) => {
     res.render('index.ejs');
});

app.get('/search', async (req, res) => {
  const query = req.query.q;
  if (!query) {
    return res.status(400).send('Search query is required');
  }

  try {
    const database = client.db('Project5'); // Replace with your database name
    const collection = database.collection('Card'); // Replace with your collection name

    const results = await collection.find({ name: { $regex: query, $options: 'i' } }).toArray();
    res.json(results);
  } catch (error) {
    console.error('Error executing search query', error);
    res.status(500).send('An error occurred while searching');
  }
});

// Is connected with the header partial ejs file.
//Keep track of what your testing often. If there is a node modules error look at app.use.

// Assigns extra directions to end of the main /cards/ folder path.
    //I.E: 
    //router.get('/show/:id', controller.show);
    //Now: '/cards' => '/cards/show/1'

    app.use(
      session({
          secret: "ajfeirf90aeu9eroejfoefj",
          resave: false,
          saveUninitialized: false,
          store: new MongoStore({mongoUrl: url}),
          cookie: {maxAge: 60*60*1000}
          })
  );


  app.use(flash());

app.use((req, res, next) => {
    res.locals.user = req.session.user || null;
    res.locals.errorMessages = req.flash('error');
    res.locals.successMessages = req.flash('success');
    next();
});

      app.post('/login', (req, res) =>{

        let email = req.body.email;
        let password = req.body.password;

        console.log(req.flash());

        model.findOne({ email: email })
        .then(user => {
            if (!user) {
                req.flash('error', 'wrong email address');  
                req.session.save(()=>res.redirect('/user/login'));
                } else {
                user.comparePassword(password)
                .then(result=>{
                    if(result) {
                        req.session.user = user._id;
                        req.flash('success', 'You have successfully logged in');
                        req.session.save(()=>res.redirect('/user/profile'));
                } else {
                    req.flash('error', 'wrong password'); 
                    req.session.save(()=>res.redirect('/user/login')); 
                }
                });     
            }     
        })
        .catch(err => next(err));
    });
    
    app.get('/profile' , (req, res, next) => {

      let id = req.session.user;
      console.log(req.flash());
      User.findById(id)
       .then(user => res.render('profile' , {user}))
       .catch(err => next(err));

    });


app.use('/cards', cardRoutes);

app.use('/user', userRoutes);

app.use((err, req, res, next)=>{
  console.log(err.stack);
  if(!err.status) {
      err.status = 500;
      err.message = ("Internal Server Error");
  }

  res.status(err.status);
  res.render('error', {error: err});
});

If anyone could give any pointers or direct me to good resources that could help me out, that be greatly appreciated! I am completely stuck right now and have been for a couple days already on how to get this done.

Thank you!

The Idea of this feature is that when I click the Enter button on my keyboard after typing in some words into the search bar, it would retrieve data stored from my database and display it on a new page containing all instances of that specific item’s name.

Selection tool for a drawing program – Rotation issue

I am trying to program a selection tool for a canvas element but I am having trouble with math I think…. Essentially I am having an issue where after rotating the selection, then trying to adjust/resize.. it no longer functions as it should. The opposite side of the part that you are using to adjust the selection should stay stationary while the rest adjust accordingly… but it’s not. Any help would be appreciated if you have the time to take a look 🙂

Thank you in advance!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Select and Modify Tool</title>
    <style>
        /* Body styling */
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background: #FFF;
            font-family: Arial, sans-serif;
            overflow: hidden;
        }
    
        /* Canvas container */
        #canvas, #overlayCanvas {
            display: block;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2), 0 6px 20px rgba(0, 0, 0, 0.19);
            border: 2px solid #ffffff;
            border-radius: 8px;
        }
    
        /* Set a fixed size for the canvases */
        canvas {
            width: 800px;
            height: 600px;
        }
    
        /* Overlay canvas styling */
        #overlayCanvas {
            z-index: 10;
            pointer-events: none; /* Ensures overlay canvas does not block interactions */
        }
    </style>     
</head>
<body>
    <canvas id="canvas" width="800" height="600"></canvas>
    <canvas id="overlayCanvas" width="800" height="600"></canvas>
    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');

        const overlayCanvas = document.getElementById('overlayCanvas');
        const overlayCtx = overlayCanvas.getContext('2d');

        let isSelecting = false;
        let isMoving = false;
        let isResizing = false;
        let isRotating = false;
        let resizeHandle = null;
        let rotationAngle = 0;
        let selection = null;
        let mouseDownCoords = null;
        let offset = { x: 0, y: 0 };

        let selectionImage = null; // Image of the selected content

        const resizeHandleSize = 10;
        const rotationHandleOffset = 30;

        // Initial canvas drawing
        ctx.fillStyle = 'blue';
        ctx.beginPath();
        ctx.arc(400, 300, 100, 0, Math.PI * 2);
        ctx.fill();

        canvas.addEventListener('mousedown', (e) => {
            const x = e.offsetX;
            const y = e.offsetY;

            if (!selection) {
                isSelecting = true;
                mouseDownCoords = { x, y };
                selection = {
                    x: mouseDownCoords.x,
                    y: mouseDownCoords.y,
                    width: 0,
                    height: 0,
                    originalWidth: 0,
                    originalHeight: 0
                };
            } else {
                const handle = getHandleUnderCursor(x, y);
                if (handle) {
                    isResizing = true;
                    resizeHandle = handle;
                    selection.originalWidth = selection.width;
                    selection.originalHeight = selection.height;
                } else if (isInsideSelection(x, y)) {
                    isMoving = true;
                    offset = {
                        x: x - selection.x,
                        y: y - selection.y,
                    };
                } else if (isRotationHandle(x, y)) {
                    isRotating = true;
                    const centerX = selection.x + selection.width / 2;
                    const centerY = selection.y + selection.height / 2;
                    rotationAngle = Math.atan2(y - centerY, x - centerX);
                } else {
                    applySelection();
                }
            }
        });

        canvas.addEventListener('mousemove', (e) => {
            if (isSelecting) {
                updateSelection(mouseDownCoords.x, mouseDownCoords.y, e.offsetX, e.offsetY);
            } else if (isMoving) {
                moveSelection(e.offsetX, e.offsetY);
            } else if (isResizing) {
                resizeSelection(e.offsetX, e.offsetY);
            } else if (isRotating) {
                rotateSelection(e.offsetX, e.offsetY);
            }
        });

        canvas.addEventListener('mouseup', () => {
            if (isSelecting) {
                finalizeSelection();
            }
            isSelecting = isMoving = isResizing = isRotating = false;
            resizeHandle = null;
        });

        function updateSelection(startX, startY, currentX, currentY) {
            selection.x = Math.min(startX, currentX);
            selection.y = Math.min(startY, currentY);
            selection.width = Math.abs(startX - currentX);
            selection.height = Math.abs(startY - currentY);

            drawOverlay();
        }

        function moveSelection(x, y) {
            selection.x = x - offset.x;
            selection.y = y - offset.y;
            drawOverlay();
        }

        function resizeSelection(x, y) {
            const centerX = selection.x + selection.width / 2;
            const centerY = selection.y + selection.height / 2;

            // Transform the mouse position into the selection's local space
            const { x: localX, y: localY } = getRotatedPoint(x, y, -rotationAngle, centerX, centerY);

            // Adjust selection bounds based on the handle being used
            if (resizeHandle === 'top') {
                const bottomY = selection.y + selection.height; // Keep the bottom stationary
                selection.y = Math.min(localY, bottomY);
                selection.height = Math.abs(bottomY - selection.y);
            } else if (resizeHandle === 'bottom') {
                const topY = selection.y; // Keep the top stationary
                selection.height = Math.abs(localY - topY);
            } else if (resizeHandle === 'left') {
                const rightX = selection.x + selection.width; // Keep the right stationary
                selection.x = Math.min(localX, rightX);
                selection.width = Math.abs(rightX - selection.x);
            } else if (resizeHandle === 'right') {
                const leftX = selection.x; // Keep the left stationary
                selection.width = Math.abs(localX - leftX);
            } else if (resizeHandle === 'top-left') {
                const bottomRight = { x: selection.x + selection.width, y: selection.y + selection.height }; // Keep bottom-right stationary
                selection.x = Math.min(localX, bottomRight.x);
                selection.y = Math.min(localY, bottomRight.y);
                selection.width = Math.abs(bottomRight.x - selection.x);
                selection.height = Math.abs(bottomRight.y - selection.y);
            } else if (resizeHandle === 'top-right') {
                const bottomLeft = { x: selection.x, y: selection.y + selection.height }; // Keep bottom-left stationary
                selection.y = Math.min(localY, bottomLeft.y);
                selection.width = Math.abs(localX - bottomLeft.x);
                selection.height = Math.abs(bottomLeft.y - selection.y);
            } else if (resizeHandle === 'bottom-left') {
                const topRight = { x: selection.x + selection.width, y: selection.y }; // Keep top-right stationary
                selection.x = Math.min(localX, topRight.x);
                selection.height = Math.abs(localY - topRight.y);
                selection.width = Math.abs(topRight.x - selection.x);
            } else if (resizeHandle === 'bottom-right') {
                const topLeft = { x: selection.x, y: selection.y }; // Keep top-left stationary
                selection.width = Math.abs(localX - topLeft.x);
                selection.height = Math.abs(localY - topLeft.y);
            }

            // No need to adjust the center of rotation because the opposite side is stationary
            drawOverlay();
        }
        
        function rotateSelection(x, y) {
            const centerX = selection.x + selection.width / 2;
            const centerY = selection.y + selection.height / 2;
            const angle = Math.atan2(y - centerY, x - centerX);
            rotationAngle = angle + Math.PI / 2;

            drawOverlay();
        }

        function finalizeSelection() {
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = selection.width;
            tempCanvas.height = selection.height;
            const tempCtx = tempCanvas.getContext('2d');

            tempCtx.drawImage(
                canvas,
                selection.x,
                selection.y,
                selection.width,
                selection.height,
                0,
                0,
                selection.width,
                selection.height
            );

            selectionImage = new Image();
            selectionImage.src = tempCanvas.toDataURL();

            drawOverlay();
        }

        function applySelection() {
            if (selection) {
                ctx.save();
                ctx.translate(selection.x + selection.width / 2, selection.y + selection.height / 2);
                ctx.rotate(rotationAngle);

                if (selectionImage) {
                    ctx.drawImage(
                        selectionImage,
                        0,
                        0,
                        selectionImage.width,
                        selectionImage.height,
                        -selection.width / 2,
                        -selection.height / 2,
                        selection.width,
                        selection.height
                    );
                }

                ctx.restore();

                selectionImage = null;
                selection = null;
                rotationAngle = 0;
                overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
            }
        }

        function drawOverlay() {
            overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);

            if (selection) {
                overlayCtx.save();
                overlayCtx.translate(selection.x + selection.width / 2, selection.y + selection.height / 2);
                overlayCtx.rotate(rotationAngle);

                if (selectionImage) {
                    overlayCtx.drawImage(
                        selectionImage,
                        0,
                        0,
                        selectionImage.width,
                        selectionImage.height,
                        -selection.width / 2,
                        -selection.height / 2,
                        selection.width,
                        selection.height
                    );
                }

                overlayCtx.restore();
                drawHandles();
            }
        }

        function drawHandles() {
            if (!selection) return;

            const corners = getRotatedCorners();
            const edges = getRotatedEdges(corners);
            const centerX = selection.x + selection.width / 2;
            const centerY = selection.y + selection.height / 2;
            const rotation = getRotatedPoint(
                selection.x + selection.width / 2,
                selection.y - rotationHandleOffset,
                rotationAngle,
                centerX,
                centerY
            );

            overlayCtx.fillStyle = 'red';
            corners.forEach(point => {
                overlayCtx.fillRect(point.x - resizeHandleSize / 2, point.y - resizeHandleSize / 2, resizeHandleSize, resizeHandleSize);
            });
            edges.forEach(point => {
                overlayCtx.fillRect(point.x - resizeHandleSize / 2, point.y - resizeHandleSize / 2, resizeHandleSize, resizeHandleSize);
            });

            overlayCtx.fillStyle = 'green';
            overlayCtx.beginPath();
            overlayCtx.arc(rotation.x, rotation.y, resizeHandleSize / 2, 0, Math.PI * 2);
            overlayCtx.fill();
        }

        function getHandleUnderCursor(x, y) {
            const corners = getRotatedCorners();
            const edges = getRotatedEdges(corners);

            for (let i = 0; i < corners.length; i++) {
                const corner = corners[i];
                if (
                    x >= corner.x - resizeHandleSize / 2 &&
                    x <= corner.x + resizeHandleSize / 2 &&
                    y >= corner.y - resizeHandleSize / 2 &&
                    y <= corner.y + resizeHandleSize / 2
                ) {
                    return ['top-left', 'top-right', 'bottom-left', 'bottom-right'][i];
                }
            }

            for (let i = 0; i < edges.length; i++) {
                const edge = edges[i];
                if (
                    x >= edge.x - resizeHandleSize / 2 &&
                    x <= edge.x + resizeHandleSize / 2 &&
                    y >= edge.y - resizeHandleSize / 2 &&
                    y <= edge.y + resizeHandleSize / 2
                ) {
                    return ['top', 'right', 'bottom', 'left'][i];
                }
            }

            return null;
        }

        function getRotatedCorners() {
            const centerX = selection.x + selection.width / 2;
            const centerY = selection.y + selection.height / 2;

            return [
                getRotatedPoint(selection.x, selection.y, rotationAngle, centerX, centerY),
                getRotatedPoint(selection.x + selection.width, selection.y, rotationAngle, centerX, centerY),
                getRotatedPoint(selection.x, selection.y + selection.height, rotationAngle, centerX, centerY),
                getRotatedPoint(selection.x + selection.width, selection.y + selection.height, rotationAngle, centerX, centerY),
            ];
        }

        function getRotatedEdges(corners) {
            return [
                midpoint(corners[0], corners[1]),
                midpoint(corners[1], corners[3]),
                midpoint(corners[2], corners[3]),
                midpoint(corners[0], corners[2]),
            ];
        }

        function getRotatedPoint(x, y, angle, centerX, centerY) {
            const dx = x - centerX;
            const dy = y - centerY;

            const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle) + centerX;
            const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle) + centerY;

            return { x: rotatedX, y: rotatedY };
        }

        function midpoint(p1, p2) {
            return { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
        }

        function isInsideSelection(x, y) {
            const centerX = selection.x + selection.width / 2;
            const centerY = selection.y + selection.height / 2;
            const { x: localX, y: localY } = getRotatedPoint(x, y, -rotationAngle, centerX, centerY);

            return (
                localX >= selection.x &&
                localX <= selection.x + selection.width &&
                localY >= selection.y &&
                localY <= selection.y + selection.height
            );
        }

        function isRotationHandle(x, y) {
            const centerX = selection.x + selection.width / 2;
            const centerY = selection.y + selection.height / 2;
            const rotation = getRotatedPoint(
                selection.x + selection.width / 2,
                selection.y - rotationHandleOffset,
                rotationAngle,
                centerX,
                centerY
            );

            return (
                x >= rotation.x - resizeHandleSize / 2 &&
                x <= rotation.x + resizeHandleSize / 2 &&
                y >= rotation.y - resizeHandleSize / 2 &&
                y <= rotation.y + resizeHandleSize / 2
            );
        }
    </script>
</body>
</html>

Why isn’t “My” Mermaid JS Live Editor Code Working?

Code Sample;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mermaid JS GitGraph Live Editor</title>
    <script type="module">
        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';

        mermaid.initialize({startOnLoad: true});
    </script>
    <style>
        .mermaid {
            display: block;
            max-width: 100%;
            margin: 20px 0;
        }
        #editor {
            margin: 20px 0;
            font-family: 'Courier New', Courier, monospace;
            border: 1px solid #ccc;
            padding: 10px;
            width: 100%;
            height: 200px;
        }
    </style>
</head>
<body>

    <h1>Mermaid JS Live Editor with GitGraph</h1>

    <!-- Editor textarea to input Mermaid syntax -->
    <textarea id="editor">
gitGraph
   commit
   commit
   branch newBranch
   commit
   checkout newBranch
   commit
   checkout main
   merge newBranch
    </textarea>

    <button onclick="renderGraph()">Render Graph</button>

    <div class="mermaid" id="graphContainer"></div>

    <script>
        function renderGraph() {
            var editor = document.getElementById('editor');
            var graphContainer = document.getElementById('graphContainer');
            var graphDefinition = editor.value;
            
            // Use Mermaid to render the graph definition
            graphContainer.innerHTML = ''; // Clear previous rendering
            mermaid.render('graph', graphDefinition, function(svgCode) {
                graphContainer.innerHTML = svgCode; // Insert rendered SVG into the container
            });
        }

        // Automatically render on page load
        renderGraph();
    </script>
    
</body>
</html>

Not sure why this isn’t working?

Transfer LocalStorage data to a new domain, despite limited support for Storage Access API

I’ve had a website hosted on Github Pages without a custom domain (e.g. auroratide.github.io/my-website), and I now want to move it to a custom domain (e.g. my-website.com). The website saves data into local storage.

my-website.com cannot access the local storage data from github.io, so I need to find a way to migrate the data so people don’t suddenly lose everything.

I thought I was being clever by creating another github pages site, auroratide.github.io/storage-migration, whose only purpose is to be iframed by the new domain and postMessage its data to the parent window. To my dismay, an iframed website cannot access its own local storage due to State Partitioning, in other words storage is “double-keyed” against both the top domain and the iframed domain.

I then attempted to leverage the Storage Access API to request access from the iframe, and that works… in Chrome, and is unsupported in every other browser. Silly me for not checking before implementing an entire solution.


Without support from the Storage Access API, is there any way to migrate local storage to a new domain? Even though this exact question has been asked many times over the years, I’m definitely getting lost in the forest of old-knowledge vs new-knowledge.

If it’s useful at all, this is the code I’m currently using in my iframed website:

function postMessage(payload) {
    parent.postMessage(payload, NEW_DOMAIN)
}

function transferStorage(storage) {
    postMessage(Object.entries(storage))
}

async function hasStorageAccess() {
    if (!document.requestStorageAccess) return true
    if (await document.hasStorageAccess()) return true

    try {
        const permission = await navigator.permissions.query({ name: "storage-access" })

        if (permission.state === "granted") {
            await document.requestStorageAccess()
            return true
        } else if (permission.state === "prompt") {
            return false
        } else if (permission.state === "denied") {
            return false
        }
    } catch (error) {
        console.warn(error)
        return false
    }

    return false
}

async function manuallyTransfer() {
    /* Imagine code here that toggles button and loader visibilities */

    const hasAccess = await hasStorageAccess()
    if (!hasAccess) {
        try {
            await document.requestStorageAccess()
        } catch (error) {
            postMessage("failed")
            return
        }
    }

    const handle = await document.requestStorageAccess({ localStorage: true })
    transferStorage(handle.localStorage)
}

async function automaticallyTransfer() {
    const hasAccess = await hasStorageAccess()
    if (hasAccess) {
        if (document.requestStorageAccess) {
            const handle = await document.requestStorageAccess({ localStorage: true })
            transferStorage(handle.localStorage)
        } else {
            transferStorage(localStorage)
        }
    } else {
        console.log("Requires manual input to migrate data.")
        const button = document.querySelector("#transfer-button")
        button.addEventListener("click", manuallyTransfer)
        button.removeAttribute("hidden")
        postMessage("manual")
    }
}

if (parent != null) {
    automaticallyTransfer()
}

Avoiding the Myers Diff Algorithm “Wrong-End” Problem

I’ve been working on a project developing a more extensible way to create synopses of ancient texts (essentially side-by-side comparisons with the diffs highlighted, but with options to focus only on semantically-useful diffs.) This means that it makes the most sense to use a character diff (primarily because some of these diffs are one extra vowel letter, which is not ‘important’, so to speak.) I’ve implemented the Myers diff algorithm, as described in the wonderful blog post in https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/ (and subsequent two parts) in Javascript, which I attach at the bottom of this post.

Coglan notes that the Myers diff algorithm is greedy – ‘trying to consume as many lines that are the same before making a change’ – and thus avoids the so-called “wrong end” problem:

Good:   class Foo                   Bad:    class Foo
      def initialize(name)                def initialize(name)
        @name = name                        @name = name
      end                             +   end
  +                                   +
  +   def inspect                     +   def inspect
  +     @name                         +     @name
  +   end                                 end
    end                                 end

I’m wondering if there is a way to avoid this situation when comparing letter-by-letter. The implementation Coglan describes leads to the following result in the case of “letter-by-letter”, using the example above: the instance of “end” that the code thinks I have added, which should be the last three letters, are instead the “e” in def, “n” in inspect, and “d” in the first end.

In my use case, I have run into this problem when attempting to skip diffs which emerge from the usage of Aramaic acronyms. Said acronyms are of the form LLL”L, where the each L(etter) is taken from the first letter of the word it represents, and the double quote is placed between the penultimate and last letter of the acronym. Note that there are no capital letters in Aramaic (which would have been a natural first thing to test for potential acronyms in English.) I have highlighted in yellow tokens which contain acronyms (which are fully-spelled-out in the other text) in an example below. Spaces which are removed by the diff show up as arrows pointing down:Diff
As you might be able to see, the non-highlighted diffs work like a charm. It’s pretty easy to tell where the “extra” or “switched” letters are, data that is important for me. And it’s also possible to show that all acronym/plain-text pair – except for the first highlighted pair – demonstrate a regular pattern. (Shared letter in black, then removed letters until the end of a word – i.e. until an arrow in red – then another shared letter in black, etc…) Acronyms like these are the parallel to diffing “C.G.I” with “computer generated imagery”, where not a single letter in the balance of the full-text “omputer enerated magery” is a letter in the acronym. But when the acronym is something like “A.C.M” and “association [for] computing machinery”, where “computing” contains an M/m (again under the constraint in this case that there is no capitalization), the diff will use that m. This is what happens in the case of א״ר and אמר רבי. Even though the former is an acronym for the latter, the analysis is that אמר alone becomes א״ר; again, due to the wrong end problem.

I understand that the preferred method to solve this problem alone would likely simply be to just go back to diffing words, but then I lose much of the other functionality I’ve developed…
Anyhow, here’s the code (in Javascript, again; based on the blog post above.)

function myersDiff(oldTokens, newTokens) {
    const m = oldTokens.length;
    const n = newTokens.length;

    const max = m + n;
    //max amount of moves we may need to make
    
    const v = new Map();
    //map contains trace in order
    v.set(1, 0);

    let path = [];

    //get shortest edit graph using the 45-degree k-lines at each depth d
    for (let d = 0; d <= max; d++) {
        path[d] = new Map();
        for (let k = -d; k <= d; k += 2) {
            /* e.g. if depth is 6, consider all options from -12 to 12 since the trace is a sparse binary tree
            if k = -d, we're on the edge of the graph. We can only go downward (i.e. left). If k = d, we can only go up-right (i.e. right.)
            Otherwise, check the elements to the right and left (k+1, k-1) and take the highest x-value
            */
            let x;
            if (k === -d || (k !== d && v.get(k - 1) < v.get(k + 1))) {
                x = v.get(k + 1);
            } else {
                x = v.get(k - 1) + 1; //when moving rightward, the k-line index is higher
            }

            let y = x - k;

            //edit graph should consider diagonals to add 0 cost
            while (x < m && y < n && oldTokens[x].letter === newTokens[y].letter) {
                x++;
                y++;
            }

            v.set(k, x);
            path[d].set(k, { x, y });

            //check for end
            if (x >= m && y >= n) {
                return buildChanges(path, oldTokens, newTokens);
            }
        }
    }
}

function buildChanges(path, oldTokens, newTokens) {
    const changes = [];
    let d = path.length - 1;
    let x = oldTokens.length;
    let y = newTokens.length;

    while (d >= 0) {
        const k = x - y;
        const step = path[d].get(k);

        let prevK;
        if (k === -d || (k !== d && path[d - 1] && path[d - 1].has(k - 1) && path[d - 1].get(k - 1).x < path[d - 1].get(k + 1)?.x)) {
            prevK = k + 1;
        } else {
            prevK = k - 1;
        }

        const prevStep = path[d - 1]?.get(prevK);

        //backtrack to construct the list, privileging diagonals
        while (x > (prevStep?.x || 0) && y > (prevStep?.y || 0)) {
            changes.unshift({ type: "unchanged", token: oldTokens[x - 1].letter, capital: oldTokens[x-1].capital });
            x--;
            y--;
        }

        if (x > (prevStep?.x || 0)) {
            changes.unshift({ type: "removed", token: oldTokens[x - 1].letter, capital: oldTokens[x-1].capital });
            x--;
        } else if (y > (prevStep?.y || 0)) {
            changes.unshift({ type: "added", token: newTokens[y - 1].letter, capital: newTokens[y-1].capital });
            y--;
        }

        d--;
    }
    return changes;
}

Configure VS Code suggestions to work with TypeScript

TypeScript suggestions always come last in the list of suggestions. All the other possible suggestions come first and the types are at the bottom of the list. This is happening with Node (TypeScript) projects, also with React. In React, where emmet suggestions should come first, they come below suggestions for class constructors and all that.

Workspace settings:

{
  "javascript.preferences.suggest.wordBasedSuggestions": false,
  "javascript.preferences.suggest.methodSignatures": true
}

Global settings:

{
  "emmet.includeLanguages": {
    "javascript": "html",
    "javascriptreact": "html",
    "typescript": "html",
    "typescriptreact": "html"
  },

  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
    "editor.suggest.showClasses": false,
    "editor.suggest.showKeywords": false
  },
  "[javascriptreact]": {
    "editor.formatOnSave": true
  },
  "[typescript]": {
    "editor.formatOnSave": true,
    "editor.suggest.showClasses": false,
    "editor.suggest.showKeywords": false,
    "editor.suggest.showConstructors": false
  },
  "[typescriptreact]": {
    "editor.formatOnSave": true
  },

  "editor.acceptSuggestionOnEnter": "on",
  "editor.suggestOnTriggerCharacters": false,
  "editor.fontSize": 15,
  "editor.inlineSuggest.enabled": true,
  "editor.suggestSelection": "first",
  "editor.snippetSuggestions": "top",
  "editor.suggest.localityBonus": true,
  "editor.quickSuggestions": {
    "other": true,
    "comments": false,
    "strings": true
  },
  "scss.lint.important": "warning",
  "javascript.updateImportsOnFileMove.enabled": "always",
  "github.copilot.enable": {
    "*": true,
    "yaml": false,
    "plaintext": true,
    "markdown": true
  },
  "security.workspace.trust.untrustedFiles": "open",
  "editor.linkedEditing": true,
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "timeline.excludeSources": [],
  "editor.bracketPairColorization.enabled": true,
  "javascript.preferences.quoteStyle": "single",
  "prettier.embeddedLanguageFormatting": "off",
  "editor.formatOnSave": true,

  "editor.multiCursorMergeOverlapping": false,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "prettier.singleQuote": true,
  "prettier.semi": false,
  "prettier.tabWidth": 2,
  "prettier.useTabs": false,
  "prettier.bracketSpacing": true,
  "prettier.jsxSingleQuote": true,
  "prettier.bracketSameLine": false,
  "prettier.arrowParens": "avoid",
  "editor.inlineSuggest.suppressSuggestions": true,
  "explorer.compactFolders": false,
  "workbench.startupEditor": "none",
  "codeium.enableConfig": {
    "*": true
  },
  "workbench.editor.customLabels.patterns": {
    "**/app/**/page.tsx": "${dirname} - Page",
    "**/app/**/layout.tsx": "${dirname} - Layout",
    "**/components/**/index.tsx": "${dirname} - Component"
  },
  "typescript.updateImportsOnFileMove.enabled": "always",
  "editor.accessibilitySupport": "off",
  "codeium.enableCodeLens": false,
  "typescript.suggest.classMemberSnippets.enabled": false,
  "editor.suggest.showClasses": false,
  "workbench.iconTheme": "catppuccin-perfect-mocha"
}

How is the promise settled by the time it is logged?

I have the following code:

async function get_exam_score() {
    const exam_score = Math.random() * 10;
    if (exam_score < 5) {
        throw("You failed");
    }
}

try {
    const promise = get_exam_score();
    console.log("TRY:", promise);
}
catch (error) {
    console.log("CATCH:", error);
}

and if I run that above code, console.log() shows that the async function promise has already been settled with either a fulfilled or rejected state.

enter image description here

How is that possible? Should not the promise still be in a pending state since its microtask with the result should not be able to execute until the try / catch block, when the callstack is empty?

I would like to know in-depth how is JavaScript managing the callstack, microtask queue, event loop and anything relevant to this topic. So that it leads to the promise being in a settled state by the time console.log() is executed.

React App shows blank screen on gh-pages after deployment – existing suggestions from SO is implemented already

I am trying to deploy my react app using react-router-dom for client redirection using github pages.

After deployment I get a blank page – none of the components are loaded in DOM except the one which outside the react-router-dom context.

As part of debugging I have gone through the stack overflow discussions already discussing the issue .

I followed the changes suggested in –

React Router not working with Github Pages

but it doesn’t resolve the issue .

I did the following changes as per suggestion from other stack overflow discussion and the one present on the react app Deployment

  1. replaced createbrowserRouter with createHashRouter .

  2. provide the basename of my createHashRouter as the repo name.

  3. checked the homepage of package.json and its correct as per the documenation.

  4. set the ‘private’ property in package.json to false.

After doing the above changes when deployed the build , the index.html of build shows the following :

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

<head>
    <meta charset="utf-8" />
    <link rel="icon" href="/FrontEndQuizApp/favicon.ico" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="Web site created using create-react-app" />
    <link rel="apple-touch-icon" href="/FrontEndQuizApp/logo192.png" />
    <link rel="manifest" href="/FrontEndQuizApp/manifest.json" />
    <title>React App</title>
    <script defer="defer" src="/FrontEndQuizApp/static/js/main.ea628bb2.js"></script>
    <link href="/FrontEndQuizApp/static/css/main.2f2eb39d.css" rel="stylesheet">
</head>

<body><noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>

</html>

But my javascript is enabled for the site and browser.

code snippet from my app is as :

Index.tsx – where the routing is implemented –

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import QuestionComp from "./components/QuestionComp";
import {
  
  createHashRouter,
  FutureConfig,
  RouterProvider,
} from "react-router-dom";

import { store } from "./store";
import { Provider } from "react-redux";
import { ToggleProvider } from "./components/ThemeToggleContext";
import ThemeToggle from "./components/ThemeToggel";
import GlobalBackground from "./components/GlobalBackground";

const router = createHashRouter(
  [
    {
      path: "/*",
      element: <App />,
    },
    {
      path: "/Question",
      element: <QuestionComp />,
    },
  ],
  { basename: "/FrontEndQuizApp" }
);

// declare function RouterProvider(props: RouterProviderProps): React.ReactElement;

interface RouterProviderProps {
  fallbackElement?: React.ReactNode;
  router: any;
  future?: FutureConfig;
}

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

root.render(
  <React.StrictMode>
    <ToggleProvider>
      <GlobalBackground>
        <ThemeToggle></ThemeToggle>
        <Provider store={store}>
          <RouterProvider router={router} />
        </Provider>
      </GlobalBackground>
    </ToggleProvider>
  </React.StrictMode>
);

reportWebVitals();

Package.json – where the homepage has been set for the project –

{
  "name": "quizreactapp",
  "version": "0.1.0",
  "private": false,
  "homepage": "https://ayushi186.github.io/FrontEndQuizApp",
  "dependencies": {
    "@redux-devtools/extension": "^3.3.0",
    "@reduxjs/toolkit": "^2.2.7",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.101",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "i": "^0.3.7",
    "npm": "^10.8.3",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-js-switch": "^1.1.6",
    "react-redux": "^9.1.2",
    "react-router-dom": "^6.24.1",
    "react-scripts": "5.0.1",
    "react-switch": "^7.1.0",
    "redux-devtools-extension": "^2.13.9",
    "sass": "^1.80.3",
    "styled-components": "^6.1.11",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "gh-pages": "^6.2.0"
  }
}

Output of the app after deployment to gh-pages –

my app output after deployment to gh-pages

Any kind of help and pointers are highly appreciated .

Calculate the mean to use only one value in type: “custom”

I’m using ECHARTS but the problem I’m facing is specific to Javascript. I have a series with type: “custom” and I have a problem calculating the arithmetic mean between the values of a set of arrays. In this part precisely:

// Function to calculate the average of the values in an array
function calculateMean() {
  return value.reduce((acc, val) => acc + val, 0) / value.length;
}

// Calculates the average of all values in the `value` array
const mean = calculateMean(value);

console.log(mean);

The console returns this:

custom.html:69 182.72499999999997
custom.html:69 195.885
custom.html:69 205.775

I need the average of these three values, as I did in calculateMean, but 3 values are being returned, I need their average. I can’t get the average between them. The expected result would be:

(182.72499999999997 + 195.885 + 205.775) / 3
194.795 // <-- I need this average value

So, I need to use the value 194.795 but it’s returning 3 values as I showed above.

The console.log for value is:

const value = api.coord([api.value(0), api.value(1)]);

console.log(value)

custom.html:60 (2) [114.75, 250.69999999999996]
custom.html:60 (2) [175.95000000000002, 215.81999999999996]
custom.html:60 (2) [237.15000000000003, 174.39999999999998]

Complete code:

 // system
        const chartSystem = () => {
            return {
                "source": {
                    "custom": [
                        ["x", "y", "groups"],
                        [1, 20, "group1"],
                        [2, 36, "group1"],
                        [3, 55, "group1"],
                        [4, 24, "group2"],
                        [5, 81, "group2"],
                        [6, 61, "group2"]
                    ]
                }
            }
        }

        const pullDataset = [];

        const chartSend = () => {
            const { source } = chartSystem();
            pullDataset.push(...Object.values(source).slice(0, 1).map(item => ({
                source: item,
                sourceHeader: true
            })));
        }

        chartSend();

        // echarts init
        var chartUse = echarts.init(document.getElementsByClassName('chart')[0]);

        function chartFrameSwitch0() {

            // custom series
            const series0 = [
                {
                    type: 'custom',
                    datasetIndex: 0,
                    encode: {
                        x: 0,
                        y: 1,
                        itemGroupId: 2
                    },
                    renderItem: function (params, api) {

                        if (params.dataIndex < 3) {
                        
                            const value = api.coord([api.value(0), api.value(1)]);
                            const size = api.size([api.value(0), api.value(1)]).map(resize => resize * 0.2);
                            
                            // Function to calculate the average of the values in an array
                            function calculateMean() {
                                return value.reduce((acc, val) => acc + val, 0) / value.length;
                            }

                            // Calculates the average of all values in the `value` array
                            const mean = calculateMean(value);

                            console.log(value)

                            return {
                                type: 'group',
                                children: [
                                    {
                                        type: 'path',
                                        shape: {
                                            x: value[0] - size[0] / 2,
                                            y: value[1] - size[1] / 2,
                                            pathData: 'M416 398.9c58.5-41.1 96-104.1 96-174.9C512 100.3 397.4 0 256 0S0 100.3 0 224c0 70.7 37.5 133.8 96 174.9c0 .4 0 .7 0 1.1l0 64c0 26.5 21.5 48 48 48l48 0 0-48c0-8.8 7.2-16 16-16s16 7.2 16 16l0 48 64 0 0-48c0-8.8 7.2-16 16-16s16 7.2 16 16l0 48 48 0c26.5 0 48-21.5 48-48l0-64c0-.4 0-.7 0-1.1zM96 256a64 64 0 1 1 128 0A64 64 0 1 1 96 256zm256-64a64 64 0 1 1 0 128 64 64 0 1 1 0-128z',
                                            width: size[0],
                                            height: size[1]
                                        },
                                        style: {
                                            fill: '#000',
                                            lineWidth: .5,
                                            stroke: '#f00',
                                            shadowBlur: 5,
                                            shadowColor: '#333',
                                            shadowOffsetX: 2,
                                            shadowOffsetY: 2
                                        }
                                    },
                                    {
                                        type: 'circle',
                                        shape: {
                                            cx: mean,
                                            cy: mean,
                                            r: mean / 2
                                        },
                                        style: {
                                            fill: '#dd4b39',
                                            opacity: .2
                                        }
                                    }
                                ]
                            }

                        } else if (params.dataIndex >= 3) {

                            const value = api.coord([api.value(0), api.value(1)]);
                            const size = api.size([api.value(0), api.value(1)]).map(resize => resize * 0.2);

                            return {
                                type: 'group',
                                children: [
                                    {
                                        type: 'path',
                                        shape: {
                                            x: value[0] - size[0] / 2,
                                            y: value[1] - size[1] / 2,
                                            pathData: 'M57.7 193l9.4 16.4c8.3 14.5 21.9 25.2 38 29.8L163 255.7c17.2 4.9 29 20.6 29 38.5l0 39.9c0 11 6.2 21 16 25.9s16 14.9 16 25.9l0 39c0 15.6 14.9 26.9 29.9 22.6c16.1-4.6 28.6-17.5 32.7-33.8l2.8-11.2c4.2-16.9 15.2-31.4 30.3-40l8.1-4.6c15-8.5 24.2-24.5 24.2-41.7l0-8.3c0-12.7-5.1-24.9-14.1-33.9l-3.9-3.9c-9-9-21.2-14.1-33.9-14.1L257 256c-11.1 0-22.1-2.9-31.8-8.4l-34.5-19.7c-4.3-2.5-7.6-6.5-9.2-11.2c-3.2-9.6 1.1-20 10.2-24.5l5.9-3c6.6-3.3 14.3-3.9 21.3-1.5l23.2 7.7c8.2 2.7 17.2-.4 21.9-7.5c4.7-7 4.2-16.3-1.2-22.8l-13.6-16.3c-10-12-9.9-29.5 .3-41.3l15.7-18.3c8.8-10.3 10.2-25 3.5-36.7l-2.4-4.2c-3.5-.2-6.9-.3-10.4-.3C163.1 48 84.4 108.9 57.7 193zM464 256c0-36.8-9.6-71.4-26.4-101.5L412 164.8c-15.7 6.3-23.8 23.8-18.5 39.8l16.9 50.7c3.5 10.4 12 18.3 22.6 20.9l29.1 7.3c1.2-9 1.8-18.2 1.8-27.5zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z',
                                            width: size[0],
                                            height: size[1]
                                        },
                                        style: {
                                            fill: '#008cba',
                                            lineWidth: .5,
                                            stroke: '#000',
                                            shadowBlur: 5,
                                            shadowColor: '#333',
                                            shadowOffsetX: 2,
                                            shadowOffsetY: 2
                                        }
                                    }
                                ]
                            }

                        }

                    }
                }
            ];

            // component types
            const tooltip0 = [
                {
                    show: true
                }
            ];

            const grid0 = [
                {
                    width: '40%',
                    height: '30%',
                    left: "5%",
                    top: '15%'
                }
            ];

            const xAxis0 = [
                {
                    type: 'value',
                    min: 0,
                    max: 7
                }
            ];

            const yAxis0 = [
                {
                    type: 'value',
                    min: 0,
                    max: 90
                }
            ];

            const option = {
                dataset: [pullDataset[0]],
                tooltip: tooltip0,
                grid: grid0,
                xAxis: xAxis0,
                yAxis: yAxis0,
                series: series0
            };

            // setOption
            chartUse.setOption(option, { notMerge: true });
        }

        chartFrameSwitch0();
<head>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
</head>

<div class="chart" style="width: 100%; height: 100vh;"></div>

You can see that three circles are formed, but there would only have to be one, with the value of the average 194,795 to be applied. But I can’t seem to do it.

Error: Cannot create URL for blob! when converting blob to URL in react native

So I am fetching an image using the google places photos API, I created a request on Postman and i get back the image in a jpeg format, here are headers:

Postman headers received

When I make the same request on my react native app I get the error [Error: Cannot create URL for blob!]. I tried logging the data after hitting await response.blob() and i get back this:

{"_data": {"__collector": {}, "blobId": "88865B74-7358-4D4C-BDBA-786330A6CE5D", "name": "2022-12-09.jpg", "offset": 0, "size": 36317, "type": "image/jpeg"}}

So it all looks good but why can’t i get a URL from this blob?

const [image, setImage] = useState<string>();

  useEffect(() => {
    const fetchImage = async () => {
      try {
        const response = await fetch(
          "https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photo_reference=[PHOTO_REFERENCE]key=[API_KEY]"
        );
        const data = await response.blob();
        console.log(data);
        setImage(URL.createObjectURL(data));
      } catch (e) {
        console.log(e);
      }
    };
    fetchImage();
  }, []);

The line that causes the error is setImage(URL.createObjectURL(data));

Why can’t I send an audio stream from JavaScript via SignalR to a .NET Hub?

I’m trying to send an audio stream captured in JavaScript from a browser tab to a .NET SignalR Hub. My goal is to stream audio in chunks/realTime to the server and broadcast it to all connected clients.

Here’s the setup:

SignalR Hub:
I have a Hub named /SoundHub with the following method:

   public class SoundHub : Hub
    {
        public async Task SendAudioChunk(byte[] audioChunk)
        {
            await Clients.All.SendAsync("ReceiveAudio", audioChunk);
        }
    }

JavaScript Client:
I’m capturing the system audio using navigator.mediaDevices.getDisplayMedia and sending audio chunks through SignalR. Here’s the JavaScript code:

window.startScreenSharing = async function () {
    try {
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/soundHub")
            .withAutomaticReconnect()
            .build();

        await connection.start();
        console.log("SignalR connection established.");

        const stream = await navigator.mediaDevices.getDisplayMedia({
            video: true,
            audio: true
        });

        const audioContext = new AudioContext();
        const audioSource = audioContext.createMediaStreamSource(stream);
        const processor = audioContext.createScriptProcessor(2048, 1, 1);

        audioSource.connect(processor);
        processor.connect(audioContext.destination);

        processor.onaudioprocess = async (event) => {
            const inputBuffer = event.inputBuffer.getChannelData(0);
            const intBuffer = new Int16Array(inputBuffer.length);
            for (let i = 0; i < inputBuffer.length; i++) {
                intBuffer[i] = Math.max(-32768, Math.min(32767, inputBuffer[i] * 32767));
            }
            const audioChunk = new Uint8Array(intBuffer.buffer);

            try {
                if (connection.state === signalR.HubConnectionState.Connected) {
                    await connection.invoke("SendAudioChunk", audioChunk);
                    console.log(`Audio chunk sent: ${audioChunk.length} bytes`);
                } else {
                    console.warn("SignalR connection is not in 'Connected' state.");
                }
            } catch (err) {
                console.error("Error sending audio chunk:", err);
            }
        };
    } catch (err) {
        console.error("Error setting up screen sharing and audio streaming:", err);
    }
};

Issue:
If I change the Hub method to accept string instead of byte[] and send text data, it works fine.
However, when I send audio chunks (Uint8Array), the method in the Hub is never triggered. I’ve added logging inside the method, but it seems the method is not even reached.

Troubleshooting:
I verified that the JavaScript is capturing the audio correctly and sending the chunks.
The SignalR connection is established, and the state is Connected when invoking SendAudioChunk.

No exceptions are thrown, but I receive an error in the browser console:

Error sending audio chunk: Error: Failed to invoke 'SendAudioChunk' due to an error on the server.

Question:
Is there something wrong with the way I’m formatting the audio data (Uint8Array -> byte[])?
Do I need to preprocess the audio differently before sending it?
Why does sending text work, but sending audio chunks does not?

Additional Info:
SignalR is working correctly with text data.
I’m using Blazor Web App and the latest SignalR client.