WebDataRocks setReport Blocking Main Thread with Large Datasets

I’m using WebDataRocks in my Vue 3 project to handle large datasets. My implementation works fine with smaller datasets, but when the data size increases, the setReport method blocks the main thread, causing performance issues. Here is a simplified version of my code:

<template>
  <div>
    <webdatarocks ref="pivot"></webdatarocks>
  </div>
</template>

<script>
import WebDataRocks from "webdatarocks";

export default {
  props: {
    dataSource: {
      type: Array,
      required: true,
    },
  },
  mounted() {
    this.$refs.pivot.webdatarocks.on("reportcomplete", () => {
      this.$refs.pivot.webdatarocks.off("reportcomplete");
      this.$refs.pivot.webdatarocks.setReport(this.report);
    });
  },
  watch: {
    dataSource: {
      handler(newValue) {
        this.setReport();
      },
      deep: true,
    },
  },
  methods: {
    setReport() {
      const report = {
        dataSource: {
          data: this.dataSource,
        },
      };
      this.$refs.pivot.webdatarocks.setReport(report);
    },
  },
};
</script>

Questions:
How can I prevent setReport from blocking the main thread when dealing with large datasets?
Are there any recommended practices or alternative approaches to handle large datasets efficiently in WebDataRocks?
Additional Information:
Vue.js version: 3.x
WebDataRocks version: last version
Operating System: Windows 10
Browser: last version

I tried the following approaches to prevent setReport from blocking the main thread:

import { debounce } from 'lodash';

methods: {
  setReport: debounce(function () {
    const report = {
      dataSource: {
        data: this.dataSource,
      },
    };
    this.$refs.pivot.webdatarocks.setReport(report);
  }, 300),
},

How does AST traversal with Babel affect source maps even when no changes are made?

I have a custom Webpack loader where I’m traversing through the AST of the code using Babel to change the import name of a module. However, it seems like this is affecting the source maps of modules, even when the loader doesn’t make any changes to their AST. Why is this happening?

const parser = require('@babel/parser');
const babel = require('@babel/core');

module.exports = function(source, map) {
  const removeProxyImports = (source) => {
    try {
      const filepath = this.resourcePath;
      const ast = parser.parse(source, { sourceType: 'module' });
      
      babel.traverse(ast, {
        ImportDeclaration(path) {
          if (path.node.source && path.node.source.value.includes('ProxyFlow')) {
            const val = path.node.source.value.replace('ProxyFlow.', '');
            // ... some validation code
            path.node.source.value = val;
          }
        },
      });
      //TODO: if not modified, don't do babel transformation

      const { code } = babel.transformFromAstSync(ast, source);

      return code;
    } catch (e) {
      throw new Error('failed ' + e);
    }
  };

  const code = removeProxyImports(source);
  this.callback(null, code, map);
};

I’ve read that even if no code changes are made, simply parsing and traversing the AST can affect the source map if it’s not handled correctly. Regenerating and passing the source map ensures that the mappings between the original source code and the transformed code remain accurate.

I can fix this by also generating the sourcemap while generating the code from ast.

      if(!modified){
        return { code : source, map};
      }
      
      const { code, map: newMap } = babel.transformFromAstSync(ast, source, {
         sourceMaps: true,
         inputSourceMap: map, // Pass the existing source map
      });
      return { code , map : newMap}

But why do we even need the fix. Why does even changing the import is corrupting my sourcemap, even though my sourcemap doesn’t even have mapping for imports?

silent error “UpgradeError Not yet support for changing primary key” in useLiveQuery() in Dexie

I was not aware that it was not possible to change primary key in Dexie and it gave me this error:

UpgradeError Not yet support for changing primary key

But my main issue is that this error is silent in useLiveQuery():

  useLiveQuery(
   () => db.tournaments.limit(5).reverse().toArray(),
   [],
    "loading",
  );

  useEffect(() => {
    window.addEventListener("unhandledrejection", (event) => {
      console.error(event); // This doesn't work. no error event
    });
  }, []);


