Source maps not generating in production

I am able to see the source maps when I run the application locally i.e “npm start”. Even it points to the exact line of exception.

But when I run “npm make” and open the myApp.app, it doesn’t work. I don’t see the line causing the error.
It shows log like this .app/Contents/Resources/app/.webpack/main/index.js:8:109084 which is not reflecting the correct line number

import type { Configuration } from 'webpack';
import { rules } from './webpack.rules';

// Define base configuration
const baseConfig: Configuration = {
  devtool: 'source-map',
  plugins: [
  ],
};

export const mainConfig: Configuration = {
    /**
   * This is the main entry point for your application, it's the first file
   * that runs in the main process.
   */
  entry: './src/main/main.js',
  // Put your normal webpack config below here
  module: {
    rules,
  },
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
  },
  externals: process.platform === 'darwin' ? [
    '@myApp/ffi-napi',
    '@myApp/ref-napi',
  ] : [
    '@myApp/electron-edge-js'
  ],
};

const mergedConfig: Configuration = {
  ...baseConfig,
  ...mainConfig,
  plugins: [...(baseConfig.plugins || []), ...(mainConfig.plugins || [])],
};


export default mergedConfig;

package.json:

  "scripts": {
    "start": "electron-forge start",
    "test-win": "nyc --use-spawn-wrap --reporter=cobertura electron-mocha ./test/**/*Test.{js,ts} --require ./test/test_setup.js --timeout 10000 --require ts-node/register",
    "test": "nyc --reporter=cobertura electron-mocha ./test/**/*Test.{js,ts} --require ./test/test_setup.js --timeout 10000 --require ts-node/register",
    "coverage": "./codecov.sh",
    "win-coverage": "codecov.bat",
    "package": "electron-forge package --arch=x64",
    "make": "electron-forge make --arch=x64",
    "build": "npm install --force --loglevel=error && electron-rebuild ",
    "rebuild": "electron-rebuild",
    "installAppDmg": "npm install --force --loglevel=error [email protected]",
    "postinstall": "npmpd",
    "playwright": "NODE_ENV=prod ENV=prod npx playwright test --workers 1",
    "lint": "eslint 'src/main/**/*.ts'"
  },
  "devDependencies": {
    "@electron-forge/cli": "^6.1.1",
    "@electron-forge/maker-deb": "^6.1.1",
    "@electron-forge/maker-rpm": "^6.1.1",
    "@electron-forge/maker-squirrel": "^6.1.1",
    "@electron-forge/maker-zip": "^6.1.1",
    "@electron-forge/plugin-auto-unpack-natives": "^6.4.2",
    "@electron-forge/plugin-webpack": "^6.1.1",
    "@playwright/test": "^1.37.0",
    "@types/chai": "^5.0.0",
    "@types/debug": "^4.1.12",
    "@types/draggabilly": "^2.1.4",
    "@types/fs-extra": "^11.0.4",
    "@types/lodash.isequal": "^4.5.7",
    "@types/mocha": "^10.0.9",
    "@types/node": "^20.12.13",
    "@types/react": "^18.2.12",
    "@types/react-dom": "^18.2.5",
    "@types/sinon": "^17.0.3",
    "@types/webpack-node-externals": "^3.0.2",
    "@typescript-eslint/eslint-plugin": "^5.62.0",
    "@typescript-eslint/parser": "^5.62.0",
    "@vercel/webpack-asset-relocator-loader": "1.7.3",
    "chai": "^4.5.0",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.8.1",
    "draggabilly": "^3.0.0",
    "electron": "20.0.0",
    "eslint": "^8.57.1",
    "fork-ts-checker-webpack-plugin": "^7.3.0",
    "mocha": "^10.7.3",
    "node-fetch": "^2.6.6",
    "node-loader": "^2.0.0",
    "npm-platform-dependencies": "^0.1.0",
    "nyc": "^15.1.0",
    "rewire": "^6.0.0",
    "sass-loader": "^13.3.2",
    "sinon": "^11.1.2",
    "spectron": "^14.0.0",
    "style-loader": "^3.3.3",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "typescript": "^4.9.5",
    "webpack-node-externals": "^3.0.0"
  },

How does JavaScript evaluate this boolean conversion to arrive at the expected output?

const toBool = [() => true, () => false]

The above line is used in this MDN guide to (seemingly) evaluate the output from Node’s fs.promises.access method as a boolean.

The snippet in question is below, paraphrased from the MDN example:

const toBool = [() => true, () => false];

const prepareFile = async (url) => {
  ...
  const exists = await fs.promises.access(filePath).then(...toBool);
  ... 
};

The code works as intended; I’m hoping for an explanation as to how the compiler evaluates these lines, as I can’t quite wrap my head around it.

As best as I can tell, when fs.promises.access returns undefined (a successful resolution, according to the Node docs for the access method), exists is set to true, while the return of an Error object sets exists to false.

Can someone explain how this line evaluates to arrive at true for exists in the snippet above?

Invalid state: ReadableStream is already closed error causing from rederect() in next js 14

I am using next 14. using server action. this is the function:

export async function login(prevState: any, formData: FormData) {
  const result = loginSchema.safeParse(Object.fromEntries(formData));

  if (!result.success) {
    return {
      errors: result.error.flatten().fieldErrors,
    };
  }

  const { username, password } = result.data;

  if (username !== testUser.username || password !== testUser.password) {
    return {
      errors: {
        username: ["Invalid username or password."],
      },
    };
  }
  await createSession(testUser.id);

  redirect("/dashboard");
}

The function works perfectly in terms of validating the form, but when it comes to redirecting, it simply does not.
Trying to debug this for a day, but no clue what’s the problem,

this is the error from the server console:

⨯ Internal error: TypeError [ERR_INVALID_STATE]: Invalid state: ReadableStream is already closed

at ReadableByteStreamController.enqueue (node:internal/webstreams/readablestream:1175:13)

Stop accordion scrolling the page when clicked. Is this solution good for accessibility?

