Margin Reset to 0 in 1 Second

When you click the button once the transition is 300 seconds.

What i’m trying to do is when you click the button twice Reset it in 1 second …

    body {
        background-color: #6A6A6A;
    }

    html,
    body {
        height: 100%;
    }

    .boxxy {
        margin-left: -20000px;
        transition-duration: 1s;
    }

    .box {
        position: fixed;
        top: 0px;
        left: 0px;
        width: 20000px;
        height: calc(100% - 100px);
        overflow: hidden;
        transition-timing-function: linear;
        transition-duration: 300s;
        z-index: 5;
    }

    .box img {
        height: 100%;
        width: 100%;
    }

    .linksbox {
        position: fixed;
        bottom: 0px;
        left: 0px;
        width: 100%;
        height: 100px;
        text-align: center;
        background-color: #000000;
        font-size: 25px;
        color: #AEBC1F;
    }

<div id="box" class="box">
    <img src="https://tim-school.com/codepen/2dgame/wall.png" alt="" />
</div>
<div class="linksbox">
    <button onClick="screen()" id="blast">FLY AWAY</button>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>

    var clicked1 = 0;
    const btn = document.getElementById("blast")
    function screen() {
        if (clicked1 == 0) {
            $(".box").toggleClass("boxxy", 0);
            btn.textContent = btn.textContent === "RETURN HOME" ? "FLY AWAY" : "RETURN HOME";
            clicked1 = 1;
        }
        else if (clicked1 == 1) {
            $(".box").toggleClass("boxxy", 0);
            btn.textContent = btn.textContent === "RETURN HOME" ? "FLY AWAY" : "RETURN HOME";
            clicked1 = 0;
        }
    }

So what is happening is when you click the button once Margin-Left goes -20000px in 300 seconds ..what i want is when you click it twice it resets Margin-Left to 0 in 1 second not 300.

Any Way To Synchronize Three Randomized Values Across All User Without Creating a Server? [closed]

Overview

I’m working on a small website that has a visual three-color pattern and a start and stop button. When a user clicks start, the pattern’s three colors will start randomizing using the javascript function.

const randomColor1 = '#' + Math.floor(Math.random()*16777215).toString(16);

When they hit stop, it will set three new HEX color values and the buttons will disappear as the user can only contribute to the colors of the website. I’ve gotten this to persist with Localstorage. However, I would like the three HEX values set by one user to synchronize all instances of the site until someone new visits the site and uses the start and stop button.

What would be the easiest way to share and synchronize the three color values across multiple browser sessions without a server? Below is a sample of the values I’d want to persist.

color1: #be43fa 
color2: #bc418b 
color3: #16fe8c

What I’ve Tried

I’ve tried storing values in LocalStorage but that only persists for the user. Similarly, simpler database options like TaffyDB and PouchDb are focused on persisting for just the user.

I know that PHP is a potential option for rendering from the server but I’d still need to store the values somewhere. As for database options, I was looking into MongoDB but I don’t know how to use it.

Sample Code

HTML

<div id="circle"></div> 
<form id="css">
<button type="submit" value="submit">Start</button>
</form>

JS

let bodCol = document.getElementById('circle');
let cssForm = document.getElementById("css")
cssForm.addEventListener("submit", (e)=>{
e.preventDefault(); 
const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16);
bodCol.style.backgroundColor = randomColor;

}

Using bolt new locally not giving option of ollama

I was following a tutorial on using the bolt locally on your machine. It follows a git hub repository coleam00/bolt.new-any-llm. The repository is working fine with openai and gemini. But in the tutorial there was an option of using Ollama qwen2.5-coder model too. But in my list this option is not available. I followed the tutorial and installed Ollama and qwen2.5-coder model on my computer but it is still not showing Ollama option. Any idea how to fix this.

In html-embedded Javascript, opening an EventSource to the server breaks Chrome’s Sources and Network tabs in DevTools?

I’ve been working on a new JavaScript project to render a frontend for a bunch of data that is being collected in Python and streamed to the frontend. Something that’s been causing me a lot of trouble is the fact that the Chrome DevTools don’t work properly while this stream is open. For example, if I bring up the Sources tab no sources are displayed. If I bring up the Network tab, no connections are displayed there.

While the Sources tab is open, if I kill the backend and thereby kill the stream’s TCP connection, the source for the page pops up. Similarly, the Network tab shows nothing while the stream is open. Not only does it not show the running stream (which I know is open because the updates are being displayed in the page) but it doesn’t even show the load of localhost:8000/. Killing the backend causes the Network tab to display the failed load of favicon.ico and all the retries of the stream, but not the initial load of / nor the initial run of the stream.

I’ve stripped this down to a very simple repro example. Note that the syntax highlighting on StackOverflow is misleading. It’s getting very confused by the mix of javascript and python.

#!/usr/bin/env python3

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
import time
from urllib.parse import urlparse, parse_qs

index = '''
<html>
<head>
<title>devtools lockup demo</title>
</head>
<body>
<div id='counter'>No data received yet.</div>
<script type='text/javascript' defer>

/*TODO: this doesn't really need to be a class.*/
class DataRelay {
  constructor() {
    const stream_url = '/stream/';
    this.event_source = new EventSource(stream_url);
    this.event_source.onmessage = (event) => {
      document.getElementById('counter').textContent = event.data;
    };
    this.event_source.onerror = (error) => {
      console.error('event_source.onerror:', error);
    };
    console.log('data stream handler is set up');
  }
}

let data_relay = new DataRelay();
</script>
</body>
'''

def encode_as_wire_message(data):
  # The "data: " preamble, the "nn" terminator, and the utf8 encoding are all
  # mandatory for streams.
  return bytes('data: ' + data + 'nn', 'utf8')

#TODO: Get this constant in the class
class RequestHandler(BaseHTTPRequestHandler):
  def add_misc_headers(self, content_type):
    self.send_header('Content-type', content_type)
    self.send_header('Cache-Control', 'no-cache')
    self.send_header('Connection', 'keep-alive')
    self.send_header('Access-Control-Allow-Credentials', 'true')
    self.send_header('Access-Control-Allow-Origin', '*')

  def serve_index(self):
    self.send_response(200)
    self.add_misc_headers('text/html')
    self.end_headers()

    self.wfile.write(bytes(index, 'utf8'))

  def serve_stream(self):
    self.send_response(200)
    self.add_misc_headers('text/event-stream')
    self.end_headers()

    print('Beginning to serve stream...')

    for x in range(1000000):
      message = encode_as_wire_message(str(x))
      print(message)
      self.wfile.write(message)
      self.wfile.flush()
      time.sleep(1.0)

  def do_GET(self):
    parsed_url = urlparse(self.path)
    if parsed_url.path == '/':
      self.serve_index()
    elif parsed_url.path == '/stream/':
      self.serve_stream()