Since it is silent, my custom ErrorBoundary cannot be triggered unless I catch this error in every useLiveQuery().

useLiveQuery(
    () => {
        try{
          db.tournaments.limit(5).reverse().toArray()
        }catch(e){
          throw new Error("DB error")
        }
     }
    [],
    "loading",
  );


I think this error is caught in dexie.js from line 1274:

       Table.prototype._trans = function (mode, fn, writeLocked) {
            var trans = this._tx || PSD.trans;
            var tableName = this.name;
            var task = debug && typeof console !== 'undefined' && console.createTask && console.createTask("Dexie: ".concat(mode === 'readonly' ? 'read' : 'write', " ").concat(this.name));
            function checkTableInTransaction(resolve, reject, trans) {
                if (!trans.schema[tableName])
                    throw new exceptions.NotFound("Table " + tableName + " not part of transaction");
                return fn(trans.idbtrans, trans);
            }
            var wasRootExec = beginMicroTickScope();
            try {
                var p = trans && trans.db._novip === this.db._novip ?
                    trans === PSD.trans ?
                        trans._promise(mode, checkTableInTransaction, writeLocked) :
                        newScope(function () { return trans._promise(mode, checkTableInTransaction, writeLocked); }, { trans: trans, transless: PSD.transless || PSD }) :
                    tempTransaction(this.db, mode, [this.name], checkTableInTransaction);
                if (task) {
                    p._consoleTask = task;
                    p = p.catch(function (err) {
                        console.trace(err); <<<===== HERE
                        return rejection(err);
                    });
                }
                return p;
            }
            finally {
                if (wasRootExec)
                    endMicroTickScope();
            }
        };

enter image description here

Is this DatabaseClosedError error supposed to be silent in useLiveQuery()?

JavaFx WebView element selection. (JavaScript)

I have a WebView element, which render a html code. I want to create getSelectedIndices and setSelectedIndices method. Getter works, Setter throws IndexSizeException.

How can I set the selection area of WebView?

package hu.krisz;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class WebViewExample extends Application {
    private String content = "<h1>Title</h1> <h2>SubTitle</h2> <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr</p>";

    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        webView.getEngine().loadContent(content);

        // Create button.
        Button button = new Button("Select");

        // Button EventHandler.
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                // Work correctly.
                int[] indeices = getSelectedIndices(webView);
                System.out.println(indeices[0] + ", " + indeices[1]);

                // netscape.javascript.JSException: IndexSizeError: The index is not in the allowed range.
                // indeices[0] = 1;
                // indeices[1] = 4;
                setSelectedIndices(webView, indeices);
            }
        });
        // Create VBox and add the button.
        VBox vbox = new VBox(button);

        // Create BorderPane and add VBox.
        BorderPane root = new BorderPane();
        root.setTop(vbox);
        root.setCenter(webView);

        // Create Scene
        Scene scene = new Scene(root, 800, 600);

        // Setting Stage
        primaryStage.setTitle("WebView Példa");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    public int[] getSelectedIndices(WebView webView) {
        String script = "var selection = window.getSelection();" +
                "if (selection.rangeCount > 0) {" +
                "    var range = selection.getRangeAt(0);" +
                "    var preSelectionRange = range.cloneRange();" +
                "    preSelectionRange.selectNodeContents(document.body);" +
                "    preSelectionRange.setEnd(range.startContainer, range.startOffset);" +
                "    var start = preSelectionRange.toString().length;" +
                "    var end = start + range.toString().length;" +
                "    start + ',' + end;" +
                "} else {" +
                "    '0,0';" +
                "}";

        String result = (String) webView.getEngine().executeScript(script);
        int[] indices = new int[2];
        if (result != null && !result.isEmpty()) {
            String[] splitResult = result.split(",");
            indices[0] = Integer.parseInt(splitResult[0]);
            indices[1] = Integer.parseInt(splitResult[1]);
        } else {
            indices[0] = 0;
            indices[1] = 0;
        }
        return indices;
    }

    public void setSelectedIndices(WebView webView, int[] indices) {
        String script = "function selectText(start, end) {" +
                "    var range = document.createRange();" +
                "    var selection = window.getSelection();" +
                "    range.setStart(document.body.firstChild, start);" +
                "    range.setEnd(document.body.firstChild, end);" +
                "    selection.removeAllRanges();" +
                "    selection.addRange(range);" +
                "}" +
                "selectText(" + indices[0] + ", " + indices[1] + ");";

        webView.getEngine().executeScript(script);
        System.out.println("SELECTED FINSIHED.");
    }
}

