mailto and navigator.share() is not working in chrome 127

I am currently developing a website using angular 18.
I tried to share the title, text, and url using the window.navigator.share(title, text, url) function.
However, it worked well in edge, but in chrome(127), only the url was shared and the title and text were not shared.
So I tried to use mailto, but it worked in firefox or edge, but when I set the default email app to chrome in window, mailto did not work in chrome and there was no response when I pressed the button.
Please help.

https://stackblitz.com/edit/mailto-links-for-assets?file=src%2Fmain.ts is not working in chrome 127.
And also, window.navigator.share(title, text, url) is not working too.

Should TypeScript downcompile import attributes?

TypeScript 5.3 introduced support for import attributes. When building an ESM project, the compiler allows destructuring during import, but I don’t know any javascript runtimes that support this. Should TypeScript be downcompiling or disallowing destructured JSON imports?

index.ts

import {compilerOptions} from './tsconfig.json' with {type: 'json'};
console.log(compilerOptions);

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2023",
    "lib": ["ES2023"],
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "resolveJsonModule": true,
    "strict": true
  }
}

package.json

{
  "type": "module",
  "devDependencies": {
    "@types/node": "^22",
    "typescript": "^5.5"
  }
}

Test building with Node.js 22.6.0 and TypeScript 5.5.4.
Compilation with npx tsc produces no warnings or errors and outputs index.js:

import { compilerOptions } from './tsconfig.json' with { type: 'json' };
console.log(compilerOptions);

Attempt to run in Node.js 22:

> node index.js          
(node:11064) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
file:///C:/Users/matt.mower/source/with-json-tsc/index.js:1
import { compilerOptions } from './tsconfig.json' with { type: 'json' };
         ^^^^^^^^^^^^^^^
SyntaxError: The requested module './tsconfig.json' does not provide an export named 'compilerOptions'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:171:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:254:5)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:482:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)

Node.js v22.6.0

Attempt to run in Chrome 127.0.6533.120 (<script async type="module" src="./index.js"></script>):

Uncaught SyntaxError: The requested module './tsconfig.json' does not provide an export named 'compilerOptions' (at index.js:1:10)

If I replace the destructured import with the following, index.js runs fine in both Node.js 22 and Chrome 127:

import tsconfig from './tsconfig.json' with { type: 'json' };
console.log(tsconfig.compilerOptions);

Why is a comma (,) being added to the questionIndex when I am using it again for the following svelte component?

I have two components, Question.svelte and QuestionList.svelte. The Question component has a clear button which will clear the selection of its options, which are radio buttons. The QuestionList component has a clear button which will clear all the selections.

When I iterate over the object and console.log it in the start, it shows the objects correctly as {id: 1, text: “….”, options = […]}.

When I try to log the index from the Question component, it gives me the questionIndex in the form of “1, ” instead of just a number like “1”.
I want to pass the index in the loop as “1” instead of “1, “. It adds a comma automatically.

QuestionList.svelte

<script>
  import Question from './Question.svelte';

  export let questions = [
    { id: 1, text: 'What is your favorite color?', options: ['Red', 'Blue', 'Green'], selectedOption: "" },
    { id: 2, text: 'What is your favorite animal?', options: ['Cat', 'Dog', 'Bird'], selectedOption: "" }
  ];  

  function handleOptionChange(questionIndex, selectedOption) {
    questions[questionIndex].selectedOption = selectedOption;
  }

  function clearAllSelections() {
    questions = questions.map((question) => ({...question, selectedOption: ""}));
    console.log('Cleared all selections');
    console.log(questions);
  }

  function clearSingleSelection(e) {
    console.log(e);
    const questionIndex = e.detail;
    console.log(`Cleared Single: ${questionIndex}---`);
    console.log(questions);
    questions[questionIndex].selectedOption= "";

  }

  console.log(`QuestionList.svelte`, questions);
</script>