I’m building an accordion based on this code, as it looked good in terms of accessibility.

I wanted the transition between showing a section and hiding a section of the accordion to be animated and have changed the code slightly. My issue then was that the clicked button would be pushed up and disappear from view as the content grew. I found an explanation and chose a solution, but I’m wondering if this is the best solution accessibility-wise?

I found an explanation for my issue in this post:

The page is keeping the currently active element in view – i.e. the button that you clicked on – so the extra height will appear to move up or down depending on where the button is in relation to the screen.

I’m using the solution provided to keep the page from scrolling, as that seemed best accessibility-wise, but there’s another answer that suggests blurring the element. However, wouldn’t blurring it affect how a screen reader interacts with the button?

I was wondering if someone could help me clarify what’s best for accessibility in this case.

This is my code, for reference:

document.addEventListener("DOMContentLoaded", function() {
    // create an array of all buttons in accordion
    const accordionButtons = document.querySelectorAll(".eqd-accordion__button");
    
    accordionButtons.forEach(button => {
        const contentId = button.getAttribute("aria-controls");
        const content = document.getElementById(contentId);
        const contentHeight = document.getElementById(contentId + "-height").offsetHeight;
        // check if an accordion section is expanded
        const isExpanded = button.getAttribute("aria-expanded") === "true";

        if(isExpanded) {
            // set height of visible section
            content.style.visibility = "visible";
            content.style.height = contentHeight + "px";
        } else {
            // Collapse accordions that have aria-expanded false attr
            const contentToHide = document.getElementById(button.getAttribute("aria-controls"));

            if (contentToHide) {
                contentToHide.style.height = "0";
                contentToHide.style.visibility = "hidden";
            }
        }

        // add an onclick event to each button
        button.addEventListener("click", function() {
            // on click
            // check if an accordion section is expanded
            const isExpanded = button.getAttribute("aria-expanded") === "true";
        
            // Collapse ALL accordions
            accordionButtons.forEach(btn => {
                // click event listener tracks which button I've clicked & I can get the corresponding content via ID by passing my aria-controls attribute
                const contentToHide = document.getElementById(btn.getAttribute("aria-controls"));
                btn.setAttribute("aria-expanded", "false");
                if (contentToHide) {
                    contentToHide.style.height = "0";
                    contentToHide.style.visibility = "hidden";
                }
            });
        
            // If the clicked accordion was not expanded, expand it
            if (!isExpanded) {
                button.setAttribute("aria-expanded", "true");
                content.style.visibility = "visible";
                content.style.height = contentHeight + "px";
                const pagePosBeforeExpand = window.scrollY;
                window.scrollTo({ top: pagePosBeforeExpand });
            }
        });
    });
});

This is the live example: https://www.becky-matthews.com/content-writing/

Can’t Use Monaco-Editor and Gemini Api in the same Electron js Application

I’m trying to make a AI integrated IDE in Electron js but I can’t use Monaco-Editor and Gemini Api at the same time

Here is The Codes:

HTML:

”’

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>R-IDE</title>
    <link rel="stylesheet" href="style.css">
    <link rel="stylesheet" href="dist/themes/default/style.min.css" />
</head>
<body>
    <section class="mainapp">
        <nav style="-webkit-app-region: drag">

        </nav>
        <div id="main">
            <div class="resizable" id="file-manager">
                <button id="select-folder">Select Folder</button>
                <div id="tree-container"></div>
            </div>
            <div class="resizer" id="handle-left" data-resize="left"></div>
            <div class="resizable" id="code">
                <label id="file-name">File Name</label>
                <hr id="ayırac">
                <div id="editor"></div>
            </div>
            <div class="resizer" id="handle-right" data-resize="center"></div>
            <div class="resizable" id="chat">
                <div id="chat-main">
                </div>
                <div id="prompt-main">
                    <textarea id="prompt"></textarea>
                    <button id="send"><img src="icons/Email Send.png"></button>
                </div>
            </div>
        </div>
    </section>
</body>
<script type="module" src="script.js"></script>
<script src="loader.js"></script>
<script type="module" src="chatScript.js"></script>
<script type="module" src="jsTreeScript.js"></script>
</html>

”’

jsTreeScript.js:

”’

const button=document.getElementById('select-folder')
button.addEventListener('click', () => {
const input = document.createElement('input');
input.type = 'file';
input.webkitdirectory = true; // Klasör seçimi için
input.multiple = true;       // Çoklu dosya seçimi için
input.click();

  input.addEventListener('change', (event) => {
    const files = Array.from(event.target.files);
    const treeData = buildTreeData(files);

        // jsTree'yi oluştur
    $('#tree-container').jstree('destroy').jstree({
      core: {
        data: treeData
      }
    });

  });

    button.style.display="none";
});

// Dosya ve Klasör Yapısını Hiyerarşik Tree Data'ya Dönüştür
function buildTreeData(files) {
  const tree = [];

  files.forEach(file => {
    const pathParts = file.webkitRelativePath.split('/');
    let current = tree;

    pathParts.forEach((part, index) => {
      const existing = current.find(item => item.text === part);

      if (!existing) {
        const newNode = {
          text: part,
          state: { opened: true },
          children: []
        };

        if (index === pathParts.length - 1 && !file.type.endsWith('/')) {
          newNode.data = file; // Dosya nesnesini kaydet
        }

        current.push(newNode);
        current = newNode.children;
      } else {
          current = existing.children;
        }
    });
  });
    return tree;
}

// jsTree Tıklama Olayını Dinleme
let uzantı="";

var editor;

window.addEventListener('resize', () => {
  editor.layout(); // Pencere boyutu değiştiğinde editörü yeniden boyutlandırır
});


