Regular expression matching three character words separated by comma and space and all of partial versions of such string

I would like to validate user input in an web application with a regex. The user is allowed to type three character words and separate them by a comma or comma followed by space.

Valid input could be: ‘B’ ‘BR’ ‘BRU’ ‘BRU,’ ‘BRU, ‘ ‘BRU, A’ ‘BRU, ALC’ ‘BRU, ALC,B’ and so on.

Invalid input would be ‘BRUU’ or ‘BRU ALC’

I wanted to solve this task with a Regex match, but I could not quite get it right. I ended up with this Regex:

/^(?:[A-Z0-9]{1,3}(?:, |,)?)*$/

which unfortunately allows input like “BRUALC”

I solved it with additional runtime check:

function isValid(input) {
  const format = /^(?:[A-Z0-9]{1,3}(?:, |,)?)*$/;
  if (!format.test(input)) return false;

  // Split the words and validate each
  const words = input
    .split(/, ?/)
    .filter(w => w.length > 0); // skip empty trailing entries

  return words.every(w => /^[A-Z0-9]{1,3}$/.test(w));
}

but I wonder if there is a solution using only a regular expression

How to create 2 row titles? First for year and second for quarters

I’m implementing a 5 year view in resourceTimeline type.

resource5YearsSlide:
                {
                    type: 'resourceTimeline',
                    buttonText: '5 Years',
                    dateIncrement: { year: 1 },
                    slotDuration: {
                        month: 3
                    },
                    slotLabelInterval: {
                        month: 3
                    },
    
                    slotLabelFormat: function(arg) {
                        const date = new Date(arg.date.marker);
                        const month = date.getMonth();
                        const year = date.getFullYear();
                        const quarter = Math.floor(month / 3) + 1;
                        return 'T' + quarter + ' ' + year;
                    },
                    visibleRange: function (currentDate) {
                        const start = new Date(currentDate);
                        start.setMonth(0);
                        start.setDate(1);
                        const end = new Date(currentDate);
                        end.setFullYear(start.getFullYear() + 4);
                        end.setMonth(11); // Décembre
                        end.setDate(31);
                        return {
                            start: start.toISOString(),
                            end: end.toISOString()
                        };
                    }
                },  

This works great, but I would find a way to have a 2 row titles, first for year, and second for quarters.

I’ve tryed :

slotLabelFormat: [
                        {
                            year: 'numeric',
                        },
                        {
                            custom:function(arg) {
                                    // Conversion explicite en objet Date natif
                                    const date = new Date(arg.date.marker);
                                    const month = date.getMonth();
                                    const quarter = Math.floor(month / 3) + 1;
                                    return 'T' + quarter;
                                }
                        }
                    ],

and other key instead of ‘custom’, but the function is not called at all, but the year row well printed.

Is there any way to circunviene that?

Incomplete and Unseekable WEBM Video from MediaRecorder Streamed via WebSocket to FastAPI and Azure Append Blob

I am building a large-scale video monitoring application that needs to record a user’s webcam and screen for up to 3 hours and upload the streams in real-time to Azure Blob Storage. The target is to handle up to 10,000 concurrent users.

My current architecture is:

Frontend: Uses the MediaRecorder API in JavaScript to capture webcam and screen streams. It sends video chunks every second over two separate WebSockets.

Backend: A FastAPI server receives the binary data from the WebSockets and appends it directly to an Azure Append Blob.

Playback: A separate FastAPI endpoint (/video/{session_id}) streams the final .webm file from Azure Blob Storage for playback.

Despite the basic functionality being in place, I’m encountering critical issues with reliability and playback.

The Problems

**Incomplete Recordings: **For a 3-hour session, the final video file in Azure is often incomplete. The duration might be much shorter, or the video just stops abruptly, indicating significant data loss.

Unseekable Video Files: When playing the saved .webm video from Azure, the video player’s seek bar does not work. The video plays from the beginning, but you cannot skip to different timestamps.

Inconsistent File Sizes: For recordings of the same duration, the final file sizes vary dramatically between sessions, which I suspect is another symptom of the data loss problem.

Frontend (JavaScript)

let websocketWebcam = null;
let websocketScreen = null;
let webcamRecorder = null;
let screenRecorder = null;
let isRecording = false;

// Connects to a WebSocket endpoint
function connectWebSocket(streamType, sessionId) {
    // Replaced 'your-backend-domain.com' with your actual domain
    const wsUrl = `wss://your-backend-domain.com/upload_video/${sessionId}_${streamType}`;
    const ws = new WebSocket(wsUrl);
    ws.onopen = () => console.log(`WebSocket connected: ${streamType}`);
    ws.onclose = () => console.log(`WebSocket closed: ${streamType}`);
    ws.onerror = error => console.error(`WebSocket error (${streamType}):`, error);
    return ws;
}

