After zooming in the main camera, how do I get a sprite’s zoomed in viewport coordinates?

On a default Phaser project covering the whole browser screen, I have a sprite positioned at 100, 100.
I want to get its coordinates when zoomed in to place an absolute div on top of it in HTML.

On zoom 1 this works:

    const viewportX = (spriteX - this.cameras.main.scrollX);
    const viewportY = (spriteY - this.cameras.main.scrollY);

but as soon as I change the zoom it breaks, I’ve tried adding the cameras.main.zoom, multiplying it, dividing it, and the coordinates are not the correct one.

I understand the documentation for the zoom mentions Changing the zoom does not impact the Camera viewport in any way, it is only applied during rendering.

So I’m at a loss as to how I can zoom in everything but still obtain a zoomed in sprite’s coordinates.

Toolpad DashboardLayout ignores base route in segment and breaks active link

I’m using @toolpad/core’s DashboardLayout along with AppProvider to build a sidebar layout for an admin panel. The entire dashboard is scoped under /dashboard, meaning my routes look like this:

/dashboard

/dashboard/products

/dashboard/employees

etc.

Here’s my setup:

PortalSidebar.jsx:

import React from "react";
import PropTypes from "prop-types";
import Box from "@mui/material/Box";
import { AppProvider } from "@toolpad/core/AppProvider";
import { DashboardLayout } from "@toolpad/core/DashboardLayout";
import { NAVIGATION } from "./navItems";

function PortalSidebar({ children }) {
  return (
    <AppProvider
      navigation={NAVIGATION}
      basename="dashboard/" 
    >
      <DashboardLayout>
        <Box
          sx={{
            py: 4,
            px: 3,
            display: "flex",
            flexDirection: "column",
            alignItems: "flex-start",
          }}
        >
          {children}
        </Box>
      </DashboardLayout>
    </AppProvider>
  );
}

PortalSidebar.propTypes = {
  children: PropTypes.node,
};

export default PortalSidebar;

navItems.js

import { HomeOutlined, ShoppingCartOutlined } from "@mui/icons-material";

export const NAVIGATION = [
  {
    segment: "", // Should route to /dashboard
    title: "Dashboard",
    icon: <HomeOutlined />,
  },
  {
    segment: "products", // Should route to /dashboard/products
    title: "Products",
    icon: <ShoppingCartOutlined />,
  },
];

The issue:

To fix the routing, Itried prefixing dashboard/ to the segment values in NAVIGATION, like “dashboard/products“. This does make the routing work correctly — the sidebar items now point to the right URLs (/dashboard/products, etc).

The problem is that doing this, the active highlighting dissapears, and nothing is highligted anymore. DashboardLayout doesnt mark the current nav item as active, even when we’re on the correct route. It seems like it expects the segment to be a single path segment, and adding nested paths like dashboard/products makes it fail the match internally.

I also tried using basename=”dashboard/” in AppProvider thinking it would solve the prefixing, but that had no effect at all. Adding a custom href field to each nav item was also ignored — Toolpad seems to only use segment for routing and state, and doesn’t respect href.

I hope someone understands the issue and can help me…

NextJS MUI Sidebar – Toolpad DashboardLayout ignores base route in segment and breaks active link

I’m using @toolpad/core’s DashboardLayout along with AppProvider to build a sidebar layout for an admin panel. The entire dashboard is scoped under /dashboard, meaning my routes look like this:

/dashboard

/dashboard/products

/dashboard/employees

etc.

Here’s my setup:

PortalSidebar.jsx:

import React from "react";
import PropTypes from "prop-types";
import Box from "@mui/material/Box";
import { AppProvider } from "@toolpad/core/AppProvider";
import { DashboardLayout } from "@toolpad/core/DashboardLayout";
import { NAVIGATION } from "./navItems";

function PortalSidebar({ children }) {
  return (
    <AppProvider
      navigation={NAVIGATION}
      basename="dashboard/" 
    >
      <DashboardLayout>
        <Box
          sx={{
            py: 4,
            px: 3,
            display: "flex",
            flexDirection: "column",
            alignItems: "flex-start",
          }}
        >
          {children}
        </Box>
      </DashboardLayout>
    </AppProvider>
  );
}

PortalSidebar.propTypes = {
  children: PropTypes.node,
};

export default PortalSidebar;

navItems.js

import { HomeOutlined, ShoppingCartOutlined } from "@mui/icons-material";

export const NAVIGATION = [
  {
    segment: "", // Should route to /dashboard
    title: "Dashboard",
    icon: <HomeOutlined />,
  },
  {
    segment: "products", // Should route to /dashboard/products
    title: "Products",
    icon: <ShoppingCartOutlined />,
  },
];

The issue:

To fix the routing, Itried prefixing dashboard/ to the segment values in NAVIGATION, like “dashboard/products“. This does make the routing work correctly — the sidebar items now point to the right URLs (/dashboard/products, etc).