$('#tree-container').on('select_node.jstree', (e, data) => {
    const file = data.node.data;

    const label=document.getElementById("file-name");
    label.textContent=data.node.text;

    const dotIndex = data.node.text.lastIndexOf('.');
    
    // Eğer nokta bulunursa, noktadan sonrasını döndür
    if (dotIndex !== -1) {
        uzantı= data.node.text.substring(dotIndex + 1); // Noktadan sonrasını al

        switch(uzantı){
          case "js":
            uzantı="javascript"
            break;
          case "py":
            uzantı="python"
            break;
          case "cs":
            uzantı="csharp"
            break;
          case "rb":
            uzantı="ruby"
            break;
          case "ts":
            uzantı="typescript"
            break;
          case "rs":
            uzantı="rust"
            break;
          case "md":
            uzantı="markdown"
            break;
          case "kt":
            uzantı="kotlin"
            break;
          case "kts":
            uzantı="kotlin"
            break;
          case "txt":
            uzantı="plaintext"
            break;
          default:
            uzantı=uzantı
            break;
        }
    }
    
    else{
      uzantı= '';
      textarea.value="File can't be loaded"
    }

    if (file) {
        const reader = new FileReader();
        reader.onload = (event) => {

          if(editor==null){
            require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs' } });
            require(['vs/editor/editor.main'], function () {

              monaco.editor.defineTheme('solarized-light', {
                base: 'vs',
                inherit: true,
                rules: [
                    { token: '', foreground: '586e75', background: 'e8e8e8' },
                    { token: 'comment', foreground: 'ABABAB' },
                    { token: 'keyword', foreground: '859900' },
                    { token: 'string', foreground: '2aa198' }
                ],
                colors: {
                    'editor.background': '#e8e8e8'
                }
            });
  
                editor = monaco.editor.create(document.getElementById('editor'),     {
                    value: event.target.result,
                    language: uzantı,
                    theme: 'vs-light',
                });

                monaco.editor.setTheme("solarized-light")
              });
          }
          else{
            const model = monaco.editor.createModel(event.target.result, uzantı);
            editor.setModel(model);
          }
        }

        reader.onerror = () => {
            textarea.value="File can't be loaded"
        };

        reader.readAsText(file); // Dosyanın içeriğini oku
    }
});

”’

chatScript.js:

”’

const { GoogleGenerativeAI } = require("@google/generative-ai");

const genAI = new GoogleGenerativeAI("myAPIKEY");

let history=[

]

const sendbtn=document.getElementById("send");

sendbtn.addEventListener("click", ()=>{
  const messageDiv = document.createElement("div");
  messageDiv.classList.add("outgoing");
  messageDiv.innerText = prompttext.value;
  messagesContainer.appendChild(messageDiv);
  history.push({role: "user", parts:[{text: prompttext.value}]})
  console.log(history)
  run()
})

const prompttext=document.getElementById("prompt");
const messagesContainer=document.getElementById("chat-main")

async function run() {
  // The Gemini 1.5 models are versatile and work with multi-turn conversations (like chat)
  const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash"});

  const chat = model.startChat({
    history,
    generationConfig: {
      maxOutputTokens: 100,
    },
  });

  const result = await chat.sendMessage(prompttext.value);
  prompttext.value=""
  const response = await result.response;
  const text = response.text();
  history.push({role: "model", parts:[{text: text}]})
  const messageDiv = document.createElement("div");
  messageDiv.classList.add("incoming");
  messageDiv.innerText = text;
  messagesContainer.appendChild(messageDiv);
}

”’

Those are my files Monaco-Editor is working but the gemini api gives this error:

Uncaught Error: Check dependency list! Synchronous require cannot resolve module ‘@google/generative-ai’. This is the first mention of this module!
at c.synchronousRequire (loader.js:8:6342)
at r (loader.js:9:2065)
at chatScript.js:1:32

But when I delete the loader.js file the Gemini Api works but Monaco-Editor doesn’t

What should I do, Thanks.

I would like to update the database using remix, conform, zod, supabase and shadcn/ui with dialog

I am new to web development and currently learning.

Here is what I would like to do

  1. display the data retrieved from supabase in a table.
  2. Place an edit button on each row, and when clicked, display a dialog with the data in that row as the initial values.
  3. edit the dialog and click the update button to update the data

I do not know how to implement 2 and 3.
In 2, I cannot get the currentCampaign in the defaultValue of useForm, and the currentCampaign seems to be changed correctly in useEffect.

In 3, my code was too dirty, so I deleted it. In fact, I tried to write a process to update when actionType === “update” in the Action function, but even the formData could not be obtained correctly.

The code is as shown here.

import { Form, useActionData, redirect } from "@remix-run/react";
import { ActionFunction } from "@remix-run/node";
import { ActionFunctionArgs} from "@remix-run/node";

import { useState, useEffect } from "react";

import { useForm, getFormProps, getInputProps} from "@conform-to/react"

import { parseWithZod, getZodConstraint } from "@conform-to/zod"

import { createClient } from '@supabase/supabase-js';

import { set, z } from 'zod';

const supabaseUrl = process.env.SUPA_BASE_URL!;
const supabaseAnonKey = process.env.SUPA_BASE_API_KEY!;
const supabase = createClient(supabaseUrl, supabaseAnonKey);

import { LoaderFunction, json } from "@remix-run/node"
import {useLoaderData} from "@remix-run/react"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

import {
    Table,
    TableBody,
    TableCaption,
    TableCell,
    TableHead,
    TableHeader,
    TableRow,
  } from "@/components/ui/table"

import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogHeader,
    DialogTitle,
    DialogTrigger,
    DialogClose,
    DialogFooter,
    } from "@/components/ui/dialog"
import { captureRejectionSymbol } from "events";

const schema = z.object({
    career: z.preprocess(
        (val) => (val === ''? undefined : val),
        z.string({required_error: 'Career is required'}),
        ),
    shop_name: z.preprocess(
        (val) => (val === ''? undefined : val),
        z.string({required_error: 'Shop name is required'}),
    ),
    details: z.preprocess(
        (val) => (val === ''? undefined : val),
        z.string({required_error: 'Details is required'}),
    ),
    cb: z.preprocess(
        (val) => (val === ''? undefined : val),
        z.number({required_error: 'CB is required'}),
    ),
})