// Periodically reconnects WebSockets to keep the connection alive
function reconnectWebSockets(sessionId) {
    console.log("Attempting to reconnect WebSockets...");
    if (websocketWebcam) websocketWebcam.close();
    if (websocketScreen) websocketScreen.close();
    
    websocketWebcam = connectWebSocket("webcam", sessionId);
    websocketScreen = connectWebSocket("screen", sessionId);
    console.log("WebSockets reconnected.");
}

// Starts the recording process
async function startRecording(sessionId) {
    try {
        isRecording = true;
        
        // Assume webcamStream and screenStream are already acquired from navigator.mediaDevices
        
        websocketWebcam = connectWebSocket("webcam", sessionId);
        websocketScreen = connectWebSocket("screen", sessionId);
        
        // Recorder for webcam stream
        webcamRecorder = new MediaRecorder(webcamStream, { mimeType: "video/webm; codecs=vp9" });
        webcamRecorder.ondataavailable = event => {
            if (event.data.size > 0) sendToWebSocket(event.data, websocketWebcam);
        };
        webcamRecorder.start(1000); // Send data every 1 second

        // Recorder for screen stream
        screenRecorder = new MediaRecorder(screenStream, { mimeType: "video/webm; codecs=vp9" });
        screenRecorder.ondataavailable = event => {
            if (event.data.size > 0) sendToWebSocket(event.data, websocketScreen);
        };
        screenRecorder.start(1000); // Send data every 1 second

        console.log("Recording started for session:", sessionId);

        // Set interval to reconnect WebSockets every 45 minutes
        setInterval(() => reconnectWebSockets(sessionId), 2700000); // 45 minutes

    } catch (error) {
        console.error("Error starting recording:", error);
    }
}

// Sends a blob of data to the WebSocket
function sendToWebSocket(blob, ws) {
    if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(blob);
    } else {
        console.warn("WebSocket not open. Data chunk might be lost.");
        // Simple retry logic, which might not be robust enough
        setTimeout(() => sendToWebSocket(blob, ws), 1000);
    }
}
import asyncio
from fastapi import FastAPI, WebSocket, HTTPException, Request
from fastapi.responses import StreamingResponse, Response
from azure.storage.blob import BlobServiceClient, BlobClient, AppendBlobService
import re

# Assume blob_service_client and container_name are configured
# blob_service_client = BlobServiceClient.from_connection_string(...)
# container_name = "videos"

app = FastAPI()

@app.websocket("/upload_video/{session_id}")
async def upload_video(websocket: WebSocket, session_id: str):
    """ WebSocket endpoint to receive video chunks and append to an Append Blob """
    await websocket.accept()
    
    blob_name = f"{session_id}.webm"
    blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob_name)

    try:
        # Create Append Blob if it doesn't exist
        if not blob_client.exists():
            blob_client.create_append_blob()
    except Exception as e:
        print(f"Error initializing blob: {e}")
        await websocket.close()
        return

    try:
        while True:
            data = await websocket.receive_bytes()
            blob_client.append_block(data)
            
    except Exception as e:
        print(f"Error during video upload for session {session_id}: {e}")
    finally:
        print(f"Upload completed for session {session_id}")
        await websocket.close()

# Video streaming endpoint (simplified for brevity)
@app.get("/video/{session_id}")
async def get_video(session_id: str, request: Request):
    """ Video Streaming API with Range Support """
    blob_client = blob_service_client.get_blob_client(container=container_name, blob=f"{session_id}.webm")
    
    try:
        blob_properties = blob_client.get_blob_properties()
        file_size = blob_properties.size
        range_header = request.headers.get("Range")
        
        start, end = 0, file_size - 1
        status_code = 200 # Full content
        
        if range_header:
            match = re.search(r"bytes=(d+)-(d*)", range_header)
            if match:
                start = int(match.group(1))
                end = int(match.group(2)) if match.group(2) else file_size - 1
                status_code = 206 # Partial content

        length = end - start + 1
        headers = {
            "Content-Range": f"bytes {start}-{end}/{file_size}",
            "Content-Length": str(length),
            "Accept-Ranges": "bytes",
            "Content-Type": "video/webm",
        }
        
        def stream_video():
            stream = blob_client.download_blob(offset=start, length=length)
            yield from stream.chunks()

        return StreamingResponse(stream_video(), headers=headers, status_code=status_code)

    except Exception as e:
        raise HTTPException(status_code=404, detail="Video not found or error in streaming")

