Run a script when API endpoint is hit, and check if that script is running when hit again – Node.js

I’m working on a node.js API that needs to start/run a time-intensive javaScript file when a certain endpoint is hit. I’ve got this working, but what I’m now looking for is a way to stop the script from running again if it’s already running and the endpoint gets hit again.

I tried to use the child_process library, hoping that if the script is being executed as a separate process from the main node.js app, it would be easier to check if the script is already running. I thought of using the process exit event for the child process that I created, but this proved to be unhelpful because it would only run once the process was done, but didn’t help in checking if it was currently running.

I also had considered cron jobs, but there doesn’t seem to be a good way to just start a cron job on command without making a time-based schedule.

In another project, I used a database and simply created a new row in a table to indicate whether the script was available to run, currently running, or finished. I could definitely do it this way again, but I’m trying to keep this project as light as possible.

Lastly, I did consider just adding some global state variable that would indicate if the process is running or not, but wasn’t sure if that was the best method.

So, if anyone either knows a way to check if a child process script is currently running or has a better way of achieving what I’m trying to achieve here, any direction would be helpful.

Filter json based on key and match it against one of the values from array

I have json file: products.json:

[
{
"id": "1",
"category": "Fruit from USA",
"name": "Banana",
},
{
"id": "2",
"category": "Fruit from Brazil",
"name": "Long Banana",
},
{
"id": "3",
"category": "Vegetable",
"name": "Carrot",
},
{
"id": "4",
"category": "Car from USA",
"name": "Ford",
},
{
"id": "5",
"category": "Car from Germany",
"name": "Audi",
},
{
"id": "6",
"category": "Train from Italy",
"name": "Pendolino",
},
]

Then I have an array

testMatch.match: ['Car', 'Fruit'].

I want to filter out products.json to return only objects that have category starts with any of elements of matchCategory in ES6

What i have so far is:

const productList = products.filter(filteredCategory => filteredCategory.category.startsWith(testMatch.match));

But it does not work if there is more than 1 element in testMatch.match and if there is none – it returns all products and not none.

cast numbers to strings in a nested object (nodejs)

assuming a complex nested object such as:

{ a: [{b: 12}, {c: {r: [1, 2, {g: 55} ] } }, 5], f: {h: 76}}

I’d like to cast all numbers to strings in nodejs. external libraries (e.g. lodash) are encouraged.

{ a: [{b: '12'}, {c: {r: ['1', '2', {g: '55'} ] } }, '5'], f: {h: '76'}}

Thank you!

Why does only the first CSS class of my project working and not the rest

I am using React with CSS and for some reason, my CSS doesn’t show up. Let me demonstrate.

I have my “App.js” with this code:

import './App.css';

function App() {
return(
    <div>
      <p className="wow">hhh</p>
      <p className="pow">hhh</p>
    </div>
  );
}
export default App;

And I have my App.css with this code:

.wow {
  color: blue;
};

.pow {
  color: red;
};

When I look in my browser only the first class is working and the letters are blue but not the second one or any other after. If I create a new component and import a CSS document to it as I did in the previous one the same thing will happen, the first CSS class will work and that’s all after that non of the classes work.

Cannot register user using mongoose and postman

iam beginner at backend programmming
iam using MERN fullstack to creat a website
and a have a problem in registering a new user

every time i add a user i get server error and i dont know why

this is my api code and i commented what iam doing