The problem is that doing this, the active highlighting dissapears, and nothing is highligted anymore. DashboardLayout doesnt mark the current nav item as active, even when we’re on the correct route. It seems like it expects the segment to be a single path segment, and adding nested paths like dashboard/products makes it fail the match internally.

I also tried using basename=”dashboard/” in AppProvider thinking it would solve the prefixing, but that had no effect at all. Adding a custom href field to each nav item was also ignored — Toolpad seems to only use segment for routing and state, and doesn’t respect href.

I hope someone understands the issue and can help me…

Next.js Paywall

I’m trying to implement a paywall with Next.JS (Frontend) and Spring Boot (Backend). Currently, I’m fetching my data like this:

export const getServerSideProps = async ({ query }) => {  
    try {  
        const res = await api.get(`/v1/posts/${query.name}`);  
        const data = await res.json();  
  
        const allRes = await api.get('/v1/posts');  
        const allData = await allRes.json();  
  
        return {  
            props: { json: data, epochs: allData },  
        };  
    } catch (error) {  
        return {  
            props: { json: {}, epochs: {} },  
        };  
    }  
};  

For rendering the paywall, I’m checking if the user is logged in ( const { isLoggedIn } = useUser();). This works fine. The paywall is shown.

However, I want to shorten the request data from getServerSideProps if the user is not logged in (like newspaper paywalls where the text is cut). This is required in order to prevent bypassing the paywall (by using dev tools, e. g. network analysis).

Now I’m challenging how to do this. The “getServerSideProps“ cannot access local storage to authenticate to the backend. But I do not want to load the data with “useEffect“. Is there any alternative? I do not want to split the page into two pages (like a paid and unpaid version).

Is it bad practice to embed text within an SVG element?

Due to elements ids and classes being already assigned to elements and not being suitable to the purpose of a script, I figured doing:

     <path class="marker" id="marker-1" fill="#A0C815"
     d="M -4.1216879,-8.2290524 C -5.9110969,-12.502078 -7.23943,-15.753123 -7.3543753,-16.286999 l -0.2605528,-1.210168 
c 0,0 -0.2841967,-2.894696 0.9582581,-4.746646 
1.2424549,-1.851949 3.2568196,-3.327274 6.1627419,-3.327354 1.1162147,-3.1e-5 3.680967,-0.09509 
5.715044,1.84306 z">SOME TEXT HERE</path>

Does not cause errors, does not cause browser errors, does not cause errors in inkscape or in linux’s imageviewer.

This allows me to use SVGElement.textContent do make my script work.

I see this is syntactically valid, but would it be considered bad practice and worse, cause some unforeseen errors I might not be considering in my naivete?

Get reference to object caller in TypeScript/JavaScript NOT calling function name

Getting the Caller Object in TypeScript/JavaScript

I need to get the caller object that called an otherObject.function.

Is there something like [CallerMemberName] in a function parameter like in C#? But instead of getting the member name, I want a reference to the caller object.

class A
{
    public Name : string = "AAAAA";
    private fA()
    {
        ojectB.fFinal();
    }
}

class B
{
    public fFinal( [CallerMemberObject] parent )
    {
        console.log( parent.name );
    }
}

Or is there a mechanism by which I can access the call stack (which should be possible) to get a reference to the caller object of a function located in another object (I see that it’s not possible; you can use this because it’s a reference to the object that contains the called function)?

Something like Parent Context ?

I’m not looking to know the name of the calling function, I’m trying to get a reference to the calling object, but even getting the name of the calling function is deprecated as it is in the response.
https://stackoverflow.com/a/280396/281313

Get reference to object caller in TypeScript/JavaScript

Getting the Caller Object in TypeScript/JavaScript

I need to get the caller object that called an otherObject.function.

Is there something like [CallerMemberName] in a function parameter like in C#? But instead of getting the member name, I want a reference to the caller object.

class A
{
    public Name : string = "AAAAA";
    private fA()
    {
        ojectB.fFinal();
    }
}

class B
{
    public fFinal( [CallerMemberObject] parent )
    {
        console.log( parent.name );
    }
}

Or is there a mechanism by which I can access the call stack (which should be possible) to get a reference to the caller object of a function located in another object (I see that it’s not possible; you can use this because it’s a reference to the object that contains the called function)?

Something like Parent Context ?

What are the downsides or potential drawbacks of using `popover` attribute to create overlays, like tooltips, popovers and dialogs

TL;DR:

Why aren’t major UI frameworks leveraging the popover attribute for managing overlays, tooltips, and dialogs, given its ability to avoid z-index issues and not having issues with new stacking context created by CSS transforms? Are there hidden drawbacks or performance concerns with this approach?

Making Overlays with the popover attribute

A bit of context

I’ve worked on many custom UI libraries, and I’ve implemented Tooltips, Overlays, Popovers and Dialogs more times that I would like to admit. I’ve used plain JS, Web components and React for many of these libraries.

I’ve also used many libraries for these purposes on projects that already had them integrated or where I couldn’t use mine, like Material UI or Radix and many others.

The Problem

In all cases there were always two things that make my life really hard. The z-index issue and the position issue (caused when a position fixed element was inside an element with a transform or other properties that forced the browser to create a new stack context)

