Pinia loading fails from CDN because devtoolsApi is not defined

When trying to load Pinia via CDN (https://unpkg.com/[email protected]/dist/pinia.iife.js) it fails when it tries to run the loading function, “var Pinia = (function (exports, vue, devtoolsApi) {… })({}, Vue, devtoolsApi);” in Chrome. Chrome devtools throws a “devtoolsApi is not defined” error. And since Pinia is not defined, my Vue code that tries to call “Pinia.createPinia()” fails. This was all working at some point and then all of a sudden it is not.

Gemini says: “Chrome (and other browsers) intentionally do not provide the devtoolsApi to JavaScript functions running within a regular web page. This is a crucial security measure.” which makes me wonder how it ever worked. I don’t really want to go backwards to find out why it was working before if I can just see how to proceed.

How can I conditionaly remove the default behavior of an anchor (a) tag added by insertAdjacentHTML

I am trying to add an anchor tag that should redirect the user to another page only if a condition is met (here if array.length<3). I have followed Iananswer of this SO question, but in my case I am inserting the a tag using a function and insertAdjacentHTML as described below. The other thing is that I need to pass a parameter (the array itself) to check its length. I could not get it to work
the first snippet is form Ian answer which works perfectly. The second is another way of doing it using a div and the third one is the one I am breaking my head overbut can’t find the proper way of writing it.

<a onclick='return handleClick()' href="https://www.example.com">click</a>

<div class="check-div" id="check-div" style="cursor:pointer;">click</div>

<div class="elem1"> I am elem1</div>
//snippet 1

let checkDiv = document.querySelector('.check-div')

let array = ['1', '2', '3']

const handleClick = () =>{
    // return false
    if(array.length <= 3){
        location.href = 'https://www.example.com'
    }else{
        alert('Maximum 3 items to be compared')
        return false
    }
}

//snippet 2

checkDiv.addEventListener('click', ()=>{
    if(array.length <= 3){
        location.href = 'https://www.example.com'
    }else{
        alert('Maximum 3 items to be compared')
    }

})

//snippet 3

function addElements(elem){
    let elem1 = document.querySelector('.elem1')
    elem1.insertAdjacentHTML('beforeend',`<a class='atag' onclick="ps_block_user(elem)" href="./compare-products/" target="_blank">cliquez pour comparer</a>`)
}

function ps_block_user(elem){
    if(elem.length>3){
        alert('Un maximum de 3 produits à comparer est possible!')
        return false
    }
}

addElements(array)

I also tried using an click event listener as below but here still we can’t prevent redirection as oppposed to the first snippet..

let atag = document.querySelector('.atag')
atag.addEventListener('click', ()=>{
    if(array.length>2){
        // return false
        alert('Un maximum de 2 produits à comparer est possible!')
        return false
    }
})

fs.writeFile adds extra brackets to json of key=>values object in node.js

I’m trying to update one JSON file in a for loop called asynchronously. Each time I update the whole file, with 1 object.

This is my very complex code after half a day of research. (I know it’s too complex, but I wanted to show what I’ve tried so far)

    async function saveJsonFile(data, fileName = 'myFile') {
      try {
        const jsonData = JSON.stringify(data);

        // clear cache
        delete require.cache[require.resolve(`./${fileName}.json`)];

        // ensure the file exists
        await fs.readFile(`${fileName}.json`, 'utf8', (err) => {
          if (err) {} 
          else {
            // clear the content of the file
            fs.writeFile(`${fileName}.json`, "", "utf8", (err) => {
              if (err) {} 
              else {

                // save the json << THIS IS WHERE I STARTED WITH
                fs.writeFile(`${fileName}.json`, jsonData, 'utf8', (err) => {
                  if (err) {
                  }
                });                
                
                // try saving again
                fs.writeFile(`${fileName}.json`, jsonData, 'utf8', (err) => {
                  if (err) {
                  }
                });
              }
            });
          }
        });
      } 
    }

Called from another async func:

async function runTrading()
{
  try {
    for (let i = 1; i <= data.length; i++) {
    ...
    lastBuyPrice[symbol] = currentPrice;
    await saveJsonFile(lastBuyPrice);         

This is what I get (different loops, tries, versions, etc.)
Notice the 2 brackets at the end of each line, or in the middle. In the log it looks OK before saving!

{"prod1":32154.22}}
or
{"prod1":32154.22,"prod2":0,"prod3":0}32}}

How to hide text field floating suggestions when field is left, but not if a suggestion is clicked?

I made a vue component (my first ever!) that aims to show suggestions for options as you type:

const AutoCompleteComponent = {
    data()  {
        return {
            open: false,
            current: 0,
            /** @type {string[]} **/
            suggestions: ["first", "ble", "hello"],
            fieldWidth: 0,
        }
    },
    mounted() {
        this.fieldWidth = this.$refs.inputField.clientWidth;
    },
    methods: {

        focus() {
            console.log("Focus activated");
            this.open = true;
        },

        blur() {
            console.log("Focus deactivated")
            // when I do this, the suggestions dissappear the frame before they are
            // clicked, causing the suggestionClick to not be called
            this.open = false;
        },
    
        //For highlighting element
        isActive(index) {
            return index === this.current;
        },
    
        //When the user changes input
        change() {
            this.loadSuggestions();
            //console.log("change()");
            if (this.open == false) {
                this.open = true;
                this.current = 0;
            }
        },
    
        //When one of the suggestion is clicked
        suggestionClick(index) {
            this.currentText = this.matches[index];
            console.log("Clicked suggestion: ", index, this.matches[index]);
            this.open = false;
        },
    },
    computed: {
    
        /**
         * Filtering the suggestion based on the input
         * @this {ReturnType<AutoCompleteComponent["data"]>}
         */
        matches() {
            console.log("computed.matches() str=", this.currentText, " suggestions=", this.suggestions);
            return this.suggestions.filter((str) => {
                const withoutAccents = str.toLowerCase();
                return withoutAccents.indexOf(this.currentText.toLowerCase()) >= 0;
            });
        },
    
        //The flag
        openSuggestion() {
            return this.currentText !== "" &&
                   this.matches.length != 0 &&
                   this.open === true;
        },

        copiedWidth() {
            return this.fieldWidth + "px";
        }
    },
    template: "#vue-auto-complete-template"
};

This is it’s HTML template:

    <template id="vue-auto-complete-template">
      <div v-bind:class="{'open':openSuggestion}" class="auto-complete-field">
        <div class="field-wrapper">
          <input class="form-control" type="text" v-model="currentText" @keydown.enter='enter' @keydown.down='down'
          @keydown.up='up' @input='change' @focus="focus" @blur="blur" ref="inputField" />
        </div>

        <div class="suggestions-wrapper">
          <ul class="field-suggestions" :style="{ width: copiedWidth }" >
            <li v-for="(suggestion, suggestion_index) in matches" v-bind:class="{'active': isActive(suggestion_index)}"
              @click="suggestionClick(suggestion_index)">
              {{ suggestion }}
            </li>
          </ul>
        </div>
      </div>
    </template>

So then I create it like this:

<AutoCompleteComponent class="some class names"></AutoCompleteComponent>

To make it appear under the field, the following CSS is applied:

.auto-complete-field {
    display:inline-block;
}
.auto-complete-field .suggestions-wrapper {
    display:block;
    position: relative;
}

.auto-complete-field.open ul {
    display:initial;
}

.auto-complete-field ul {
    list-style:none;
    padding:0;
    margin:0;

    display: none;

    position: absolute;
    top:0px;
    left: 0px;

    border-bottom: 1px solid black;
}
.auto-complete-field ul li {
    background-color: white;
    border: 1px solid black;
    border-bottom: none;
}

Now the problem is if you look onto the blur() function, it sets open to false which in turn hides the suggestions ul.field-suggestions using the active class name.

Because of the order in which events are handled, blur event on the field hides the .auto-complete-field.open ul before the click event is created, causing it to instead be invoked on whatever was under it.

Quick and dirty remedy to this would be setTimeout(()=>{this.open=false}, 100). I think for this to work, the timeout must actually be two render frames at least. It didn’t work as a microtask nor RequestAnimationFrame. I don’t want to use timeout, especially a big one, because it can cause GUI flickering with fast clicks.

I am looking for a more solid solution to this. I’d hope Vue has something for this. Plain JS solution to this is usually reimplementing blur event by listening on multiple events on Window, and checking where they occured. I’d rather avoid that.

Can I use the ‘this’ argument within the typescript Array.map() function to increment a counter?

Here’s the code I’m trying to get to work:

const playlist = {
  playlist_idea_uri: uri.toString(),
  type: 'master'
}
const insertedPlaylistIdResponse = await db
  .insertInto('playlist')
  .values(playlist)
  .onConflict((oc) => oc.doNothing())
  .returning('id')
  .executeTakeFirst()
let songs = masterPlaylist.songs.map((song) => {
  return {
    track_name: song.trackName,
    track_mb_id: song.trackMbId,
    album_artwork_ref: song.albumArtwork?.ref.toString(),
    created_at: song.createdAt
  }
})
const insertedSongIdsResponse = await db
  .insertInto('song')
  .values(songs)
  .onConflict((oc) => oc.doNothing())
  .returning('id')
  .execute()
var position = 0
if (insertedPlaylistIdResponse?.id) {
  const playlistItems = insertedSongIdsResponse.map((songIdResponse) => {
    if (this !== undefined) {
      return undefined
    } else {
      const newPosition = this + 1
      return {
        playlist_id: insertedPlaylistIdResponse.id,
        song_id: songIdResponse.id,
        position: newPosition
      }
    }
  }, position)
  await db
    .insertInto('playlist_item')
    .values(playlistItems)
    .execute()
}

So basically I want to insert an object into the playlist table, insert song objects into the song table, take the resulting ids of those inserted songs and the inserted playlist and for each song’s id map to a kind of object that I can then insert into another table called playlist_item, like this:

const playlistItems = insertedSongIdsResponse.map((songIdResponse) => {
  if (this !== undefined) {
    return undefined
  } else {
    const newPosition = this + 1
    return {
      playlist_id: insertedPlaylistIdResponse.id,
      song_id: songIdResponse.id,
      position: newPosition
    }
  }
}, position)

The playlist_item table has this schema:

export interface PlaylistItemTable {
  id: Generated<number>
  playlist_id: number
  song_id: number
  position: number
}

Basically a PlaylistItem is a song within the playlist, at a certain position (like the first song in the playlist will have position = 1, the next would have position = 2, etc.

The thing to focus on is the position variable, and whether I can use the thisArg parameter of the Array.map() function as a kind of iterator that each time it maps it will increment, based on the way I’ve used it. Is that possible, or will it not work? Currently I’m getting the error that the variable this could be undefined, but I’m not sure why because I’ve checked for that right before attempting to use it.

Any help is appreciated!

Why do my Three.js game camera controls glitch / not work

I have made a game, and everything works, except when I move the camera with my mouse, everything duplicates in my view. I see a quickly alternating version alternating between my original camera view and my new camera view.

I made this code, and I have tried to make the camera update and change it instead of duplicating it, but it doesn’t work. Here is my broken code so far:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Platformer</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // Create a scene
        var scene = new THREE.Scene();

        // Create a camera with increased FOV
        var camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);

        // Create a renderer
        var renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // Create a large platform
        var platformGeometry = new THREE.BoxGeometry(20, 1, 20);
        var platformMaterial = new THREE.MeshBasicMaterial({ color: 0xaaaaaa });
        var platform = new THREE.Mesh(platformGeometry, platformMaterial);
        platform.position.y = -1;
        scene.add(platform);

        // Create a player
        var playerGeometry = new THREE.BoxGeometry(1, 1, 1);
        var playerMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
        var player = new THREE.Mesh(playerGeometry, playerMaterial);
        scene.add(player);

        // Create additional cubes
        var cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
        var cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        for (var i = 0; i < 5; i++) {
            var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
            cube.position.set(Math.random() * 18 - 9, 0.5, Math.random() * 18 - 9);
            scene.add(cube);
        }

        // Create an AI that runs around randomly
        var aiGeometry = new THREE.BoxGeometry(1, 1, 1);
        var aiMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        var ai = new THREE.Mesh(aiGeometry, aiMaterial);
        ai.position.set(0, 0.5, -5); // Initial position
        scene.add(ai);

        // AI movement variables
        var aiSpeed = 0.05;
        var aiDirection = new THREE.Vector3(Math.random() * 2 - 1, 0, Math.random() * 2 - 1).normalize();
        var aiJumpSpeed = 0.2;
        var aiVelocityY = 0;
        var aiOnGround = true;

        // Player controls
        var playerSpeed = 0.1;
        var jumpSpeed = 0.2;
        var velocityY = 0;
        var onGround = true;

        var keys = {
            w: false,
            a: false,
            s: false,
            d: false,
            space: false
        };

        window.addEventListener('keydown', function(event) {
            if (keys.hasOwnProperty(event.key)) keys[event.key] = true;
        });

        window.addEventListener('keyup', function(event) {
            if (keys.hasOwnProperty(event.key)) keys[event.key] = false;
        });

        // Mouse controls
        var mouse = {
            isDragging: false,
            previousX: 0,
            previousY: 0,
            deltaX: 0,
            deltaY: 0
        };

        document.addEventListener('mousedown', function(event) {
            mouse.isDragging = true;
            mouse.previousX = event.clientX;
            mouse.previousY = event.clientY;
        });

        document.addEventListener('mouseup', function() {
            mouse.isDragging = false;
        });

        document.addEventListener('mousemove', function(event) {
            if (mouse.isDragging) {
                mouse.deltaX = event.clientX - mouse.previousX;
                mouse.deltaY = event.clientY - mouse.previousY;

                camera.rotation.y -= mouse.deltaX * 0.002;
                camera.rotation.x -= mouse.deltaY * 0.002;

                camera.rotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, camera.rotation.x)); // Prevent flipping

                mouse.previousX = event.clientX;
                mouse.previousY = event.clientY;
            }
        });

        // Animation loop
        function animate() {
            requestAnimationFrame(animate);

            // Player movement
            if (keys.w) player.position.z -= playerSpeed;
            if (keys.a) player.position.x -= playerSpeed;
            if (keys.s) player.position.z += playerSpeed;
            if (keys.d) player.position.x += playerSpeed;

            // Prevent jumping off the edge
            player.position.x = Math.max(-9.5, Math.min(9.5, player.position.x));
            player.position.z = Math.max(-9.5, Math.min(9.5, player.position.z));

            // Player jumping
            if (keys.space && onGround) {
                velocityY = jumpSpeed;
                onGround = false;
            }

            player.position.y += velocityY;
            velocityY -= 0.01; // Gravity effect

            if (player.position.y <= 0) {
                player.position.y = 0;
                onGround = true;
            }

            // AI random movement and jumping
            ai.position.add(aiDirection.clone().multiplyScalar(aiSpeed));
            if (Math.random() < 0.05 && aiOnGround) { // Increased jump frequency
                aiVelocityY = aiJumpSpeed;
                aiOnGround = false;
            }

            ai.position.y += aiVelocityY;
            aiVelocityY -= 0.01; // Gravity effect

            if (ai.position.y <= 0) {
                ai.position.y = 0;
                aiOnGround = true;
            }

            // Avoid getting stuck with AI
            var distance = player.position.distanceTo(ai.position);
            if (distance < 1) {
                var pushDirection = new THREE.Vector
                                Vector3().subVectors(player.position, ai.position).normalize();
                player.position.add(pushDirection.multiplyScalar(0.1));
            }

            // Position camera
            camera.position.set(player.position.x, player.position.y + 1.5, player.position.z);
            camera.lookAt(player.position.x + Math.sin(camera.rotation.y), player.position.y + 1.5, player.position.z - Math.cos(camera.rotation.y));

            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>

What is the correct code for the game Mastermind? [closed]

My code is not working in Codehs to make the game Mastermind. It is something I’ve been working on for a couple of weeks and is refusing to work. I just need the code to work.

I tried multiple different ways to make the code work and I expected at least one to work. I used for loops, while loops, arrays, etc but for some reason it still isn’t working.

Custom tooltips not displayed for all lines

I created a dashboard using Google Charts and wanted to annotate data by using custom tooltips. But I only manage to display my custom tooltips for the latest created line (line of plot87 in JSFiddle), but lines created before are using the default tooltip (line of plot66 in JSFiddle).

It seems like I am somehow causing a fallback to the default tooltip, but I do not see how. After logging the data seems to be the same structure for both lines, so I do not know, where I force the fallback or cancel out my custom tooltip.

Here is my code, where I tried to reproduce my problem:

const jsonData = {
    "title": "Dashboard",
    "updated_on": "2025-02-21T14:08:28.741617",
    "panels": [
        {
            "type": "timeseries",
            "table": "air_temp",
            "title": "Air Temperature 2 m above Surface",
            "unit": "u00b0C",
            "data": {
                "plot66": [
                    {
                        "time": "2025-01-01T00:00:00",
                        "value": 7.468658447265625
                    },
                    {
                        "time": "2025-01-01T03:00:00",
                        "value": 7.459381103515625
                    },
                    {
                        "time": "2025-01-01T06:00:00",
                        "value": 7.389312744140625
                    },
                    {
                        "time": "2025-01-01T09:00:00",
                        "value": 9.727935791015625
                    },
                    {
                        "time": "2025-01-01T12:00:00",
                        "value": 10.906646728515625
                    },
                    {
                        "time": "2025-01-01T15:00:00",
                        "value": 11.067535400390625
                    },
                    {
                        "time": "2025-01-01T18:00:00",
                        "value": 8.872955322265625
                    },
                    {
                        "time": "2025-01-01T21:00:00",
                        "value": 8.459625244140625
                    },
                    {
                        "time": "2025-01-02T00:00:00",
                        "value": 8.005035400390625
                    },
                    {
                        "time": "2025-01-02T03:00:00",
                        "value": 7.952545166015625
                    },
                    {
                        "time": "2025-01-02T06:00:00",
                        "value": 7.978424072265625
                    },
                    {
                        "time": "2025-01-02T09:00:00",
                        "value": 10.216461181640625
                    },
                    {
                        "time": "2025-01-02T12:00:00",
                        "value": 10.678863525390625
                    },
                    {
                        "time": "2025-01-02T15:00:00",
                        "value": 10.543609619140625
                    },
                    {
                        "time": "2025-01-02T18:00:00",
                        "value": 9.369049072265625
                    },
                    {
                        "time": "2025-01-02T21:00:00",
                        "value": 9.340240478515625
                    },
                    {
                        "time": "2025-01-03T00:00:00",
                        "value": 8.868316650390625
                    },
                    {
                        "time": "2025-01-03T03:00:00",
                        "value": 8.211090087890625
                    },
                    {
                        "time": "2025-01-03T06:00:00",
                        "value": 7.642486572265625
                    },
                    {
                        "time": "2025-01-03T09:00:00",
                        "value": 9.831451416015625
                    },
                    {
                        "time": "2025-01-03T12:00:00",
                        "value": 10.954010009765625
                    },
                    {
                        "time": "2025-01-03T15:00:00",
                        "value": 11.588531494140625
                    },
                    {
                        "time": "2025-01-03T18:00:00",
                        "value": 10.276763916015625
                    },
                    {
                        "time": "2025-01-03T21:00:00",
                        "value": 10.511627197265625
                    },
                    {
                        "time": "2025-01-04T00:00:00",
                        "value": 10.682769775390625
                    },
                    {
                        "time": "2025-01-04T03:00:00",
                        "value": 10.562896728515625
                    },
                    {
                        "time": "2025-01-04T06:00:00",
                        "value": 10.225006103515625
                    },
                    {
                        "time": "2025-01-04T09:00:00",
                        "value": 10.914947509765625
                    },
                    {
                        "time": "2025-01-04T12:00:00",
                        "value": 11.296051025390625
                    },
                    {
                        "time": "2025-01-04T15:00:00",
                        "value": 10.217437744140625
                    },
                    {
                        "time": "2025-01-04T18:00:00",
                        "value": 8.537506103515625
                    },
                    {
                        "time": "2025-01-04T21:00:00",
                        "value": 7.460845947265625
                    },
                    {
                        "time": "2025-01-05T00:00:00",
                        "value": 6.343170166015625
                    },
                    {
                        "time": "2025-01-05T03:00:00",
                        "value": 5.995269775390625
                    },
                    {
                        "time": "2025-01-05T06:00:00",
                        "value": 6.114410400390625
                    },
                    {
                        "time": "2025-01-05T09:00:00",
                        "value": 6.575592041015625
                    },
                    {
                        "time": "2025-01-05T12:00:00",
                        "value": 7.698150634765625
                    },
                    {
                        "time": "2025-01-05T15:00:00",
                        "value": 7.824859619140625
                    },
                    {
                        "time": "2025-01-05T18:00:00",
                        "value": 7.553375244140625
                    },
                    {
                        "time": "2025-01-05T21:00:00",
                        "value": 7.653472900390625
                    }
                ],
                "plot87": [
                    {
                        "time": "2025-01-01T00:00:00",
                        "value": 7.423492431640625
                    },
                    {
                        "time": "2025-01-01T03:00:00",
                        "value": 7.418609619140625
                    },
                    {
                        "time": "2025-01-01T06:00:00",
                        "value": 7.349761962890625
                    },
                    {
                        "time": "2025-01-01T09:00:00",
                        "value": 9.715728759765625
                    },
                    {
                        "time": "2025-01-01T12:00:00",
                        "value": 10.905670166015625
                    },
                    {
                        "time": "2025-01-01T15:00:00",
                        "value": 11.054107666015625
                    },
                    {
                        "time": "2025-01-01T18:00:00",
                        "value": 8.802398681640625
                    },
                    {
                        "time": "2025-01-01T21:00:00",
                        "value": 8.401763916015625
                    },
                    {
                        "time": "2025-01-02T00:00:00",
                        "value": 7.939361572265625
                    },
                    {
                        "time": "2025-01-02T03:00:00",
                        "value": 7.887359619140625
                    },
                    {
                        "time": "2025-01-02T06:00:00",
                        "value": 7.899566650390625
                    },
                    {
                        "time": "2025-01-02T09:00:00",
                        "value": 10.189361572265625
                    },
                    {
                        "time": "2025-01-02T12:00:00",
                        "value": 10.664215087890625
                    },
                    {
                        "time": "2025-01-02T15:00:00",
                        "value": 10.540191650390625
                    },
                    {
                        "time": "2025-01-02T18:00:00",
                        "value": 9.300933837890625
                    },
                    {
                        "time": "2025-01-02T21:00:00",
                        "value": 9.263092041015625
                    },
                    {
                        "time": "2025-01-03T00:00:00",
                        "value": 8.797027587890625
                    },
                    {
                        "time": "2025-01-03T03:00:00",
                        "value": 8.135162353515625
                    },
                    {
                        "time": "2025-01-03T06:00:00",
                        "value": 7.564117431640625
                    },
                    {
                        "time": "2025-01-03T09:00:00",
                        "value": 9.803619384765625
                    },
                    {
                        "time": "2025-01-03T12:00:00",
                        "value": 10.940582275390625
                    },
                    {
                        "time": "2025-01-03T15:00:00",
                        "value": 11.579498291015625
                    },
                    {
                        "time": "2025-01-03T18:00:00",
                        "value": 10.209869384765625
                    },
                    {
                        "time": "2025-01-03T21:00:00",
                        "value": 10.452056884765625
                    },
                    {
                        "time": "2025-01-04T00:00:00",
                        "value": 10.628082275390625
                    },
                    {
                        "time": "2025-01-04T03:00:00",
                        "value": 10.505523681640625
                    },
                    {
                        "time": "2025-01-04T06:00:00",
                        "value": 10.157867431640625
                    },
                    {
                        "time": "2025-01-04T09:00:00",
                        "value": 10.914459228515625
                    },
                    {
                        "time": "2025-01-04T12:00:00",
                        "value": 11.318023681640625
                    },
                    {
                        "time": "2025-01-04T15:00:00",
                        "value": 10.220611572265625
                    },
                    {
                        "time": "2025-01-04T18:00:00",
                        "value": 8.527984619140625
                    },
                    {
                        "time": "2025-01-04T21:00:00",
                        "value": 7.444732666015625
                    },
                    {
                        "time": "2025-01-05T00:00:00",
                        "value": 6.330230712890625
                    },
                    {
                        "time": "2025-01-05T03:00:00",
                        "value": 5.982574462890625
                    },
                    {
                        "time": "2025-01-05T06:00:00",
                        "value": 6.104888916015625
                    },
                    {
                        "time": "2025-01-05T09:00:00",
                        "value": 6.563629150390625
                    },
                    {
                        "time": "2025-01-05T12:00:00",
                        "value": 7.687896728515625
                    },
                    {
                        "time": "2025-01-05T15:00:00",
                        "value": 7.812896728515625
                    },
                    {
                        "time": "2025-01-05T18:00:00",
                        "value": 7.523345947265625
                    },
                    {
                        "time": "2025-01-05T21:00:00",
                        "value": 7.621490478515625
                    }
                ]
            }
        }
    ]
}

// Load Google Charts
google.charts.load('current', { packages: ['corechart', 'line'] });
google.charts.setOnLoadCallback(() => drawTimeSeriesChart(jsonData.panels));

// This part is not need for this fiddle, but in original script this is used for loading JSON data
/* async function fetchAndDrawCharts() {
  try {
    const response = await fetch('../db_data/google_dashboard/ketipis/dashboard.json');
    const text = await response.text(); // Read as text first
    // console.log("Raw JSON Response:", text); // Debug log

    const data = JSON.parse(text); // Parse manually to catch errors
    console.log("Parsed JSON:", data);

    drawTimeSeriesChart(data.panels);
  } catch (error) {
    console.error("Error loading JSON:", error);
  }
}
*/

function createChartContainer(id) {
  // Select the container where you want to add the chart div
  const container = document.querySelector(".dashboard-container");
  if (!container) {
    console.error("Dashboard container not found.");
    return;
  }

  // Create a new div element
  const chartDiv = document.createElement("div");
  chartDiv.id = id;  // Set ID
  chartDiv.className = "chart";  // Set class

  // Append the new div inside the container
  container.appendChild(chartDiv);
}

function drawTimeSeriesChart(panels) {
  if (!panels || panels.length === 0) {
    console.error("No panels found in JSON data");
    return;
  }

  panels.forEach(panel => {
    let timeSeriesData = panel.data;
    let chartData = new google.visualization.DataTable(); // Unique DataTable per chart

    // Define columns (Time, Value per sensor, Tooltip)
    chartData.addColumn({ type: 'date', label: 'Time' });  
    const plots = Object.keys(timeSeriesData);
    plots.forEach(plot => chartData.addColumn({ type: 'number', label: plot }));
    chartData.addColumn({ type: 'string', role: 'tooltip' });

    const rows = [];
    let originalData = [];

    // Store the original data for reference
    plots.forEach(plot => {
      timeSeriesData[plot].forEach(entry => {
        const dateObj = new Date(entry.time);
        const date = entry.time.split("T")[0];
        const time = entry.time.split("T")[1].slice(0,5);
        const tooltipText = `${date} ${time} n${plot}: ${entry.value.toFixed(2)} ${panel.unit}`;

        const row = [dateObj];
        let rowValues = {};
        plots.forEach(p => {
          const value = (p === plot ? entry.value : null);
          row.push(value);
          rowValues[p] = value;
        });
        row.push(tooltipText); // Ensure tooltip is attached

        rows.push(row);
        originalData.push({ dateObj, rowValues, tooltipText });
      });
    });

    chartData.addRows(rows);

    // Track visibility of each series
    let seriesVisibility = plots.reduce((obj, plot) => {
      obj[plot] = true;
      return obj;
    }, {});

    // Chart options
    let options = {
      title: panel.title,
      curveType: 'function',
      backgroundColor: '#1e1e1e',
      titleTextStyle: { color: '#fff' },
      legendTextStyle: { color: '#fff' },
      hAxis: { textStyle: { color: '#fff' }, titleTextStyle: { color: '#fff' } },
      vAxis: { textStyle: { color: '#fff' }, titleTextStyle: { color: '#fff' } },
      legend: { position: 'top' },
      tooltip: { isHtml: true },
      explorer: { axis: 'horizontal', keepInBounds: true, maxZoomIn: 0.05 },
    };

    // Create separate chart container
    createChartContainer(panel.table);
    const chart = new google.visualization.LineChart(document.getElementById(panel.table));

    // Function to toggle series visibility
    function updateChart() {
      let newData = new google.visualization.DataTable(); // Create fresh DataTable
      newData.addColumn({ type: 'date', label: 'Time' });
      plots.forEach(plot => newData.addColumn({ type: 'number', label: plot }));
      newData.addColumn({ type: 'string', role: 'tooltip' });

      let newRows = originalData.map(entry => {
        let row = [entry.dateObj];
        plots.forEach(plot => {
          row.push(seriesVisibility[plot] ? entry.rowValues[plot] : null);
        });
        row.push(entry.tooltipText);
        return row;
      });

      newData.addRows(newRows);
      chart.draw(newData, options);
    }

    // Click event for legend toggling
    google.visualization.events.addListener(chart, 'select', () => {
      const selection = chart.getSelection();
      if (selection.length > 0 && selection[0].column > 0) {
        const seriesIndex = selection[0].column - 1;
        const plotKey = plots[seriesIndex];

        // Toggle visibility
        seriesVisibility[plotKey] = !seriesVisibility[plotKey];
        updateChart();
      }
    });

    updateChart(); // Initial chart render
  });
}
body {
  background-color: #1e1e1e;
  color: #fff;
  font-family: Arial, sans-serif;
  padding: 20px;
}
.dashboard-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.chart {
  width: 900px;
  height: 450px;
  margin: 20px 0;
  background-color: #2e2e2e;
  padding: 10px;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(255, 255, 255, 0.2);
}
<!DOCTYPE html>
<html>
  <head>
    <title>Dashboard</title>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  </head>
  <body>
    <h1>Sensor Dashboard</h1>
    <div class="dashboard-container"></div>
  </body>
</html>

How to wait until hcaptcha is executed

I’m trying to implement module to send hcaptcha via ajax, but load/show it only when some specific checks are completed, so after showing it I need to wait until user completes the captcha and right after it’s completed I need to submit the form without any other user interaction. Hcaptcha has an option to call a function on captcha completion

<div
    class="h-captcha"
    data-sitekey="your_site_key"
    data-callback="onSuccess" <-
></div>

but I need not to call another function. I need to continue executing this one, is there a way to track when it tries to call the function?

Angular Swiper Only Shows One Slide Initially Before Loading Others

import { Component } from '@angular/core';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { TmdbService } from '../../services/tmdb.service';
import { OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
import { ITrending } from '../../interfaces/trending';
import Swiper from 'swiper';
import { RouterLink } from '@angular/router';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { swiperInitialization } from '../../swiper/swiperConfig';
import { ShowCardScoreComponent } from '../show-card-score/show-card-score.component';

@Component({
  selector: 'app-trending',
  imports: [
    MatButtonToggleModule,
    ShowCardScoreComponent,
    CommonModule,
    FormsModule,
    RouterLink,
    NgxSkeletonLoaderModule,
  ],
  templateUrl: './trending.component.html',
  styleUrl: './trending.component.css',
})
export class TrendingComponent implements OnInit, OnDestroy {
  loading: boolean = false;
  timeSelectedValue: string = 'day';
  trendingShows: Array<ITrending> = [];
  swiper: Swiper | null = null;

  $unsubscribe: Subject<void> = new Subject<void>();

  constructor(private tmdbService: TmdbService) {}

  ngOnInit(): void {
    this.getTrendingData(this.timeSelectedValue);
  }

  ngOnDestroy(): void {
    this.$unsubscribe.next();
    this.$unsubscribe.complete();
    if (this.swiper) {
      this.swiper.destroy(true, true);
    }
  }

  // This method is triggered when the selection changes
  onSelectionChange(event: any) {
    this.timeSelectedValue = event.value;
    this.getTrendingData(this.timeSelectedValue);
  }

  getTrendingData(timeSelectedValue: string) {
    this.trendingShows = [];
    this.loading = true;
    if (this.swiper) {
      this.swiper.destroy(true, true); // Destroy the current Swiper instance
    }
    this.swiper = swiperInitialization('#swiperTrending', {
      300: { slidesPerView: 1 },
      500: { slidesPerView: 2 },
      900: { slidesPerView: 4 },
      1200: { slidesPerView: 5 },
      1400: { slidesPerView: 6 },
      1800: { slidesPerView: 8 },
    });
    
    this.tmdbService
      .getTrendingAll(timeSelectedValue)
      .pipe(takeUntil(this.$unsubscribe))
      .subscribe((data) => {
        console.log(data);
        // Process data here
        data.results.map((show: any) => {
          if (show.name !== undefined) show['title'] = show.name;
          this.trendingShows.push(show);
        });
        this.loading = false;
      });
  }
}
<div class="container-fluid mb-3">
  <div class="trendingContainer container">
    <div class="row align-items-center">
      <div class="col-md-4 mt-3">
        <h3>Trending</h3>
      </div>
      <div class="col-md-8 mt-3" *ngIf="timeSelectedValue">
        <mat-button-toggle-group
          name="fontStyle"
          aria-label="Font Style"
          (change)="onSelectionChange($event)"
          [value]="timeSelectedValue"
        >
          <mat-button-toggle value="day">Today</mat-button-toggle>
          <mat-button-toggle value="week">This Week</mat-button-toggle>
        </mat-button-toggle-group>
      </div>
    </div>
  </div>
  <!-- Skeleton loaders: show 8 when loading -->
  <div class="d-flex mt-3" *ngIf="loading">
    <div
      *ngFor="let _ of [].constructor(8); let i = index" class="d-flex"
    >
      <div class="skeleton-card-item">
        <!-- Card image skeleton -->
        <div class="position-relative">
          <ngx-skeleton-loader
            [theme]="{
              'border-radius': '12px',
              height: '292px',
              width: '192px',
              margin: '0px 10px'
            }"
          ></ngx-skeleton-loader>
          <!-- Score placeholder skeleton -->
        <ngx-skeleton-loader
        [theme]="{
          'border-radius': '12px',
          height: '50px',
          width: '50px',
          position: 'absolute',
          top: '100%',
          left: '100%',
          transform: 'translate(-50%, -50%)'
        }"
      ></ngx-skeleton-loader>
        </div>

        <!-- Title placeholder skeleton -->
        <ngx-skeleton-loader
          appearance="line"
          [theme]="{
            'border-radius': '12px',
            width: '198px',
            'margin-top': '30px'
          }"
        ></ngx-skeleton-loader>

        <!-- Watchlist button placeholder skeleton -->
        <ngx-skeleton-loader
          appearance="line"
          [theme]="{
            'border-radius': '24px',
            height: '40px',
            width: '150px',
            margin: '10px auto'
          }"
        ></ngx-skeleton-loader>
      </div>
    </div>
  </div>
  <div id="swiperTrending" class="swiper-container mt-5 mb-5">
    <div class="swiper-wrapper">
        @for(show of trendingShows;track show.id;let index=$index){
        <div class="swiper-slide">
          <app-show-card-score
            [show]="show"
            [index]="index"
            [routerLink]="
              show.media_type === 'movie'
                ? ['/movie', show.id]
                : show.media_type === 'tv'
                ? ['/tv', show.id]
                : ['/unknown', show.id]
            "
          ></app-show-card-score>
        </div>
        }
    </div>
  </div>