def run(server_class=ThreadingHTTPServer, handler_class=RequestHandler):
  server_address = ('', 8000) # serve on all interfaces, port 8000
  httpd = server_class(server_address, handler_class)
  print('starting httpd... Open a connection to http://localhost:8000')
  httpd.serve_forever()

run()

Why would the JS “pipeline operator” |> be useful, and what is its current status in ES14? [closed]

I’ve been exploring new JavaScript features and came across the pipeline operator (|>), which seems to improve function composition. However, it doesn’t seem to have lots of traction (also wondering about TS or Babel support – or if this is far in the future).

  • How could such chaining be implemented in current stable versions of JS?
  • Is this just syntactic sugar or is it a big deal I didn’t know I was waiting for?

How to add a Matter.js MouseConstraint to an svg viewbox element?

I’m trying to use Matter.js for it’s physics engine while using D3.js for rendering the Matter physics bodies as SVG elements.

This demo is just a box dropping onto the ground. Everything works as expected up until I try to add a MouseConstraint. My mouse.element is the SVG viewbox, which is good, but clicking on the box doesn’t do anything. The code below is in this codepen as well.

I made a very similar working example in another codepen using Matter’s default renderer (which uses canvas) where the mouse interactions “just work” and you can click/drag the box around.

TLDR: What leg-work do I need to do here to make the MouseConstraint work without Matter’s renderer? I don’t use Javascript in my day-to-day (besides some D3) so this is all a little opaque to me.

Here’s the non-working code:

const Engine = Matter.Engine
const Bodies = Matter.Bodies
const Composite = Matter.Composite
const World = Matter.World

const width = 400;
const height = 400;

// Helpers
vertices_to_points = function(vertices) {
  return vertices.map(vertex => `${vertex.x},${vertex.y}`).join(" ");
}

// Initialize D3 selection
const svg = d3.select("#container")
  .append("svg")
  .attr("viewBox", [0, 0, width, height])
  .attr("style", "max-width: 100%; height: auto;");

// Initialize physics objects
const engine = Engine.create();
const world = engine.world;
const bodies = world.bodies;

const box = Bodies.rectangle(width/2, height/2, 100, 100);
const ground = Bodies.rectangle(width/2, height - 25, width - 10, 40, { isStatic: true });
Composite.add(engine.world, [box, ground]);

// Render
bodies.forEach(body => {
  svg.append("polygon")
    .attr("id", `body-${body.id}`)
    .attr("points", vertices_to_points(body.vertices))
    .attr("fill", "white")
    .attr("stroke", "black")
    .attr("stroke-width", 2)
});

// Update + render loop
(function update() {
  window.requestAnimationFrame(update);
  Engine.update(engine, 5);
  
  // Move the vertices to sync the D3 <polygon> with the Matter
  // physics body.
  bodies.forEach(body => {
    svg.select(`#body-${body.id}`)
      .attr("points", vertices_to_points(body.vertices))
  });
})();

// Mouse interaction (DOES NOT WORK)
const mouse = Matter.Mouse.create(svg.node());
mouse.pixelRatio = 2;
const mouse_constraint = Matter.MouseConstraint.create(engine, {
  mouse: mouse
})
World.add(world, mouse_constraint);

in Node.js /Electron register content handlers and window handlers

For an environment i am developing (elecron + bs5.3 + node.js + go + cobra …), the objective of which is to create a web interface so that users can execute system commands using buttons and forms on: Windows, Linux or Mac to Manage Dockers Container Environments.

I am currently getting this error:

Error: No handler registered for ‘check-admin-rights’

According to this answer:

https://stackoverflow.com/a/65830600/29561751

I had this same issue. My problem was that I was instructing electron
to navigate to my app before my IPC handlers were being registered.
The reason it was only happening on the first load and not subsequent
loads/reloads (and hot-reloads) was because the BrowserWindow was
getting recreated and my IPC handlers were already registered (and
getting re-registered in my code).

So be absolutely sure that your handlers are being registered before
the BrowserWindow loads the renderer code that calls the IPC channels.

The problem I have is because of the order in which this.mainWindow and the ipcController are built in my case I have this:

initial code:

await app.whenReady();

        try {

            this.windowManager = new WindowManager();
            this.mainWindow = await this.windowManager.createMainWindow();

            if (this.mainWindow) {
                this.ipcController = new IpcController(
                    this.windowManager,
                    this.backendController,
                    this.appConfig
                );

                DevToolsManager.setupDevToolsHandlers(this.mainWindow);
            }
        } catch (error) {
            console.error('Setup error:', error);
            throw error;
        }
    }

I don’t know how I should implement that response in my code since there is no example.

The other point is that inside my ipcController I am passing this.windowManager which currently already contains the mainWindow deployed, this is so I can re-create the events for the minimize, maximize and close buttons, as I understand it and what I have learned. This is done so that my custom buttons work perfectly.