I didn’t want to allow consumers of those components to have to deal with setting z-indexes, as there will always be a chance of other element having just 1 unit above the one they’ve chosen breaking all their layouts. I hate to admit that I’ve seem it many times and found using the max css index value not very elegant.

I implemented many tricks to try to manage it, at some point I centralized the z-index setting by having a manager with a stack that tracked how many overlays or anything needing a z-index property were created so we can always have the proper one without having to ask for a z-index property. While this seemed to have worked fine for a while, there were cases when the positioning issue appeared at the same time. The Overlay was inside an element with a css transform set, which effectively break the positioning of fixed overlays, forcing me to now use portals, while this worked again for a while in the case of React this also introduced issues with access to data in providers. I was super tired of that. While I found solutions for all those issues, I wouldn’t call them simple by any means.

A light at the end of the tunnel. The popover

I’m sure I’ve dealt with other set of issues as well, that I fail to remember now, but in any case I think I’ve found a solution that doesn’t suffer from any of the previously described issues:

https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover

Apparently the popover attribute is widely available now so I went ahead and created a super simple Overlay like this:

import { ComponentProps, FC, useEffect, useRef } from "react";

export type OverlayProps = ComponentProps<"div"> & {
  open: boolean;
};

export const Overlay: FC<OverlayProps> = ({ children, open, ...rest }) => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (open) {
      ref.current?.showPopover?.();
    } else {
      ref.current?.hidePopover?.();
    }
  }, [open, ref]);

  return (
    <div
      {...rest}
      ref={ref}
      // @ts-expect-error React types complain but the property works
      popover="manual"
    >
      {children}
    </div>
  );
};

The usage is super simple just use it like this:


const useOverlayState = () => {
  const [open, setOpen] = useState(false);

  return {
    isOpen: open,
    open: () => setOpen(true),
    close: () => setOpen(false),
    toggle: () => setOpen((o) => !o),
  };
};

export default function App() {
  const { isOpen, toggle } = useOverlayState();
  
  return (
    <div className="App">
      <div id="bad-element"></div>
      <h1>Overlay with popover attribute example</h1>
      <button onClick={toggle}>
        {isOpen ? `Close overlay` : `Open overlay`}
      </button>
  
      <Overlay className="overlay-popover" open={isOpen}>
        I'm an overlay using popover
      </Overlay>
    </div>
  );
}

You can find a working example of this super simple component here: https://codesandbox.io/p/sandbox/67wrtl

as I’ve mentioned this does not suffer from any of the previous issues thanks to the fact that the Overlay is moved by the browser to the top-layer (https://developer.mozilla.org/en-US/docs/Glossary/Top_layer)

I’ve constructed a set of derived components from this small component

  • PositionableOverlay (for tooltips and popovers)
  • Dialog (for dialogs, I know I’ve could have used the Dialog element, but this was super simple to reuse)
  • SlidingPanels

Basically anything that need to behave like an overlay.

With this approach, there are no z-index issues, the browser will guarantee when the element is shown is always on the top. No issues with css transforms and position fixed elements either.

The concern

But here is my main concern: I’ve not seem any major UI framework leveraging this strategy ever. So, I wonder if this is because there are some drawbacks or downsides from using this approach that I might be not taking into consideration.

Are there performance issues? Why is this approach not much popular now that is widely available.

Should I use Dialog for Dialogs? I didn’t use it because I wanted to keep it simple and the humble Overlay component works as intended so I didn’t want to complicate things

Online Game Fast In Browser Desktop But Slow On Mobile And Tablet Desktops [closed]

I built an online game app using Javascript, Typescript, React, Tailwind & PostgreSQL. The web app is performing very well on desktop browser, in-game FPS is at around 250 FPS, but on smartphones and tablets, it’s really slow and sluggish at some periods of the game with max 120 Frame-Per-Second.

I suspect device throttling, if yes, then how to solve it?
Can it be also that the iGPU is not called and need to be called manually on mobiles?
Or is it anything else?

Tell me what you need as information and I’ll provide it.

JavaScript EvenLoop

I have a trouble with event loop understanding.

Why in this code

setTimeout(() => {
    setTimeout(() => {
        console.log("Timeout 1 (40+10ms)");
    }, 10);
}, 40);

setTimeout(() => {
    console.log("Timeout 2 (50ms)");
}, 50);


setTimeout(() => {
    setTimeout(() => {
        console.log("Timeout 1 (20+10ms)");
    }, 10);
}, 20);

setTimeout(() => {
    console.log("Timeout 2 (30ms)");
}, 30);

—— Timeout 1 (40+10ms)
—— Timeout 2 (50ms)

and if we change only timer

—— Timeout 2 (20ms)
—— Timeout 1 (10+10ms)

Isn’t it supposed to be the same?

Why only by changing time in delay it comes weird result???

I installed tailwindcss and tried to start dev server but it gives me an error instead on react

When I want to start my dev server the terminal gives me this error instead:

failed to load config from E:Codetailtestsvite.config.ts
error when starting dev server:
Error: Cannot find module '../lightningcss.win32-x64-msvc.node'
- E:Codetailtestsnode_moduleslightningcssnodeindex.js
    at Function._resolveFilename (node:internal/modules/cjs/loader:1405:15)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1061:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1066:22)
    at Function._load (node:internal/modules/cjs/loader:1215:37)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
    at Module.require (node:internal/modules/cjs/loader:1491:12)
    at require (node:internal/modules/helpers:135:16)
    at Object.<anonymous> (E:Codetailtestsnode_moduleslightningcssnodeindex.js:22:22)
    at Module._compile (node:internal/modules/cjs/loader:1734:14)