</div>

I’m using Angular with Swiper.js to display a trending list of shows fetched from the TMDB API. However, when the component loads, only one slide appears at first, and then after a brief moment, all slides load in. I want all slides to appear immediately after data is fetched.

I have tried this but none worked:

-Initializing Swiper after fetching the data.

-Destroying and reinitializing Swiper before loading new data.

-Making sure trendingShows updates before Swiper initializes.

-Delaying Swiper initialization slightly using setTimeout.

Firs shows the normal skeleton before data is fetched
first shows skeleton

When data is fetched flashes this at first
second flashes one time for a brief second

and then this
third shows the carroussel normally

Serving React application hoping through servers and using iframe

I have a scenario where I have two React applications. Application-1 opens Application-2 using an iframe.

I can access Application-2 either directly or through the Application-1 iframe. However, when I use the Application-1 iframe, it must go through multiple server hops to reach Application-2, something like the following.

Application-1 —-> Express-Server-1(serving Application-1 & forwarding request for Application-2) —-> Express-Server-2(proxy/passthrough) ——> Express-Server-3(serving Application-2)—–> Application-2

while I can access Application-2 directly I can’t access using Application-1 using an Iframe.

so below is the app.js file which serves the Application-2 and is running on server-3.

const createError = require("http-errors");
const express = require("express");
const cors = require('cors')
const path = require("path");
const fs = require("fs");
const cookieParser = require("cookie-parser");