Try changing the order to this:

        await app.whenReady();

        try {
            this.windowManager = new WindowManager();
            
            this.ipcController = new IpcController(
                this.windowManager,
                this.backendController,
                this.appConfig
            );
            
            this.mainWindow = await this.windowManager.createMainWindow();
            DevToolsManager.setupDevToolsHandlers(this.mainWindow);

The error: No handler registered … disappears!!! … The problem now is that the other handlers stopped working and in the IDE console I get this:

[IPC] No main window reference

This error appears when clicking on my custom minimize, maximize and close buttons.

I managed to solve one problem only to fall into another… and now I don’t know how to solve this one, should I have 2 ipcControllers, one for the events associated with the window and another for the rendering events?


resources

this is my preload file:

const { contextBridge, ipcRenderer } = require('electron');

const validChannels = new Set([
    'window-control',
    'open-settings',
    'open-help',
    'open-about',
    'maintenance',
    'execute-command',
    'open-user-data-path',
    'refresh-security-nonce',
    'check-admin-rights'
]);


contextBridge.exposeInMainWorld('electronAPI', {
    invoke: async (channel, data) => {
        if (validChannels.has(channel)) {
            return await ipcRenderer.invoke(channel, data);
        }
        throw new Error(`Invalid channel: ${channel}`);
    },
    send: (channel, data) => {
        if (validChannels.has(channel)) {
            ipcRenderer.send(channel, data);
        }
    },
    receive: (channel, func) => {
        if (validChannels.has(channel)) {
            ipcRenderer.on(channel, (event, ...args) => func(...args));
        }
    }
});

this is my renderer.js file:

document.addEventListener('DOMContentLoaded', async () => {
    const windowControls = {
        minimize: document.getElementById('minimizeBtn'),
        maximize: document.getElementById('maximizeBtn'),
        close: document.getElementById('closeBtn')
    };

    Object.entries(windowControls).forEach(([action, element]) => {
        element.addEventListener('click', () => {
            window.electronAPI.send('window-control', action);
        });
    });

    //...

    // Get the admin button element
    const adminButton = document.getElementById('isAdmin');

    try {
        const isAdmin = await window.electronAPI.invoke('check-admin-rights');
        console.log('Admin privileges check result:', isAdmin);
        if (isAdmin) {
            adminButton.style.display = 'block';
            adminButton.setAttribute('title', 'Running with Administrator privileges');
        } else {
            adminButton.style.display = 'none';
        }
    } catch (error) {
        console.error('Failed to check admin privileges:', error);
        adminButton.style.display = 'none';
    }

});

this is my IpcController:

const { ipcMain } = require('electron');
const { shell } = require('electron');
const AdminPrivilegesManager = require('../core/AdminPrivilegesManager');

class IpcController {
    constructor(windowManager, backendController, appConfig) {
        this.mainWindow = windowManager.getMainWindow();
        this.backendController = backendController;
        this.appConfig = appConfig;
        this.setupHandlers();
    }

    setupHandlers() {

        ipcMain.on('window-control', (event, command) => {
            if (!this.mainWindow) {
                console.error('[IPC] No main window reference');
                return;
            }

            switch (command) {
                case 'minimize':
                    this.mainWindow.minimize();
                    break;
                case 'maximize':
                    if (this.mainWindow.isMaximized()) {
                        this.mainWindow.unmaximize();
                    } else {
                        this.mainWindow.maximize();
                    }
                    break;
                case 'close':
                    this.mainWindow.close();
                    break;
                default:
                    console.error(`[IPC] Unknown window command: ${command}`);
            }
        });

        ipcMain.on('open-settings', () => {
            // Implement settings window logic
            console.log('Settings requested');
        });

        ipcMain.on('open-help', () => {
            // Implement help window logic
            console.log('Help requested');
        });

        ipcMain.on('open-about', () => {
            // Implement about window logic
            console.log('About requested');
        });

        // Maintenance handlers
        ipcMain.on('maintenance', (event, action) => {
            switch(action) {
                case 'check-updates':
                    console.log('Checking for updates...');
                    break;
                case 'backup-settings':
                    console.log('Backing up settings...');
                    break;
                case 'clear-cache':
                    console.log('Clearing cache...');
                    break;
                case 'reset-preferences':
                    console.log('Resetting preferences...');
                    break;
                case 'exit':
                    this.mainWindow.close();
                    break;
                default:
                    console.error(`Unknown maintenance action: ${action}`);
            }
        });

        ipcMain.handle('open-user-data-path', () => {
            shell.openPath(this.appConfig.getUserDataPath())
                .then(result => console.log('Opened user data path:', result))
                .catch(err => console.error('Error opening user data path:', err));
        });

        // Command execution handler
        ipcMain.handle('execute-command', async (event, cmd) => {
            return await this.backendController.executeCommand(cmd);
        });

        // Add this handler with the existing ones
        ipcMain.handle('check-admin-rights', async () => {
            try {
                return await AdminPrivilegesManager.isRunningAsAdmin();
            } catch (error) {
                console.error('Error checking admin rights:', error);
                return false;
            }
        });
    }
}

module.exports = IpcController;

CSS with react-text-transition – how to align text properly for all browsers

On my landing page, I have some static text which is then followed by a word which I want to change every couple of seconds with transition. I decided to use react-text-transition for this. My issue is that I can’t seem to align those to look properly and consistently across browsers.

Currently this is the code:

<Box sx={{display: "inline-block", mt: {xs: 5, sm: 8}, mb: 1}}>
    <Typography sx={{display: "ruby", typography: { xs: "h4", sm: "h3"}}}>Blah blah blah in your
        <Box sx={{width: '10px'}} />
        {<Box sx={{ display: "inline", minWidth: {xs: `${18 * ctaChangingTextListLongestWordLength + 5}px`, sm: `${24 * ctaChangingTextListLongestWordLength + 10}px`}}}><IntervalChangingText textList={ctaChangingTextList} interval={4000}/></Box>}
    </Typography>
</Box>

and the IntervalChangingText component:

import { useEffect, useState } from "react"
import TextTransition, { presets } from 'react-text-transition';

export default function IntervalChangingText(props) {
    const {textList, interval} = props
    const [textListIndex, setTextListIndex] = useState(0)

    useEffect(() => {
        const intervalId = setInterval(() => {
            setTextListIndex(textListIndex + 1)
        }, interval);
    
        return () => clearInterval(intervalId);
    }, [textListIndex]);

    return (
        <TextTransition springConfig={presets.slow}>{textList[textListIndex % textList.length]}</TextTransition>
    )
}

On Chrome, things look good aka like this:

-----------------------------
|Blah blah blah              |
|in your [text that changes] |
-----------------------------

However on Safari, they do not:

-----------------------------
|Blah blah blah              |
|in your                     |
|[text that changes]         |
-----------------------------

And in Firefox it’s even worse, the text is all in one line and leaves its box:

-----------------------------
|Blah blah blah in your [text| that changes]
-----------------------------

I suspect the issue is with the display: “ruby” but I don’t know how to make this work consistently with css across all browsers. Any ideas?

D3.js Bug in the resize function

I am in the process of writing a resize function for my chart. So far everything works quite well.

However, I noticed the following bug when using the resize function.

After I have rendered the chart and then resize the window, my heatmap chart extends some “px” to the right. This causes the chart to be cut off on the right side.

Can someone please explain why this bug occurs and how I can fix it?

I would be grateful for any advice.

enter image description here

 //Datasource
            const data1= [
                {id: 1,group:"A",variable:"v1",value: 98},{id: 2,group:"A",variable:"v2",value:95},
                {id: 3,group:"A",variable:"v3",value: 22},{id: 4,group:"A",variable:"v4",value:14},
                {id: 11,group:"B",variable:"v1",value: 37},{id: 12,group:"B",variable:"v2",value:50},
                {id: 13,group:"B",variable:"v3",value: 81},{id: 14,group:"B",variable:"v4",value:79}, 
                {id: 21,group:"C",variable:"v1",value: 96},{id: 22,group:"C",variable:"v2",value:13},
                {id: 23,group:"C",variable:"v3",value: 98},{id: 24,group:"C",variable:"v4",value:10},
                {id: 31,group:"D",variable:"v1",value: 75},{id: 32,group:"D",variable:"v2",value:18},
                {id: 33,group:"D",variable:"v3",value: 92},{id: 34,group:"D",variable:"v4",value:43},
                {id: 41,group:"E",variable:"v1",value: 44},{id: 42,group:"E",variable:"v2",value:29},
                {id: 43,group:"E",variable:"v3",value: 58},{id: 44,group:"E",variable:"v4",value:55},
                {id: 51,group:"F",variable:"v1",value: 35},{id: 52,group:"F",variable:"v2",value:80},
                {id: 53,group:"F",variable:"v3",value: 8},{id: 54,group:"F",variable:"v4",value:46}
            ];

            var i_region_static_id = "heatmap"
            var i_height = 450;
            var margin = {top: 0, right: 50, bottom: 25, left: 70}; 
            var width_client = document.body.clientWidth;
            var height_client = document.body.clientHeight;

            const innerWidth = width_client - margin.left - margin.right;
            const innerHeight = i_height - margin.top - margin.bottom; 

            var colorMidValue = 20;

            const labelGroup = "X-Label"
            const labelVars = "Y-Label"
            const color_start = "#f9f9f9"
            const colorEnd = "#6e69b3"
            const colorMid = "#bd326c"

            // Funktion zum Aufrunden der Y-Axis auf 5
            function updateColorAxis(a,b){
                if(b%5 !== 0){
                    b = b+(5-(b%5)); 
                }
                return [a,b];
            }
        
            // Funktion zum Berechnen der Anzahl der Ticks 
            function setColorAxisTicks(a,b){
                if(b%5 === 0){
                    return b/5;
                }
                return b/10;
            }

            // Returns Black/White depending on node color
            function calculateContrast(values){
                var rgb = values.match(/d+/g);
                // Wenn rgb leer, dann das erste sonst das zweite
                const brightness = (rgb != undefined) ? Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) /1000) : 0;
                return brightness < 150 ? 'white' : 'black'   
            }
                
            
            //-----------------------------------------------------------------------------------
            // Anfügen von svg Elementen zu #heatmap
            const svg = d3.select('#svg_heatmap')
                                // Attribute svg
                                .attr("height", innerHeight)
                                .attr("width", width_client + margin.left + margin.right)

            const g_area = d3.select("#svg_" + i_region_static_id)                           
                                // Anfügen einer Gruppe an das SVG Element
                                .append("g")
                                    .attr("id", i_region_static_id + "_area")
                                    .attr("transform", `translate(${margin.left},${margin.top})`);

            const g_nodes = g_area
                                .append("g")
                                .attr("id", i_region_static_id + "_nodes")
            //-----------------------------------------------------------------------------------
            // X-Skalierung
            const x_scale = d3.scaleBand()
                        .range([0, innerWidth])
                        .padding(0.01);

            const xAxisCall = d3.axisBottom(x_scale)

            // X-Achse erstellen
            const xAxis = g_area
                                .append("g")
                                .attr("id", i_region_static_id + "_x_axis")

                        // Einfügen xAxis Values    
                        xAxis.append("g")
                                .attr("id", i_region_static_id + "_x_axis_value")
                                .attr("class", "x axis")
                                .attr("transform", `translate(0, ${innerHeight})`)

                        // Einfügen xAxis Label
                        xAxis.append("text")
                                .attr("id", i_region_static_id + "_x_axis_label")
                                .attr("class", "x label")
                                .attr("x", innerWidth / 2)
                                .attr("y", innerHeight + 50)
                                .attr("text-anchor", "middle")
                                .text(labelGroup);

            
            //-----------------------------------------------------------------------------------
            // Y-Skalierung
            const y_scale = d3.scaleBand()
                        .range([innerHeight, 0])
                        .padding(0.03);
            
            const yAxisCall = d3.axisLeft(y_scale)
                        
            // Y-Achse erstellen
            const yAxis = g_area
                            .append("g")
                                .attr("id", i_region_static_id + "_y_axis")

                        // Einfügen yAxis Value
                        yAxis.append("g")
                            .attr("id", i_region_static_id + "_y_axis_value")
                            .attr("class", "y axis")

                        // Einfügen yAxis Label
                        yAxis.append("text")
                                .attr("id", i_region_static_id + "_y_axis_label")
                                .attr("class", "y label")
                                .attr("y", - 45)                // Y ist hierbei die horizontale ausrichtung
                                .attr("x", - innerHeight / 2)   // X ist hierbei die vertikale ausrichtung
                                .attr("transform", "rotate(-90)")
                                .attr("text-anchor", "middle")
                                .text(labelVars)

            //-----------------------------------------------------------------------------------     
            // Erstelle yColorscale
            const yColorscale = d3.scaleLinear()
                                    .range([innerHeight - 70, 0]);

            // ColorScale Legend
            const g_colorScale = g_area
                                    .append("g")
                                        .attr("id", i_region_static_id + "_colorLegend")

            // Einfügen der Color Achse
            g_colorScale.append("g")
                            .attr("id" , i_region_static_id + "_colorscale_axis")
                            .attr("transform", `translate(${innerWidth + 27},${35})`)
            
            // Einfügen des Farbbalkens
            g_colorScale.append("rect")
                            .attr("id", i_region_static_id + "_colorScale_legend")
                            .attr("x", innerWidth + 15)
                            .attr("y", 35)
                            .attr("rx", "4px")
                            .attr("width", 12)
                            .attr("height", innerHeight - 70)
                            .attr("stroke", "black")
                            .attr("stroke-width", "0.5px")
                            .attr("text-anchor", "middle")
                            .style("fill", "url(#"+ i_region_static_id + "_grad)");
 
            const g_def = g_colorScale.append("defs")
                                        .append("linearGradient")
                                            .attr("id", i_region_static_id + "_grad")
                                            .attr("x1", "0%")
                                            .attr("x2", "0%")
                                            .attr("y1", "0%")
                                            .attr("y2", "100%");
            
            //-----------------------------------------------------------------------------------               
            // Funktion für das Create, Update or Delete der D3 nodes
            function update_heatmap(data){
                const myId = data.map(function(d){return d.id});
                const myGroups = data.map(function(d){return d.group});
                const myVars = data.map(function(d){return d.variable});
                const value = data.map(function(d){return d.value});
                const extent = d3.extent(data, function(d){;return d.value}); 
                var colorArrayValue = updateColorAxis(extent[0], extent[1]);
                    colorArrayValue.push(colorMidValue);
                    colorArrayValue.sort(function(a, b){return a - b});

                const myColor = d3.scaleLinear()
                                .range([color_start, colorMid, colorEnd])
                                .domain(colorArrayValue)

                const GradColors = [myColor(updateColorAxis(extent[0], extent[1])[1]), myColor(colorMidValue), myColor(updateColorAxis(extent[0], extent[1])[0])];
                

                //----------------------------------------------------------------------------------- 
                // Dynamisches Update der X-Achse
                x_scale.domain(myGroups)
                const xAxisGroup = xAxis.select("#" + i_region_static_id + "_x_axis_value")
                                                .call(xAxisCall);

                //----------------------------------------------------------------------------------- 
                // Dynamisches Update der Y-Achse
                y_scale.domain(myVars)
                const yAxisGroup = yAxis.select("#" + i_region_static_id + "_y_axis_value")
                                                .call(yAxisCall);

                //-----------------------------------------------------------------------------------  
                // ColorScale Legend
                yColorscale.domain(updateColorAxis(extent[0], extent[1]))

                const yAxisColorCall = d3.axisRight(yColorscale)
                                                .ticks(setColorAxisTicks(updateColorAxis(extent[0], extent[1])[0], updateColorAxis(extent[0], extent[1])[1]))
                
                const yAxisColorGroup = g_colorScale.select("#" + i_region_static_id + "_colorscale_axis")
                                                .call(yAxisColorCall);

                g_def.selectAll(i_region_static_id + "stop")
                                                .data(GradColors)
                                                .enter()
                                                .append("stop")
                                                .style("stop-color", function(d){return d;})
                                                .attr("offset", function(d,i){
                                                    return 100 * (i / (GradColors.length - 1)) + "%";
                })

                //-----------------------------------------------------------------------------------  
                // Create, Delete and Update
                const rect_nodes = g_nodes.selectAll("." + i_region_static_id + "_node_grp")
                                                .data(data)

                const rect_node_grp = rect_nodes.enter()
                                                .append("g")
                                                /*each() für Create Sektion*/
                                                /*Mit der Each() wird alles in der gruppe geupdated*/
                                                .each(function(d, i) {
                                                //Append Elemente an g
                                                    d3.select(this).append('rect')   
                                                        .style("fill", function(d) {return myColor(d.value)}) 
                                                    d3.select(this).append('text')
                                                })
                                                .merge(rect_nodes)
                                                .attr("class", i_region_static_id + "_node_grp")
                                                //jedes Rect eine ID zu ordnen
                                                .attr("id", function (d){return i_region_static_id + "_node_grp_" + d.id})
                                                .attr("group", function(d) {return d.group})
                                                .attr("variable", function(d) {return d.variable})
                                                .attr("value", function(d) {return d.value})
                                                /*each() für Update Sektion*/
                                                .each(function(d, i) {
                                                    // Update meine Elemente in g tag
                                                    d3.select(this).select('rect')
                                                        .attr("class", i_region_static_id + "_node")
                                                        .transition()
                                                        .delay(50)
                                                        .attr("x", function(d) {return x_scale(d.group)})
                                                        .attr("y", function(d) {return y_scale(d.variable)})
                                                        .attr("width", x_scale.bandwidth())
                                                        .attr("height", y_scale.bandwidth())
                                                        .style("fill", function(d) {return myColor(d.value)})

                                                    d3.select(this).select('text').attr("class", i_region_static_id + "_label")
                                                        .transition()
                                                        .delay(50)
                                                        .attr("x", function(d) {return x_scale(d.group)})
                                                        .attr("y", function(d) { return y_scale(d.variable)})
                                                        .attr("dx", x_scale.bandwidth() / 2)
                                                        .attr("dy", y_scale.bandwidth() / 2)
                                                        .attr("text-anchor", "middle")
                                                        .attr("dominant-baseline", "central")
                                                        .attr("fill", function(d){return calculateContrast(myColor(d.value))})
                                                        .style("font-size", "14px")
                                                        .style("font-family", "sans-serif")
                                                        .text(function(d) {return (d.value)})
                                                    })

                rect_nodes.exit().remove()
            }

            function resize() {
                var innerWidth = parseInt(d3.select("#svg_" + i_region_static_id).style("width")) - margin.left - margin.right,
                    innerHeight

                if(parseInt(d3.select("#svg_" + i_region_static_id).style("height")) < i_height){
                    innerHeight = parseInt(d3.select("#svg_heatmap").style("height")) + margin.top + margin.bottom;
                }else{
                    innerHeight = i_height - margin.top - margin.bottom;
                }
                
                // SVG Dimension
                svg.attr("height", innerHeight)
                    .attr("width", innerWidth)

                // Aktualisieren der Range vom scale mit neuer breite und Höhe
                x_scale.range([0, innerWidth], 0.1);
                y_scale.range([innerHeight, 0]);

                // Position der X-Achse und X-Label
                xAxis
                    .call(xAxisCall)
                    .select("#" + i_region_static_id + "_x_axis_label")
                            .attr("x", innerWidth / 2)
                            .attr("y", innerHeight + 50);
                
                // Position der Y-Achse und X-Label
                yAxis
                    .call(yAxisCall)
                    .attr("transform", "translate(0," + 0 + ")")
                    .select("#" + i_region_static_id + "_y_axis_label")
                        .attr("y", - 45)
                        .attr("x", - innerHeight / 2)

                // Position ColorSkale Farb-Balken 
                g_colorScale
                        .select("#" + i_region_static_id + "_colorScale_legend")
                            .attr("x", innerWidth + 15)
                            .attr("y", 35)
                            .attr("height", innerHeight - 70)
                    
                // Position ColorSkale Achse 
                g_colorScale
                        .select("#" + i_region_static_id + "_colorscale_axis")
                            .attr("transform", `translate(${innerWidth + 27},${35})`)
    
                // D3 Kästchen dimension wird neu kalkuliert
                g_nodes.selectAll("." + i_region_static_id + "_node")
                        .attr("x", function(d) {return x_scale(d.group)})
                        .attr("y", function(d) {return y_scale(d.variable)})
                        .attr("width", x_scale.bandwidth())
                        .attr("height", y_scale.bandwidth())
                        
                // D3 Kästchen Label wir neu Positioniert
                g_nodes.selectAll("." + i_region_static_id + "_label")
                    .attr("x", function(d) {return x_scale(d.group)})
                    .attr("y", function(d) { return y_scale(d.variable)})
                    .attr("dx", x_scale.bandwidth() / 2)
                    .attr("dy", y_scale.bandwidth() / 2)
            };
            //-----------------------------------------------------------------------------------               
            d3.select(window).on('resize', resize);

            update_heatmap(data1)
        <style>
            .label{
                fill: rgba(0, 0, 0, 0.65);
                font-size: 18px;
                font-weight: bold;
                font-family: sans-serif;
            }

            .axis{
                color: rgba(0, 0, 0, 0.65);
                font-size: 16px;
                font-family: arial;
            }

            #svg_heatmap {
                width: 100%;
                height: 100%;
                position: absolute;
            } 