Are there any solutions to fix this error? I tried to reinstall lightningcss and I tried some other things but none of them worked.

I installed tailwindcss properly like on the website. I also installed it the same way on my laptop and it works on it but on my pc it wont work for some reason.

React: “appendChild” not working because parameter is not of type “node” even though I am using the “createElement” function?

So in my react app, I have a SVG container and I am attempting to append a tag (which has paths inside it) to it.

I am referring to the SVG container with a ref.

My code does the following:

let path1Black = createElement("path", {d: pathString, stroke: "black"});  
let path2Black = createElement("path", { d: pathString, stroke: "black"}); 
let blackPathsArr= [];  blackPathsArr.push(path1Black );  blackPathsArr.push(path2Black );

let blackPathGroup = createElement("g", { className: 'blackPG' }, blackPathsArr);

let path1Blue = createElement("path", {d: pathString, stroke: "blue"});  
let path2Blue = createElement("path", { d: pathString, stroke: "blue"}); 
let bluePathsArr= [];  bluePathsArr.push(path1Blue );  bluePathsArr.push(path2Blue );

let bluePathGroup = createElement("g", { className: 'bluePG' }, bluePathsArr);

 let combinedPaths = [blackPathGroup, bluePathsGroup];
 let finalGContainer = createElement("g", {}, combinedPaths );

//now finally attaching it to the ref
svgRef.current.appendChild(finalGContainer);

However when I do this, I am getting an error saying “Failed to execute ‘appendChild’ on ‘Node’: parameter 1 is not of type ‘Node’.”

I don’t understand. How is it not a node if the finalGContainer was succesfully created using the “createElement” function? I mean if it truly is not a node, then an error would have been generated there right?

How to display content of dwg file in my NextJS app?

I am desperate. I am building NextJS app with AdonisJS backend. This app will work like warehouse planner, user should be able to put some elements like floor, wall etc. on grid and move it around, resize, put together with another elements etc. It was working well until I came to request from my user to add import of dwg files. I should be able to upload dwg file and then use it like another elements on the grid, so I should be able to move it, resize it etc.
I was able to do some work, I can upload that dwg file to my backend, which respond with svg, but I am unable to display content of that svg on my grid, just something like this

Image of imported dwg file

I am sharing my frontend code and also my backend code, I hope it is even possible to do this.

Btw, dwg files are aroung 5MB.

import_controller.ts (backend)

const execFileAsync = promisify(execFile)

export default class ImportController {
  public async importDwg({ request, response }: HttpContext) {
    try {
      const file = request.file('file')
      if (!file) return response.badRequest({ message: 'No file uploaded' })

      const tmpDir = path.join(process.cwd(), 'tmp')
      await fs.mkdir(tmpDir, { recursive: true })

      const timestamp = Date.now()
      const dwgPath = path.join(tmpDir, `${timestamp}_${file.clientName}`)

      await file.move(tmpDir, { name: path.basename(dwgPath) })

      // Convert DWG to SVG
      const dwg2svgPath = '/usr/local/bin/dwg2svg'
      const { stdout, stderr } = await execFileAsync(dwg2svgPath, [dwgPath], {
        maxBuffer: 1024 * 1024 * 64, // 64 MB
      })

      console.log(`dwg2svg stderr: ${stderr}`)

      let svgString = stdout

      // Fix malformed width/height line
      svgString = svgString.replace(/width="([^"]+)s+height="([^"]+)"/, 'width="$1" height="$2"');

      // Ensure viewBox exists
      const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/)
      if (!viewBoxMatch) {
        const width = svgString.match(/width="([^"]+)"/)?.[1] ?? '2000'
        const height = svgString.match(/height="([^"]+)"/)?.[1] ?? '1000'
        // svgString = svgString.replace(/<svg([^>]*)>/, `<svg$1 viewBox="0 0 ${width} ${height}">`)
      }

      // Constrain to iframe view
      svgString = svgString
        .replace(/width="[^"]+"/, 'width="100%"')
        .replace(/height="[^"]+"/, 'height="100%"')

      await fs.writeFile(path.join(tmpDir, 'debug_output.svg'), svgString)

      const base64Svg = `data:image/svg+xml;base64,${Buffer.from(svgString).toString('base64')}`

      return response.ok({
        message: 'DWG converted successfully',
        image: base64Svg,
        elements: [
          {
            id: Date.now(),
            type: 'dwg',
            x: 0,
            y: 0,
            width: 800,
            height: 600,
            content: base64Svg,
            originalFileName: file.clientName,
          }
        ]
      })

    } catch (error: any) {
      console.error('Error importing DWG:', error)
      return response.internalServerError({
        message: 'Failed to import DWG',
        error: error.message,
      })
    }
  }
}