//Action Function
export const action: ActionFunction = async ({request}) => {
    const formData = await request.formData();
    const actionType = formData.get('_action');
    const id = formData.get('id');
    const career = formData.get('career');
    const shop_name = formData.get('shop_name');
    const details = formData.get('details');
    const cb = parseFloat(formData.get('cb') as string);

    if(actionType === 'edit'){
        console.log('edit mode');
        if(typeof id === 'string'){
            const {data, error} = await supabase
                .from('campaigns')
                .select('*')
                .eq('id', id)
                .single();

            if(error) throw new Error(error.message);
            data.mode = 'edit';
            console.log(data);
            return json(data);
        }
        
    }

    if(actionType === 'update'){
        console.log('update mode');
        console.log(formData);
        const submission = parseWithZod(formData, {schema});
        console.log(career, shop_name, details, cb);

        return 
    }

};

//Loader Function
export const loader: LoaderFunction = async () => {
    const { data, error } = await supabase
        .from('campaigns')
        .select('*');
    if (error) throw new Error(error.message);
    return json(data);
};

//Component
export default function Campaigns() {
    const data = useLoaderData();
    const result = useActionData();

    const [editDialogOpen, setEditDialogOpen] = useState(false);
    const [currentCampaign, setCurrentCampaign] = useState({});

    useEffect(() => {
        if(!result) return;

        if(result.mode === 'edit'){
            console.log('edit mode');
            console.log('result', result);
            setCurrentCampaign({...result});
        }
    }, [result]);

    useEffect(() => {
        console.log('currentCampaign', currentCampaign);
    }, [currentCampaign]);

    const [form, fields ] = useForm({
        defaultValue: {
            career: currentCampaign?.career || 'default career',
            shop_name: currentCampaign?.shop_name || 'default shop',
            details: currentCampaign?.details || 'default details',
            cb: currentCampaign?.CB || 1000,
        },
        constraint: getZodConstraint(schema),
        onValidate({formData}){
            return parseWithZod(formData,{schema});
        },
    });

    return (
      <div>
          <Table>
            <TableHeader>
                <TableRow>
                <TableHead className="w-[100px]">Operation</TableHead>
                <TableHead className="w-[100px]">Career</TableHead>
                <TableHead>Shop_name</TableHead>
                <TableHead>Details</TableHead>
                <TableHead className="text-right">CB</TableHead>
                </TableRow>
            </TableHeader>
            <TableBody>
                {data.map((campaign: any) => (
                <TableRow key={campaign.id}>
                <TableCell className="font-medium">
                    <Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
                        <DialogTrigger asChild>
                            <Form method="post">
                                <input type="hidden" name="id" value={campaign.id} />
                                <Button variant="outline" 
                                type="submit" 
                                value="edit" 
                                name="_action"
                                    >Edit</Button>
                            </Form>
                        </DialogTrigger>
                        <DialogContent>
                            <DialogHeader>
                                <DialogTitle>Edit campaign</DialogTitle>
                            </DialogHeader>
                                <Form method="post" {...getFormProps(form)} >
                                    <input type="hidden" name="id" value={currentCampaign?.id || ''} />
                                    <Label htmlFor={fields.career.id}>Career</Label>
                                    <Input {...getInputProps(fields.career, {type:'text'})} />
                                    <Label htmlFor={fields.shop_name.id}>Shop_name</Label>
                                    <Input {...getInputProps(fields.shop_name, {type:'text'})}  />
                                    <Label htmlFor={fields.details.id}>Details</Label>
                                    <Input {...getInputProps(fields.details, {type:'text'})} />
                                    <Label htmlFor={fields.cb.id}>CB</Label>
                                    <Input {...getInputProps(fields.cb, {type:'number'})}  />
                                    <DialogFooter className="sm:justify-start">
                                        <Button type="submit" variant="destructive" name="_action" value="update">Update</Button>
                                    </DialogFooter>
                                </Form>
                            </DialogContent>
                        </Dialog>
                </TableCell>
                <TableCell className="font-medium">{campaign.career}</TableCell>
                <TableCell>{campaign.shop_name}</TableCell>
                <TableCell>{campaign.details}</TableCell>
                <TableCell className="text-right">{campaign.CB.toLocaleString()}yen</TableCell>
                </TableRow>
                ))}
            </TableBody>
            </Table>
      </div>
    );
  }

Node.js server doesn’t start

I’m trying to start a server from my course but when I use node .server.js
nothing happens though I should get console info and see some basic page in the browser at localhost.
server.js

    const express = require('express'); 
const path = require('path'); 
const bodyParser = require('body-parser'); 
const cookieParser = require('cookie-parser'); 
const flash = require('connect-flash'); 
const session = require('express-session'); 
const routes = require('./routes/index'); 
 
const app = express(); 
 
app.set('views', path.join(__dirname, 'views')); 
app.set('view engine', 'pug'); 
 
app.use(express.static(path.join(__dirname, 'public'))); 
 
app.use(bodyParser.json()); 
app.use(bodyParser.urlencoded({ extended: true })); 

app.use(cookieParser()); 
app.use(session({ 
    secret: "tajnehaslo", 
    resave: false, 
    saveUninitialized: true, 
    cookie: {} 
})); 
app.use(flash()); 
app.use('/', routes); 
module.exports = app;

app.js

const app = require("./app");
app.set("port", process.env.PORT || 8080);
const server = app.listen(app.get("port"), () => {
  console.log(`Listening on ${server.address().port}`);
});

index.js

const express = require('express');
const router = express.Router();

const PagesController = require('../controllers/PagesControler');
const ApplicationsController = require('../controllers/ApplicationsController'); 

router.get('/', PagesController.home); 
router.post('/applications', ApplicationsController.store); 

router.get('/index', (req, res) => {
  res.render('login'); 
});

module.exports = router;

I have express installed

How to make a three.js cube have different colors on different sides?