<html>             
    <head>              
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title> 1.Grundgerüst </title>
    </head>
    <body>             
        <svg id="svg_heatmap"></svg>
        <script src="https://d3js.org/d3.v7.js" charset="utf-8"></script>
        <script src="https://unpkg.com/d3fc" charset="utf-8"> </script> 
    </body>
</html>

How to persist auth across app refresh in React Native app

I am using React Native / Expo to build an app with auth. I can log in fine but when I refresh the app, the auth is lost and I need to log in again.

My firebase config looks like this:

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: "<key>",
  authDomain: "<name>.firebaseapp.com",
  projectId: "<id>",
  storageBucket: "<id>.appspot.com",
  messagingSenderId: "<sender_id>",
  appId: "<app_id>",
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export { auth };

I have a self hosted log in form in my app that is as so

import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

const LoginScreen = () => {
  const auth = getAuth();
  
  const handleLogin = useCallback(async (email: string, password: string) => {
    const user = await signInWithEmailAndPassword(auth, email, password);
  }, [auth]);

  return (
    <LoginForm onLogin={handleLogin} />
  )
}

The log in works fine and I can retrieve the users details and even see auth state change in this useEffect

useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      console.log("Auth state changed:", user);
    });
  
    return unsubscribe;
  }, [auth]);