My Questions

How to Create a Seekable WEBM file? My current process of appending raw MediaRecorder chunks results in a webm file without the proper metadata (like a Cues element) needed for seeking. How can I fix this? Should I be post-processing the file on the server (e.g., with FFmpeg) after the stream ends to inject the right metadata? Is there a way to generate this metadata on the client?

How to Prevent Data Loss? My strategy of reconnecting the WebSocket every 45 minutes feels wrong and is likely a major source of data loss. What is a more robust method for maintaining a long-running, stable connection for 3+ hours? Should I implement a heartbeat (ping/pong) mechanism instead?

**Is Append Blob the Right Architecture? **Is streaming to a single, large Append Blob for 3 hours a sound strategy? Or would it be more reliable to create smaller, timed video chunks (e.g., a new blob every 5 minutes) and then create a manifest file or concatenate them later?

Can setting innerText then copying innerHTML avoid XSS attacks?

I’m using library that creates certain HTML elements but only lets me set their innerHTML (not innerText) so I want to mitigate XSS attacks.

If I put untrusted text into a temporary element using innerText, then copy that element’s innerHTML into the innerHTML of a new element, is the new element vulnerable to XSS attacks?

For example, could newElement below be a vulnerable entry point for a script?

function libraryCodeToCreateElement(text) {
    const newElement = document.createElement('p');
    newElement.innerHTML = text;
    document.body.appendChild(newElement);
}

const untrustedText = 'some malicious string';
const sanitizingElement = document.createElement('span');
sanitizingElement.innerText = untrustedText;
libraryCodeToCreateElement(sanitizingElement.innerHTML);

This seems to protect against attacks I can come up with, but I could be missing something.

Property ‘children’ does not exist on type ‘AvatarFallbackProps’

I’m using Chakra UI with Ark (or @chakra-ui/react components like Avatar.Root, Avatar.Fallback, etc.) in a Next.js TypeScript project.I’m building a custom Avatar component using Chakra UI with Ark-style subcomponents like Avatar.Root, Avatar.Fallback, Avatar.Image, etc., in a Next.js + TypeScript project.

I created a custom AvatarFallback component using React.forwardRef, and inside it, I’m trying to access props.children. However, TypeScript throws the following error:

Type ‘{ children: string | number | bigint | true | Iterable<ReactNode> | Promise<AwaitedReactNode> | Element; ref: ForwardedRef<HTMLDivElement>; }’ is not assignable to type ‘IntrinsicAttributes & AvatarFallbackProps & RefAttributes<HTMLDivElement>’.

Property ‘children’ does not exist on type ‘IntrinsicAttributes & AvatarFallbackProps & RefAttributes<HTMLDivElement>

'use client'

import type { GroupProps, SlotRecipeProps } from '@chakra-ui/react'
import { Avatar as ChakraAvatar, Group } from '@chakra-ui/react'
import * as React from 'react'

type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>

export interface AvatarProps extends ChakraAvatar.RootProps {
  name?: string
  src?: string
  srcSet?: string
  loading?: ImageProps['loading']
  icon?: React.ReactElement
  fallback?: React.ReactNode
}
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(function Avatar(props, ref) {
  const { name, src, srcSet, loading, icon, fallback, children, ...rest } = props
  return (
    <ChakraAvatar.Root ref={ref} {...rest}>
      <AvatarFallback name={name} icon={icon}>
        {fallback}
      </AvatarFallback>
      {/* Pass only the correct string values here */}
      <ChakraAvatar.Image
        src={typeof src === 'string' ? src : undefined}
        srcSet={typeof srcSet === 'string' ? srcSet : undefined}
        loading={loading}
        layout="fill"
        alt={name}
      />
      {children}
    </ChakraAvatar.Root>
  )
})

interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
  name?: string
  icon?: React.ReactElement
  children?: React.ReactNode
}

const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
  function AvatarFallback(props, ref) {
    const { name, icon, children, ...rest } = props
    let fallbackContent = null
    if (children) {
      fallbackContent = children
    } else if (name != null) {
      fallbackContent = <>{getInitials(name)}</>
    } else {
      fallbackContent = <ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
    }
    return (
      <ChakraAvatar.Fallback ref={ref} {...rest}>
        {fallbackContent}
      </ChakraAvatar.Fallback>
    )
  }
)