I tried to changed the Js script.

String script = "function selectText(start, end) {" +
    "    var node = document.body;" +
    "    var range = document.createRange();" +
    "    var selection = window.getSelection();" +
    "    range.setStart(node, start);" +
    "    range.setEnd(node, end);" +
    "    selection.removeAllRanges();" +
    "    selection.addRange(range);" +
    "}" +
    "selectText(" + indices[0] + ", " + indices[1] + ");";

This way, the method doesn’t always throw errors, but either doesn’t select anything or doesn’t select the specified range

Delayed and incorrect updates from react context to backend

I am creating a food ordering application as project. Here, user can add items from a single restaurant only. If they tried adding items from another restaurant too, they will be shown an alert regarding, and given a choice, 1) Either cancel adding current item, or 2) It will clear your existing cart and add current item.
Suppose I have existing item A from restaurant A in cart and try adding item B from restaurant B in cart now.
Issue I am facing is that when I select option 2), it updates the itemID from A -> B, but restaurantID remains same as restaurant A only, it doesn’t update. In order to update restaurantID, I had to increase/ decrease the item quantity. It would update the restaurantID, but request send to backend will have stale quantity only. Attached associated codes and screenshot below, any help would be appreciated.

Cart-Context.js (frontend)

import { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
import { getCartAPI, updateCartAPI } from "../services/CartService";
import { AuthContext } from "./Auth-Context";
import { debounce } from "../util/debounce";
import { Alert } from "../components/UI/Alert";

export const CartContext = createContext({
    items: [],
    addToCart: () => {},
    removeFromCart: () => {},
    restaurantId: null,
    setRestaurantId: null
});

export const CartCtxProvider = ({ children }) => {
    const { token } = useContext(AuthContext);
    const [cart, setCart] = useState({ items: [] });
    const [restaurantId, setRestaurantId] = useState(null);
    const [alert, setAlert] = useState(null)
    let batchedCartUpdatesRef = useRef([]);
    
    useEffect(() => {
        const fetchCart = async () => {
            try {
                const loadedCart = await getCartAPI(token);
                setCart({ items: loadedCart.items || [] });
                setRestaurantId(loadedCart.restaurant ? loadedCart.restaurant._id : null);
            } 
            catch (error) {
                console.error("Failed to fetch cart:", error);
            }
        };
        if(token){
            fetchCart();
        }
    }, [token]);

    const processBatchedUpdates = useCallback(debounce(async () => {
        if (batchedCartUpdatesRef.current.length > 0) {
            try {
                await updateCartAPI(token, { updates: batchedCartUpdatesRef.current, restaurant: restaurantId });
                batchedCartUpdatesRef.current = [];
            } 
            catch (err) {
                throw new Error("Failed updating cart");
            }
        }
    }, 300), [token, restaurantId]);

    const confirmHandler = () => {
        setCart({ items: [{ id: alert.itemId, name: alert.name, quantity: 1, price: alert.price }] });
        //Clear previous and add current
        setRestaurantId(alert.currentRestaurantId);
        batchedCartUpdatesRef.current = [{ type: 'clear'}, { type: 'add', itemId: alert.itemId, name: alert.name, price: alert.price }];
        processBatchedUpdates();
        setAlert(null)
    }

    const cancelHandler = () => {
        setAlert(null)
    }

    const addToCart = ({ itemId, name, price, currentRestaurantId }) => {
        //If there's no existing cart, create a new one
        if (!restaurantId) {
            setRestaurantId(currentRestaurantId);
            setCart({ items: [{ id: itemId, name, quantity: 1, price }] });
            batchedCartUpdatesRef.current = [{ type: 'add', itemId, name, price }];
        } 
        //If switching restaurants, show alert
        else if (restaurantId !== currentRestaurantId) {
            setAlert({ itemId, name, price, currentRestaurantId });
        } 
        // Add to existing cart
        else {
            addItemToCart({ itemId, name, price, currentRestaurantId });
        }
    };

    const addItemToCart = ({itemId, name, price, currentRestaurantId }) => {
        setCart((prevCart) => {
            let newCart = [...prevCart.items];
            let currentItemIdx = newCart.findIndex((item) => item.id === itemId);
            let newItem;
            if (currentItemIdx !== -1) {
                newItem = {
                    ...newCart[currentItemIdx],
                    quantity: newCart[currentItemIdx].quantity + 1
                };
                newCart[currentItemIdx] = newItem;
            } 
            else {
                newItem = {
                    id: itemId,
                    name,
                    quantity: 1,
                    price
                };
                newCart.push(newItem);
            }
            return {
                items: newCart
            };
        });
        if(restaurantId !== currentRestaurantId){
            setRestaurantId(currentRestaurantId);
        }
        batchedCartUpdatesRef.current.push({ type: 'add', itemId, name, price });
        processBatchedUpdates();
    };


    const removeFromCart = ({ itemId }) => {
        setCart((prevCart) => {
            const newCart = [...prevCart.items];
            const currentItemIdx = newCart.findIndex((item) => item.id === itemId);
            if (currentItemIdx !== -1) {
                if (newCart[currentItemIdx].quantity > 1) {
                    newCart[currentItemIdx].quantity -= 1;
                } else {
                    newCart.splice(currentItemIdx, 1);
                }
            }
            return {
                items: newCart
            };
        });
        batchedCartUpdatesRef.current.push({ type: 'remove', itemId });
        processBatchedUpdates();
    };

    const ctxValue = {
        items: cart.items,
        addToCart,
        removeFromCart,
        restaurantId,
        setRestaurantId
    };

    return <CartContext.Provider value={ctxValue}>
        {children}
        {alert && (
            <Alert onConfirm={confirmHandler} onCancel={cancelHandler}/>
        )}
    </CartContext.Provider>;
};

CartService.js (frontend)

export const getCartAPI = async (token) => {
    const response = await fetch('http://localhost:3000/cart', {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`
        }
    });
    if (!response.ok) {
        throw new Error("Failed to get cart");
    }
    const result = await response.json();
    return result;
};

export const updateCartAPI = async (token, { updates, restaurant }) => {
    const response = await fetch('http://localhost:3000/cart/update', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({ updates, restaurant })
    });
    if (!response.ok) {
        throw new Error("Failed to update cart");
    }
    const result = await response.json();
    return result;
};

debounce.js (frontend)

//Function and wait as arguments to limit calling function in case no activity within "wait"
export const debounce = (func, wait) => {
    let timeout;
    return function(...args){
        const context = this;
        //Reset if activity happns
        clearTimeout(timeout)
        //Wait to call
        timeout = setTimeout(() => func.apply(context, args), wait)
    }
}

cartController.js (backend)

exports.updateCart = async (req, res, next) => {
    try {
        const { updates, restaurant } = req.body;
        const user = req.user._id;
        let cart = await Cart.findOne({ user });
        if (!cart) {
            cart = new Cart({ user, restaurant, items: [] });
        } 
        else if (restaurant && restaurant !== cart.restaurant.toString()) {
            cart.items = []; 
            cart.restaurant = restaurant;
        }
        updates.forEach(update => {
            const { type, itemId, name, price } = update;
            if (type === 'clear') {
                cart.items = [];
                cart.restaurant = restaurant;
            } 
            else {
                const itemIdx = cart.items.findIndex(cartItem => cartItem.item.toString() === itemId);
                if (type === 'add') {
                    if (itemIdx !== -1) {
                        cart.items[itemIdx].quantity += 1;
                    } 
                    else {
                        cart.items.push({ item: itemId, quantity: 1, name, price });
                    }
                }
                 else if (type === 'remove') {
                    if (itemIdx !== -1) {
                        if (cart.items[itemIdx].quantity > 1) {
                            cart.items[itemIdx].quantity -= 1;
                        } else {
                            cart.items.splice(itemIdx, 1);
                        }
                    }
                }
            }
        });
        await cart.save();
        res.status(200).json({ cart });
    } 
    catch (err) {
        next(err);
    }
};

Look as I add item, it has previous restaurantID, but when I increase the quantity, it updates the restaurantID, but the quantity is stale. IMAGE 1 and IMAGE 2

I was unsure whether it occurred because of the logic I added for debouncing and batched updates. So, tried eliminating them, still issue persists.

Prevent PerformanceEntries (marks and measures) to pile up

I use the Performance API to measure the speed of a complex frontend project where resources (available memory) are scare and UI runs like an embedded system for months without reloading.

Can I remove the older PerformanceEntries (marks and measures) and keep the latest ones?
Or can I limit the “kept” PerformanceMark/PerformanceMeasure object? (so chrome automatically discard old objects)

clearMarks and clearMeasures are not the best option for me.

setResourceTimingBufferSize sounds good but it is only affect “resource” performance entries per my understanding.

Bonus question: Is it good practice to measure the speed of the UI with Performance API in production code that is going to be deployed to a costumer, and difficult to modify later on (if some issue were come up)?

Failed to execute ‘drawImage’ on ‘CanvasRenderingContext2D’ even though image source is correct

I am trying to create an animation on scroll using an image sequence, but keep running into this error:

“Uncaught DOMException: Failed to execute ‘drawImage’ on ‘CanvasRenderingContext2D’: The HTMLImageElement provided is in the ‘broken’ state.
at render”

This implies that the image path is incorrect, but I’m almost certain it’s correct. “console.log(images[frameIndex]);” is returning an image element with the correct path. Is there any other reason I would be getting this error?

import React, { useRef, useEffect, useState } from 'react';


function getCurrentFrame(index) {
  return `${index.toString().padStart(4, "0")}.jpg`;
}

const ImageCanvas = ({ scrollHeight, numFrames, width, height }) => {
  const canvasRef = useRef(null);
  const [images, setImages] = useState([]);
  const [frameIndex, setFrameIndex] = useState(0);

  // Step 1: Load images
  function preloadImages() {
    for (let i = 1; i <= numFrames; i++) {
      const img = new Image();
      const imgSrc = getCurrentFrame(i);
      console.log(imgSrc);
      img.src = imgSrc;
      setImages((prevImages) => [...prevImages, img]);
    }
  }

  // Step 2: Handle scroll events
  const handleScroll = () => {
    const scrollFraction = window.scrollY / (scrollHeight - window.innerHeight);
    const index = Math.min(
      numFrames - 1,
      Math.ceil(scrollFraction * numFrames)
    );

    if (index <= 0 || index > numFrames) {
      return;
    }

    setFrameIndex(index);
  };

  // Step 3: Set up canvas
  const renderCanvas = () => {
    const context = canvasRef.current.getContext("2d");
    context.canvas.width = width;
    context.canvas.height = height;
  };

  // Step 4: Render images to canvas
  useEffect(() => {
    preloadImages();
    renderCanvas();
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  useEffect(() => {
    if (!canvasRef.current || images.length < 1) {
      return;
    }

    const context = canvasRef.current.getContext("2d");
    let requestId;

    const render = () => {
      console.log(images[frameIndex]);
      context.drawImage(images[frameIndex], 0, 0);
      requestId = requestAnimationFrame(render);
    };

    render();

    return () => cancelAnimationFrame(requestId);
  }, [frameIndex, images]);

  return (
    <div style={{ height: scrollHeight }}>
      <canvas ref={canvasRef} />
    </div>
  );
};


export default ImageCanvas;

How to I select the records only when I click on the button?

I am using Airtable blocks to create an extension. I have a button that, when pressed, should retrieve the records. However, I am not able to get it to run when the button is clicked…only when the button is loaded. I only want it to run when clicked since I have a lot of tables and loading all the records from all the tables at once could be overkill. How do I get it to run only when I press the button? The error I get is Error: Invalid hook call. Hooks can only be called inside of the body of a function component. Without the

 import React, { useState, useEffect } from "react";
 import { useBase, useRecords, Button } from "@airtable/blocks/ui";
 
 function getRecords(Item) {
   const base = useBase();
   const table = base.getTableIfExists(Item.table);
   return useRecords(table);
 }
 
 export default function RunButton({ Item }) {
   // This works but will potentially load a LOT of data
   const records = getRecords(Item);
   records.map((record) => {
     console.log(record.id);
   });
 
   // This errors
   function onClick() {
     const records = getRecords(Item);
     records.map((record) => {
       console.log(record.id);
     });
   }
 
   return (
     <Button size="small" icon="play" onClick={onClick}>
       Run
     </Button>
   );
 }

JavaScript HTML Element remove method – undocumented bahavior

Some time ago I was refactoring some legacy JS code, when I noticed PHPStorm complaining about the use of remove function: mySelect.remove(0)Invalid number of arguments, expected 0. I made a quick look to the docs and found no information about the argument so I deleted it without further investigation (my bad) and deployed changes.

Recently I’ve been reported a bug that browser freezes after changing some form element. It seems that my change has broken the form.

The relevant code is very simple:

const mySelect = document.getElementById('mySelect');
while (mySelect.options.length > 0) {
    mySelect.remove();
}

With the proper call of remove the browser freezes as it looks like the loop is infinite, but changing the call to undocumented mySelect.remove(0) makes it work as expected.

Any ideas about that behavior?

[tested on Chrome]

Callback function from IFRAME javascript to google colab

So im learning google colab and ive encountered a problem, running my code in google colab, im openning a server and using IFRAME to see my website, the issue im trying to solve is when selecting a json file and clicking upload i want the file to be uploaded to my notebook local memory , my index.html file has a callback function:

<script>
        function uploadJson() {
           google.colab.kernel.invokeFunction('notebook.uploadJSON', [file.name, content], {})
...
....

in colab itself i have registared the function :

output.register_callback('notebook.uploadJSON', upload_json)

which is under the function :

def upload_json(filename, content): ...

the issue im experiencing in the console is google is not defined and basically the callback not working.

ive read that callback might encounter problems when using iframe etc, anyone knows the reason or how to fix it? ive also tried to inject these function from cells in colab without using iframe and that kinda worked but then ive encountered other problems with css and js files not working

from google.colab import output
import IPython
from IPython.display import display, IFrame
import json
import os
import requests
import time


def upload_json(filename, content):
    data = json.loads(content)
    filepath = os.path.join(os.getcwd(), filename)
    with open(filepath, 'w') as f:
        json.dump(data, f, indent=2)
    print(f"File '{filename}' has been saved successfully in the Colab folder.")
    if isinstance(data, dict):
        first_key = next(iter(data))
        print(f"First item in the JSON: {first_key}: {data[first_key]}")
    elif isinstance(data, list) and len(data) > 0:
        print(f"First item in the JSON: {data[0]}")
    else:
        print("The JSON file is empty or has an unexpected structure.")

output.register_callback('notebook.uploadJSON', upload_json)

# Function to download files from Firebase Storage
def download_file_from_firebase(bucket_name, file_name, local_path):
    url = f'https://firebasestorage.googleapis.com/v0/b/{bucket_name}/o/{file_name}?alt=media'
    response = requests.get(url)
    if response.status_code == 200:
        with open(local_path, 'wb') as file:
            file.write(response.content)
        print(f'File {file_name} downloaded to {local_path}.')
    else:
        print(f'Failed to download {file_name}. Status code: {response.status_code}')

# Download necessary files
bucket_name = 'KEY.com'
file_names = [
    'index.html',
    'TeamGraph.html',
    'glossary.html',
    'notifications.html',
    'style.css',
    'KEY.json',
    'app.js',
    'notifications.js'
]

for file_name in file_names:
    download_file_from_firebase(bucket_name, file_name, file_name)

# List downloaded files
print("Downloaded files:", os.listdir())

# Serve the downloaded HTML files from Colab
time.sleep(2)
os.chdir('/content')
get_ipython().system_raw('python3 -m http.server 8000 &')

# Get the server URL
server_url = output.eval_js("google.colab.kernel.proxyPort(8000)")

# Function to display an HTML file in an iframe
def display_html(filename):
    display(IFrame(src=f"{server_url}/{filename}", width='100%', height='600px'))

# Display the initial HTML file
display_html('index.html')

and thats the code in my index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON Upload</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://apis.google.com/js/platform.js"></script>
</head>
<body>
    <div class="button-container">
        <a href="index.html" class="nav-button" id="btnIndex">JSON Upload</a>
        <a href="glossary.html" class="nav-button" id="btnGlossary">Glossary</a>
        <a href="TeamGraph.html" class="nav-button" id="btnTeamGraph">Team Stats</a>
        <a href="notifications.html" class="nav-button" id="btnNotifications">Notifications</a>
    </div>
    <div class="container">
        <h2>Upload JSON File</h2>
        <form id="jsonUploadForm">
            <input type="file" id="fileInput" accept=".json">
            <button type="button" id="uploadButton" onclick="uploadJson()">Upload</button>
        </form>
    </div>
    <script>
        function uploadJson() {
            console.log("uploadJson function called"); // Debugging statement
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];

            if (file) {
                const reader = new FileReader();
                reader.onload = function(event) {
                    const content = event.target.result;
                    console.log("File content:", content); // Debugging statement
                    google.colab.kernel.invokeFunction('notebook.uploadJSON', [file.name, content], {})
                        .then(response => {
                            console.log('Upload successful:', response);
                            alert('Upload successful');
                        })
                        .catch(error => {
                            console.error('Invoke function error:', error);
                            alert('Invoke function error: ' + error);
                        });
                };
                reader.onerror = function(event) {
                    console.error("Error reading file:", event.target.error);
                    alert("Error reading file: " + event.target.error);
                };
                reader.readAsText(file);
            } else {
                alert('No file selected');
            }
        }

        // Disable the button for the current page
        const currentPage = window.location.pathname.split("/").pop();
        document.querySelector(`a[href="${currentPage}"]`).classList.add('active');
        document.querySelector(`a[href="${currentPage}"]`).disabled = true;
    </script>
</body>
</html>

How do I create a Story for a React component that wraps HTML in Storybook?

I’m new to React and have been trying to Google a solution to this for hours – I probably just don’t know the right terminology.

I know I can create a Story for a React component by importing the component and setting it’s arguments as such:

import Button from './button';

export default {
  title: 'Atoms/button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
};

export const Standard = {
  args: {
     label: "Click me",
     size: "lg"
  },
};

But how do I create a story for a component that wraps HTML (or another component), eg Tippy Tooltips:

<Tooltip
  title="Welcome to React"
  position="bottom"
  trigger="click"
>
  <p>Click here to show popup</p>
</Tooltip>

How to I send <p>Click here to show popup</p> as an arg in Storybook?

html2pdf rendering partial DOM

I am using the html2pdf JS library to generate a PDF page with a title with a table.

function generatePDF(form_id, user_id, title) {

  console.log('Ready');

    var element = document.getElementById(form_id);
    var opt = {
      margin: 1,
      filename: `${title}_${user_id}.pdf`,
      image: { type: "jpeg", quality: 0.98 },
      html2canvas: { scale: 2},
      jsPDF: { unit: "mm", format: "letter", orientation: "portrait" }
    };
    // Start PDF generation
    
    $(document).ready(function() {
        html2pdf().set(opt).from(element).save();
    })
}

When I run it on the first try, I get parts of what should be on the page. But on the second try, everything is there. I even tried to use the setTimeout() function to make sure the DOM is complete, but got the same results.

First try
Second try