router.post(
  "/",
  [
    check("name", "Name is required").not().isEmpty(),
    check("email", "please include a valid email").isEmail(),
    check("address","please enter valid address").not().isEmpty(),
    check("phone","please enter your phone number (01*********) 11 digit phone number").isLength({min:12 , max:12}),
    check("password", "Please enter a password with atleast 6 chars").isLength({min: 6}),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { name, email, password } = req.body;

    try{
      // see if user exist
      let user = await User.findOne({email});

      if (user){
        res.status(400).json({errors:[{msg:'user already exists'}]});
      }
      
      // get users gravatar
      
      const avatar = gravatar.url(email ,{
          s: '200',
          r: 'pg',
          d: 'mm'
        }
        );

        user = new User({
          name,
          email,
          password,
          phone,
          address
        });

      // encrypt password

        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(password , salt);
        await user.save();
        res.send('User registered');
      // return jsownwebtokern

    }
    catch(err){
      console.log(err.massage);
      res.status(500).send('server error');
    }
  }

and this is the user schema and the attributes i use

const mongoos = require('mongoose');

const userSchema= new mongoos.Schema({
    Name : {
        type: String,
        required: true
    },
    phone:{
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        unique: true
    },
    password:{
        type: String,
        required: true
    },
    avatar:{
        type: String,
    },
    date:{
        type: Date,
        default: Date.now
    },
    address:{
        type:String,
        required:true,
    }

});

module.exports= user = mongoos.model('user', userSchema);
const mongoos = require('mongoose');

and this is how i add user using json and postman

{
    "name": "ijasdiojn",
    "address": "11 ybihoni-jbojnnin",
    "phone": "010121628590",
    "email": "[email protected]",
    "password": "1234567"
}

after postman run the json code i see undefined in my vs code and server error in postman
so please can any one help me in this problem

how to store a function so it works only after clicked and results stays after refreshing the page?

    function hd() {
        document.getElementById("Y").style.display = "none";
    }
body {
display:grid;
place-content: center;
height:100vh;
overflow:hidden;
}
<div id="Y">i will be hidden</div>
<br>
<button onclick="hd();">click me</button>

The important thing is to store the function so it works even user refreshes the page I tried to use cookies but it did not work by just calling the function

“message”: “errorMongooseError: Operation `userinfos.insertOne()` buffering timed out after 10000ms”

I’m currently creating a new API with MongoDB and Express, and I’m currently having this issue
“message”: “errorMongooseError: Operation userinfos.insertOne() buffering timed out after 10000ms”

his is the way that I setup my API calls:

    const express=require('express');

const router=express.Router();
const Userinfo=require("../Models/Userinfo")

router.get('/',(req,res)=>{
    res.send("we are Post")

});



router.get('/Specic', async(req,res)=>{
    try{
        const data=await Userinfo.find();
        console.log(data);
        res.json(data)
    }
    catch(err)
    {
        res.json({message:err})
    }

})
router.post("/",(req,res)=>{
const test= new Userinfo({
    "Fname":req.body.Fname,
    "Lname":req.body.Lname
});
console.log(test);
test.save().then(data=>{
    res.json(data);
}).catch((err)=>{res.json({message:"error"+err})})
})
module.exports=router;

MoDel Defining like this Userinfo.js

const mongoose=require('mongoose');
/*const PoistSchema=mongoose.Schema({
    Fname:String,
    Lname:String,
    DOB:Date.now()
});*/

const PostSchema=mongoose.Schema({
    Fname:{
        type:String,
        require:true
    },
    Lname:{
        type:String,
        require:true
    },
    DOB:{
        type:String,
        default:Date.now
    },
});

module.exports=mongoose.model("Userinfo",PostSchema)

App.js

const express=require('express');
const app=express();
const mongoose=require('mongoose');
require("dotenv/config");
const bodyParser=require("body-parser")
///Middlewares
//app.use(auth);
app.use(bodyParser.json());
const postroutes=require("./Routes/Posts");
app.use("/post",postroutes)

app.get('/',(req,res)=>{
    res.send("we are om")

})

app.get('/posts',(req,res)=>{
    res.send("we are Post")

})
try {
     mongoose.connect(process.env.DBConnection,{useNewUrlParser: true,
        useUnifiedTopology: true,
        useCreateIndex:true},()=>{
         console.log("Sucess");
     },(error)=>{
         console.log(error);

     });
     console.log("ConnectedZZ")

  } catch (error) {
      console.log(error);
  }
app.listen(3000);

Is there any suggestion to inset and get data.While getting data not getting any error.

React state initialization in constructor triggering ESLint error

I am creating a “wrapper” class component for the <input> element, whose aim is to prevent the insertion of some characters.

The problem is that, when I initialize its state in the constructor, I get the ESLint error react/no-direct-mutation-state. I know I could simply ignore it by disabling the ESLint rule for that line, but I would like to understand whether the problem I am experiencing is a “bug” in the plugin, as it seems to me (documentation here: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md), or whether I am doing something wrong.

Here it is the component code:

import React from 'react';

const FORBIDDEN_CHARS = '[@&*%?(){}[]|^!:;\/~+"`=$]';

class SecuredInput extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
    this.state = { // <-- this is the line generating the error
      value: ''
    };
    this.handleInput = this.handleInput.bind(this);
  }
  
  componentDidMount() {
    if (this.props.value) {
      this.setState({
        value: this.props.value
      });
    }
  }
  
  handleInput(str) {
    const re = this.props.type === 'number'
      ? new RegExp('[^0-9]', 'g')
      : new RegExp(this.props.forbiddenChars, 'gi');
    
    this.setState({
      value: str.replace(re, '');
    });
  }
  
  render() {
    return (
      <input
        {...this.props}
        ref={this.inputRef}
        value={this.state.value}
        onInput={(event) => this.handleInput(event.target.value)}
      />
    );
  }
}