function getInitials(name: string) {
  const names = name.trim().split(' ')
  const firstName = names[0] != null ? names[0] : ''
  const lastName = names.length > 1 ? names[names.length - 1] : ''
  return firstName && lastName ? `${firstName.charAt(0)}${lastName.charAt(0)}` : firstName.charAt(0)
}

interface AvatarGroupProps extends GroupProps, SlotRecipeProps<'avatar'> {}

export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(function AvatarGroup(
  props,
  ref
) {
  const { size, variant, borderless, ...rest } = props
  return (
    <ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
      <Group gap="0" spaceX="-3" ref={ref} {...rest} />
    </ChakraAvatar.PropsProvider>
  )
})

This error is saying that the props you passed to include children, but your AvatarFallbackProps type does not define children — so TypeScript doesn’t allow it.

three js does not load model (Angular) (GLTF Loader)

I’m using an Angular 19 component to load a 3-D model of Earth and make it spin.
The only console message I see is
THREE.GLTFLoader: Unknown extension "KHR_materials_pbrSpecularGlossiness",
which doesn’t appear to stop the scene from rendering.
Has anyone run into this issue or know how to resolve it?
Thanks!

import { AfterViewInit, Component, OnInit } from '@angular/core';
import * as THREE from 'three';
import { DOCUMENT } from '@angular/common';
import { Directive, EventEmitter, HostListener, Inject, Input, Output } from '@angular/core';
import { ShowArticleComponent } from "../show-article/show-article.component";
import { OrbitControls } from 'three-orbitcontrols-ts';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

@Component({
  selector: 'app-world-spinning',
  imports: [],
  templateUrl: './world-spinning.component.html',
  styleUrl: './world-spinning.component.css'
})
export class WorldSpinningComponent implements OnInit, AfterViewInit {
  private doc: Document;
  
  // Three.js global variables
  private scene!: THREE.Scene;
  private camera!: THREE.PerspectiveCamera;
  private loader!: GLTFLoader;
  private loadedModel: any = null;
  private renderer!: THREE.WebGLRenderer;
  private topLight!: THREE.DirectionalLight;
  private topLight2!: THREE.DirectionalLight;
  private ambientLight!: THREE.AmbientLight;
  private controls!: OrbitControls;
  private startRotation: boolean = true;
  private canvas: any = null;

  constructor(@Inject(DOCUMENT) doc: any) {
    this.doc = doc;
  }

  ngOnInit(): void {
  }

  ngAfterViewInit(): void {
    this.createThreeJsBox();
  }

  createThreeJsBox(): void {
    // Scene for animation + Camera

    this.canvas = this.doc.getElementById("canvas-box");

    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 1000);
    this.camera.position.set(100, 100, 400); // Position camera away from the model
    this.camera.lookAt(0, 0, 4); // Point camera to the origin

    // Loader
    this.loader = new GLTFLoader();

    // Render the model
    this.loader.load(
      `earth/scene.gltf`,
      (gltf) => {
        // Add to scene
        this.loadedModel = gltf.scene;
        this.scene.add(this.loadedModel);
      },
      (xhr) => {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
      },
      (error) => {
        // In case of errors
        console.error(error);
      }
    );

    // Instantiate a new renderer and set its size
    this.renderer = new THREE.WebGLRenderer({ alpha: true }); // Alpha: true allows for the transparent background
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    // Add the renderer to the DOM
    this.canvas.appendChild(this.renderer.domElement);

    // Add lights to the scene, so we can actually see the 3D model
    this.topLight = new THREE.DirectionalLight(0xffffff, 4); // (color, intensity)
    this.topLight.position.set(100, 100, 1000); // top-right-ish
    this.topLight.castShadow = true;
    this.scene.add(this.topLight);

    this.topLight2 = new THREE.DirectionalLight(0xffffff, 4); // (color, intensity)
    this.topLight2.position.set(500, 500, 500); // top-right-ish
    this.topLight2.castShadow = true;
    this.scene.add(this.topLight2);

    this.ambientLight = new THREE.AmbientLight(0x333333, 3);
    this.scene.add(this.ambientLight);

    this.renderer.render(this.scene, this.camera);

    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.minDistance = 0; // Prevent zooming too close
    this.controls.maxDistance = 1000; // Prevent zooming too far
    this.controls.update();

    // Animation Loop
    this.animate();

    // Resize Listener
    window.addEventListener("resize", () => {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    });
  }

  animate = (): void => {
    requestAnimationFrame(this.animate);
    
    if (this.loadedModel && this.startRotation) {
      this.loadedModel.rotation.y += 0.01; // Adjust speed as needed
    }
    
    this.controls.update();
    this.renderer.render(this.scene, this.camera);
  }
}