I am new to three.js, and in a program I am creating, I would like to make different colors for different sides of the cube. The arrow keys control cube rotation. For example, could I have red on one facet, orange on another, yellow on another, green on another, blue on another, and purple on the last?

Here is the javascript code I have so far:

var scene, camera, renderer, cube;
var rotateSpeed = 0.1; // initial rotation speed
var cubeRotation = { x: 0, y: 0, z: 0 };

function createCube() {
  var geometry = new THREE.BoxGeometry( 1, 1, 1 );
  var cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ffff })
  cube = new THREE.Mesh(geometry, cubeMaterial);
  scene.add(cube);
}
function init() {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.z = 5;
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
  createCube();
}

function animate() {
  requestAnimationFrame(animate);

  // Update cube rotation based on arrow key input
  cube.rotation.x = cubeRotation.x;
  cube.rotation.y = cubeRotation.y;
  cube.rotation.z = cubeRotation.z;

  renderer.render(scene, camera);
}

// Function to handle key presses (arrow keys)
function onKeyDown(event) {
  switch(event.keyCode) {
    case 37: // Left arrow
      cubeRotation.y -= rotateSpeed;
      break;
    case 38: // Up arrow
      cubeRotation.x -= rotateSpeed;
      break;
    case 39: // Right arrow
      cubeRotation.y += rotateSpeed;
      break;
    case 40: // Down arrow
      cubeRotation.x += rotateSpeed;
      break;
  }
}

// Add event listener for keydown
window.addEventListener('keydown', onKeyDown, false);

// Initialize and animate
init();
animate();

“EntityMetadataNotFoundError: No metadata for ‘Card’ was found” with Express TypeScript TypeORM app

EntityMetadataNotFoundError: No metadata for “Card” was found.

When I un-comment await cardController.create(newCard); in my User class my TypeORM set up stops working.

Before I explicitly mapped the foreign key column in my card class entity using the @JoinColumn decorator, I was getting this error:

TypeORMError: Entity metadata for Card#owner was not found. Check if you specified a correct entity object and if it’s connected in the connection options.

I am using the following dependencies on my package.json.

"express": "^4.17.1",
"@types/express": "^4.17.17",
"typescript": "^4.4.4",
"typeorm": "^0.3.20"

Links I already consulted:

My guess is that there should be an error with circular dependencies affecting metadata resolution, but I am not sure how to resolve it, I thought I did by using aliases in the path and defining an index for all entities, this way when another entity imports from the same index, the resolution path should avoid circular imports ?

Outputs

When line is commented

DB Connection Stablished Correctly, Entities and Metada Columns Loaded Ok

When comment is removed

Connection error, no metadata for Card entity

DB TypeORM Settings

I already made sure that the values obtained from the .env file are ok. Also, the import paths resolve correctly, and every entity file on my app use the same @entities shortcut path.

Also, I already tried declaring a cardStore that is the returned object from getting the Card repository associated to the DBContextSource object, and adding this line doesn’t throw an error.

const cardStore = DBContextSource.getRepository(Card);
export default DBContextSource;

I can guarantee all of that because the connection to the DB is done correctly when I don’t use the cardController.create() method.

import { ServerError } from "@errors";
import { DataSource, DataSourceOptions } from "typeorm";

const type          = "postgres";
const username = process.env.DB_USER;
const password = process.env.DB_PASSWORD;
const host         = process.env.DB_HOST;
const port         = parseInt(process.env.DB_PORT ?? "0");
const database = process.env.DB_NAME;

if(!username) throw new ServerError("Must provide a username environment variable");
if(!password) throw new ServerError("Must provide a password environment variable");
if(!host)     throw new ServerError("Must provide a host environment variable");
if(port <= 0) throw new ServerError("Must provide a port environment variable");
if(!database) throw new ServerError("Must provide a database environment variable");

import { User, Card } from "@entities";

const TypeORMConfig: DataSourceOptions = {
    type,
    host,
    port,
    username,
    password,
    database,
    synchronize: true,
    logging: true,
    entities: [ User, Card ],
    subscribers: [],
    migrations: []
};

const DBContextSource = new DataSource(TypeORMConfig);

(async () => {
    await DBContextSource.initialize();
    console.log("DB connection successfully established!");
    console.log(`Entities loaded: ${DBContextSource.entityMetadatas.map((e) => e.name)}`);
    console.log(DBContextSource.getMetadata(Card).columns.map((col) => col.propertyName));
})().catch((error) => console.log(error));

export default DBContextSource;

Endpoint

I already made sure the attribute values in newCard are set correctly, so assume newCard has all the required properties when await user.addCard(newCard) line is executed.

import { Router } from "express";
import { User, Card } from "@entities";
import { CreateCardPayload } from "@common/types/cards";

const router = Router();

router.post("/", async (req, res, next) => {
    try {
        const user: User = req.userData;
        const options = req.body as CreateCardPayload;
        const newCard: Card = new Card(options, parsedType);
        
        // add card to user array of cards
        await user.addCard(newCard);

        return res.status(200).json(newCard);
    } catch(error) { return next(error); }
});

Entities

User Class

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Card } from "@backend/lib/entities";
import cardController   from "@entities/cards/cardController";

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    public readonly id!: number; // Assertion added since TypeORM will generate the value hence TypeScript does eliminates compile-time null and undefined checks
    @Column()
    public readonly email!: string;
    @Column()
    public readonly password!: string;

    // One-to-Many relationship: A user can have many cards
    // eager: when i fetch the user, typeorm will automatically fetch all cards
    @OneToMany(() => Card, (card) => card.owner, {
        eager: true
    })
    public cards!: Card[];

    // TypeORM requires that entities have parameterless constructors (or constructors that can handle being called with no arguments).
    constructor(email?: string, password?: string) {
        if(email && password) {
            this.email    = email;
            this.password = password;
            this.cards    = [];
        }
    }

    public async addCard(newCard: Card) {
        newCard.owner = this;
        // save card in db
        await cardController.create(newCard);

        // save card locally to persist data and avoid re fetch
        this.cards.push(newCard);
    }
}