SecuredInput.defaultProps = {
  forbiddenChars: FORBIDDEN_CHARS
};

export default SecuredInput;

Jquery Returning undefined when using “click” listener

THIS IS MY HTML CODE


<div action="https://github.com/">
     <div class="menu-bar-item">
         <h1 class="menu-item-text">GitHub</h1>
     </div>
</div>

AND JAVASCRIPT


$(document).on("click", "*[action]", (e) => {

    try {
        var attribute = $(e.target).attr("action");
        alert(attribute)

    } catch(e){}
})

Here attribute action is sometimes retuning “undefined”.. ANY FIX??

how to optimize code like reduce code with same functionality

    let response = {}
    var filters = {
        topfeaturedandotherfields: req.body.topfeaturedandotherfields
    }

    if(req.body.minprice && req.body.maxprice && req.body.brandName)
    {

        var filters = {
            
            $and :[
                {"brandName" : { "$in": req.body.brandName }}, 
                {topfeaturedandotherfields: req.body.topfeaturedandotherfields },
                {salePrice:{ $gte: req.body.minprice, $lte: req.body.maxprice }}]
            
             }
 

         var result = await productService.getAllProductofhomepage(filters,req.body.ordername,req.body.orderby)


    }
    else{
    if (req.body.minprice && req.body.maxprice) {
        
        var filters = {
           $and: [{topfeaturedandotherfields: req.body.topfeaturedandotherfields},{salePrice:{ $gte: req.body.minprice, $lte: req.body.maxprice }}]
        }

       
        
        
        var result = await productService.getAllProductofhomepage(filters,req.body.ordername,req.body.orderby)
    }
    if (req.body.brandName) {
        
        var filters = {
            
           $and :[
               {"brandName" : { "$in": req.body.brandName }}, 
               {topfeaturedandotherfields: req.body.topfeaturedandotherfields }]
           
            }


        var result = await productService.getAllProductofhomepage(
            filters,req.body.ordername,req.body.orderby
        )
    }
}

    if (req.body.limit == true)
        var result = await productService.getAllProductofhomepagewithlimit(
            filters
        )
   else if (req.body.minprice || req.body.maxprice || req.body.brandName)
    {}
    else
    {
        var result = await productService.getAllProductofhomepage(filters,req.body.ordername,req.body.orderby)
    }

    if (result.length > 0) {
        response = {
            message: 'Home page products successfully retrieved',
            error: false,
            data: result,
        }
    } else {
        response = {
            message: 'Faild to get products',
            error: true,
            data: {},
        }
    }

    res.status(200).json(response)

This code is used to filter like to see top feature and bestseller or min and max price and the brand name also in this code sort by order name which could be price or brand name or category also in ascending and descending order so now you can see this code is like if and else but I want to optimize and reduce code

Is it faster to bake javascript into html?

I heard that requesting many single Javascript files in a HTML page is slow, so I wonder if it is faster to bake all those files directly into the HTML code using a build system or is it better to merge all JS files into one file and than requesting that file in the HTML document?

create a circle object and push them in array

I need to create circles in canvas using fabric. Every click, there is a circle created. However, if the new circle created it will replace old circle

HTML

<canvas #canvas id="canvas" width="900" height="400" >
  <p>Your browser doesn't support canvas!</p>
</canvas>

