How can I write a JSDocs for TypeScript Generics?

I am trying to a JSDocs for the isEqual(object1: T, object2: T)
but I am getting a warning: The type ‘T’ is undefined .eslintjsdoc/no-undefined-types.

How can I fix this eslint warning here?

/**
 * isEqual - Check if two objects are equal
 * @param {T} object1 - Object 1
 * @param {T} object2 - Object 2
 * @returns {boolean} - True if objects are equal
 */
export function isEqual<T>(object1: T, object2: T): boolean {
  if (!isObject(object1) || !isObject(object2)) {
    return object1 === object2;
  }
  const keys1 = Object.keys(object1) as Array<keyof typeof object1>;
  const keys2 = Object.keys(object2) as Array<keyof typeof object2>;

  if (keys1.length !== keys2.length) {
    return false;
  }
  for (const key of keys1) {
    const val1 = object1[key];
    const val2 = object2[key];

    const areObjects = isObject(val1) && isObject(val2);
    if ((areObjects && !isEqual(val1, val2)) || (!areObjects && val1 !== val2)) {
      return false;
    }
  }

  return true;
}
/**
 * isObject - Check if value is an object
 * @param {unknown} value - Value to check
 * @returns {boolean} - True if value is an object
 */
function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null;
}

Tailwind Merge with Colors on Text and Text Shadows

tl;dr I have a text-shadow utility class in my tailwind.config.ts that allows me to change both the dimensions and colors of the shadow. I’m using Tailwind Merge, I can’t figure out how to stop text-shadow-{size/color} from conflicting with text-{color}.

The Problem

Often in CSS, using text shadows is very helpful for adding cool designs around text or even adding contrast for the text, rather than using a drop shadow. I’ve been created a text-shadow utility class to my Tailwind Config a while ago, and it works great, until I use it on a component that utilizes Tailwind Merge. Tailwind Merge is a great package, but—when using custom utility classes—it can get confused.

The Solution

Naturally, my goal was to use extendTailwindMerge to correct this issue. The documentation on configuring Tailwind Merge is well detailed, but since it only gives foo, bar, and baz for examples, I was a bit confused how to do something specific.

The Ask

Please look at my tailwind.config.ts and custom twMerge() function and let me know if you have any ideas. Thanks!

The Code

// tailwind.config.ts

// * Types
import type { Config } from 'tailwindcss'
import type { CSSRuleObject, ThemeConfig } from 'tailwindcss/types/config'

/**
 * ### Decimal Alpha to HEX
 * - Converts an RGB decimal alpha value to hexidecimal alpha format
 * @param decimalAlpha
 * @returns
 */
export function decimalAlphaToHex(decimalAlpha: number): string {
  // Ensure the input is within the valid range
  if (decimalAlpha < 0 || decimalAlpha > 1)
    throw new Error('Decimal alpha value must be between 0 and 1')

  // Convert decimal alpha to a hexadecimal value
  const alphaHex = Math.floor(decimalAlpha * 255)
    .toString(16)
    .toUpperCase()

  // Ensure the hexadecimal value is two digits long (e.g., 0A instead of A)
  if (alphaHex.length < 2) {
    return '0' + alphaHex
  } else {
    return alphaHex
  }
}

type GetTheme = <
  TDefaultValue =
    | Partial<
        ThemeConfig & {
          extend: Partial<ThemeConfig>
        }
      >
    | undefined,
>(
  path?: string | undefined,
  defaultValue?: TDefaultValue | undefined,
) => TDefaultValue

// * Plugins
import plugin from 'tailwindcss/plugin'
import headlessui from '@headlessui/tailwindcss'

// @ts-ignore
import { default as flattenColorPalette } from 'tailwindcss/lib/util/flattenColorPalette'