I have tried to change the camera position multiple times and also the camera.LookAt. I also put the gltf model in the public folder and it seems to be ok.

How to query a remote sqlserver? [closed]

in a Windows 11 pc (named : pcdata, ip : 10.20.30.40), i have installed sql server express, and created a database. I have published into this pc a web api for crud objects into the local IIS and named apiweb.com.

This service is created with ip 10.20.30.45 and ip port 5283. I can request this api remotely as apiweb.com:5283/api/product which gives me the list of the products.
So for memory : sqlserverexpress is : 10.20.30.40 and web api is apiweb.com:5283/api/product web api service with ip 10.20.30.45.
Now, from my dev PC, I want to create an aspnet core querying this web api with javascript.

my js file is :

'use strict'  
const uri = 'api/product';
let data = [];

function getItems() {
  
    fetch(uri)
            .then(response => response.json())
            .then(data => _displayItems(data))
        .catch(error => console.error('Unable to get items.', error));
}

The program uses the path descride in localsettings.json and concatene the uri located in my js file.

 "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5023",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },

My question is how to setup my aspnet core app to query the remote web api.

NextAuth v5 req.auth Undefined with `wrangler pages dev` on Cloudflare Pages, Works with `npm run dev`

I’m using NextAuth v5 with Next.js 15.3.2 for authentication with JWT strategy. Everything works as expected when running the app locally using:

npm run dev
The req.auth object in my middleware is properly populated after login, and auth.user contains the expected user information.

Problem:
When I run the app using:

wrangler pages dev
to emulate the Cloudflare Pages environment, I face this issue:

The authentication flow appears successful (cookies are set in the browser).

However, req.auth is undefined inside my Next.js middleware.ts.

Consequently, users are redirected to /login even after successful login.

My Middleware Example:

import { auth } from "@/auth";

export default auth((req) => {
  console.log("req.auth.user:", req.auth?.user);
  console.log("AUTH_SECRET:", process.env.AUTH_SECRET);

  if (!req.auth && req.nextUrl.pathname !== "/login") {
    return Response.redirect(new URL("/login", req.nextUrl));
  }
});

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

My NextAuth Config Snippet:

export const auth = NextAuth({
  providers: [/*...*/],
  secret: process.env.AUTH_SECRET,
  session: { strategy: 'jwt' },
  callbacks: {
    async authorized({ auth, request }) {
      console.log("auth.user:", auth?.user);
      console.log("AUTH_SECRET:", process.env.AUTH_SECRET);
      return !!auth?.user;
    },
  },
  experimental: {
    runtime: 'edge',
  },
});

What I’ve Verified:
Cookies like __Host-authjs.csrf-token, authjs.session-token, etc., are present in both environments.
process.env.AUTH_SECRET is defined and logged correctly in both environments.
In npm run dev, I see additional nextauth.message in localStorage, but unsure if that’s relevant.
Using JWT sessions, no database involved.
Login redirects appear to work, but req.auth stays undefined under wrangler pages dev.

Using latest versions: [email protected], NextAuth v5.

Question:
Why is req.auth undefined when running with wrangler pages dev, but working with npm run dev?
Is there additional NextAuth configuration required specifically for Cloudflare Pages or Workers runtime?

Is There a Simple Config to Export All Classes and Types in a Node Package?

I come from an OOP background, and having to add the export keyword to all my classes just to use them outside the file already felt odd, but whatever.

Now, setting up a monorepo, I have to repeat the nonsense twice—once in the class file, and again in index.ts to export everything. Here’s the repetitive mess:

// src/ClassA.ts
export class ClassA { /* ... */ }

// src/ClassB.ts
export class ClassB { /* ... */ }

// src/index.ts
export { ClassA } from './ClassA';
export { ClassB } from './ClassB';

And I also have to manually write the exports field in package.json:

{
  "exports": {
    ".": "./dist/index.js",
    "./ClassA": "./dist/ClassA.js"
  }
}

In Dart for example, code under lib/src is private while lib/ exposes public APIs. Is there any benefit to use `src` sub-folder inside `lib` folder

the library code lives under the lib directory and is public to other
packages. You can create any hierarchy under lib, as needed. By
convention, implementation code is placed under lib/src. Code under
lib/src is considered private;

Is there a similar folder-based configuration in Node/NPM/TypeScript to automatically export all public parts of a package? Or do I have to manually handle this with index.ts for each files? Any better solution for monorepos? Is there some single line config like exportAllModulesAndTypes: "/lib and hideAllModulesAndTypes: "/lib/src