TS

    this.canvas = new fabric.Canvas('canvas');
    var circle = new fabric.Circle({
    radius: 20,
    fill: '#eef',
    originX: 'center',
    originY: 'center'
});
    var text = new fabric.Text(`${data.data.name}`, {
    fontSize: 30,
    originX: 'center',
    originY: 'center'
});
    this.group = new fabric.Group([circle, text], {
    left: event.e.offsetX,
    top: event.e.offsetY,
    angle: 0
});
console.log(this.group);
this.canvas.add(this.group);

this.canvas.setActiveObject(this.canvas.item[0]);
this.canvas.renderAll();

Youtube Old mini player extension issues

first time using this site. I have been using this mini player youtube extension for a while. Its not on the chrome store anymore but I have a folder of it from the developer. The developer isn’t working on this anymore so I am asking here. I like this extension but I have been having this issue whenever I open a youtube link externally, it throws off the youtube player when scrolling. It’s not the biggest issue in the world but I am just seeing if there is something that can be done to fix it. I am not a big coder so I am asking here. I am not sure if this can be fixed or its something conflicting with new youtube since this extension is pretty old. I pasted some links with the full code and some pictures.

Thank you

https://imgur.com/a/tAi84xP

https://pastebin.com/7nBrFhrk

“use strict”;