// Server-static middleware
const {
    appLogger,
    expressLogger,
    expressErrorLogger,
} = require("./lib/logger");

const {corsOptionsDelegate} = require("./routes/corsDelegate");
const app = express();

app.disable("x-powered-by");
app.options('*', cors());
// serves static pages
app.use("/application-2/static", express.static(path.join(__dirname, "public")));

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(expressLogger);
app.use(expressErrorLogger);
app.use(cookieParser());

app.use(
    express.static(path.join(__dirname, ".", "webapp")),
    (req, res, next) => {
        try {
            decodeURIComponent(req.url);
            return next();
        } catch (err) {
            return res.status(400).json({message: "Invalid request"});
        }
    }
);

const managementRouter = require("./routes/management");
app.use("/application-2/management", managementRouter);

// serves the application-2 app
app.use("/application-2", express.static(path.join(__dirname, ".", "webapp")));

// connect to the "src/routes" directory
const routersPath = path.join(__dirname, "routes/v1");
// read all files in the "/src/routers" directory
fs.readdirSync(routersPath).forEach((file) => {
    if (file.endsWith(".js")) {
        // dynamically import the router module
        const routerModule = require((path.join(routersPath, file).replace(".js","")));
        // register the router
        app.use(routerModule);
    }
});

app.get("/application-2/unauthorized", cors(corsOptionsDelegate), function (req, res, next) {
    res.status(403);
    res.sendFile(path.resolve(__dirname, ".", "views", "unauthorized.html"));
});

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next(createError(404));
});