const config: Config = {
  content: [
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    textShadow: {
      sm: '0 0 0.125rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      DEFAULT: '0 0 0.25rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      md: '0 0 0.5rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      lg: '0 0 0.75rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      xl: '0 0 1rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      '2xl': '0 0 2rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      '3xl': '0 0 3rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
      none: 'none',
    },
  },
  plugins: [
    plugin(function ({ matchUtilities, theme }) {
      const colors: { [key: string]: string } = {},
        opacities: { [key: string]: string } = flattenColorPalette(
          theme('opacity'),
        ),
        opacityEntries = Object.entries(opacities)

      Object.entries(flattenColorPalette(theme('colors'))).forEach((color) => {
        const [key, value] = color

        if (typeof key !== 'string' || typeof value !== 'string') return null

        colors[key] = value.replace(' / <alpha-value>', '')

        if (value.startsWith('#') && value.length === 7)
          opacityEntries.forEach(([opacityKey, opacityValue]) => {
            colors[`${key}/${opacityKey}`] = `${value}${decimalAlphaToHex(
              Number(opacityValue),
            )}`
          })

        if (value.startsWith('#') && value.length === 4)
          opacityEntries.forEach(([opacityKey, opacityValue]) => {
            colors[`${key}/${opacityKey}`] = `${value}${value.slice(
              1,
            )}${decimalAlphaToHex(Number(opacityValue))}`
          })

        if (value.startsWith('rgb') || value.startsWith('hsl'))
          opacityEntries.forEach(([opacityKey, opacityValue]) => {
            colors[`${key}/${opacityKey}`] = `${value.slice(
              0,
              -1,
            )} / ${opacityValue})`.replace(' / <alpha-value>', '')
          })
      })

      matchUtilities(
        {
          'text-shadow': (value) => {
            const cssProperties: CSSRuleObject = {}

            if (
              typeof value === 'string' &&
              (value.startsWith('#') ||
                value.startsWith('rgb') ||
                value.startsWith('hsl'))
            ) {
              cssProperties['--tw-text-shadow-color'] = value
            } else {
              cssProperties['text-shadow'] = value
            }

            return cssProperties
          },
        },
        { values: { ...theme('textShadow'), ...colors } },
      )
    }),
  ],
}

export default config

My Best Attempt

// utils/custom-tailwind-merge.ts

import { extendTailwindMerge } from 'tailwind-merge'
import colors from 'tailwindcss/colors'

const colorList: { [key: string]: string[] }[] = []

Object.entries(colors).forEach(([colorName, valueList]) => {
  if (
    colorName === 'inherit' ||
    colorName === 'transparent' ||
    colorName === 'white' ||
    colorName === 'black'
  )
    return
  return colorList.push({ [colorName]: Object.keys(valueList) })
})

type AdditionalClassGroupIds = 'text-shadow'

export const twMerge = extendTailwindMerge<AdditionalClassGroupIds>({
  extend: {
    classGroups: {
      'text-shadow': [
        'sm',
        'DEFAULT',
        'md',
        'lg',
        'xl',
        '2xl',
        '3xl',
        'none',
        ...colorList,
        'transparent',
        'white',
        'black',
      ],
    },
  },
})
// components/link.tsx

import type { LinkProps } from '@/typings/components'
import { twMerge } from '@/utils/custom-tailwind-merge'

export default function Link({ children, className }: LinkProps) {
  const defaultClasses = 'text-blue-500'

  return (
    <a className={twMerge(defaultClasses, className)}>{children}</a>
  )
}

Displaying JSON data with Vue3

This is weird, but it is the first time I have dealt with JSON in Vue3, and it doesn’t seem to be reacting as I expected.

I have some JSON in a Vue DataObject called dataObject

data() {
  return {
    dataObject: {}
  }
},

My JSON data looks, something, like this,