(function() {

let DOCK_DOCKED = 'docked', DOCK_FLOATING = 'floating';
let PAGE_CHANNEL = 'channel', PAGE_HOME = 'home', PAGE_SEARCH = 'search', PAGE_SUBSCRIPTIONS = 'subscriptions', PAGE_WATCH = 'watch';
let SCREEN_PADDING = 16;

let currentAnimation, currentAnimationCancelled, currentDockMode, currentVideoId, currentPage;
let queryParams, isNewPage;
let screen, screenHeader, screenTitle, screenButtonClose, screenButtonMove, screenButtonResizeTopLeft, screenButtonResizeTopRight, screenButtonResizeBottomLeft, screenButtonResizeBottomRight, isScreenMove, isScreenResize;
let iframe, iframeVideo, moviePlayer, moviePlayerObserver = new MutationObserver(onMoviePlayerObserve), playerAPI, video, videoTitle;
let intervalCurrentTime, intervalIframeInitialize, intervalVideoInitialize;
let localStorage = window.localStorage['rjgtav.youtube-mini-player'] ? JSON.parse(window.localStorage['rjgtav.youtube-mini-player']) : {};

// Thanks to https://www.reddit.com/user/roombascj
// Workaround: Chrome doesn't fire 'resize' event when it adds the scrollbar
// Source: https://pastebin.com/tgDxgPza
let windowResizeListener = document.createElement('iframe');
windowResizeListener.id = 'rj-resize-listener';
windowResizeListener.onload = () => windowResizeListener.contentWindow.addEventListener('resize', onWindowResize);

window.addEventListener('scroll', onWindowScroll);
document.addEventListener('DOMContentLoaded', onPageLoad);
document.addEventListener('spfdone', onPageLoad);

// #YoutubeMaterialDesign
document.addEventListener('yt-navigate-finish', onPageLoad);
document.addEventListener('yt-update-title', onPageTitle);

function initializeIframe () {
    if (iframe != null) return;

    screen = document.createElement('div');
    screen.id = 'rj-miniplayer-screen';
    screen.style.position = 'fixed';
    document.body.appendChild(screen);
    
    screenHeader = document.createElement('div');
    screenHeader.className = 'rj-miniplayer-header';
    screen.appendChild(screenHeader);

    screenButtonMove = document.createElement('div');
    screenButtonMove.className = 'rj-miniplayer-move';
    screenButtonMove.title = 'Move';
    screenButtonMove.style.backgroundImage = "url('" + chrome.extension.getURL('assets/button/move.png') + "')";
    screenButtonMove.addEventListener('mousedown', onScreenMouseDown);
    screenHeader.appendChild(screenButtonMove);

    screenTitle = document.createElement('div');
    screenTitle.className = 'rj-miniplayer-title';
    screenTitle.addEventListener('click', onScreenTitleClick);
    screenHeader.appendChild(screenTitle);

    screenButtonClose = document.createElement('div');
    screenButtonClose.className = 'rj-miniplayer-close';
    screenButtonClose.title = 'Close';
    screenButtonClose.style.backgroundImage = "url('" + chrome.extension.getURL('assets/button/close.png') + "')";
    screenButtonClose.addEventListener('click', onScreenButtonCloseClick);
    screenHeader.appendChild(screenButtonClose);

    screenButtonResizeBottomRight = document.createElement('div');
    screenButtonResizeBottomRight.className = 'rj-miniplayer-resize bottom-right';
    screenButtonResizeBottomRight.title = 'Resize';
    screenButtonResizeBottomRight.addEventListener('mousedown', onScreenMouseDown);
    screen.appendChild(screenButtonResizeBottomRight);

    screenButtonResizeTopRight = document.createElement('div');
    screenButtonResizeTopRight.className = 'rj-miniplayer-resize top-right';
    screenButtonResizeTopRight.title = 'Resize';
    screenButtonResizeTopRight.addEventListener('mousedown', onScreenMouseDown);
    screenHeader.appendChild(screenButtonResizeTopRight);

    iframe = document.createElement('iframe');
    iframe.setAttribute('allowfullscreen', '1');
    iframe.setAttribute('frameborder', '0');
    iframe.onload = onIframeLoad;
    screen.appendChild(iframe);
}
function updateIframeSource () {
    if (iframe == null) return;

    // If it's the same video, we keep playing it
    if (currentVideoId == queryParams.v) return;
    currentVideoId = queryParams.v;

    iframeVideo = null;
    iframe.contentWindow.location.replace('https://www.youtube.com/embed/' + currentVideoId + '?autoplay=1&modestbranding=1&rel=0&showinfo=0');

    // We periodically update the video.currentTime, so the Share button on Youtube works as expected
    // Update: this has a high performance impact :/
    //clearInterval(intervalCurrentTime);
    //intervalCurrentTime = setInterval(() => iframeVideo ? video.currentTime = iframeVideo.currentTime : null, 1000);
}

function initializeVideo() {
    if (video != null) {
        video.removeEventListener('playing', onVideoPlay);
        video.removeEventListener('play', onVideoPlay);
        video.removeEventListener('seeking', onVideoSeek);
        video.removeEventListener('seeked', onVideoSeek);
    }

    // Prevent Youtube from auto-playing
    // Workaround: we use intervals so this still runs when the tab isn't focused
    clearInterval(intervalVideoInitialize);
    intervalVideoInitialize = setInterval(() => {
        // Hide moviePlayer to increase performance
        moviePlayer = document.getElementById('movie_player');
        moviePlayerObserver.observe(moviePlayer, { childList: true });
        if (moviePlayer == null) return;

        playerAPI = moviePlayer.parentNode;
        video = playerAPI.getElementsByTagName('video')[0];
        if (video == null) return;

        video.pause();
        video.volume = 0;

        video.addEventListener('playing', onVideoPlay);
        video.addEventListener('play', onVideoPlay);
        video.addEventListener('seeking', onVideoSeek);
        video.addEventListener('seeked', onVideoSeek);

        clearInterval(intervalVideoInitialize);
    }, 25);
}

function _updateScreenState(noAnimation, forceAnimation) {
    if (playerAPI == null || screen == null) return;

    let dockMode = (currentPage != PAGE_WATCH || window.scrollY > screen.offsetHeight / 2) ? DOCK_FLOATING : DOCK_DOCKED;
    
    // Cancel current animation
    if (currentDockMode != dockMode) {
        screen.removeEventListener('transitionend', onScreenTransitionEnd);
        currentAnimationCancelled = currentAnimation != null;
        currentAnimation = null;
    }
        
    let positionEnd = _loadScreenState(dockMode), positionStart;
    let isAnimating = forceAnimation || currentDockMode != dockMode || currentAnimation != null;
    currentDockMode = dockMode;

    if (isAnimating) {
        // Update animation states
        if (currentAnimation == null) {
            // Note: updating the height/width causes a stutter/redraw on the iframe. Therefore we do it at the beginning, where it is less noticeable
            positionStart = screen.getBoundingClientRect();
            screen.style.height = positionEnd.height + 'px';
            screen.style.width = positionEnd.width + 'px';
            screen.style.transform = 'scale(' + (positionStart.width / positionEnd.width) + ')';
        } else {
            positionStart = currentAnimation.start;
        }
        currentAnimation = { end: positionEnd, start: positionStart, onScreenTransitionEnd: currentAnimation ? currentAnimation.onScreenTransitionEnd : null };

        if (currentDockMode == DOCK_DOCKED) {
            iframe.style.boxShadow = 'initial';
        }

        // Animate
        if (!noAnimation) {
            window.requestAnimationFrame(() => window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
                // Abort animation if cancelled
                if (currentAnimation == null)
                    return;
                
                screen.style.transition = '.2s ease-in-out';
                if (currentAnimationCancelled) {
                    screen.style.transform = '';
                    onScreenTransitionEnd();
                } else {
                    screen.style.transform =
                        'translateX(' + (currentAnimation.end.left - currentAnimation.start.left) + 'px)' +
                        'translateY(' + (currentAnimation.end.top - currentAnimation.start.top) + 'px)';
                }

                // Prevent multiple addEventListener during a single transition (e.g. when scrolling, this method is spammed)
                if (!currentAnimation || !currentAnimation.onScreenTransitionEnd) {
                    currentAnimation.onScreenTransitionEnd = onScreenTransitionEnd;
                    screen.addEventListener('transitionend', onScreenTransitionEnd);
                }
            })));
        } else {
            onScreenTransitionEnd();
        }
    } else {
        // When the movie is docked at the top of the watch page
        screen.style.transition = '';
        screen.style.height = positionEnd.height + 'px';
        screen.style.width = positionEnd.width + 'px';
        screen.style.top = positionEnd.top + 'px';
        screen.style.left = positionEnd.left + 'px';
    }
}
function _updateScreenVisibility(visible) {
    if (visible) {
        if (moviePlayer) moviePlayer.style.display = 'none';
        screen.style.display = 'initial';
        _updateScreenState(true);
    } else {
        if (moviePlayer) moviePlayer.style.display = 'initial';
        screen.style.display = 'none';
    }
}