Someone even made a VSCode Extension for it https://github.com/estruyf/vscode-typescript-exportallmodules but I refuse to believe that’s the only way.

How go through option in select?

how to go through option in select

I tried but I get an error here is the code:

       const valInput = modalBody.querySelector('#conditionValue');
    

        if (valInput ) {
            for (let i = 0; i < valInput.options.length; i++) {
                if (valInput.options[i]) {
                    const option = valInput.options[i];
                }
                option.className = option.value;
                const optionClass = option.className;
                const optionValue = option.value;

                console.log(` Class: : ${optionClass}`)
                console.log(` Value: : ${optionValue}`)
            }
        }

Expo-Managed React-Native for Web Drag & Drop “findDOMNode is deprecated” warning

I’m working on an Expo-managed React-Native for Web app and I’m trying to get some specific drag & drop functionality to work. I’ve tried a variety of changes to DraggableWord but everything I do either 1) creates the deprecation warning or 2) causes the DraggableWord to lose draggability (it simply acts like regular text).

I think the problem is inside DraggableWord but I’m not sure.

Here is a fully working, self-contained component script that should throw the warning when you test it.

import React, { useState, useEffect, useRef } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, useAnimatedRef } from 'react-native-reanimated';

const theme = {
  colors: {
    primaryTwo: '#007bff',
    successTwo: '#28a745',
    error: '#dc3545',
    text: '#212529',
    textMuted: '#6c757d',
    grey: '#f8f9fa',
    border: '#dee2e6',
  },
};

const SentenceBlankAnimatedDropZone = ({ blankIndex, droppedWord, isCorrect, answer, hoverStates, onLayoutMeasured }) => {
  const viewRef = useRef(null);
  const isAttempted = droppedWord !== null;

  const animatedDropZoneStyle = useAnimatedStyle(() => {
    const isHovering = hoverStates.value[blankIndex] || false;
    return {
      borderColor: isHovering ? theme.colors.primaryTwo : (isAttempted ? (isCorrect ? theme.colors.successTwo : theme.colors.error) : theme.colors.text),
      borderWidth: isHovering ? 3 : 1,
    };
  });

  const handleLayout = () => {
    if (viewRef.current && typeof viewRef.current.getBoundingClientRect === 'function') {
      const rect = viewRef.current.getBoundingClientRect();
      onLayoutMeasured(blankIndex, {
        x: rect.left + window.scrollX,
        y: rect.top + window.scrollY,
        width: rect.width,
        height: rect.height,
      });
    }
  };

  const styles = StyleSheet.create({
    dropZone: { borderWidth: 1, borderRadius: 4, minWidth: 80, height: 40, justifyContent: 'center', alignItems: 'center', marginHorizontal: 5, paddingHorizontal: 8 },
    blankNumber: { color: theme.colors.textMuted, fontSize: 16 },
    droppedWordText: { fontSize: 22, color: theme.colors.text },
  });

  return (
    <Animated.View
      ref={viewRef}
      onLayout={handleLayout}
      style={[styles.dropZone, animatedDropZoneStyle]}
    >
      {isAttempted ? (<Text style={styles.droppedWordText}>{isCorrect ? droppedWord : answer}</Text>) : (<Text style={styles.blankNumber}>{blankIndex + 1}</Text>)}
    </Animated.View>
  );
};