But when I refresh the app, auth is === null and I need to log in again.

Apparently as of firebase >v9 it should auto persist the auth so I shouldn’t need to configure anything else?

How can I make my RN app have persisted log in using Firebase?

Package versions:

"firebase": "^11.3.1",
"react": "18.3.1",
"expo": "~52.0.31"

How to Use AJAX in WordPress Widget on Admin Backend only?

I’m trying to create a simple WordPress widget for AI text generation. The widget should allow the user to type a prompt, set a word limit, and click a “Generate Text” button. I want to use the OpenAI API to generate text based on the prompt and word limit. However, I’m facing an issue with how the widget behaves depending on where it’s loaded.

Problem:

The AI text generation works fine when all the options (form) are loaded on the WordPress front page (working example in CODE-1 below). But I want the widget to only be available in the backend (as part of the widget options).

So, my goal is to make the AI text generator available only in the backend for editing widgets, and have the API generate text and insert it into the widget’s text area. The main issue is that AJAX doesn’t seem to work properly within widget options, and I’m struggling to find a solution.

What I want to achieve:

  • Create a widget in the WordPress backend.
  • Use the OpenAI API to generate text based on user input (prompt and
    word limit).
  • Insert the generated text into the widget text area.

Questions:

How can I make AJAX work properly inside the WordPress widget options in the backend?
Is there a recommended approach for integrating OpenAI API text generation with a WordPress widget in the backend?
I’m not sure what else to check or why API requests can’t be handled through the backend widget options. I tested AI text generation on a backend page (not widgets), and it works fine there. The issue is only with the widgets, and I can’t figure out why it’s not working. There are no errors in the console, nothing—it just doesn’t work in the widget.