// error handling
app.use(function (err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get("env") === "development" ? err : {};

    // render the error page
    res.status(err.status || 500);
    res.render("error", {title: "ABC Images | application Search"});
});

module.exports = app;

below is code at server-1 to fetch application-2, i.e when application-1 opens iframe it makes a call to server-1 at /application-search.

app.use("/application-search", createProxyMiddleware({
  target: "http://localhost:9012",
  changeOrigin: true,
  logLevel: 'debug',
  pathRewrite: (path, req) => {
    // Extract the query string from the original URL
    const queryString = req.url.split('?')[1] || "11x";
    // Rewrite the path to include the query string
    return `/?${queryString}`;
  },
  onProxyReq: (proxyReq, req, res) => {
    let cookieArray = [];
    try {
      // Split the cookies into an array and log them
      const cookies = req.headers.cookie || "";
      cookieArray = cookies.split(';').map(row => {
        return row.trim();
      });
    } catch (error) {
      console.error('Error processing cookies:', error);
    }
    //const tokenCookie = cookieArray.find(row => row.startsWith('ckns_pp_id=')) || "";
    const tokenValue = tokenCookie.split('=')[1].trim();

    // Add custom headers if needed
    proxyReq.setHeader('Authorization', `Bearer ${tokenValue}`);
  }

}));