const DraggableWord = ({ choice, answer, onDrop, dropZoneLayout, isAttempted, isCorrect, droppedWord, blankIndex, hoverStates }) => {
  const opacity = useSharedValue(1);
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const animatedRef = useAnimatedRef();

  useEffect(() => { if (!isAttempted) { opacity.value = 1; } }, [isAttempted]);

  const panGesture = Gesture.Pan()
    .runOnJS(true)
    .enabled(!isAttempted)
    .onUpdate((event) => {
      translateX.value = event.translationX;
      translateY.value = event.translationY;

      let isHovering = false;
      if (dropZoneLayout) {
        isHovering = (
          event.absoluteX > dropZoneLayout.x && event.absoluteX < dropZoneLayout.x + dropZoneLayout.width &&
          event.absoluteY > dropZoneLayout.y && event.absoluteY < dropZoneLayout.y + dropZoneLayout.height
        );
      }
      hoverStates.value = { ...hoverStates.value, [blankIndex]: isHovering };
    })
    .onEnd((event) => {
      hoverStates.value = { ...hoverStates.value, [blankIndex]: false };

      const wordIsOverDropZone = dropZoneLayout && (
        event.absoluteX > dropZoneLayout.x && event.absoluteX < dropZoneLayout.x + dropZoneLayout.width &&
        event.absoluteY > dropZoneLayout.y && event.absoluteY < dropZoneLayout.y + dropZoneLayout.height
      );

      if (wordIsOverDropZone) {
        onDrop(blankIndex, choice);
        if (choice === answer) {
          opacity.value = withTiming(0, { duration: 200 });
        } else {
          translateX.value = withSpring(0);
          translateY.value = withSpring(0);
        }
      } else {
        translateX.value = withSpring(0);
        translateY.value = withSpring(0);
      }
    });

  const animatedStyle = useAnimatedStyle(() => {
    let backgroundColor = theme.colors.grey;
    if (isAttempted) {
      if (choice === answer) { backgroundColor = theme.colors.successTwo; }
      else if (choice === droppedWord && !isCorrect) { backgroundColor = theme.colors.error; }
    }
    return {
      transform: [{ translateX: translateX.value }, { translateY: translateY.value }],
      zIndex: translateX.value !== 0 || translateY.value !== 0 ? 100 : 1,
      opacity: opacity.value,
      backgroundColor,
      padding: 10,
      margin: 5,
      borderRadius: 8,
      borderWidth: 1,
      borderColor: theme.colors.border,
    };
  });

  const styles = StyleSheet.create({ wordText: { fontSize: 18, color: theme.colors.text } });

  // THIS WORKS BUT PRODUCES THE WARNING
  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View ref={animatedRef} style={animatedStyle}>
        <Text style={styles.wordText}>{choice}</Text>
      </Animated.View>
    </GestureDetector>
  );
};


const WordBank = ({ blank, blankIndex, onDrop, dropZoneLayout, droppedWord, isCorrect, hoverStates }) => {
  const styles = StyleSheet.create({
    bankContainer: { alignItems: 'center', marginVertical: 10, padding: 10, borderWidth: 1, borderColor: theme.colors.border, borderRadius: 8, width: '100%' },
    bankTitle: { color: theme.colors.textMuted, fontSize: 16, marginBottom: 5 },
    wordsWrapper: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center' },
  });
  return (
    <View style={styles.bankContainer}>
      <Text style={styles.bankTitle}>Bank {blankIndex + 1}</Text>
      <View style={styles.wordsWrapper}>
        {blank.choices.map((choice) => (
          <DraggableWord
            key={choice} choice={choice} answer={blank.answer} onDrop={onDrop}
            dropZoneLayout={dropZoneLayout} isAttempted={droppedWord !== null}
            isCorrect={isCorrect} droppedWord={droppedWord} blankIndex={blankIndex}
            hoverStates={hoverStates}
          />
        ))}
      </View>
    </View>
  );
};


const CaseDragAndDrop = () => {
  const sentenceWithBlank = "The quick brown fox [BLANK] over the lazy dog.";
  const blanks = [
    {
      answer: "jumps",
      choices: ["runs", "sleeps", "jumps"],
    },
  ];
  const numBlanks = blanks.length;

  const [droppedWords, setDroppedWords] = useState(() => Array(numBlanks).fill(null));
  const [correctness, setCorrectness] = useState(() => Array(numBlanks).fill(null));
  const [dropZoneLayouts, setDropZoneLayouts] = useState({});
  const hoverStates = useSharedValue({});

  const handleDrop = (blankIndex, word) => {
    if (droppedWords[blankIndex]) return;
    setDroppedWords(prev => { const newWords = [...prev]; newWords[blankIndex] = word; return newWords; });
    setCorrectness(prev => { const newCorrectness = [...prev]; newCorrectness[blankIndex] = word === blanks[blankIndex].answer; return newCorrectness; });
  };

  const handleDropZoneLayout = (index, layout) => {
    if (JSON.stringify(dropZoneLayouts[index]) !== JSON.stringify(layout)) {
      setDropZoneLayouts(prev => ({ ...prev, [index]: layout }));
    }
  };

  const styles = StyleSheet.create({
    container: { flex: 1, alignItems: 'center', padding: 20, width: '100%', maxWidth: 700, alignSelf: 'center' },
    sentenceOuterContainer: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', paddingHorizontal: 10, lineHeight: 50 },
    sentenceText: { fontSize: 22, color: theme.colors.text, marginHorizontal: 2, lineHeight: 40 },
    wordBanksContainer: { width: '100%', marginTop: 20 },
  });

  let blankCounter = 0;
  const renderedSentence = sentenceWithBlank.split(/([BLANK])/g).map((segment, index) => {
    if (segment === '[BLANK]') {
      const currentBlankIndex = blankCounter++;
      return (
        <SentenceBlankAnimatedDropZone
          key={`blank-${currentBlankIndex}`}
          blankIndex={currentBlankIndex}
          droppedWord={droppedWords[currentBlankIndex]}
          isCorrect={correctness[currentBlankIndex]}
          answer={blanks[currentBlankIndex].answer}
          hoverStates={hoverStates}
          onLayoutMeasured={handleDropZoneLayout}
        />
      );
    }
    return segment ? <Text style={styles.sentenceText} key={`text-${index}`}>{segment}</Text> : null;
  });

  return (
    <View style={styles.container}>
      <View style={{ height: 40 }} />
      <View style={styles.sentenceOuterContainer}>{renderedSentence}</View>
      <View style={{ height: 20 }} />
      <View style={styles.wordBanksContainer}>
        {blanks.map((blank, index) => (
          <WordBank
            key={`bank-${index}`} blank={blank} blankIndex={index} onDrop={handleDrop}
            dropZoneLayout={dropZoneLayouts[index]} droppedWord={droppedWords[index]}
            isCorrect={correctness[index]} hoverStates={hoverStates}
          />
        ))}
      </View>
    </View>
  );
};