function _calculateDefaultScreenState(dockMode) {
    let state = {};
    state.height = playerAPI.offsetHeight / 2;
    state.width = playerAPI.offsetWidth / 2;

    // #YoutubeMaterialDesign - when switching to the Home page and such
    if (currentPage != PAGE_WATCH && (state.width == 0 || state.height == 0)) {
        let backupState = _loadScreenState(DOCK_DOCKED, PAGE_WATCH);
        state.height = backupState.height / 2;
        state.width = backupState.width / 2;
    }

    if (currentPage == PAGE_CHANNEL || currentPage == PAGE_HOME || currentPage == PAGE_SUBSCRIPTIONS) {
        state.top = (window.innerHeight - state.height - SCREEN_PADDING);
        state.left = (window.innerWidth - state.width - SCREEN_PADDING - SCREEN_PADDING);
    }

    if (currentPage == PAGE_SEARCH) {
        state.top = (window.innerHeight - state.height) / 2;
        state.left = (window.innerWidth - state.width - SCREEN_PADDING - SCREEN_PADDING);
    }

    if (currentPage == PAGE_WATCH) {
        if (dockMode == DOCK_FLOATING) {
            let sidebar = document.getElementById('watch7-sidebar') || document.getElementById('related'); // #YoutubeMaterialDesign
            state.top = (window.innerHeight - state.height) / 2;
            state.left = (sidebar.getBoundingClientRect().left - (state.width - sidebar.offsetWidth) / 2);
        } else {
            state = playerAPI.getBoundingClientRect();
        }
    }

    return _saveScreenState(dockMode, state.height, state.width, state.left, state.top);
}
function _loadScreenState(dockMode, page) {
    let state = localStorage.screenState = localStorage.screenState || {};
        state = state[page || currentPage] = state[page || currentPage] || {};
        state = state[dockMode] = state[dockMode] || {};
    
    // Load default state, if none was previously saved
    if ((currentPage == PAGE_WATCH && dockMode == DOCK_DOCKED) || state.height == null || state.width == null || state.left == null || state.top == null)
        state = _calculateDefaultScreenState(dockMode);

    // In case the screen is outside the screen, move it back inside
    if (dockMode == DOCK_FLOATING) {
        if (state.left + state.width + SCREEN_PADDING > window.innerWidth)
            state.left = window.innerWidth - state.width - SCREEN_PADDING;
        if (state.left < SCREEN_PADDING)
            state.left = SCREEN_PADDING;
        if (state.top + state.height + SCREEN_PADDING > window.innerHeight)
            state.top = window.innerHeight - state.height - SCREEN_PADDING;
        if (state.top < 100)
            state.top = 100;
    }

    return state;
}
function _saveScreenState(dockMode, height, width, left, top) {
    let state = localStorage.screenState = localStorage.screenState || {};
        state = state[currentPage] = state[currentPage] || {};
        state = state[dockMode] = state[dockMode] || {};
    
    // Save to localStorage
    state.height = height;
    state.width = width;
    state.left = left;
    state.top = top;

    window.localStorage['rjgtav.youtube-mini-player'] = JSON.stringify(localStorage);

    return state;
}