{
  "myDataList": [
    {
      "list_item_1_1": "1234",
      "list_item_1_2": "2023-09-21T09:06:13.000Z",
      ...
      "list_item_1_6": [
        {
          "inline_list_item_1_6_1": "ABCD",
          ...
        }
      ],
    },
    {
      "list_item_2_1": "EFGH",
      "list_item_2_2": "5678",
      "list_item_2_3": "IJKL",
      ...
      "list_item_2_8": {
        "list_item_2_8_1": "OK",
        "list_item_2_8_2": "MAYBE",
        "list_item_2_8_3": "9011"
      },
    },
    {
      ...
    }
  ]
}

Now for some reason I can’t reach into the data and pull out, for example, list_item_2_8. As this is Vue I am using <p>Data Value: {{this.dataObject.1.list_item_2_8}</p>, however this gives me an Unexpected token error in the console. When I run this through a JSON tool, this configuration of JSON is valid…

StackOverflow, Google, JSONFormatter.org and JSONQueryTool.com

Could someone point me in the right direction please? What am I doing wrong?

D3.js transition only works for the deleted Elements

When I update my data source, the transition only works for the deleted elements, but not for the updated elements.

I have inserted a .transition() in the “deleted section” and in the “update section”. I suspect that the transition() function in my update section is being used incorrectly.

No error is displayed, which could help me to solve the problem.

Can someone please help me?

            // Data
            const data1 = {
            "name": "TOPICS",
            "id": 1,
            "children": [{
                "name": "Topic A",
                "id": 2,
                "children": [{
                    "name": "Sub A1",
                    "id": 5,
                    "size": 4
                    }, {
                    "name": "Sub A2",
                    "id": 6,
                    "size": 4
                    }]
            }, {
                "name": "Topic B",
                "id": 3,
                "children": [{
                    "name": "Sub B1",
                    "id": 7,
                    "size": 3
                    }, {
                    "name": "Sub B2",
                    "id": 8,
                    "size": 3
                    }, {
                    "name": "Sub B3",
                    "id": 9,
                    "size": 3
                    }]
            }, {
                "name": "Topic C",
                "id": 4,
                "children": [{
                    "name": "Sub A3",
                    "id": 10,
                    "size": 4
                    }, {
                    "name": "Sub A4",
                    "id": 11,
                    "size": 4
                    }]
            }]
            };

            const data2 = {
            "name": "TOPICS",
            "id": 1,
            "children": [{
                "name": "Topic A",
                "id": 2,
                "children": [{
                    "name": "Sub A1",
                    "id": 5,
                    "size": 4
                    }, {
                    "name": "Sub A2",
                    "id": 6,
                    "size": 4
                    }]
            }, {
                "name": "Topic B",
                "id": 3,
                "children": [{
                    "name": "Sub B1",
                    "id": 7,
                    "size": 3
                    }, {
                    "name": "Sub B2",
                    "id": 8,
                    "size": 3
                    }, {
                    "name": "Sub B3",
                    "id": 9,
                    "size": 3
                    }]
            }]
            };

            //-------------------------------------------------------------------------------------------            
            // Declare variables
            let i_region_static_id = "sunburst",
                parentDiv = document.getElementById(i_region_static_id),
                width = parentDiv.clientWidth,
                height = 450,
                root,
                x,
                y,
                color = d3.scaleOrdinal(d3.schemeCategory10);
                maxRadius = (Math.min(width, height) / 2) - 5;


            const partition = d3.partition();
            //-----------------------------------------------------------------------------------
            // SVG-Element
            let svg = d3.select('#' + i_region_static_id).append('svg')
                            .style('width', width)
                            .style('height', height)
                            .attr('viewBox', `${-width / 2} ${-height / 2} ${width} ${height}`)

            //-----------------------------------------------------------------------------------
            // X-Scale
            x = d3.scaleLinear()
                    .range([0, 2 * Math.PI])
                    .clamp(true);

            //-----------------------------------------------------------------------------------
            // Y-Scale
            y = d3.scaleSqrt()
                    .range([maxRadius * .1, maxRadius]);

            //-----------------------------------------------------------------------------------
            // Create Arc generator
            const arc = d3.arc()
                .startAngle(d => x(d.x0))
                .endAngle(d => x(d.x1))
                .innerRadius(d => Math.max(0, y(d.y0)))
                .outerRadius(d => Math.max(0, y(d.y1)))

            //-------------------------------------------------------------------------------------------
            // Initialize and Update sun 
            function update_sun(pData) {
                const valueAccessor = (d) => d.size;

                root = d3.hierarchy(pData); //set data
                
                valueAccessor == null ? root.count() : root.sum((d) => Math.max(0, valueAccessor(d)));

                // Sort Nodes
                root.sort((d) => d3.descending(d.value))

                const slice = svg.selectAll('g.slice')
                    .data(
                        partition(root).descendants(),
                        function(d) { return d.data.id; }
                    );

                //-------------------------------------------------------------------------------------------
                // Enter Section
                const newSlice = slice.enter()
                                        .append('g').attr('class', 'slice')
                                        .attr('display', d => d.depth < 2 ? null : 'none') // Hide levels lower depth

                newSlice.append('path')
                            .attr('class', 'main-arc')
                            .style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color) //set source color, otherwise default color
                            .attr('d', arc);                

                // Update Section
                slice.select('path')
                        .style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color)
                        .each(function(d) {
                            this.x0 = d.x;
                            this.dx0 = d.dx;
                        })
                        .transition().duration(1000)
                        .attrTween("d", function(d) { return function() { return arc(d); }; })
       
                // Delete Section
                slice.exit().transition().duration(500).style("fill-opacity", 0.2).remove();
            }

            //-------------------------------------------------------------------------------------------

            update_sun(data1)

            let i = 0;
            d3.interval(() => {
            if (i++ % 2 === 0) {
                console.log("data2")
                update_sun(data2);
            } else {
                console.log("data1")
                update_sun(data1);
            }

            }, 4000)