Here is my warehouse-grid.tsx file

"use client";

export const WarehouseGrid = forwardRef<
  { fitAllElementsToView: () => void },
  WarehouseGridProps
>(function WarehouseGrid(
  {
    selectedTool,
    setSelectedTool,
    lang = "sk",
    is3DMode,
    elements,
    onElementsChange,
  }: WarehouseGridProps,
  ref
) {
  return (
    <div className="flex flex-col h-full">
      {/* Controls section - always visible in both modes */}
      <div className="flex items-center gap-4 p-2 bg-white border-b">
        <div className="flex items-center gap-2">
          <Button
            variant={selectedTool === "move" ? "default" : "ghost"}
            onClick={() => setSelectedTool("move")}
            size="icon"
            title={t(lang, "moveTool")}
          >
            <Move className="h-4 w-4" />
          </Button>
          <Button
            variant={selectedTool === "resize" ? "default" : "ghost"}
            onClick={() => setSelectedTool("resize")}
            size="icon"
            title={t(lang, "resizeTool")}
          >
            <Maximize className="h-4 w-4" />
          </Button>
          <Button
            variant={selectedTool === "pan" ? "default" : "ghost"}
            onClick={() => setSelectedTool("pan")}
            size="icon"
            title={t(lang, "panTool")}
          >
            <Hand className="h-4 w-4" />
          </Button>
          <Separator orientation="vertical" className="h-4" />
          <Button
            variant="outline"
            size="icon"
            title={t(lang, "zoomOut")}
            onClick={handleZoomOut}
          >
            <Minus className="h-4 w-4" />
          </Button>
          <Button
            variant="outline"
            size="icon"
            title={t(lang, "zoomIn")}
            onClick={handleZoomIn}
          >
            <Plus className="h-4 w-4" />
          </Button>
          <Button
            variant="outline"
            size="icon"
            title={t(lang, "resetZoom")}
            onClick={handleZoomReset}
          >
            <Maximize2 className="h-4 w-4" />
          </Button>
          <Button
            variant="ghost"
            size="icon"
            title={t(lang, "fitToScreen")}
            onClick={fitAllElementsToView}
          >
            <Maximize2 className="h-4 w-4" />
          </Button>
        </div>
      </div>
  
      {/* Main content area */}
      <div className="flex-1 relative">
        {is3DMode ? (
          // 3D Mode
          <div ref={containerRef} className="w-full h-full" />
        ) : (
          // 2D Mode
          <div className="flex h-full">
            {/* Grid container */}
            <div
              ref={gridRef}
              className={cn(
                "relative flex-1 bg-gray-50 overflow-hidden",
                {
                  "cursor-grab": selectedTool === "pan",
                  "cursor-grabbing": isDraggingViewport,
                  "cursor-default": selectedTool === "move" || selectedTool === "resize",
                }
              )}
              onClick={handleGridClick}
              onMouseDown={handleMouseDown}
              onMouseMove={handleMouseMove}
              onMouseUp={handleMouseUp}
              onMouseLeave={handleMouseUp}
              onDragOver={handleDragOver}
              onDrop={handleDrop}
            >
              <div
                style={{
                  transform: translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.scale}),
                  transformOrigin: "0 0",
                  width: "100%",
                  height: "100%",
                  position: "absolute",
                }}
              >
                {renderGrid()}
                {elements.map((element) => {
                  return (
                    <DraggableWithoutFindDOMNode
                      key={element.id}
                      position={{ x: element.x, y: element.y }}
                      onStop={(e, data) => handleDragStop(element.id, e, data)}
                      bounds="parent"
                      disabled={selectedTool !== "move"}
                      handle=".drag-handle"
                    >
                      <ResizableBox
                        width={element.width}
                        height={element.height}
                        onResizeStop={(e, data) => handleResizeStop(element.id, e, data)}
                        draggableOpts={{ disabled: selectedTool !== "resize" }}
                        handle={
                          <div className="absolute right-0 bottom-0 w-4 h-4 bg-blue-500 cursor-se-resize rounded-bl" />
                        }
                      >
                        <div
                          className={cn(
                            "relative border-2 drag-handle",
                            selectedElementIds.includes(element.id)
                              ? "border-blue-500"
                              : "border-gray-200"
                          )}
                          onClick={(e) => handleElementClick(e, element.id)}
                        >
                          {renderElement(element)}
                        </div>
                      </ResizableBox>
                    </DraggableWithoutFindDOMNode>
                  );
                })}
              </div>
            </div>
  
            {/* Properties panel */}
            {selectedElementIds.length === 1 && (
              <div className="w-80 border-l border-gray-200">
                <ElementProperties
                  selectedElement={elements.find((el) => el.id === selectedElementIds[0]) || null}
                  onUpdate={handleUpdateElement}
                  onDelete={handleDeleteElement}
                  lang={lang}
                />
              </div>
            )}
          </div>
        )}
      </div>
  
      {/* Language selector */}
      <div className="fixed bottom-0 right-0 p-4">
        <LanguageSelector lang={lang} />
      </div>
    </div>
  );
});