{#each questions as question, index}
  <Question
    questionId={question.id}
    question={question.text}
    options={question.options}
    selectedOption={question.selectedOption}
    questionIndex = {Number(index)},
    on:clearSingleSelection={clearSingleSelection}
  />
{/each}

<button on:click={clearAllSelections}>Clear All</button>

Question.svelte

<script>
  import { createEventDispatcher } from 'svelte';

  export let question, questionId, questionIndex;
  export let options = [];
  export let selectedOption = '';

  const dispatch = createEventDispatcher();

  // Function to handle option change
  function handleChange(event) {
    const selectItem = {
      id: questionId,
      selectedOption: event.target.value,
    }
    console.log(`From Question.svelte, changed ${questionIndex}--`);
    dispatch('optionChange', selectItem);
  }

  function handleSingleClear() {
    dispatch('clearSingleSelection', questionIndex);
    console.log(`From Question.svelte, cleared ${questionIndex}--`);
  }
  console.log(`Question Index from Question.svelte: ${questionIndex}---`);

</script>

<div class="question-item">
  <div class="question-section">
    <p>{question}</p>
  </div>
  <div class="option-section">
    {#each options as option}
    <input
      type="radio"
      name={questionId} 
      value="{option}"
      checked={option === selectedOption}
      on:change={handleChange}
    />
    <label for="{option}">{option}</label>
  {/each}
  </div>
  <button type="button" on:click={handleSingleClear}>Clear</button>
</div>

Referral authentication

How to implement referral login. How can I handle it?

Is it okay to generate referral code which will be stored to db for every single user that log in to app?
and after that I need to trigger front-end that user registered with referral link.

PaperJs: subtract is not working as expected

In the application I’m using, I’m subtracting a closed Path from an open Path. But for some reason, the subract() function is breaking the Path into two parts and in the result I see a Compound Path.

The path from which subtraction is done:

[
    "Path",
    {
        "applyMatrix": true,
        "segments": [
            [41.47363, -24.54467],
            [-19.17782, 46.31651],
            [-97.3719, -21.43777],
            [-36, -90],
            [-45.06335, -79.27252],
            [-29.81168, -66.3868],
            [-29.69724, -66.52225],
            [-26.21473, -64.17428],
            [-20.74832, -63.07066],
            [-15.28191, -64.17428],
            [-10.81799, -67.18394],
            [-10.15629, -68.16537]
        ],
        "strokeScaling": false
    }
]

The path that is subtracted:

[
    "Path",
    {
        "applyMatrix": true,
        "segments": [
            [-29.81168, -66.3868],
            [32.41027, -13.81719],
            [41.47363, -24.54467],
            [-20.74832, -77.11428]
        ],
        "closed": true,
        "strokeScaling": false
    }
]

This is the result I’m getting:

[
    "CompoundPath",
    {
        "applyMatrix": true,
        "children": [
            [
                "Path",
                {
                    "applyMatrix": true,
                    "segments": [
                        [-19.17782, 46.31651],
                        [-97.3719, -21.43777],
                        [-36, -90],
                        [-45.06335, -79.27252],
                        [32.3415, -13.8753]
                    ],
                    "closed": true
                }
            ],
            [
                "Path",
                {
                    "applyMatrix": true,
                    "segments": [
                        [-29.69724, -66.52225]
                    ],
                    "closed": true
                }
            ]
        ],
        "strokeScaling": false
    }
]

You can see it pictorially here: https://www.desmos.com/calculator/xmi7ifhpul

Before deletion:
enter image description here

After deletion:
enter image description here
enter image description here

You can see that the one point remains even after deletion even though it was inside the polygon to be subtracted. I couldn’t figure out why this is happening.

How do I wait for an HttpClient subscription to wrap up before returning the data in Angular 18

I’m working with Angular 18 and I’m trying to query data from an API so I can store it inside my component. I’m able to successfully console.log the API data stored in my object with both my component and the service that I’m using to retrieve the data. However, whenever I try to console.log its key or any of its values on their own, undefined shows up in the console. In addition, using Object.keys with the object returns an empty array.

I found a similar stackoverflow post here, and while it says that the issue stems from the opened object in the console not accurately depicting its current value, I’m not sure how to fix the issue. I tried looking into ways to delay the data I receive from my query’s subscription, so that I can store the data into the variable before the rest of the code executes. Sadly though, the only stuff I found was what I had already been trying. Making it so that the object require the this keyword to be used didn’t help either. I don’t want to use async/await if I can help it because I don’t want to have to deal with returning Promises, but I can if there is no other way.

tldr: How do I wait for an HttpClient subscription to wrap up before returning the data from the API its querying?

api.service.ts

export class ApiService {
  constructor(private httpClient: HttpClient) { }
  private collection: Collection = { cards: {} };

  queryApi(query: string): Observable<Data> {
    return this.httpClient.get<Data>(`https://api.scryfall.com/${query}`);
  }

  retrieveCard(name: string, set?: string): Cards {
    let card: Cards = {}

    this.queryApi(`cards/named?exact=${name}${typeof set != undefined ? `&set=${set}` : ""}`)
    .subscribe((json) => {
      card[json["id"]] = {
        name: json["name"],
        qty: 1,
        set: json["set"],
        imageURL: json["image_uris"]["png"]
      }
    });

    return card;
  }
}

add-cards.component.ts

export class AddCardsComponent {
  constructor(private apiService: ApiService) {}
  private tempCollection: Collection = { cards: {} };
  name: string = "";
  set: string = "";

  addTempCard(): void {
    const card: Cards = this.apiService.retrieveCard(this.name.toLowerCase(), this.set?.toLowerCase());
    console.log(card) // returns key and values
    console.log(Object.keys(card)[0]) // returns undefined

    this.name = "";
    this.set = "";
  }
}

Using Webworkers for both parallel rendering and data generation in Chartjs

I am changing from Dygraph to Chartjs because of rendering webworkers and wish to fully multithread the graph to keep main thread empty as much as possible. I am trying to use Workers for handling data and rendering as mentioned on the Chart.js page:
https://www.chartjs.org/docs/master/general/performance/. I have problems at reusing the canvas and implementing the parallel rendering.

I am able to use Webworkers for the data but unable to get it to draw due to

Error: Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID 'myChart' can be reused.


<template>
<canvas id="myCanvas"></canvas>
</template>

<script>
  testGraph() {

  let data = [];
  const worker = new Worker(new URL('./worker.js', import.meta.url));

  worker.onmessage = function(e) {
    data = e.data;
// Add new data to the existing dataset
    console.log(data);
    myChart.update();  // Update the chart with new data
  };

  // Set up the chart
  const ctx = document.getElementById('myChart');
  const myChart = new Chart(ctx, {
    type: 'line',
    data: {
      datasets: [{
        label: 'TestLabel',
        data: data,
        backgroundColor: 'rgba(255, 99, 132, 0.2)',
        borderColor: 'rgba(255, 99, 132, 1)',
        borderWidth: 1
      }]
    },
    options: {
      animation: false,
      scales: {
        x: {
          type: 'linear',
          position: 'bottom'
        },
        y: {
          beginAtZero: true
        }
      }
    }
  });


  setInterval(() => {
    worker.postMessage('start');
  }, 5000);
},

</script>

Worker.js code bellow

function getRandomInt(max) {
    return Math.floor(Math.random() * max);
  }

self.onmessage = function() {
    var data = [];
        for (let j=0;j < 10; j++) {
            data.push({ x: j, y: getRandomInt(1000) });
        }
        postMessage(data);
};


Javascript JSon String Counting and Output

I manage the string and pass it to a class like this:

loadEvents() {
        $(".lbtbevent").remove();

        if (!this.eventsLoaded) {
            //this.events = JSON.parse(this.lbtbdata.getItem("events"));
            this.events = JSON.parse(this.lbtbdata);
            if (this.events) {
                for (const date of Object.keys(this.events)) {
                    for (const id of Object.keys(this.events[date])) {
                        const event = new Event(this.events[date][id]);
                        this.events[date][id] = event;
                    }
                }
            }
            this.eventsLoaded = true;
        }
        if (this.events) {
            for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
                const date = dateString(addDays(this.weekStart, dayIndex));
                if (this.events[date]) {
                    for (const event of Object.values(this.events[date])) {
                        event.showIn(this);
                        //console.log(event);
                    }
                }
            }
        } else {
            this.events = {};
        }
    }

Then I output each individual value according to the hour and date. This results in a list of entries:

showIn(calendar) {
        if (
            this.date < dateString(calendar.weekStart) ||
            this.date > dateString(calendar.weekEnd)
        ) {
            $(`#${this.id}`).remove();
            return;
        }
        let eventSlot;
        if ($(`#${this.id}`).length) {
            eventSlot = $(`#${this.id}`);
        } else {
            eventSlot = $("<div></div>")
                .addClass("lbtbevent")
                .attr("id", this.id)
        }

        (function($) {
            $.renameTag = function($el, tagName) {
              $el.wrap('<' + tagName + '>');
              var $newElement = $el.parent();
              $.each($el.prop('attributes'), function() {
                  $newElement.attr(this.name,this.value);
              });
              $el.contents().unwrap();
              return $newElement;
          }
          })(jQuery);
          let test = Object.keys(this.id).length;

        let textoutput = this.sus + "-" + this.description;
        $.renameTag(eventSlot, 'a')
            .text(test)
            .prop('href', `schulzentrum_detail.php?id=${this.id}`) // Die URL zur Anzeige des Inhalts setzen.
            .css("backgroundColor", `var(--color-${this.color})`)
            .css("white-space", "nowrap")
            .css("position", "relative")
            .css("display", "block")
            .appendTo(`.slot[data-dayIndex=${this.dayIndex}].slot[data-hour=${this.startHour}]`);
    }

Now I don’t want every single value to be output per hour and per date, but rather the values ​​should be counted.

Previously the output was as follows:

Date: 19.08.2024 - 1 hour: OUTPUT1
Date: 19.08.2024 - 1 hour: OUTPUT2
Date: 19.08.2024 - 1 hour: OUTPUT3
Date: 19.08.2024 - 1 hour: OUTPUT4
Date: 19.08.2024 - 1 hour: OUTPUT5

Number of values. Above there were 5 values ​​that were output individually and now one value should be output with the number.
Now it should look like this:

Date: 19.08.2024 - 1 hour: TOTAL OUTPUT 5 PIECES

So far I have

Object.keys(jsonArray).length

NVDA doesn’t read aria-label / aria-labelledby on hover

I need to add an additional text to tag for screen readers. Unfortunately, I discovered that the NVDA reader does not read either aria-label or aria-labelledby when I hover over the element.

It reads additional text when I move with arrows around the page, but I need it to work on hover

The code I tested looks like this:

<a href="#" id="btn" role="button" aria-labelledby="btn text">Find Out More<span id="text" class="visually-hidden">about [slide title]</span></a>

<a href="#" aria-label="Find Out More about [slide title]">Find Out More</a>

Currently NVDA only reads the content of “Find out more” on hover and ignores both aria-label and aria-labelledby. Unfortunately I can’t remove “Find Out More” from the tag, it needs to be visible to users and I have to add additional text so that NVDA will read “Find out more about [slide title]”.

Does anyone know a way to make NVDA read additional text on hover when has its own text? Perhaps there is a way around this

google maps update to AdvancedMarkerElement with personalized renderer

I am updating my Google Maps code due to the deprecation warning: “As of February 21st, 2024, google.maps.Marker is deprecated. Please use google.maps.marker.AdvancedMarkerElement instead.”

I’ve successfully replaced new google.maps.Marker with new google.maps.marker.AdvancedMarkerElement for individual markers, and everything works fine. However, when I try to update the renderer function inside my MarkerClusterer, I get an error:

Uncaught InvalidValueError at _.Jj

This error occurs when I change new google.maps.Marker to new google.maps.marker.AdvancedMarkerElement inside the render function for marker clustering.

Here is the relevant part of the code:

const renderer = {
    render({ count, position }) {
        const clusterIcon = getClusterIcon(count);

        // Changing this from google.maps.Marker to google.maps.marker.AdvancedMarkerElement causes the error
        return new google.maps.marker.AdvancedMarkerElement({
            label: { text: String(count), color: "white", fontSize: clusterIcon.fontSize },
            position,
            icon: clusterIcon,
            zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
        });
    }
};

I expect the clusters to render with the AdvancedMarkerElement as they did with google.maps.Marker. What is the proper way to update the renderer in MarkerClusterer with the new AdvancedMarkerElement?

Spatial navigation fo tizen app development

I m developing an sdk for samsung on tizen platform. I m facing issues related with spatial navigation. Can anyone suggest a robust library for spatial navigation.

i ask chatgpt and google but it didn’t work. Can anyone help me regarding this problem.

How can I use toFixed method to array object values? [closed]

I have an array that consists of currency words and numbers. I need to round DIME and NICKEL values to 3.1 and 2.05 correspondingly

[
  [ ' ONE HUNDRED: $', 100 ],
  [ ' TWENTY: $', 60 ],
  [ ' TEN: $', 20 ],
  [ ' FIVE: $', 55 ],
  [ ' ONE: $', 90 ],
  [ ' QUARTER: $', 4.25 ],
  [ ' DIME: $', 3.1000000000000014 ],
  [ ' NICKEL: $', 2.0500000000000007 ],
  [ ' PENNY: $', 0.05 ] 
]

I tried to use toFixed and map methods, they didn’t work.

let my array = arr.map(e => e[1].toFixed(2)

The screen is stuck and taking a long time to display or process the image

screen is getting stuck while saving the image and it is taking 40 to 50 seconds to display the saving image.
ImageCaptureDialog.razor code:

@using System.ComponentModel.DataAnnotations
@using Newtonsoft.Json
@using System.Text
@using MudBlazor
 
@inject IJSRuntime JSR
@inject HttpClient HttpClient
@inject IDialogService DialogService
@inject ISnackbar Snackbar
 
<div>
<MudDialog Style="overflow: hidden;">
<DialogContent>
<MudCard Class="pa-2">
<MudCardContent>
<div id="spinner" style="display: none; justify-content: center; align-items: center; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.8); z-index: 1000;">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
</div>
<div style="display: flex; justify-content: center; align-items: center;">
 
                      
<video id="videoFeed" width="600" height="300"></video>
</div>
<canvas class="d-none" id="currentFrame" width="800" height="600"></canvas>
 
                   
                    @if (!string.IsNullOrEmpty(frameUri))
                    {
<img src="@frameUri" alt="Captured Image" width="1280" height="720" />
<p>Image Size: @imageSizeInKB KB</p>
                    }
 
                    <div style="display: flex; justify-content: center; margin-top: 16px;">
<MudIconButton Icon="@Icons.Material.Filled.CameraAlt" Color="Color.Primary" Size="Size.Large" OnClick="@Save"/>
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Cancel" Size="Size.Large" OnClick="@Cancel"/>
</div>
</MudCardContent>
</MudCard>
</DialogContent>
</MudDialog>
</div>
 
@code {
    [CascadingParameter] MudDialogInstance MudDialog { get; set; }
    [Parameter] public string ImageUri { get; set; }
 
    private DotNetObjectReference<ImageCaptureDialog> oCounter;
    private string frameUri;
    private bool isVideoStarted = false;
    private double imageSizeInKB;
 
    protected override async Task OnInitializedAsync()
    {
        try
        {
            await JSR.InvokeVoidAsync("startVideo", "videoFeed");
            isVideoStarted = true;
        }
        catch (Exception ex)
        {
            Snackbar.Add("Camera access is denied. Please allow access to the camera.", Severity.Error);
        }
    }
 
    private async Task Save()
    {
        if (!isVideoStarted)
        {
            Snackbar.Add("Camera access is denied. Please allow access to the camera.", Severity.Error);
            MudDialog.Cancel();
            return;
        }
 
        if (oCounter == null)
            oCounter = DotNetObjectReference.Create(this);
 
        try
        {
            await JSR.InvokeVoidAsync("showLoadingSpinner");
            await JSR.InvokeAsync<string>("getFrame", "videoFeed", "currentFrame", oCounter);
            await JSR.InvokeVoidAsync("stopVideo", "videoFeed");
            isVideoStarted = false;
 
            // Hide the spinner after the image is captured
            await JSR.InvokeVoidAsync("hideLoadingSpinner");
 
            MudDialog.Close(DialogResult.Ok(frameUri));
        }
        catch (Exception ex)
        {
            // Hide the spinner in case of an error
            await JSR.InvokeVoidAsync("hideLoadingSpinner");
 
            Snackbar.Add($"An error occurred while capturing the image. Please try again. Error details: {ex.Message}", Severity.Error);
            MudDialog.Cancel();
        }
    }
 
 
   
    [JSInvokable]
    public async Task ProcessImage(string base64Image, int imageSizeInBytes)
    {
            //await Task.Delay(10000);
            // Simulate heavy processing or saving logic here
            frameUri = base64Image;
            imageSizeInKB = Math.Round(imageSizeInBytes / 1024.0, 2); // Convert bytes to KB
 
            // Example: Save the image data or do further processing.
            // SaveImageToDisk(frameUri);
    }
 
 
    private async Task Cancel()
    {
        if (isVideoStarted)
        {
            try
            {
                await JSR.InvokeVoidAsync("stopVideo", "videoFeed");
                isVideoStarted = false;
                MudDialog.Cancel();
            }
            catch (Exception ex)
            {
                MudDialog.Cancel();
            }
        }
    }
}

camera.js code:

async function getFrame(src, dest, dotnetHelper) {
    try {
        showLoadingSpinner();
        let video = document.getElementById(src);
        let canvas = document.getElementById(dest);

        if (video && canvas) {
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            let ctx = canvas.getContext('2d');
            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

            let dataUrl = canvas.toDataURL("image/png");
            const base64ImageSize = Math.round((dataUrl.length * 3) / 4);

            await dotnetHelper.invokeMethodAsync('ProcessImage', dataUrl, base64ImageSize);
        } else {
            console.error('Video or canvas element not found.');
            throw new Error('Video or canvas element not found.');
        }
    } catch (error) {
        console.error('Error capturing frame:', error);
        throw error;
    } finally {
        hideLoadingSpinner();
    }
}

the image quality is good but while processing image it is taking so much time and also screen is getting stuck.

how to display the image immediately with good quality after clicking the save button.

How to add a secondary animation on click while maintaining a default animation

I have two animations: robot_idle.fbx and robot_shoot.fbx. I’m displaying the robot_idle animation by default when the page loads. However, I need to display the robot_shoot animation whenever the user clicks on the page, and then return to the idle animation. Could I please have some assistance with this? I’m relatively new to Three.js.

"use client";

import { useRef, useEffect, useState } from "react";
import * as THREE from "three";
import { Canvas, useLoader, useFrame } from "@react-three/fiber";
import { useAnimations } from "@react-three/drei";
import { FBXLoader } from "three/examples/jsm/Addons.js";

const Model = () => {
  const [error, setError] = useState<string | null>(null);
  const [loaded, setLoaded] = useState(false);

  const fbx = useLoader(FBXLoader, "/models/robot.fbx", undefined, (error) => {
    setError(`Failed to load FBX: ${error}`);
  });

  const idleAnimation = useLoader(FBXLoader, "/models/animations/robot_idle.fbx", undefined, (error) => {
    setError(`Failed to load idle animation: ${error}`);
  });

  const { mixer } = useAnimations(idleAnimation.animations, fbx);
  const mouseX = useRef(0);

  const [
    baseTexture1,
    normalTexture1,
    roughnessTexture1,
    metallicTexture1,
    emissiveTexture1,
    aoTexture1,
    baseTexture2,
    normalTexture2,
    roughnessTexture2,
    metallicTexture2,
    emissiveTexture2,
    aoTexture2,
  ] = useLoader(THREE.TextureLoader, [
    "/models/textures/torso_base_color.png",
    "/models/textures/torso_normal.png",
    "/models/textures/torso_roughness.png",
    "/models/textures/torso_metallic.png",
    "/models/textures/torso_emissive.png",
    "/models/textures/torso_ao.png",
    "/models/textures/limbs_base_color.png",
    "/models/textures/limbs_normal.png",
    "/models/textures/limbs_roughness.png",
    "/models/textures/limbs_metallic.png",
    "/models/textures/limbs_emissive.png",
    "/models/textures/limbs_ao.png",
  ]);

  const groupRef = useRef<THREE.Group>(null);

  useEffect(() => {
    if (fbx && idleAnimation) {
      const torsoMaterial = new THREE.MeshStandardMaterial({
        map: baseTexture1,
        normalMap: normalTexture1,
        roughnessMap: roughnessTexture1,
        metalnessMap: metallicTexture1,
        emissiveMap: emissiveTexture1,
        aoMap: aoTexture1,
        emissive: new THREE.Color(0xffffff),
        emissiveIntensity: 1,
        roughness: 0.5,
        metalness: 0.8,
      });

      const limbsMaterial = new THREE.MeshStandardMaterial({
        map: baseTexture2,
        normalMap: normalTexture2,
        roughnessMap: roughnessTexture2,
        metalnessMap: metallicTexture2,
        emissiveMap: emissiveTexture2,
        aoMap: aoTexture2,
        emissive: new THREE.Color(0xffffff),
        emissiveIntensity: 1,
        roughness: 0.5,
        metalness: 0.85,
      });

      fbx.traverse((child) => {
        if (child instanceof THREE.Mesh) {
          if (child.name.toLowerCase().includes("torso")) {
            child.material = torsoMaterial;
          } else {
            child.material = limbsMaterial;
          }

          child.castShadow = true;
          child.receiveShadow = true;
        }
      });

      const idleAction = mixer.clipAction(idleAnimation.animations[0]);
      idleAction.play();

      setLoaded(true);
    }

    const handleMouseMove = (event: MouseEvent) => {
      mouseX.current = (event.clientX / window.innerWidth) * 2 - 1;
    };

    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [
    fbx,
    idleAnimation,
    mixer,
    baseTexture1,
    normalTexture1,
    roughnessTexture1,
    metallicTexture1,
    emissiveTexture1,
    aoTexture1,
    baseTexture2,
    normalTexture2,
    roughnessTexture2,
    metallicTexture2,
    emissiveTexture2,
    aoTexture2,
  ]);

  useFrame(() => {
    if (groupRef.current) {
      groupRef.current.rotation.y = THREE.MathUtils.lerp(
        groupRef.current.rotation.y,
        mouseX.current * 0.5,
        0.1,
      );
    }
  });

  if (error) {
    console.error(error);
    return null;
  }

  if (!loaded) {
    return null;
  }

  return (
    <group ref={groupRef}>
      <primitive
        object={fbx}
        scale={0.013}
        position={[0, -1.2, 0]}
        rotation={[0, Math.PI * 0.1, 0]}
      />
    </group>
  );
};

const Scene = () => {
  return (
    <>
      <ambientLight intensity={0.5} />
      <directionalLight
        color="#dec3c4"
        position={[5, 5, 5]}
        intensity={1}
        castShadow
      />
      <Model />
    </>
  );
};

const AnimatedRobot = () => {
  return (
    <div className="h-full w-full">
      <Canvas shadows camera={{ position: [0, 0, 3], fov: 60 }}>
        <Scene />
      </Canvas>
    </div>
  );
};

export default AnimatedRobot;

I tried implementing same useLoader for the shoot animation to display on click, but it didn’t really work as expected.