.slice .main-arc {
    stroke: #fff;
    stroke-width: 1px;
}
<!DOCTYPE html>
<html>             
    <head>              
        <link rel="stylesheet" href="sun.css">
        <meta charset="utf-8" /> 
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body>             
        <div id="sunburst"></div>
        <script src="https://d3js.org/d3.v7.js" charset="utf-8"></script> 
    </body>
</html>

Webauthn AuthenticatorAttestationResponse “getPublicKey” returns a restricted object?

I’m trying to implement passkeys for my web app according to these guides, and I’m primarily working on the registration flow. After I’ve created the passkey using navigator.credential.create, I try obtaining the newly generated public key using the getPublicKey method on the returned response attribute. However, when I try inspecting the object, my browser says:

Restricted { }

According to MDN, this object should be an ArrayBuffer. In order to send it to the server I try to convert it to a base64-encoded string, however whenever I try to do something with this, the browser throws an error saying I can’t access this object.

I’ve already worked around this by sending the attestationObject to the server and having the server decode it, but according to everything I’ve read, I should be able to do this client-side. Am I missing something obvious, or is this expected behavior?

Typescript join

<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Today's Date</title>
    <script>
        let competitionToday = "no competition"
        alert("Today there is " + competitionToday + "on");
    </script>
</head>
<body>
</body>
</html>

I can’t figure out why the

alert("Today there is " + competitionToday + "on");

is only alerting:
“Today there is no competition”
not:
“Today there is no competition on”

Change the path of images in JSON returned using AJAX

I want to change image whenever I choose the color the view so I write an Ajax to return the product of that color. Here the code :