below is code at server-2 which when get request from server-1 will hit the server-3 that is serving application-2

app.use("/", createProxyMiddleware({
  target: "https://xxx.co.uk/application-2",
  changeOrigin: true,
  logLevel: 'debug',
  pathRewrite: (path, req) => {
    // Extract the query string from the original URL
    const queryString = req.url.split('?')[1] || "11x";
    // Rewrite the path to include the query string
    return `/?${queryString}`;
  },
  onProxyReq: (proxyReq, req, res) => {
    // Add the authorization header
    const bearerToken = req.headers['authorization'] || "";
    proxyReq.setHeader('Authorization', bearerToken);
  },
  onProxyRes: (proxyRes, req, res) => {
    console.log(`⬅️ Response from Server-3: ${proxyRes.statusCode} for ${req.url}`);
  }
}));

I am either getting 404 not find “Not Found, when from application-1 I try to open iframe to show application-2

404
NotFoundError: Not Found
    at /Users/abc01/projects/application-2/server/app.js:104:10

Currently I am using createProxyMiddleware strategy to pass on the request, please do advise if there is a better strategy for this requirements, as I have to hop though the servers as there are some authentication requirements and can’t access the application-2 directly.

Why is my websocket failing to connect? NextJS -> NGINX -> Scala(akka-http)

Background:

I have a Scala (with akka-http) webserver running behind NGINX. I also have a NextJS application running behind the same NGINX.

My goal here is for the NextJS application to establish a (secure) ws connection with the webserver.

I have followed official documentation and guidelines, and this is my current implementation:

The webserver side:

  val responseQueue: mutable.Queue[ApiResponse] = mutable.Queue()

  private def webSocketFlow: Flow[Message, Message, _] = {
    Flow[Message].mapAsync(1) { _ =>
      if (responseQueue.nonEmpty) {
        system.log.info("Flushing responseQueue")
        val response = responseQueue.dequeue()
        val protobufMessage = ByteString(response.toByteArray)

        Future.successful(BinaryMessage(protobufMessage))
      } else {
        system.log.warn("Response queue empty")
        Future.successful(BinaryMessage(ByteString.empty))
      }
    }
  }

  private def websocketRoute: Route = {
    pathPrefix("ws") {
      pathEndOrSingleSlash {
        extractRequest { req =>
          // extract auth token from header
          val tokenOpt = req.headers.collectFirst {
            case header if header.name() == "Sec-WebSocket-Protocol" =>
              OAuth2BearerToken(header.value()) // Extract the value of the Sec-WebSocket-Protocol header
          }

          system.log.info(s"Handling ws auth:${tokenOpt.toString}")

          // run it through token verification
          extractUri { uri =>
            val callingURI = uri.toRelative.path.dropChars(1).toString

            system.log.info(s"===== $callingURI")

            oAuthAuthenticator(Credentials(tokenOpt), handleWebSocketMessages(webSocketFlow), callingURI).get
          }
        }
      }
    }
  }

  private def oAuthAuthenticator(credentials: Credentials, protectedRoutes: Route, callingURI: String): Option[Route] =
    credentials match {
      case [email protected](_) =>
        system.log.info("Credentials provided")
        val user = loggedInUsers.find(user => p.verify(user.oAuthToken.access_token))
        if (user.isDefined) {
          system.log.info(s"User found:${user.head.toString}")
          val userPermissions = Permissions.valueOf(user.head.user.username.toUpperCase) // <- get permissions for this user

          if (userPermissions.getAllowedRoutes.contains(callingURI)) {
            system.log.info(s"User has permission for route: $callingURI")
            if (user.head.oneTime) loggedInUsers -= user.head // remove token if it's a one-time use token
            Option(protectedRoutes)
          } else {
            system.log.error(s"User does not have permission for route: $callingURI")
            Option(complete(ApiResponse().withStatusResponse(HydraStatusCodes.UNAUTHORISED_ROUTE.getStatusResponse)))
          }
        } else {
          system.log.error("We did not distribute this token or its expired")
          Option(complete(ApiResponse().withStatusResponse(HydraStatusCodes.INVALID_AUTH_TOKEN.getStatusResponse)))
        }
      case _ =>
        system.log.error(s"No credentials provided: ${credentials.toString}")
        Option(complete(ApiResponse().withStatusResponse(HydraStatusCodes.MISSING_CREDENTIALS.getStatusResponse)))
    }