Any help would be greatly appreciated!

CODE-1 (works OK if loaded as front-end widget):

<?php
/*
Plugin Name: OpenAI Widget
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        ?>
        <div class="openai-widget">
            <label for="openai_prompt">Prompt:</label>
            <textarea id="openai_prompt" rows="3"></textarea>

            <label for="openai_word_limit">Word Limit:</label>
            <input type="number" id="openai_word_limit" value="100" min="1" max="500"/>

            <button id="openai_generate">Generate Text</button>

            <label for="openai_output">Output:</label>
            <textarea id="openai_output" rows="5" readonly></textarea>
        </div>

        <script>
document.addEventListener("DOMContentLoaded", function() {
    document.getElementById("openai_generate").addEventListener("click", function() {
        let prompt = document.getElementById("openai_prompt").value.trim();
        let wordLimit = parseInt(document.getElementById("openai_word_limit").value);

        if (prompt.length === 0) {
            alert("Please enter a prompt.");
            return;
        }

        fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: `action=openai_generate_text&prompt=${encodeURIComponent(prompt)}&word_limit=${wordLimit}`
        })
        .then(response => response.json())
        .then(data => {
            console.log(data);  // Debugging: log the response data

            // Check if the response is as expected
            if (data.success) {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = data.data.text || "No text generated.";  // Access text inside `data.data.text`
                } else {
                    console.error('Output textarea not found.');
                }
            } else {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = "Error: " + (data.message || "Unknown error");
                } else {
                    console.error('Output textarea not found.');
                }
            }
        })
        .catch(error => {
            console.error("Error:", error);
            const output = document.getElementById("openai_output");
            if (output) {
                output.value = "Error: Unable to reach API or process request.";
            }
        });
    });
});


        </script>
        <?php
        echo $args['after_widget'];
    }

    public function form($instance) {
        echo '<p>No settings required.</p>';
    }

    public function update($new_instance, $old_instance) {
        return $new_instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Debugging: Log the full response body to see the structure of the response
    error_log('OpenAI Response: ' . print_r($body, true));

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

CODE-2 (loaded in widgets backend options – not working):

<?php
/*
Plugin Name: OpenAI Widget - backend options
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        // On the front-end, just show the widget without any fields or outputs.
        echo $args['before_widget'];
        echo $args['after_widget'];
    }

    public function form($instance) {
        // Backend form
        $prompt = !empty($instance['prompt']) ? $instance['prompt'] : '';
        $word_limit = !empty($instance['word_limit']) ? $instance['word_limit'] : 100;
        ?>
        <p>
            <label for="<?php echo $this->get_field_id('prompt'); ?>"><?php _e('Prompt:'); ?></label>
            <textarea class="widefat" id="<?php echo $this->get_field_id('prompt'); ?>" name="<?php echo $this->get_field_name('prompt'); ?>" rows="3"><?php echo esc_attr($prompt); ?></textarea>
        </p>
        <p>
            <label for="<?php echo $this->get_field_id('word_limit'); ?>"><?php _e('Word Limit:'); ?></label>
            <input class="widefat" id="<?php echo $this->get_field_id('word_limit'); ?>" name="<?php echo $this->get_field_name('word_limit'); ?>" type="number" value="<?php echo esc_attr($word_limit); ?>" min="1" max="500" />
        </p>
        <p>
            <!-- Button in backend -->
            <button class="widefat" type="button" id="generate_openai_backend_text">Generate Text (Backend Only)</button>
        </p>
        <div id="openai_backend_output_area" style="display:none;">
            <h4>Generated Text:</h4>
            <textarea id="openai_backend_output" rows="5" readonly></textarea>
        </div>

        <?php
    }

    public function update($new_instance, $old_instance) {
        // Save the widget options
        $instance = array();
        $instance['prompt'] = !empty($new_instance['prompt']) ? sanitize_text_field($new_instance['prompt']) : '';
        $instance['word_limit'] = !empty($new_instance['word_limit']) ? intval($new_instance['word_limit']) : 100;
        return $instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

// Handle the AJAX request for text generation
function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

How to Use OpenAI API for Text Generation in WordPress Widget Backend with AJAX? (full plugin code)

I’m trying to create a simple WordPress widget for AI text generation. The widget should allow the user to type a prompt, set a word limit, and click a “Generate Text” button. I want to use the OpenAI API to generate text based on the prompt and word limit. However, I’m facing an issue with how the widget behaves depending on where it’s loaded.

Problem:

The AI text generation works fine when all the options (form) are loaded on the WordPress front page (working example in CODE-1 below). But I want the widget to only be available in the backend (as part of the widget options).

So, my goal is to make the AI text generator available only in the backend for editing widgets, and have the API generate text and insert it into the widget’s text area. The main issue is that AJAX doesn’t seem to work properly within widget options, and I’m struggling to find a solution.

What I want to achieve:

  • Create a widget in the WordPress backend.
  • Use the OpenAI API to generate text based on user input (prompt and
    word limit).
  • Insert the generated text into the widget text area.

Questions:

How can I make AJAX work properly inside the WordPress widget options in the backend?
Is there a recommended approach for integrating OpenAI API text generation with a WordPress widget in the backend?
I’m not sure what else to check or why API requests can’t be handled through the backend widget options. I tested AI text generation on a backend page (not widgets), and it works fine there. The issue is only with the widgets, and I can’t figure out why it’s not working. There are no errors in the console, nothing—it just doesn’t work in the widget.

Any help would be greatly appreciated!

CODE-1 (works OK if loaded as front-end widget):

<?php
/*
Plugin Name: OpenAI Widget
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        ?>
        <div class="openai-widget">
            <label for="openai_prompt">Prompt:</label>
            <textarea id="openai_prompt" rows="3"></textarea>

            <label for="openai_word_limit">Word Limit:</label>
            <input type="number" id="openai_word_limit" value="100" min="1" max="500"/>

            <button id="openai_generate">Generate Text</button>

            <label for="openai_output">Output:</label>
            <textarea id="openai_output" rows="5" readonly></textarea>
        </div>

        <script>
document.addEventListener("DOMContentLoaded", function() {
    document.getElementById("openai_generate").addEventListener("click", function() {
        let prompt = document.getElementById("openai_prompt").value.trim();
        let wordLimit = parseInt(document.getElementById("openai_word_limit").value);

        if (prompt.length === 0) {
            alert("Please enter a prompt.");
            return;
        }

        fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: `action=openai_generate_text&prompt=${encodeURIComponent(prompt)}&word_limit=${wordLimit}`
        })
        .then(response => response.json())
        .then(data => {
            console.log(data);  // Debugging: log the response data

            // Check if the response is as expected
            if (data.success) {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = data.data.text || "No text generated.";  // Access text inside `data.data.text`
                } else {
                    console.error('Output textarea not found.');
                }
            } else {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = "Error: " + (data.message || "Unknown error");
                } else {
                    console.error('Output textarea not found.');
                }
            }
        })
        .catch(error => {
            console.error("Error:", error);
            const output = document.getElementById("openai_output");
            if (output) {
                output.value = "Error: Unable to reach API or process request.";
            }
        });
    });
});


        </script>
        <?php
        echo $args['after_widget'];
    }

    public function form($instance) {
        echo '<p>No settings required.</p>';
    }

    public function update($new_instance, $old_instance) {
        return $new_instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Debugging: Log the full response body to see the structure of the response
    error_log('OpenAI Response: ' . print_r($body, true));

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

CODE-2 (loaded in widgets backend options – not working):

<?php
/*
Plugin Name: OpenAI Widget - backend options
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        // On the front-end, just show the widget without any fields or outputs.
        echo $args['before_widget'];
        echo $args['after_widget'];
    }

    public function form($instance) {
        // Backend form
        $prompt = !empty($instance['prompt']) ? $instance['prompt'] : '';
        $word_limit = !empty($instance['word_limit']) ? $instance['word_limit'] : 100;
        ?>
        <p>
            <label for="<?php echo $this->get_field_id('prompt'); ?>"><?php _e('Prompt:'); ?></label>
            <textarea class="widefat" id="<?php echo $this->get_field_id('prompt'); ?>" name="<?php echo $this->get_field_name('prompt'); ?>" rows="3"><?php echo esc_attr($prompt); ?></textarea>
        </p>
        <p>
            <label for="<?php echo $this->get_field_id('word_limit'); ?>"><?php _e('Word Limit:'); ?></label>
            <input class="widefat" id="<?php echo $this->get_field_id('word_limit'); ?>" name="<?php echo $this->get_field_name('word_limit'); ?>" type="number" value="<?php echo esc_attr($word_limit); ?>" min="1" max="500" />
        </p>
        <p>
            <!-- Button in backend -->
            <button class="widefat" type="button" id="generate_openai_backend_text">Generate Text (Backend Only)</button>
        </p>
        <div id="openai_backend_output_area" style="display:none;">
            <h4>Generated Text:</h4>
            <textarea id="openai_backend_output" rows="5" readonly></textarea>
        </div>

        <?php
    }

    public function update($new_instance, $old_instance) {
        // Save the widget options
        $instance = array();
        $instance['prompt'] = !empty($new_instance['prompt']) ? sanitize_text_field($new_instance['prompt']) : '';
        $instance['word_limit'] = !empty($new_instance['word_limit']) ? intval($new_instance['word_limit']) : 100;
        return $instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

// Handle the AJAX request for text generation
function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

Math.random() based function not working inside another function

I was coding a simulator for a TTRPG with ‘exploding dice’, which basically means if you roll a max number on one die then you reroll that die and add the result to the max number. That means it is 100% impossible to get a result equal to a multiple of the max number.

I coded one function, explosiveDiceRoll(), that rolls a single exploding dice and it works, it can never equal eight (tested with a never-ending while loop).

However, I coded another function, multiDiceRoll(), that can roll multiple exploding dice and roll with advantage/disadvantage. In multiDiceRoll(), I use explosiveDiceRoll() and run it through loops and whatnot to simulate multiple dice and advantages.

The problem is that explosiveDiceRoll() is somehow rolling the max number while inside the multiDiceRoll() function, which should be impossible. Why is this? Also, how do I solve it?

I encountered a similar problem where Math.random() was the same each time when put into a loop. I don’t know if that’s related or helpful.

I ran both functions at separate times in a while loop. While loop general design:

while([function with an input of a 1d8, no advantage] != 8) {}

The idea is that since getting an 8 is simply impossible, the while loop will continue forever. I waited at most a few seconds each time.

explosiveDiceRoll() continued forever, it works properly

multiDiceRoll() quickly stopped, always ending with this in the console:

1d8
8,8 - SHOULD NOT EQUAL
[ 8 ]
function explosiveDiceRoll(maxRoll) {
    let roll = diceRoll(maxRoll);
    let sum = roll;
    while(roll === maxRoll) { //Reroll and add everytime it hits the max roll
        roll = diceRoll(maxRoll);
        if(roll === 0) console.log("zero error");
        sum += roll
    }
    return sum;
    
}
function multiDiceRoll(diceString,advantage) {
    diceString = diceString.split("d");
    // console.log(diceString);
    let diceAmount= diceString[0];
    let diceType = diceString[1];
    console.log(`${diceAmount}d${diceType}`);


    let results = [];
    for(let i = 0; i <= Math.min(2,Math.abs(advantage)); i++) {
        let result = 0;
        for(let d = 1; d <= diceAmount; d++) {
            let roll = explosiveDiceRoll(diceType);
            console.log(`${roll},${diceType} - SHOULD NOT EQUAL`);
            result += roll;
        }
        results.push(result);
    }
    console.log(results);
    if(advantage >= 0) return Math.max(...results);
    else return Math.min(...results);
}
export function diceRoll(maxRoll) {
    return Math.ceil(Math.random()*maxRoll);
}

How can I resolve this radio button issue for my custom AWS Cognito user pool attribute during account creation in my authentication flow?

I have 2 custom attributes in my cognito user pool. When the user creates an account it assigns them to one of the groups, and then routes the user to that experience. The code works completely fine when I use a text box input

'custom:userType': {
        label: 'User Type  (You are not required to have a therapist)',
        placeholder: 'Enter "groupA" or "groupB"',
        required: true,
        order: 2,
      },

However,when I change it to the radio button using this code the radio buttons do not show up (see image) How do I fix this???

'custom:userType': {
        label: 'User Type',
        required: true,
        order: 2,
        type: 'radio',
        options: [
          { label: 'groupA', value: 'groupA' },
          { label: 'groupB', value: 'groupB' },
        ],
      },

**Full code example
**

// RootAuth.jsx
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  Authenticator,
  ThemeProvider,
  createTheme,
} from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { fetchAuthSession } from 'aws-amplify/auth';
import { Amplify } from 'aws-amplify';
import awsconfig from './aws-exports';

import { useDataContext } from './context/DataContext';
import { createProfileAfterSignUp } from './utils/createUserProfile';

// Configure Amplify
Amplify.configure(awsconfig);

const myCustomTheme = createTheme({
  name: 'MyCustomTheme',
  tokens: {
    colors: {
      brand: {
        primary: {
          100: '#5a3eaf', // your main purple
        },
      },
    },
    components: {
      tabs: {
        item: {
          _active: {
            color: '#5a3eaf',
            borderColor: '#5a3eaf',
          },
        },
      },
      button: {
        link: {
          color: '#5a3eaf',
        },
        primary: {
          backgroundColor: '#5a3eaf',
        },
      },
    },
  },
});

export default function RootAuth() {
  const formFields = {
    signUp: {
      email: { label: 'Email', type: 'email', required: true, order: 1 },

      given_name: {
        label: 'First Name',
        placeholder: 'Enter your first name',
        required: true,
        order: 3,
      },
      family_name: {
        label: 'Last Name',
        placeholder: 'Enter your last name',
        required: true,
        order: 4,
      },
      password: {
        label: 'Password',
        placeholder: 'Enter your password',
        required: true,
        order: 5,
      },
      confirm_password: {
        label: 'Confirm Password',
        placeholder: 'Confirm your password',
        required: true,
        order: 6,
      },
      'custom:userType': {
        label: 'User Type',
        required: true,
        order: 2,
        type: 'radio',
        options: [
          { label: 'groupA', value: 'groupA' },
          { label: 'groupB', value: 'groupB' },
        ],
      },
    },
  };

  return (
    <ThemeProvider theme={myCustomTheme}>
      {/* 
         This div is absolutely positioned to fill the viewport (no extra scroll)
         and is centered so that the Authenticator sits in the middle on mobile. 
       */}
      <div
        style={{
          position: 'fixed',
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
          margin: 0,
          padding: 0,
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          // Choose overflow: 'hidden' if you never need scrolling;
          // or 'auto' if your form might grow taller than the screen.
          overflow: 'hidden',
          backgroundColor: '#fff', // so we don't see underlying page
          zIndex: 9999, // ensure it sits above any nav
        }}
      >
        <Authenticator formFields={formFields}>
          {({ user }) => {
            if (!user) return null;
            return <PostSignInRedirect />;
          }}
        </Authenticator>
      </div>
    </ThemeProvider>
  );
}