$(document).on('change', '#colorId', function () {
    GetImages(colorIds)
});
function GetImages(productId) 
{
    $.ajax({
        url: '@Url.Action("GetImages","ProductView")',
        type: 'GET',
        data: { id : productId },
        success: function (data) {

            console.log("Hello " + data);

            var images = [
                data.image1,
                data.image2,
                data.image3,
                data.image4
            ];

            var image = document.querySelectorAll('#product-imgs .product-preview .image');

            for (var i = 0; i < images.length; i++) {
                image.src = '/Contents/img/' + images[i].split('/').pop();
            }
        }
    })
}

Here the json return JsonResult. As you can see its include 4 images path. So I want to change in the view below by the image path that I received in Json

<div class="col-md-5 col-md-push-2">
    <div id="product-main-img">
        <div class="product-preview">
            <img class="image" src="~/Contents/img/@Model.ProductItems.Image1" alt="">
        </div>

        <div class="product-preview">
            <img class="image" src="~/Contents/img/@Model.ProductItems.Image2" alt="">
        </div>

        <div class="product-preview">
            <img class="image" src="~/Contents/img/@Model.ProductItems.Image3" alt="">
        </div>

        <div class="product-preview">
            <img class="image"  src="~/Contents/img/@Model.ProductItems.Image4" alt="">
        </div>
    </div>
</div>
<!-- /Product main img -->
<!-- Product thumb imgs -->
<div class="col-md-2  col-md-pull-5">
    <div id="product-imgs">
        <div class="product-preview">
            <img class="image" src="~/Contents/img/@Model.ProductItems.Image1" alt="">
        </div>

        <div class="product-preview">
            <img class="image" src="~/Contents/img/@Model.ProductItems.Image2" alt="">
        </div>

        <div class="product-preview">
            <img class="image"  src="~/Contents/img/@Model.ProductItems.Image3" alt="">
        </div>

        <div class="product-preview">
            <img class="image" src="~/Contents/img/@Model.ProductItems.Image4" alt="">
        </div>
    </div>
</div>

I have tried many way but still doesn’t work. When I change

 var image = document.querySelector('.image');

Its only change one image. Does anyone know solutions? I will appreciate any instructions. Thanks for your reading.

lora-packet configure Join Accept

I’m trying to configure the Join Accept using the lora-packet library as follows:

            const NetIdHexString = "001122"; //network identifier
            const AppNonceHexString = "334455"; // or JoinNonce non-repeating value provided by Join Server
            const NetIdBuffer = Buffer.from(NetIdHexString, "hex");
            const AppNonceBuffer = Buffer.from(AppNonceHexString, "hex");
            const sessionKeys = lora_packet.generateSessionKeys10(AppKey, NetIdBuffer, AppNonceBuffer, Buffer.from(devNonce, "hex"));
    
            keysInstance.setSessionKeys(sessionKeys.AppSKey, sessionKeys.NwkSKey);
    
            if (debugMode) {
                console.log("AppSKey:", sessionKeys.AppSKey.toString("hex"));
                console.log("NwkSKey:", sessionKeys.NwkSKey.toString("hex"));
            }
    
            const constructedPacket = lora_packet.fromFields(
                {
                    MType: "Join Accept", // (default)
                    NetID: NetIdBuffer,
                    AppNonce: AppNonceBuffer,
                    DevAddr: Buffer.from("01766f7f", "hex"), // big-endian
                    DLSettings: 0,
                    RxDelay: 0
                },
                null, // AppSKey
                null, // NwkSKey
                AppKey
    
            );
            
            if (debugMode) {
                console.log("constructedPacket.toString()=n" + constructedPacket);
            }
            const base64 = constructedPacket.getPHYPayload().toString('base64');
            if (debugMode) {
                console.log("wireFormatPacket.toString()=" + constructedPacket.getPHYPayload().toString("hex") + " base64= " + base64  + " wireFormatPacket length:" + constructedPacket.MACPayloadWithMIC.length);
            }

But when I check it in the LoRaWAN 1.0.x packet decoder, I get a message that the MIC is incorrect. What am I doing wrong?

Im trying to check constructedPacket.MACPayloadwithMIC but its inccorect too.