The goal is for the server to notify the web page when a new ApiResponse has been placed in the queue.

I can confirm the Authorization section is working from the webserver logs:

INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - Handling ws auth:Some(Bearer 61704059-2e51-4d0f-b574-bdcebf3aeae3)
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - ===== ws
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - Credentials provided
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - User found:LoggedInUser(database.objects.User@bb81dcf5, username: admin, password: 517ffce87ad701f071040b32ddaa7f4b7b0bb6774b02ff45bf2eef3f2fc1a549,AuthToken(61704059-2e51-4d0f-b574-bdcebf3aeae3,bearer,3600),2025-02-21T16:59:06.343277,false)
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - User has permission for route: ws

On the NextJS side:

    const [pageToken, setPageToken] = useState("")
    const router = useRouter()

    useEffect(() => {
        if (pageToken) {
            const ws = new WebSocket("/ws", pageToken);

            ws.onopen = () => {
                console.log("Connected to WebSocket server");
            };

            ws.onmessage = (event) => {
                try {
                    if (event.data.byteLength === 0) {
                        console.log("No message to decode, queue was empty.");
                        return; // Ignore empty messages
                    }

                    // Deserialize the Protobuf message
                    const buffer = new Uint8Array(event.data);
                    const decodedMessage = ApiResponse.deserializeBinary(buffer)

                    console.log(decodedMessage)
                } catch (err) {
                    console.error("Failed to decode Protobuf message:", err);
                }
            };

            ws.onerror = (err) => {
                console.error("WebSocket error:", err);
            };

            return () => {
                if (ws) {
                    ws.close();
                }
            };
        }
    }, [pageToken]); // <-- listen to token changes

    const fetcher = (url) => fetchWithErrors(url, {}, (error) => {
        if (error.status === 401) {
            setToken(null) //<-- for anything that still might be using token
            setPageToken(null)
            router.push("/login");
        } else {
            errorToast("Unknown Internal Error:" + "[" + error.status + "]" + error.message);
        }
    })
        .then(data => {
            console.log("Fetched token data:", data)
            if (data.access_token) {
                if (pageToken !== data.access_token) {
                    setPageToken(data.access_token);
                    setToken(data.access_token);
                }
            }
            return data
        });

    // read the token every second to ensure it has not expired
    const {data, mutate} = useSWR(
        "/api/gettoken",
        fetcher,
        {
            refreshInterval: 100
        }
    );

    useEffect(() => {
        if (data) return;
        // mark as stale
        mutate()
    }, [data]);

This code:

  1. fetches the token from its own server route (extracts it from a cookie) and sets it globally
  2. When the token changes, establishes a connection with the websocket

The final part of the process is NGINX:

        location /ws {
                proxy_pass http://localhost:8080;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
        }

The Problem:

On my NextJS side, I get the error: Page.jsx:19 WebSocket connection to 'wss://192.168.0.11/ws' failed:

I have captured the output as a HAR which can be found here.

What am I missing/done wrong?

(Shopware 6) Integrating a custom npm package into administration

I want to try to integrate the functionality of the vuedraggable package https://www.npmjs.com/package/vue-draggable into the administration of Shopware 6 (Version 6.6.10.0).

I’ve followed this guide
https://developer.shopware.com/docs/guides/plugins/plugins/plugin-fundamentals/using-npm-dependencies.html , nevertheless I still seem to be getting some errors.

My current files:

Resource/app/administration/build/webpack.config.js:


const { join, resolve } = require('path');

module.exports = () => {
    return {
        resolve: {
            alias: {
                '@chartjs': resolve(
                    join(__dirname, '..', 'node_modules', 'vuedraggable')
                ),
            }
        }
    };
}

I’ve also tried the following example as well:

module.exports = (params) => {
    return { 
        resolve: { 
            modules: [
                `${params.basePath}/Resources/app/storefront/node_modules`,
            ],
       } 
   }; 
}

The administration component I’m trying to override (index.js)


import template from './sw-product-detail.html.twig';
import './sw-product-detail.scss';
import Draggable from "vuedraggable";


const { Criteria } = Shopware.Data
const { Mixin } = Shopware;

Shopware.Component.override('sw-product-detail-base', {
    template,
    components: {
        Draggable
    },

    data() {
        return {
            items: [
                { id: 1, name: "Item 1" },
                { id: 2, name: "Item 2" },
                { id: 3, name: "Item 3" }
            ]
        };
    }
});

The administration component I’m trying to override (twig template)


{% block sw_product_detail_base_media %}
    {% parent() %}
    {% block media_list %}
        <sw-card>
         <h2>Draggable List</h2>
          <Draggable v-model="items" item-key="id">
           <template #item="{ element }">
            <div class="draggable-item">
              {{ element.name }}
            </div>
           </template>
          </Draggable>
        </sw-card>
    {% endblock %}
{% endblock %}

Right now, I’m just trying to reproduce a very simple draggable list. The code above works fine in a standard vue project and produces the following result.
enter image description here

Code example from my custom vue project:


<script>
import Draggable from "vuedraggable";


export default {
  components: {
    Draggable
  },
  data() {
    return {
      items: [
        { id: 1, name: "Item 1" },
        { id: 2, name: "Item 2" },
        { id: 3, name: "Item 3" },
      ],
    };
  },
};
</script>

<template>
  <div>
    <h2>Draggable List</h2>
    <Draggable v-model="items" item-key="id">
      <template #item="{ element }">
        <div class="draggable-item">
          {{ element.name }}
        </div>
      </template>
    </Draggable>
  </div>
</template>

<style>
.draggable-item {
  padding: 10px;
  margin: 5px;
  background-color: lightgray;
  cursor: grab;
}
</style>


Shopware however, seems to struggle with loading the component itself, as I end up having this error message when trying to render the list:

enter image description here

I think my general approach is correct, as I’ve tried it with some other packages, that didnt involve custom vue components and it worked just fine.

Rendering components from other packages however, seems to produce this issue.

Any help would be greatly appreciated!

Thank you in advance.

Why is my saved icon label resets every time i return from a new canvas?

So I am using canvas in react and as i drag and drop my board icon, when i click it, it brings me to a new canvas. On the first canvas(canvas id: 1), I drag and drop my board or with a text that i created. Clicking to that board, i am navigated to a new canvas again (canvas id: 2) and i drag and drop my board icon with some text/label i created. Now, when i turn back to my previous canvas (canvas id: 1), the text/label disappears or prompts me to write the label/text again. Clicking on the board that takes me to canvas id: 2, the text/label disappears as well. It’s like everytime i leave this canvas and return, the saved label/text disappears.

import Navbar from "../components/Navbar"
import { PlusCircleIcon, ArrowUturnLeftIcon, WindowIcon, TrashIcon} from 
'@heroicons/react/24/solid';
import StickyNote2Icon from '@mui/icons-material/StickyNote2';
import {Link, useLocation} from "react-router-dom";
import InfiniteCanvas from "../components/InfiniteCanvas";
import { useState, useEffect, useMemo} from "react";

type IconData = {
  id: string;
  type: string; 
  x: number;
  y: number;
  text: string;
  showInput: boolean;
  isLabel?: boolean;
  showIcon: boolean; 
  dropped?: boolean;
};

type Entry = {
  id: string;
  description: string[]
}