function _getBoundingDocumentRect(e) {
    var box = e.getBoundingClientRect();

    var body = document.body;
    var docEl = document.documentElement;

    var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
    var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;

    var clientTop = docEl.clientTop || body.clientTop || 0;
    var clientLeft = docEl.clientLeft || body.clientLeft || 0;

    var height = e.offsetHeight;
    var width = e.offsetWidth;
    var top  = box.top +  scrollTop - clientTop;
    var left = box.left + scrollLeft - clientLeft;

    return { height, width, top, left };
}

//---------------------------------------------------------------------
// Event Handlers
//---------------------------------------------------------------------
// Thanks to http://stackoverflow.com/questions/34077641/how-to-detect-page-navigation-on-youtube-and-modify-html-before-page-is-rendered
function onPageLoad() {
    let isFirstTime = iframe == null;

    // Parse query params
    queryParams = {};
    let params = window.location.search.substring(1).split('&');
    params.forEach((param) => {
        let pair = param.split('=');
        queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
    })

    // Parse current page
    currentPage = null;
    currentPage = currentPage == null && queryParams.v != null ? PAGE_WATCH : currentPage;
    currentPage = currentPage == null && queryParams.search_query != null ? PAGE_SEARCH : currentPage;
    currentPage = currentPage == null && window.location.href.indexOf('/channel/') != -1 ? PAGE_CHANNEL : currentPage;
    currentPage = currentPage == null && window.location.href.indexOf('/feeed/subscriptions') != -1 ? PAGE_SUBSCRIPTIONS : currentPage;
    currentPage = currentPage == null ? PAGE_HOME : currentPage;

    if (currentPage == PAGE_WATCH) {
        initializeIframe();
        initializeVideo();
        onPageTitle();
        updateIframeSource();

        // Make sure the mini player is visible (in case it was hidden by the user)
        _updateScreenVisibility(true);
    }
    
    document.body.appendChild(windowResizeListener); // Note: we need to append this before the window.onload
    window.requestAnimationFrame(() => {
        _updateScreenState(isFirstTime, true);
    });
}
function onPageTitle(event) {
    let index = document.title.indexOf('- YouTube');
    if (index > -1)
        screenTitle.innerText = document.title.substring(0, index);
}

function onIframeLoad(event) {
    // Workaround: we use intervals so this still runs when the tab isn't focused
    clearInterval(intervalIframeInitialize);
    intervalIframeInitialize = setInterval(() => {
        iframeVideo = iframe.contentWindow.document.getElementsByTagName('video')[0];
        if (iframeVideo) {
            iframeVideo.currentTime = video.currentTime;
            iframeVideo.onended = onIframeVideoEnded;
            clearInterval(intervalIframeInitialize);
        }
    }, 25);
}
function onIframeVideoEnded(event) {
    if (currentPage != PAGE_WATCH) return;

    // In order to trigger Youtube's functionality (e.g. AutoPlay, Playlist), we need to make the original video trigger the 'ended' event
    // Therefore, we seek to the end and play it. See #onVideoSeek() for details
    video.currentTime = video.duration - 0.25;
    _updateScreenVisibility(false);
}