export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <CaseDragAndDrop />
    </GestureHandlerRootView>
  );
}

Object.setPrototyeOf with ‘this’ as object’s new prototype

I noticed that I can access the properties of the function, ruter, returned by Ruter using ‘this’. My understanding of setPrototypeOf is that of inheritance. I don’t understand how the properties of the inner function get injected to the ‘this’?

function Ruter(options) {

  const opts = options || {};

  function ruter(req, res, next) {
    ruter.handler(req, res, next);
  }

  Object.setPrototypeOf(ruter, this);

  ruter.params = {};
  ruter.stack = [];
  ruter.store = {};
  ruter.iname = "Local Function";
  console.log(this);

  return ruter;

}

Ruter.prototype = function () {}

Ruter.prototype.param = function param(name, fn) {
    let params = this.params[name];
    console.log(this);
    if(!params) {
        params = this.params[name] = [];
    }

    params.push(fn);
    
    return this;
}

Ruter.prototype.handler = function handler(req, resp) {
    console.log("This is handler");
}


let ruter = new Ruter();
ruter.param("id", () => console.log("Way maker"));
console.log(ruter);
console.log(ruter.iname);
console.log(ruter.params)

Why does returning a Promise that I don’t want awaited from an async function await the promise?

I have an async function that returns a promise

async function paginate() {
  const recs = await db.selectFrom().execute()
  return db.selectFrom().select().where(recs.map(a => a.id))
}

I call await paginate()

but for some reason the function’s return value is being awaiting, but I don’t want to await it. Somehow, doing:

async function paginate() {
  const recs = await db.selectFrom().execute()
  return { builder: db.selectFrom().select().where(recs.map(a => a.id)) }
}

const builder = (await paginate()).builder

async function utilFunction(query) {
  return await query.select().execute()
}

utilFunction(builder)

works just fine! But I don’t like the unnecessary wrapping of the object.

For context:

I use Kysely. It has a .execute() function which actually performs the query. Currently, I omit the .execute() in favor of the utilFunction to run it. I believe what’s going on is that my query builder in it of itself is somehow a Promise because it emits a custom error from the library: don’t await SelectQueryBuilder instances directly. To execute the query you need to call execute or executeTakeFirst. at SelectQueryBuilderImpl.value

Can’t open fetched blob files in Firefox, on iOS only

In a react website, I’ve got the following code to open a blob file (PDF or a few image types) which I have fetched from the server after the user clicks a button.

const url = window.URL.createObjectURL(new Blob([blob], {type: blob.type}));
window.open(url);

My content security policy includes the following:

default-src 'self';
frame-src 'self' blob:;
img-src 'self' blob:;
object-src 'self' blob:;

With this code and configuration, my website works as intended on the latest versions of Chrome (Android & iOS), Safari (iOS), Edge (Android & iOS), and Firefox (Android) by opening a new tab with the blob contents. In some instances, I had to give permissions or unblock popups in settings, and that is to be expected.

For some reason, it simply doesn’t work on Firefox (iOS) even though I have unblocked popups. I have tested other websites on that browser and it seems to open new tabs just fine for them. For my website, it simply does nothing when I click the button. This behavior is similar to what I saw in Safari (iOS) before I unblocked popups there, but unblocking popups in Firefox (iOS) has not helped at all … a new tab is not opened when I click the button. I am hoping someone has seen this behavior and knows what is going on.