function PostSignInRedirect() {
  const navigate = useNavigate();
  const { setUserAndFetchData } = useDataContext();

  useEffect(() => {
    async function handleSignIn() {
      try {
        console.log('Fetching auth session...');
        const session = await fetchAuthSession();
        const payload = session.tokens.idToken.payload;
        console.log('ID token payload:', payload);

        // Attempt to create user profile if it doesn't exist
        try {
          console.log('Attempting to create user profile...');
          await createProfileAfterSignUp(payload);
        } catch (error) {
          console.warn(
            'User profile creation error (may already exist):',
            error
          );
        }

        // Set user in context & conditionally fetch data
        setUserAndFetchData(payload);

        // Route based on user type
        const userTypeValFromCustom =
          payload['custom:userType']?.toLowerCase() || '';
        let userTypeVal = userTypeValFromCustom;

        if (!userTypeVal) {
          const groups = payload['cognito:groups'] || [];
          if (groups.includes('groupA')) {
            userTypeVal = 'groupA';
          } else if (groups.includes('groupB')) {
            userTypeVal = 'groupB';
          }
        }

        if (userTypeVal === 'groupA') {
          navigate('/groupA');
        } else {
          navigate('/groupB');
        }
      } catch (error) {
        console.error('Error during post-sign-in:', error);
        navigate('/groupB');
      }
    }
    handleSignIn();
  }, [navigate, setUserAndFetchData]);

  return null;
}