Card Class

import { Entity, PrimaryColumn, Column, ManyToOne, JoinColumn, Index } from "typeorm";
import { CreateCardPayload } from "@common/types/cards";
import { User } from "@entities";

@Entity()
export class Card {
    // Assertion! added since TypeORM will generate the value hence TypeScript does eliminates compile-time null and undefined checks
    @PrimaryColumn()
    public cardNumber!: string; // id

    // Many-to-One relationship: A card belongs to one user, but a user can have many cards
    // Since querying by owner is frequent, adding database indexes to improve performance
    @ManyToOne(() => User, (user) => user.cards, { nullable: false })
    @JoinColumn({ name: "ownerId" }) // Explicitly map the foreign key column
    @Index()
    public owner!: User;
    @Column()
    public ownerId!: number; // Explicitly define the foreign key column

    // TypeORM requires that entities have parameterless constructors (or constructors that can handle being called with no arguments).
    public constructor(options?: CreateCardPayload) {
        if(options) {
            this.cardNumber = options.cardNumber;
            this.issuer     = options.issuer;
        }
    }
}

Card Controller (Data Mapper)

import { BadRequestError } from "@backend/lib/errors";
import { Card }  from "@entities";
import DBContextSource from "@db";

class CardController {
    protected cardStore = DBContextSource.getRepository(Card);

    // WHEN THIS METHOD IS CALLED THE ERROR IS THROWN.
    public async create(newCard: Card) {
        console.log(newCard);
        const foundCard = await this.getByCardNumber(newCard.cardNumber);
        if(foundCard) {
            throw new BadRequestError(`A card with the same "${newCard.cardNumber}" card number already exists.`);
        }
        await this.cardStore.save(newCard);
    }

    public async getByCardNumber(cardNumber: string) {
        return await this.cardStore.findOne({
            where: {
                cardNumber: cardNumber
            }
        });
    }

    public async getUserCards(userId: number) {
        return await this.cardStore.find({
            where: {
                owner: {
                    id: userId
                }
            },
            relations: [ "owner" ] // Explicitly load the `owner` relation
        });
    }
}

const cardController = new CardController();

export default cardController;

ffmpeg.wasm – How to do literally anything with a blob url

I’m using the ffmpeg.wasm for the first time and I can’t get anything working, beyond loading it. I have this function that does nothing in particular (I got it from the vite + react example in the docs, slightly modified) and all I want to do is pass it a blob URL like this blob:http://localhost:5173/c7a9ea7c-aa26-4f4f-9c80-11b8aef3e81f and run through the function and have it give me anything back. But instead, it hangs on the ffmpeg.exec command and never completes. And yes, I’ve determined that the input blob does work – it’s an 8MB, 12-second long mp4 clip.

Here’s the function:

    const processOutputVideo = async (videoURL) => {
      const ffmpeg = ffmpegRef.current;

      await ffmpeg.writeFile("input.mp4", await fetchFile(videoURL));
      await ffmpeg.exec(["-i", "input.mp4", "output.mp4"]);

      const fileData = await ffmpeg.readFile("output.mp4");
      const blob = new Blob([fileData.buffer], { type: "video/mp4" });
      const blobUrl = URL.createObjectURL(blob);

      return blobUrl;
    };

And here’s the ffmpeg logs from my terminal.

[FFMPEG stderr] ffmpeg version 5.1.4 Copyright (c) 2000-2023 the FFmpeg developers
Post.jsx:35 [FFMPEG stderr]   built with emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.40 (5c27e79dd0a9c4e27ef2326841698cdd4f6b5784)
Post.jsx:35 [FFMPEG stderr]   configuration: --target-os=none --arch=x86_32 --enable-cross-compile --disable-asm --disable-stripping --disable-programs --disable-doc --disable-debug --disable-runtime-cpudetect --disable-autodetect --nm=emnm --ar=emar --ranlib=emranlib --cc=emcc --cxx=em++ --objcc=emcc --dep-cc=emcc --extra-cflags='-I/opt/include -O3 -msimd128 -sUSE_PTHREADS -pthread' --extra-cxxflags='-I/opt/include -O3 -msimd128 -sUSE_PTHREADS -pthread' --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libmp3lame --enable-libtheora --enable-libvorbis --enable-libopus --enable-zlib --enable-libwebp --enable-libfreetype --enable-libfribidi --enable-libass --enable-libzimg
Post.jsx:35 [FFMPEG stderr]   libavutil      57. 28.100 / 57. 28.100
Post.jsx:35 [FFMPEG stderr]   libavcodec     59. 37.100 / 59. 37.100
Post.jsx:35 [FFMPEG stderr]   libavformat    59. 27.100 / 59. 27.100
Post.jsx:35 [FFMPEG stderr]   libavdevice    59.  7.100 / 59.  7.100
Post.jsx:35 [FFMPEG stderr]   libavfilter     8. 44.100 /  8. 44.100
Post.jsx:35 [FFMPEG stderr]   libswscale      6.  7.100 /  6.  7.100
Post.jsx:35 [FFMPEG stderr]   libswresample   4.  7.100 /  4.  7.100
Post.jsx:35 [FFMPEG stderr]   libpostproc    56.  6.100 / 56.  6.100
Post.jsx:35 [FFMPEG stderr] Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Post.jsx:35 [FFMPEG stderr]   Metadata:
Post.jsx:35 [FFMPEG stderr]     major_brand     : mp42
Post.jsx:35 [FFMPEG stderr]     minor_version   : 0
Post.jsx:35 [FFMPEG stderr]     compatible_brands: mp42mp41isomavc1
Post.jsx:35 [FFMPEG stderr]     creation_time   : 2019-03-15T17:39:05.000000Z
Post.jsx:35 [FFMPEG stderr]   Duration: 00:00:12.82, start: 0.000000, bitrate: 5124 kb/s
Post.jsx:35 [FFMPEG stderr]   Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080, 4985 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)
Post.jsx:35 [FFMPEG stderr]     Metadata:
Post.jsx:35 [FFMPEG stderr]       creation_time   : 2019-03-15T17:39:05.000000Z
Post.jsx:35 [FFMPEG stderr]       handler_name    : L-SMASH Video Handler
Post.jsx:35 [FFMPEG stderr]       vendor_id       : [0][0][0][0]
Post.jsx:35 [FFMPEG stderr]       encoder         : AVC Coding
Post.jsx:35 [FFMPEG stderr]   Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 137 kb/s (default)
Post.jsx:35 [FFMPEG stderr]     Metadata:
Post.jsx:35 [FFMPEG stderr]       creation_time   : 2019-03-15T17:39:05.000000Z
Post.jsx:35 [FFMPEG stderr]       handler_name    : L-SMASH Audio Handler
Post.jsx:35 [FFMPEG stderr]       vendor_id       : [0][0][0][0]
Post.jsx:35 [FFMPEG stderr] Stream mapping:
Post.jsx:35 [FFMPEG stderr]   Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
Post.jsx:35 [FFMPEG stderr]   Stream #0:1 -> #0:1 (aac (native) -> aac (native))
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0x154e4f0] using cpu capabilities: none!
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0x154e4f0] profile High, level 4.0, 4:2:0, 8-bit
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0x154e4f0] 264 - core 164 - H.264/MPEG-4 AVC codec - Copyleft 2003-2022 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00