export default function TruthJournal(){
  const [icons, setIcons] = useState<IconData[]>([
    { id: "note", type: "note", x: 50, y: 12, text: "", showInput: false, showIcon: 
true },
    { id: "delicious", type: "delicious", x: 110, y: 10, text: "", showInput: false, 
showIcon: true},
  ]);

  const [showCanvas, setShowCanvas] = useState(true);
  const [canvasId, setCanvasId] = useState(1);
  const [previousCanvasIds, setPreviousCanvasIds] = useState<number[]>([]);
  const location = useLocation();
  const entries: Entry[] = useMemo(() => {
    return location.state?.entries || [];
  }, [location.state?.entries]);  


  const loadIcons = (id: number) => {
    const savedIcons = localStorage.getItem(`icons-${id}`);
    if (savedIcons) {
      setIcons(JSON.parse(savedIcons));
    } else {
      setIcons([]);  
    }
  };


  useEffect(() => {
    const savedCanvasIds = localStorage.getItem("canvas-ids");
    if (savedCanvasIds) {
      const parsedIds = JSON.parse(savedCanvasIds);
      setPreviousCanvasIds(parsedIds);
      setCanvasId(parsedIds[parsedIds.length - 1] || 1);
    }
  }, []);

  useEffect(() => {
    if (canvasId) {
      loadIcons(canvasId);
    }
  }, [canvasId]);

  const handleCreateNewCanvas = () => {
    setCanvasId((prevId) => {
      const newCanvasId = prevId + 1;  

 
      setPreviousCanvasIds((prev) => {
        const updatedIds = [...prev, newCanvasId];
        localStorage.setItem("canvas-ids", JSON.stringify(updatedIds));
        return updatedIds;
      });

      setIcons([]);
      return newCanvasId;
    });

    setShowCanvas(true);

  };



const handleBackClick = () => {
  if (previousCanvasIds.length > 1) {
    const prevIndex = previousCanvasIds.indexOf(canvasId) - 1;
    if (prevIndex >= 0) {
      setCanvasId(previousCanvasIds[prevIndex]);
    }
  }
}

  const handleMouseDown = (e: React.MouseEvent) => {
    console.log("Mouse down", e);
};

const handleMouseMove = (e: React.MouseEvent) => {
    console.log("Mouse move", e);
};

const handleMouseUp = () => {
    console.log("Mouse up");
};

const handleWheel = (e: React.WheelEvent) => {
    console.log("Mouse wheel", e);
};



const handleDragOver = (e: React.DragEvent<HTMLCanvasElement>) => {
  e.preventDefault(); 
};

const handleDrop = (e: React.DragEvent<HTMLCanvasElement>) => {
  e.preventDefault();
  const imageType = e.dataTransfer.getData("image-type");
  const canvasBounds = e.currentTarget.getBoundingClientRect();
  const mouseX = e.clientX - canvasBounds.left;
  const mouseY = e.clientY - canvasBounds.top;

  if (imageType) {
    const newIcon = {
      id: `${imageType}-${Date.now()}`, 
      type: imageType,
      x: mouseX,
      y: mouseY,
      text: "", 
      showInput: imageType === "note",
      isLabel: imageType === "delicious" ,
      showIcon: imageType !== "note", 
      dropped: true
       };

       const updatedIcons = [...icons, newIcon];
    setIcons(updatedIcons);
    localStorage.setItem(`icons-${canvasId}`, JSON.stringify(updatedIcons));

  }


}

const handleDragStart = (e: React.DragEvent<HTMLDivElement>, imageType: string) => {
  e.dataTransfer.setData("image-type", imageType); 
  console.log(`Drag started with type: ${imageType}`);  
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, id: string) => {
  setIcons((prevIcons) =>
    prevIcons.map((icon) =>
      icon.id === id ? { ...icon, text: e.target.value } : icon
    )
  );
};




console.log(showCanvas);

useEffect(() => {
  const savedIcons = localStorage.getItem("icons");
  if (savedIcons) {
    setIcons(JSON.parse(savedIcons));
  }
}, []);

useEffect(() => {
  if (entries.length > 0) {
    setIcons((prevIcons) => {
      const updatedIcons = entries.map((entry, index) => {
        const generateRandomPosition = () => {
          const canvasWidth = window.innerWidth;
      const canvasHeight = window.innerHeight;
      return {
        x: Math.floor(Math.random() * (canvasWidth - 100)),
        y: Math.floor(Math.random() * (canvasHeight - 100)), 
      };
    };

    const isOverlapping = (x: number, y: number, icons: IconData[]) => {
      const margin = 20; 
      return icons.some(
        (icon) =>
          Math.abs(icon.x - x) < margin && Math.abs(icon.y - y) < margin
      );
    };

    let position = generateRandomPosition();
    while (isOverlapping(position.x, position.y, prevIcons)) {
      position = generateRandomPosition();
    }

    return {
      id: `entry-${entry.id || index}`,
      type: "note",
      x: position.x,
      y: position.y,
      text: entry.description.join(", "),
      showInput: true,
      showIcon: false,
      isLabel: false,
      dropped: true,
    };
  });

  return [...prevIcons, ...updatedIcons];
});
  }
}, [entries]); 




return (
    <>
    <div className="bg-customYellow  h-[100vh] ref={divRef} overflow-hidden">
      <Navbar
    Journal='Journal'
    Community = 'Community'
    About = 'About'
     />
     <div  className="bg-customYellow relative top-[3rem] w-full" >

    {showCanvas &&
    (
          <InfiniteCanvas
          width={window.innerWidth}
          height={window.innerHeight}
          onMouseDown={handleMouseDown}
          onMouseMove={handleMouseMove}
          onMouseUp={handleMouseUp}
          onWheel={handleWheel}
          onDragOver={handleDragOver}
          onDrop={handleDrop}
/>
         )}
    
   
    
    <div className="w-full  h-[3em]  flex items-center justify-between pt-5 relative bottom-[40em]">
     
     {icons.map((icon) => (
      <div key={icon.id} className="w-[15%] flex items-center justify-center gap-2 object-contain ">
               
               {icon.showInput ? (
                <input
                  type="text"
                  value={icon.text}
                  onChange={(e) => handleInputChange(e, icon.id)}
                  autoFocus
                  style={{ left: `${icon.x}px`, top: `${icon.y}px` }}
                  className="absolute font-annie pl-3 placeholder-white  text-[12px] text-white border-none bg-customBrown w-[18rem] h-[3rem]"
                  placeholder="Enter text"
                />
              ) : icon.isLabel  && (
                <div
                  className="absolute bottom-0 left-0 text-sm"
                  style={{ top: `${icon.y + 30}px`, left: `${icon.x}px` }}
                >
                 <input
                  type="text"
                  value={icon.text}
                  onChange={(e) => handleInputChange(e, icon.id)}
                  autoFocus
                  className="font-annie placeholder-customBrown text-[12px] text-center border-none text-customBrown  bg-transparent w-[5rem] relative top-3 right-[1.50rem] focus:border-none focus:outline-none"
                  placeholder="Enter text"
                />
                 
                </div>
              )} 
              
               {icon.showIcon && (
                <div
                onClick={handleCreateNewCanvas}
                draggable="true"
                onDragStart={(e) => handleDragStart(e, icon.type)}
                className="w-[2.5rem] h-[2.5rem] object-contain border-box absolute"
                style={{
                  left: `${icon.x}px`,
                  top: `${icon.y}px`,
                  width: "2rem",
                  height: "3rem",
                }}
              >
                {icon.type === "note" ? (
                 <StickyNote2Icon sx={{ width: "2rem", height: "3rem" }} className="w-[2rem] h-[3rem]"/>
                ) : icon.type === "delicious" ? (
                  <WindowIcon className="w-[2rem] h-[3rem]" />
                ) : null}
              </div>
   
      )}
      
                 
                         </div> 

              ))}


              
              <TrashIcon  className="absolute w-[2rem] h-[3rem] right-[83.5%]"/>
              
      
                
     <div className="mr-3 flex gap-3">
     <ArrowUturnLeftIcon onClick={handleBackClick} className="w-8 h-8 fill-customBrown"/>
        <Link to="/journalentrythree">
        <PlusCircleIcon className="w-9 h-9 fill-customBrown"/>
        </Link>
       
        </div>
        </div>
        
   
       
            </div>
           
          
          </div>
          </div>
         
         
      
     
  

    </>
   
)
}

I tried using local storage to save the data but still it resets every time i return to a specific canvas page.

How do I get rid of this warning?: Microsoft Edge is moving towards a new experience that allows users to choose to browse without third-party cookies

I’m making a personal card database for the One Piece Trading Card Game. It’s written in HTML, JavaScrip, and CSS, and when I run the HTML with the “Preview on Web Server” VSCode extension, it gives me exactly 72,819 copies of that error and spins my laptop’s fans while it does so. I’m running it in Microsoft Edge because my school has “Inspect” blocked on Chrome. Aside from that, it is running perfectly. Any advice is appreciated! (Code below)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" type="text/css" href="Search/OP_search.css">
        <title>THE ONE PIIIIEEEECE</title>
        <script src="./Search/OP_search_logic.js" defer></script>
    </head>
    
    <body>
        <form>
            <select name="color" id="colors" oninput="searchCards()">
                <option value="All">All Colors</option>
                <option value="Red">Red</option>
                <option value="Green">Green</option>
                <option value="Blue">Blue</option>
                <option value="Purple">Purple</option>
                <option value="Black">Black</option>
                <option value="Yellow">Yellow</option>
            </select>
            <select name="type" id="types" oninput="searchCards()">
                <option value="All">All Types</option>
                <!--JS will add the rest from JSON-->
            </select>
            <select name="card_type" id="card_types" oninput="searchCards()">
                <option value="All">All Card Types</option>
                <option value="Character">Character</option>
                <option value="Leader">Leader</option>
                <option value="Event">Event</option>
            </select>
            <select name="cost" id="costs" oninput="searchCards()">
                <option value="All">All Costs</option>
                <option value="0">0</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <option value="4">4</option>
                <option value="5">5</option>
                <option value="6">6</option>
                <option value="7">7</option>
                <option value="8">8</option>
                <option value="9">9</option>
                <option value="10">10</option>
            </select>
            <select name="counter" id="counters" oninput="searchCards()">
                <option value="All">All Cards</option>
                <option value="0">0</option>
                <option value=">0">>0</option>
                <option value="1000">1000</option>
                <option value="2000">2000</option>
            </select>
            <select name="set" id="sets" oninput="searchCards()">
                <option value="All">All</option>
                <!--JS will add sets from JSON-->
            </select>
        </form>
        <div id="card_body" class="card_list">
            <!--JS will add displays for individual cards-->
        </div>

    </body>