function onScreenButtonCloseClick(event) {
    iframeVideo.pause();
    _updateScreenVisibility(false);
}

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

    isScreenMove = event.target == screenButtonMove;
    isScreenResize = event.target == screenButtonResizeBottomRight || event.target == screenButtonResizeTopRight;
    iframe.style.pointerEvents = 'none';
    screen.classList.add('dragging');

    screenButtonMove.removeEventListener('mousedown', onScreenMouseDown);
    screenButtonResizeBottomRight.removeEventListener('mousedown', onScreenMouseDown);
    screenButtonResizeTopRight.removeEventListener('mousedown', onScreenMouseDown);

    document.body.addEventListener('mouseup', onScreenMouseUp);
    document.body.addEventListener('mousemove', onScreenMouseMove);
}
function onScreenMouseUp(event) {
    event.preventDefault();

    if (isScreenMove) {
        // Save
        _saveScreenState(currentDockMode, screen.offsetHeight, screen.offsetWidth, screen.offsetLeft, screen.offsetTop);
    }
    if (isScreenResize) {
        var currentSize = screen.getBoundingClientRect();
        //screen.style.left =(screen.offsetLeft - (currentSize.width - screen.offsetWidth) / 2) + 'px';
        //screen.style.top = (screen.offsetTop - (currentSize.height - screen.offsetHeight) / 2) + 'px';
        screen.style.height = currentSize.height + 'px';
        screen.style.width = currentSize.width + 'px';
        screen.style.transform = '';

        // Save
        _saveScreenState(currentDockMode, currentSize.height, currentSize.width, screen.offsetLeft, screen.offsetTop);
    }

    isScreenMove = isScreenResize = false;
    iframe.style.pointerEvents = 'initial';
    screen.classList.remove('dragging');

    document.body.removeEventListener('mouseup', onScreenMouseUp);
    document.body.removeEventListener('mousemove', onScreenMouseMove);

    screenButtonMove.addEventListener('mousedown', onScreenMouseDown);
    screenButtonResizeBottomRight.addEventListener('mousedown', onScreenMouseDown);
    screenButtonResizeTopRight.addEventListener('mousedown', onScreenMouseDown);
}
function onScreenMouseMove(event) {
    event.preventDefault();

    if (isScreenMove) {
        screen.style.left = Math.min(window.innerWidth - screen.offsetWidth - SCREEN_PADDING, Math.max(SCREEN_PADDING, parseInt(screen.style.left) + event.movementX)) + 'px';
        screen.style.top = Math.min(window.innerHeight - screen.offsetHeight - SCREEN_PADDING, Math.max(148, parseInt(screen.style.top) + event.movementY)) + 'px';
    }
    if (isScreenResize) {
        var currentSize = screen.getBoundingClientRect();

        var width = screen.offsetWidth;
        var newWidth = currentSize.width + event.movementX;
        screen.style.transform = 'scaleX(' + newWidth / width + ') scaleY(' + newWidth / width + ')';
    }
}

function onScreenTitleClick(event) {
    var videoUrl = window.location.href.split('&t=')[0];
    window.location.href = videoUrl + (iframeVideo ? ('&t=' + parseInt(iframeVideo.currentTime)) : '');
}

function onScreenTransitionEnd(event) {
    if (event && event.propertyName != 'transform') return;
    screen.removeEventListener('transitionend', onScreenTransitionEnd);

    if (currentDockMode == DOCK_FLOATING) {
        iframe.style.boxShadow = '0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)';
    }

    screen.className = currentDockMode;
    screen.style.left = currentAnimation.end.left + 'px';
    screen.style.top = currentAnimation.end.top + 'px';     
    screen.style.transform = '';
    screen.style.transition = '';
    
    currentAnimation = null;
    currentAnimationCancelled = false;
}

function onMoviePlayerObserve(mutators) {
    if (mutators[0].addedNodes.length > 0)
        initializeVideo();
}

function onVideoPlay(event) {
    if (video == null) return;
    if (!(video.duration > 0 && Math.abs(video.currentTime - video.duration) <= 1)) // See #onIframeVideoEnded() for details
        video.pause();
}
function onVideoSeek(event) {
    if (video == null) return;
    
    if (video.duration > 0 && Math.abs(video.currentTime - video.duration) <= 1) { // See #onIframeVideoEnded() for details
        video.play();
    } else {
        video.pause();
        if (iframeVideo != null)
            iframeVideo.currentTime = video.currentTime;
        
        // Make sure the mini player is visible (in case it was hidden by the user)
        _updateScreenVisibility(true);
    }
}

function onWindowResize() {
    window.requestAnimationFrame(() => {
        _updateScreenState();
    });
}
function onWindowScroll(event) {
    _updateScreenState();
}

})();