And it just hangs there. When I use the example video URL from the official example (https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi), it doesn’t hang and it completes the function and returns me a blob URL in the same format as that first blob URL I showed you guys and this is what the ffmpeg output looks like in my console in that case:

Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] frame P:160   Avg QP:23.62  size:  1512
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] frame B:385   Avg QP:26.75  size:   589
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] consecutive B-frames:  5.5%  3.6%  0.0% 90.9%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] mb I  I16..4: 12.6% 87.4%  0.0%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] mb P  I16..4:  3.8% 47.5%  1.6%  P16..4: 12.9%  7.4%  5.0%  0.0%  0.0%    skip:21.7%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] mb B  I16..4:  1.2% 10.3%  0.4%  B16..8: 22.3%  6.9%  1.4%  direct: 2.7%  skip:54.8%  L0:46.9% L1:40.2% BI:12.9%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] 8x8 transform intra:88.7% inter:74.7%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] coded y,uvDC,uvAC intra: 68.3% 0.0% 0.0% inter: 11.8% 0.0% 0.0%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] i16 v,h,dc,p: 33% 40% 24%  3%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 15% 26% 52%  2%  1%  1%  1%  1%  3%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 27% 21% 20%  5%  5%  5%  4%  6%  5%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] i8c dc,h,v,p: 100%  0%  0%  0%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] Weighted P-Frames: Y:12.5% UV:0.0%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] ref P L0: 48.9% 12.5% 22.3% 14.7%  1.6%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] ref B L0: 77.5% 15.7%  6.8%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] ref B L1: 90.9%  9.1%
Post.jsx:35 [FFMPEG stderr] [libx264 @ 0xdf3000] kb/s:242.65
Post.jsx:35 [FFMPEG stderr] Aborted()

Where am I going wrong, what should I convert my input blob into? And just FYI, ChatGPT has been completely garbage at helping me solve this lmao.

An unhandled exception occurred: You must provide the URL of lib/mappings.wasm by calling

Getting below error with below configuration

Outout

Generating ES5 bundles for differential loading…
An unhandled exception occurred: You must provide the URL of lib/mappings.wasm by calling SourceMapConsumer.initialize({ ‘lib/mappings.wasm’: … }) before using SourceMapConsumer

Windows 11 Pro
system configuration
node -v v18.20.5
npm -v 10.9.0
ng –version 18.2.12

Used with below suggested option but same error

  1. $env:NODE_OPTIONS = "--no-experimental-fetch"

I do not want downgrade version.

Please suggest.

Safari text recognition in images (live text)

I’m building a new portfolio and when I open it in Safari, it add some artifacts on images when hovering. It’s because of the Safari “Live Text” default setting that recognise text and numbers inside pixelated images. So, I would like to know if there is a way to disable Safari Live Text through css or javascript? Any help would be greatly appreciated.

Don’t know how to fix specific GitHub DevSkim Code Scanning Alert

on my web-application GitHub repository appeared a code scanning alert (DevSkim) and I don’t know if the alert is false positive and what I have to do in this special case.

The alert:
If untrusted data (data from HTTP requests, user submitted files, etc.) is included in an setTimeout statement it can allow an attacker to inject their own code.

My code:

function confirmForm(event) {
    event.preventDefault();

    const form = document.getElementById('Form');
    const formData = new FormData(form);

    fetch(form.getAttribute('action'), {
        method: 'POST',
        body: formData
    })
    .then(response => {
        if (!response.ok) {
            throw new Error('Network request was not OK!');
        }
        return response.json();
    })
    .then(data => {
        displayNotification(data.status, data.message);
    
        if (data.status === 'success') {
            setTimeout(() => {
                window.location.href = '/aktuell';
            }, 500);
        }        
    })
    .catch(error => {
        console.error('Error:', error);
        displayNotification('error', 'An error happened. Try again later...');
    });
}

(Just the part you need I think)

It also says: “Visit https://github.com/Microsoft/DevSkim/blob/main/guidance/DS172411.md for guidance on this issue.”

So my Question is if this alert is a security risk for the web-application or an user or if it is not exploitable in this case?

I tried to ask ChatGPT and read the DevSkim docs above. ChatGPT said that it is not a security risk but I don’t trust him. For me it’s very important to keep the website safe (because it’s for a big school newspaper) and I want to get a second opinion from real people.

Why is property not updating in view when item is removed and added back in v-for

I’m making a feature wherein the user can add product images and it has an undo-redo functionality. I kept it as minimal as possible to show my issue.