</html>
function getSearchTerms() {
    //Taking info from selected options in the HTML
    let color     = document.getElementById("colors").value;
    let type      = document.getElementById("types").value;
    let card_type = document.getElementById("card_types").value;
    let cost      = document.getElementById("costs").value;
    let counter   = document.getElementById("counters").value;
    let set       = document.getElementById("sets").value;

    //returning info as a list
    return [card_type, cost, color, type, counter];
};

async function getStorage(file) {
    //Obtaining a JSON
    const response = await fetch(file);
    const json = await response.json();
    return json;
};

async function getDropdowns() {
    let optcgJSON = await getStorage('./Storage/OPTCG.json');

    getTypes(optcgJSON);
    getSets(optcgJSON);
}

function getTypes(optcgJSON) {
    let set = new Set();
    let split;

    //Pulling all unique 'Types' from the JSON
    for (let i = 0; i < optcgJSON.length; i++) {
        split = optcgJSON[i].types.split(';');
        for (let j = 0; j < split.length; j++) {
            set.add(split[j]);
        }
    }

    //Adding new elements to the 'Types' search term (from the JSON)
    let typesSelect = document.querySelector('#types');
    for (i of [...set.values()].sort()) {
        let tempEl = document.createElement('option');
        tempEl.value = i;
        tempEl.innerHTML = i;
        typesSelect.appendChild(tempEl);
    }
};

function getSets(optcgJSON) {
    let set = new Set();

    //Pulling all unique 'Sets' from the JSON
    for (let i = 0; i < optcgJSON.length; i++) {
        set.add(optcgJSON[i].id.split('-')[0]);
    }

    let setSelect = document.querySelector('#sets');
    for (i of [...set.values()].sort()) {
        let tempEl = document.createElement('option');
        tempEl.value = i;
        tempEl.innerHTML = i;
        setSelect.appendChild(tempEl);
    }
};

function displayText(txtid) {

    console.log(txtid);
    let text = document.getElementById(txtid);

    text = text.firstChild;

    if (text.style.display == "none") {
        text.style.display = "block";
    } else {
        text.style.display = "none";
    }

};

function clearElementById(id) {
    document.getElementById(id).innerHTML = "";
};

async function displayCards(arr) {
    //Get the cardBody div
    let cardBody = document.querySelector('#card_body');

    for (i of arr) {

        //temporary div (for the card, to be referenced with CSS)
        let carddiv = document.createElement('div');
        carddiv.class = "card";
        cardBody.appendChild(carddiv);

        //img div
        let imgdiv = document.createElement('div');
        let i_img = document.createElement('img');
        i_img.style.float = "left";
        imgdiv.class = "card_individual";
        i_img.setAttribute("src", i.imageUrl);
        imgdiv.appendChild(i_img);

        //text div
        let txtdiv1 = document.createElement('div');
        txtdiv1.id = `${i.cleanName}`;
        txtdiv1.style = "background-color:#f5f3f1; border-radius: 5vw 5vw 5vw 5vw;";

        let txtdiv2 = document.createElement('div');
        txtdiv2.id = "txtdiv";

        let i_text = document.createElement('p');
        i_text.id = "itxt";
        i_text.style = 'padding-left: 5vw; padding-right: 5vw; font-family: "Courier-New", monospace; font-size: 10pt;';
        i_text.style.display = "none";


        i_img.setAttribute("onclick", `displayText('${txtdiv1.id}')`);

        //Add text
        i_text.innerHTML  = `Name: ${i.name}`;
        i_text.innerHTML += `<br>Id: ${i.id}`;
        i_text.innerHTML += `<br>Card Type: ${i.card_type}`;
        i_text.innerHTML += `<br>Color(s): ${i.colors}`;
        i_text.innerHTML += `<br>Type(s): ${i.types}`;
        if (i.attribute != null)
        i_text.innerHTML += `<br>Attribute: ${i.attribute}`;
        if (i.power != null)
        i_text.innerHTML += `<br>Power: ${i.power}`;
        if (i.counter != null)
        i_text.innerHTML += `<br>Counter: ${i.counter}`;
        if (i.cardText != null)
        i_text.innerHTML += `<br>Effect: ${i.cardText}`;

        txtdiv1.appendChild(i_text);
        txtdiv2.appendChild(txtdiv1);

        carddiv.appendChild(imgdiv);
        carddiv.appendChild(txtdiv1);

    }
};

async function searchCards() {

    clearElementById('card_body');

    let optcgJSON = await getStorage('./Storage/OPTCG.json');

    //0 = card_type, 1 = cost, 2 = color, 3 = type, 4 = counter
    let terms = getSearchTerms();

    //Array of cards that are included in the search
    let includedCards = [];

    //Search logic

    //"optcgJSON" is a global variable (all cards stored as an array of objects)
    for (let i in optcgJSON) {

        //card_type (0)
        if (optcgJSON[i].card_type != terms[0]
         && terms[0] != "All") {
            continue;
        }

        //cost (1)
        if (optcgJSON[i].cost != terms[1]
         && terms[1] != "All") {
            continue;
        }

        //color (2)
        ColorCheck: {
            if (terms[2] == "All") {
                break ColorCheck;
            }
            if (optcgJSON[i].colors.split(';').length == 1) {
                if (optcgJSON[i].colors != terms[2]) {
                    continue;
                }
            } else if (optcgJSON[i].colors.split('/')[0] != terms[2]
                    && optcgJSON[i].colors.split('/')[1] != terms[2] 
                    && terms[2] != "All") {
                continue;
            }
        }

        //type (3)
        let booleanGuy = false;
        TypeCheck: {

            if (terms[3] == "All") {
                break TypeCheck;
            }

            for (j of optcgJSON[i].types.split(';')) {
                if (j == terms[3]) {
                    booleanGuy = true;
                    break;
                }
            }

            if (booleanGuy == false) {
                continue;
            }
        }

        //counter (4)
        booleanGuy = false
        CounterCheck: {
            if (terms[4] == "All") {
                break CounterCheck;
            }
            if (terms[4] == ">0") {
                if (optcgJSON[i].counter > 0) {
                    break CounterCheck;
                } else {
                    continue;
                }
            }

            if (optcgJSON[i].counter != terms[4]) {
                continue;
            }
        }

        includedCards.push(optcgJSON[i]);
    }

    //duh
    displayCards(includedCards);
};

window.onload = () => {
    getDropdowns();
    searchCards();
};
body {
    background-color: #d9d9d9;
    width: 100vw;
}

#txtdiv {
    background-color:white;
    border-radius: 0px 0px 5vw 5vw;
    outline-color: black;
}

#txtdiv p {
    padding-left: 5vw;
    padding-right: 5vw;
    font-family: "Courier-New", monospace;
    font-size: 10pt;
}

.card_list {
    width: 80vw;
    padding-left: 10vw;
    padding-right: 10vw;
    padding-top: 10vw;
    margin: auto;
}

.card_individual {
    width: 100%;
        padding-top: 5vw
}

.card_list img {
    width: 100%;
    margin: auto;
}

I’ll only give one example of what one of the JSON elements looks like, as there are 2000+ items in the JSON. the JSON file contains an array of all of the card objects.

{
  "name": "Trafalgar Law (002)",
  "cleanName": "Trafalgar Law 002",
  "imageUrl": "https://tcgplayer-cdn.tcgplayer.com/product/453505_200w.jpg",
  "cardType": "Leader",
  "rarity": "L",
  "types": "Heart Pirates;Supernovas",
  "id": "OP01-002",
  "color": "Green;Red",
  "life": "4",
  "cardText": "[Activate:Main] [Once Per Turn] (2) <em>(You may rest the specified number of DON!! cards in your cost area.)</em>: If you have 5 Characters, return 1 of your Characters to your hand. Then, play up to 1 Character with a cost of 5 or less from your hand that is a different color than the returned Character.<br><br>rn<a href="https://en.onepiece-cardgame.com/rules/errata_card/#group_20221111-1">This card has been officially errata'd.</a>",
  "cost": null,
  "power": "5000",
  "counter": null,
  "attribute": "Slash",
  "count": ""
}

I’ve sifted through my code, looking for anything that might produce a cookie, but I honestly have no idea what could be doing this. This warning has shown up a few times on occasion, but never to this degree or scale.