WarehouseGrid.displayName = "WarehouseGrid";

export default WarehouseGrid;

And to be complete, here is my page.tsx, from where I call warehouse-grid.


"use client";

export default function DashboardPage() {
  const router = useRouter();
  const { isLoading, isAuthenticated } = useAuth();

  const [selectedTool, setSelectedTool] = useState("select");
  const [activeCategory, setActiveCategory] = useState<ElementCategory>(null);
  const [isSearching, setIsSearching] = useState(false);
  const [searchQuery, setSearchQuery] = useState("");
  const [language, setLanguage] = useState("sk"); // Default to "en"
  const [showWarehouseDialog, setShowWarehouseDialog] = useState(true);
  // eslint-disable-next-line
  const [gridElements, setGridElements] = useState<any[]>([]);
  const [is3DMode, setIs3DMode] = useState(false);
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  const [showSaveDialog, setShowSaveDialog] = useState(false);
  const [showSaveWarehouseDialog, setShowSaveWarehouseDialog] = useState(false);
  const [showLoadWarehouseDialog, setShowLoadWarehouseDialog] = useState(false);
  const [showUnsavedChangesDialog, setShowUnsavedChangesDialog] = useState(false);
  const [pendingAction, setPendingAction] = useState<'new' | 'load' | null>(null);

  // new
  const [forgeUrn, setForgeUrn] = useState<string | null>(null);

  // Add a ref to access the fitAllElementsToView function
  const warehouseGridRef = useRef<{ fitAllElementsToView: () => void } | null>(null);

  const handleNewWarehouse = useCallback(() => {
    const createNew = () => {
      setGridElements([]);
      setHasUnsavedChanges(false);
      setShowWarehouseDialog(false);
    };
  
    if (hasUnsavedChanges) {
      setShowUnsavedChangesDialog(true);
      setPendingAction('new');
    } else {
      createNew();
    }
  }, [hasUnsavedChanges]);
  
  // Update the handleUnsavedChangesAction callback
  const handleUnsavedChangesAction = useCallback((action: 'save' | 'discard' | 'cancel') => {
    setShowUnsavedChangesDialog(false);
    
    if (action === 'save') {
      setShowSaveWarehouseDialog(true);
    } else if (action === 'discard') {
      if (pendingAction === 'new') {
        setGridElements([]);
        setHasUnsavedChanges(false);
        setShowWarehouseDialog(false);
      } else if (pendingAction === 'load') {
        setShowLoadWarehouseDialog(true);
      }
      setPendingAction(null);
    }
    // Always clear the pending action if cancelled
    if (action === 'cancel') {
      setPendingAction(null);
    }
  }, [pendingAction]);

  // Handle language cookie
  useEffect(() => {
    const langCookie = getCookie("lang");
    setLanguage(langCookie?.toString() || "en");
  }, []);

  useEffect(() => {
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
      if (hasUnsavedChanges) {
        const message = t(language, "unsavedChangesWarning");
        e.preventDefault();
        e.returnValue = message;
        return message;
      }
    };
  
    window.addEventListener('beforeunload', handleBeforeUnload);
  
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
    };
  }, [hasUnsavedChanges, language]);

  useEffect(() => {
    return () => {
      // Prompt for unsaved changes when component unmounts
      if (hasUnsavedChanges) {
        const shouldSave = window.confirm(t(language, "unsavedChangesWarning"));
        if (shouldSave) {
          // You might want to handle auto-saving here
          console.log('Auto-saving on unmount...');
        }
      }
    };
  }, [hasUnsavedChanges, language]);

  if (isLoading) {
    return (
      <div className="flex h-screen items-center justify-center">
        <LoadingSpinner />
      </div>
    );
  }

  if (!isAuthenticated) {
    router.replace("/login");
    return null;
  }

  const handleLoadWarehouseClick = () => {
    setShowLoadWarehouseDialog(true);
  };

  const handleSaveWarehouse = () => {
    setShowSaveWarehouseDialog(true)
  }

  const handleLoadWarehouse = () => {
    if (hasUnsavedChanges) {
      setShowUnsavedChangesDialog(true)
      setPendingAction('load')
    } else {
      setShowLoadWarehouseDialog(true)
    }
  }

  // eslint-disable-next-line
  const handleLoadWarehouseData = (data: any) => {
    console.log("handleLoadWarehouseData called with data:", data);
    if (!data || !Array.isArray(data)) {
      console.error("Invalid warehouse data received:", data);
      toast.error(t(language, 'error_loading_warehouse'));
      return;
    }
    setGridElements(data)
    setHasUnsavedChanges(false)
    
    // Center the view on the loaded elements after a short delay
    setTimeout(() => {
      warehouseGridRef.current?.fitAllElementsToView();
    }, 100);
  }

  const handleFileImport = async (file: File) => {
    try {
      // Create FormData properly
      const formData = new FormData();
      formData.append('file', file);
      
      toast.loading(t(language, 'importing_file'));

      // Use native fetch
      const response = await fetch(${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3333'}/api/warehouses/import/dwg, {
        method: 'POST',
        body: formData,
        credentials: 'include',
      });
      
      if (!response.ok) {
        throw new Error(Upload failed: ${response.status} ${response.statusText});
      }
      
      const data = await response.json();
      toast.dismiss();

      if (data && data.elements && Array.isArray(data.elements)) {
        console.log('Received elements:', data.elements);
        
        // Add the imported elements to the grid
        setGridElements(prev => [...prev, ...data.elements]);
        
        toast.success(t(language, 'file_imported_successfully'));
      } else {
        throw new Error('Invalid response from server');
      }
    } catch (error: any) {
      console.error('Error importing file:', error);
      toast.dismiss();
      toast.error(error.message || t(language, 'error_importing_file'));
    }
  };

  const findElementCategory = (searchTerm: string): ElementCategory => {
    const searchTermLower = searchTerm.toLowerCase();

    const elementCategories = {
      wall: "building",
      door: "building",
      floor: "building",
      rack: "storage",
      shelf: "storage",
      forklift: "vehicles",
    } as const;

    const foundElement = Object.entries(elementCategories).find(([element]) =>
      element.includes(searchTermLower)
    );

    return foundElement ? (foundElement[1] as ElementCategory) : null;
  };

  const handleSearch = (query: string) => {
    setSearchQuery(query);
    const category = findElementCategory(query);
    if (category) {
      setActiveCategory(category);
    }
  };

  const handleExitWarehouse = () => {
    router.push("/");
  };

  const handleLogout = async () => {
    try {
      // Find the session cookie (it will be a long random string)
      const sessionId = Object.keys(
        document.cookie.split("; ").reduce((acc, cookie) => {
          const [key, value] = cookie.split("=");
          acc[key] = value;
          return acc;
        }, {} as Record<string, string>)
      ).find((key) => key.length > 20);

      await api.post("/auth/logout", {}, { withCredentials: true });

      // Clear any client-side storage
      localStorage.clear();

      // Clear the session cookie if found
      if (sessionId) {
        deleteCookie(sessionId, {
          path: "/",
          domain:
            process.env.NODE_ENV === "production"
              ? ".intralogisticgrid.com"
              : undefined,
          maxAge: 0,
          expires: new Date(0),
        });
      }

      // Force a router refresh and redirect
      router.refresh();
      router.replace("/");
    } catch (error) {
      console.error("Logout failed", error);
      toast.error(t(language, "logout_error"));
    }
  };

  return (
    <SidebarProvider>
      <WarehouseDialog 
        open={showWarehouseDialog} 
        onOpenChange={setShowWarehouseDialog}
        onFileImport={handleFileImport}
        onNewWarehouse={handleNewWarehouse}
        onLoadWarehouse={(data) => {
          console.log("LoadWarehouseDialog onLoad called with data:", data);
          if (data && Array.isArray(data)) {
            console.log("Valid data, calling onLoadWarehouse");
            handleLoadWarehouseData(data);
            setShowLoadWarehouseDialog(false);
            setShowWarehouseDialog(false);
          } else {
            console.error("Invalid data in WarehouseDialog onLoad:", data);
            toast.error("Invalid warehouse data format");
          }
        }}
      />

      <AppSidebar>
        <WarehouseSidebar
          selectedTool={selectedTool}
          setSelectedTool={setSelectedTool}
          activeCategory={activeCategory}
          lang={language}
        />
      </AppSidebar>
      <SidebarInset className="flex flex-col h-screen">
        <header className="fixed top-0 left-0 right-0 z-50 flex h-14 shrink-0 items-center justify-between bg-white border-b px-3">
          <div className="flex items-center gap-2">
            <ToolbarIcons onCategoryChange={setActiveCategory} />
          </div>
          <div className="flex items-center gap-4">
            {/* View mode switch */}
            <div className="flex items-center gap-2">
              <Square className={h-4 w-4 ${!is3DMode ? 'text-primary' : 'text-muted-foreground'}} />
              <Switch
                checked={is3DMode}
                onCheckedChange={setIs3DMode}
                aria-label="Toggle 3D mode"
              />
              <Cuboid className={h-4 w-4 ${is3DMode ? 'text-primary' : 'text-muted-foreground'}} />
            </div>

            {/* File operations */}
            <Button
              variant="ghost"
              size="icon"
              onClick={handleNewWarehouse}
              title={t(language, "newWarehouse")}
            >
              <FilePlus className="h-4 w-4" />
            </Button>
            <Button
              variant="ghost"
              size="icon"
              onClick={handleLoadWarehouseClick}
              title={t(language, "loadWarehouse")}
            >
              <FolderOpen className="h-4 w-4" />
            </Button>
            <Button
              variant="ghost"
              size="icon"
              onClick={() => setShowSaveWarehouseDialog(true)}
              title={t(language, "saveWarehouse")}
              className={hasUnsavedChanges ? 'text-yellow-500' : ''}
            >
              <Save className="h-4 w-4" />
            </Button>

            {isSearching ? (
              <div className="relative">
                <Input
                  placeholder={t(language, "search")}
                  value={searchQuery}
                  onChange={(e) => handleSearch(e.target.value)}
                  className="w-40 h-8"
                  autoFocus
                />
                <Button
                  variant="ghost"
                  size="icon"
                  className="absolute right-1 top-1/2 -translate-y-1/2 h-6 w-6"
                  onClick={() => {
                    setSearchQuery("");
                    setIsSearching(false);
                  }}
                >
                  ×
                </Button>
              </div>
            ) : (
              <Button
                variant="ghost"
                size="icon"
                title={t(language, "searchElements")}
                onClick={() => setIsSearching(true)}
              >
                <Search className="h-4 w-4" />
              </Button>
            )}
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button
                  variant="ghost"
                  size="icon"
                  title={t(language, "settings")}
                >
                  <Settings className="h-4 w-4" />
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end">
                <DropdownMenuItem onClick={handleExitWarehouse}>
                  {t(language, "exitWarehouse")}
                </DropdownMenuItem>
                <DropdownMenuItem onClick={handleLogout}>
                  {t(language, "logout")}
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
        </header>

        <div className="flex-1 overflow-hidden mt-14">
          <WarehouseGrid
            ref={warehouseGridRef}
            selectedTool={selectedTool}
            setSelectedTool={setSelectedTool}
            lang={language}
            is3DMode={is3DMode}
            elements={gridElements}
            onElementsChange={(newElements) => {
              setGridElements(newElements);
              setHasUnsavedChanges(true);
            }}
          />
        </div>
      </SidebarInset>

      {/* Unsaved changes dialog */}
      <AlertDialog open={showSaveDialog} onOpenChange={setShowSaveDialog}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>
              {t(language, "unsavedChangesTitle")}
            </AlertDialogTitle>
            <AlertDialogDescription>
              {t(language, "unsavedChangesDescription")}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
          <AlertDialogCancel onClick={() => setShowSaveDialog(false)}>
            {t(language, "cancel")}
          </AlertDialogCancel>
          <AlertDialogAction
        onClick={async () => {
          handleSaveWarehouse();
          setGridElements([]);
          setShowSaveDialog(false);
          setHasUnsavedChanges(false);
        }}
      >
        {t(language, "save")}
      </AlertDialogAction>
      <AlertDialogAction
        onClick={() => {
          setGridElements([]);
          setHasUnsavedChanges(false);
          setShowSaveDialog(false);
        }}
      >
        {t(language, "dontSave")}
      </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      {/* Save warehouse dialog */}
      <SaveWarehouseDialog
        open={showSaveWarehouseDialog}
        onOpenChange={setShowSaveWarehouseDialog}
        gridData={{
          elements: gridElements || [], // Ensure we never pass null/undefined
          scale: 1,
          width: GRID_SIZE_METERS,
          height: GRID_SIZE_METERS
        }}
        onSaved={() => setHasUnsavedChanges(false)}
      />

      {/* Load warehouse dialog */}
      <LoadWarehouseDialog
        open={showLoadWarehouseDialog}
        onOpenChange={setShowLoadWarehouseDialog}
        onLoad={(data) => {
          console.log("LoadWarehouseDialog onLoad called with data:", data);
          if (!data || !Array.isArray(data)) {
            toast.error(t(language, 'error_loading_warehouse'))
            return
          }
          setGridElements(data)
          setHasUnsavedChanges(false)
          
          // Center the view on the loaded elements after a short delay
          setTimeout(() => {
            warehouseGridRef.current?.fitAllElementsToView();
          }, 100);
        }}
      />

      {/* Unsaved changes dialog */}
      <UnsavedChangesDialog
        open={showUnsavedChangesDialog}
        onAction={handleUnsavedChangesAction}
      />

      {forgeUrn && (
        <div style={{ margin: 20, height: 500 }}>
          <ForgeViewer urn={forgeUrn} />
        </div>
      )}
      

    </SidebarProvider>
  );
}

I removed all imports. Thanks for any help

How to get text to expand with parent element width? (exactly!) [duplicate]

I am attempting to make a div which resizes itself dynamically across my webpage and I am wanting text to scale up and down with the parent div’s width.

So far i have not been able to do this as font width cannot be given any kind of pixel scaling to do this in any accurate way.

I have found one website which I will link below which has been able to do this. They seem to break each letter down into its own container individually and I’m not exactly sure why.

I’d love an explanation of the JS and structure of what is going on.

Thanks for any help!

Link: https://visualcreatures.com/?section=services-wrapper

I tried several scripts to find some kind of ratio which would help my JS scale it appropriately to the texts parent but nothing seemed to work accurately.