I Created a minimal example of my issue in the Vue Playground:
See it here

There’s a component called ImageItem and it is rendered in a v-for scope. It gets a prop imageModel containing an object, and internally it uses that imageModel prop to access property description and shows/changes this using a textarea. When an item to the list is added, removed and added back, it seems that the binding to the textarea’s value attribute is broken for some reason.

Try the following in my example app:

  1. Click the “Add image” button. A row with a textarea is added.
  2. Type some text in the textarea (the image description).
  3. Click “Undo” button. The description change is undone (textarea becomes empty).
  4. Click “Undo” button again. The image item is removed (The “add” action is undone).
  5. Until now the behavior is as intended.
  6. Now, click “Redo” button. The item is added back to where it was. The textarea containing the description is still empty, since typing in it is a separate action. Still intended behavior.
  7. Click “Redo” again, the description in the textarea should appear now, but it isn’t. That’s is the issue.

I included the code in this question for completeness. But it’s the same code as in the Vue Playground.

App.vue

<script setup>
  import { ref } from 'vue'
  import ImageItem from './ImageItem.vue';
  import { ProductEditModel } from './ProductEditModel';
  const productEditModel = ref(new ProductEditModel());
  function addImage() { 
    productEditModel.value.addImage(); 
  }
  function handleChangeImageDescription(imageModel, description) { 
    productEditModel.value.changeImageDescription(imageModel, description);
  }
  function undo() { productEditModel.value.editHistory.undo(); }
  function redo() { productEditModel.value.editHistory.redo(); }
</script>

<template>
  <div v-for="productImage of productEditModel.images" :key="productImage.key">
    <ImageItem :imageModel="productImage" @changeImageDescription="handleChangeImageDescription"></ImageItem>
  </div>
  <button @click="undo">Undo</button>
  <button @click="redo">Redo</button>
  <button @click="addImage">Add image</button>
</template>

ProductImage.js

export class ProductImage {
  constructor()
  {
    this.key = Math.random();
    this.description = '';
  }
}

ImageItem.vue

<script setup>
import { defineProps, defineEmits } from 'vue';
const emit = defineEmits(['removeImage', 'changeImage', 'changeImageDescription']);
const props = defineProps({
    imageModel: {
        type: Object,
        required: true
    }
});
function handleDescriptionChange(description)
{
  emit('changeImageDescription', props.imageModel, description);
}
</script>

<template>
  <div class="image-item">
    <img src="" alt="Example image here..." />
    Description:
    <textarea :value="props.imageModel.description" rows="4"
      @change="(event) => handleDescriptionChange(event.target.value)" />
  </div>
</template>

<style>
.image-item {
  border: 1px solid #aaa;
  padding: 10px;
}
.image-item * {
  vertical-align: middle;
}
img {
  min-width: 100px;
  min-height: 100px;
  border: 1px solid #d0d0d0;
}
</style>

EditHistory.js

export class EditHistory
{
    constructor()
    {
        this.undoStack = [];
        this.redoStack = [];
    }

    do(action)
    {
        action.execute(); 
        this.undoStack.push(action);
        this.redoStack = [];
    }

    undo(count = 1)
    {
        count = Math.min(this.undoStack.length, count);
        for (let i = count; i > 0; i--)
        {
            const undoAction = this.undoStack.pop();
            undoAction.unExecute();
            this.redoStack.push(undoAction);
        }
    }

    redo(count = 1)
    {
        count = Math.min(this.redoStack.length, count);
        for (let i = count; i > 0; i--)
        {
            const redoAction = this.redoStack.pop();
            redoAction.execute();
            this.undoStack.push(redoAction);
        }
    }

    canUndo() 
    {
        return this.undoStack.length > 0;
    }

    canRedo() 
    {
        return this.redoStack.length > 0;
    }
}

ProductEditModel.js

import { AddImageAction } from "./AddImageAction";
import { ChangeImageDescriptionAction } from "./ChangeImageDescriptionAction";
import { EditHistory } from "./EditHistory";

export class ProductEditModel
{
  constructor()
  {
    this.images = []; // Array of ProductImage instances.
    this.editHistory = new EditHistory();
  }
  addImage()
  {
    this.editHistory.do(new AddImageAction(this));
  }
  changeImageDescription(imageModel, description)
  {
    this.editHistory.do(new ChangeImageDescriptionAction(this, imageModel, description));
  }
}

AddImageAction.js

import { ProductImage } from './ProductImage.js';
export class AddImageAction
{
    constructor(productEditModel) 
    {
        this.productEditModel = productEditModel;
        this.addedImageModel = null;
    }

    getDescription()
    {
        return "Afbeelding toevoegen";
    }

    execute()
    {
        this.addedImageModel = new ProductImage();
        this.productEditModel.images.push(this.addedImageModel);
    }

    unExecute()
    {
        const index = this.productEditModel.images.indexOf(this.addedImageModel);
        console.assert(index > -1);
        this.productEditModel.images.splice(index, 1);
    }
}

ChangeImageDescriptionAction.js

export class ChangeImageDescriptionAction
{
    constructor(productEditModel, productImageEditModel, description) 
    {
        this.productEditModel = productEditModel;
        this.productImageEditModel = productImageEditModel;
        this.description = description;

        this.originalDescription = this.productImageEditModel.description;
    }
    getDescription()
    {
        return "Afbeelding omschrijving veranderen";
    }
    execute()
    {
        this.productImageEditModel.description = this.description;
        console.log("Changed description to: " + this.description);
    }
    unExecute()
    {
        this.productImageEditModel.description = this.originalDescription;
        console.log("Changed description back to original: " + this.originalDescription);
    }
}

Running JavaScript in VSCode [closed]

My problem entails my inability to run JavaScript in VSCode:

specifically when just do something simple like console.log(“Javascript”), nothing comes out in the terminal

I’ve downloaded node.js and even download code runner but its not working. I’ve downloaded JavaScript in VSCode and that also doesn’t work . So what would the solution to this be?