Why Javascript multiple async awaits behaves differently when it awaits promises vs when it awaits functions returning promises?

I have the below code with both the implementations.

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise 1');
    }, 1000);
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise 2');
    }, 1000);
})

const p1func = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise 1 func');
    }, 1000);
});

const p2func = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise 2 func');
    }, 1000);
})

async function TestPromise() {
    console.log("started");
    const val = await p1;
    console.log(val);

    const val2 = await p2;
    console.log(val2);
    //checkpoint 1: reaches this point in 1 second
    const val3 = await p1func();
    console.log(val3);

    const val4 = await p2func();
    console.log(val4);
    //from checkpoint 1, it takes 2 seconds to reach here
}

TestPromise();

When I ran the code I noticed that when js awaits 2 promises, it starts executing them parallelly & completes within 1 second(asynchronously). When the functions returning promises were run JS ran them one by one(synchronously).Is JS engine detecting 2 awaits on promises prior run inorder to be able to execute them parallelly? Why can’t it do the same with functions returning promises?

JavaScript Web Audio API – Sampling Problem and FFT

I am having a problem with the Web Audio API where the audio sampling rate is incorrect on an Android phone using Chrome, Opera and Edge browser, but it works fine using the Firefox browser.

I am recoding the microphone into an array using Web Audio API as follows, where just snippets of my code have been included, as I cannot paste all of it, but this is just to show which functions I am using.

let audioContext;
dataArrayFft = new Float32Array(bufferLength);
audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate:48000});
analyser = audioContext.createAnalyser();
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);

I then call the following routine periodically (30Hz) to update my chart.js:

analyser.getFloatFrequencyData(dataArrayFft);

The code works fine on my PC using the Chrome browser and shows the full FFT spectrum up to 24kHz with a 48kHz sampling rate.

With my Android phone I point each of the browsers (Chromer, Opera, Firefox, Edge) to my laptop IP address (https://192.168.1.149:3000) to view the web page on my phone to test my app.

The following IMAGE 1 shows the app running in the Chrome browser on my Windows 10 laptop and it is working as expected.

PC Chrome Browser running as expected

The following images 2,3,4 show my Android phone viewing the web page on my laptop and as you can see for Chrome, Opera and Edge (IMAGES 2,3,5) the FFT spectrum rolls off at 8kHz but for Firefox (IMAGE 4) it shows the full spectrum as expected and I have confirmed it works by using a signal generator.

Android Chrome, Opera, Firefox, Edge Browsers showing the problem

I do not understand why it works as expected on Firefox but there is this 8kHz limit on the other browsers indicating the sampling rate is being limited to 16kHz? Any thoughts on what this problem could be caused by?

How do i make a vertical bar to reveal the image underneath? [closed]

I have a little bit of a problem and i don t know how to approach it. So basically I have to pictures of a car, identical, one is scratched and one is not. The two images are overlaped and have a vertical bar in the middle. To the left of the bar only the scratched image is visible to the right only the normal one. As i drag the bar to the left, what is to the right of the car turns into the normal image. Vice versa if i move the bar to the right, whatever is to the left of the bar turns into the scratched car.

I have tried a lot of things but nothing seemed to work.

Typescript: matches 2 arrays [duplicate]

I need to find out if 2 arrays match (have common elements) in one or more elements.

Is there a more elegant way than the 2 loops?

In Java I would use a stream with lambda.

lst1.stream ().anyMatch (c -> lst2.contains (c))

But I am not sure hot to do that in Typescript/Javascript.

let lst1 : string [] = ["aaa", "bbb", "ccc"];
let lst2 : string [] = ["ddd", "eee", "bbb"];    // 1 match at "bbb"

let matches : boolean = false;
for (let a of this.lst1)
{
    for (let b of this.lst2)
    {
        if (a == b)
        {
            matches = true;
        }
    }
}