How to call one javascript class method from another class

I have a Javascript class which each instance is attached to a DOM object. That class contains a method to hide the DOM object. For example

index.html

<div id="myDomObject">Content</div>

main.js

class MyClass {
    constructor(element)
    {
        this.component = element;
    }

    hideElement()
    {
        this.component.classList.add('hidden');
    }
}

let myDiv = new MyClass(document.getElementById("myDomObject");

I have a series of other classes which will also hide the same DOM object after something happens, such as the click of a button or result of an ajax request.

main2.js

class MySecondClass {
    constructor(element)
    {
        this.component = element;
        this.addClickEvent(some_button);
    }

    addClickEvent(element)
    {
        element.addEventListener('click', function()
        {
            document.getElementById("myDomObject").classList.add('hidden');
        }
    }
}

It doesn’t feel right to re-create the same method over and over again in each of my classes, just to replicate the method of the original class. That is not very DRY. What I would like to do is call the ‘hide’ method of this original class

I have tried this as below

class MySecondClass {
    constructor(element)
    {
        this.component = element;
        this.addClickEvent(some_button);
        this.domObject = new MyClass(document.getElementById("myDomObject");
    }

    addClickEvent(element)
    {
        element.addEventListener('click', function()
        {
            this.domObject.hide();
        }
    }
}

This obviously instantiates multiple instances of the main class. If I have ten other classes, I will be calling those ten classes and then ten instances of the main class. Event listeners are added to the DOM object ten times etc.

How can I achieve this without overloading class instantiations?