<commit_before>//Request AnimationFrame polyfill
    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
    // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

    // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel

    // MIT license

    (function() {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
            window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
                                       || window[vendors[x]+'CancelRequestAnimationFrame'];
        }

        if (!window.requestAnimationFrame)
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() { callback(currTime + timeToCall); },
                  timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };

        if (!window.cancelAnimationFrame)
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
    }());

var Game;

//(function() { //Mask everything but Game object

//The main Game constructor
Game = function(elem, options) {
//Pretty straightforward variables...
    var self     = this;
    self.options = options || {};
    self.canvas  = elem;
    self.context = self.canvas.getContext("2d");
    self.Draw    = new Draw(this.context);
    self.width   = self.canvas.offsetWidth;
    self.height  = self.canvas.offsetHeight;

    self.hasStarted      = false;
    self.frames          = 0;
    self.offset          = 0;
    self.refreshRate     = self.options.refreshRate || 16;
    self.speed           = self.options.speed || 5;

    self.drawBuffer      = [];
    self.worlds          = [];
    self.world           = [];
    self.worldNum        = 0;
    self.scene           = null;
    self.sceneNum        = 0;

    self.timer           = null;
    self.createTime      = new Date();
    self.initTime        = null;
    self.startTime       = null;
    self.lastStartTime   = null;
    self.stopTime        = null;
    this.worldLoadTime   = null;
    self.levelLoadTime   = null;
    self.drawTimes       = [];

//Register some events
    document.addEventListener("resize", function() {
        self.width = self.canvas.offsetWidth;
        self.height = self.canvas.offsetHeight;
    })

    return self;
}

/* LOOP
 * ==== */

//Start the game
Game.prototype.start = function(levelID) {
    this.lastStartTime = new Date();
    if(!this.hasStarted) {
        this.startTime = this.lastStartTime;
        this.init(levelID);
    }
    this.hasStarted = true;

    if(!this.timer) {
        var game = this;
        this.timer = setInterval(function() {
            game.Loop.apply(game);
        }, this.refreshRate);
        game.Loop();
    }

    return this;
}
//Stop the game
Game.prototype.stop = function() {
    this.stopTime = new Date();
    clearInterval(this.timer);
    this.timer = null;
    return this;
}
//Initialization on first start()
Game.prototype.init = function(levelID) {
    this.initTime = new Date();
    this.levelNum = (levelID || 0);
    this.loadLevel(this.levelNum);
    return this;
}
//The main Game Loop
Game.prototype.Loop = function() {
    var START = new Date();
    this.frames++;
    this.offset += this.speed;
    this.clearCanvas();
    this.updateBuffer();
    this.drawTerrain();
    var END = new Date();
    this.drawTimes.push(END - START);
    return this;
}
//Draw the background (=blocks)
Game.prototype.drawTerrain = function() {
    var column, i, j, leni, lenj;
    var self = this;
    var h = game.height;

//Loop drawBuffer
    for(i = 0, leni = self.drawBuffer.length; i < leni; i++) {
        column = self.drawBuffer[i];

        for(j = 0, lenj = column.length; j < lenj; j++) {
//Reuse offsetX and offsetY for memory efficiency
            this.Draw.offsetX = ((i)*20) - (this.offset % 20);
            this.Draw.offsetY = h-((j+1)*20);
            column[j](this.Draw);
        }
    }
    return this;
}
//Update the drawBuffer
Game.prototype.updateBuffer = function() {
    this.drawBuffer = this.level.slice(Math.floor(this.offset/20), Math.ceil((this.offset + this.width)/20));
    return this;
}
//Clear the whole canvas
Game.prototype.clearCanvas = function() {
    this.context.clearRect(0, 0, this.width, this.height);
    return this;
}

/* LEVELS
 * ====== */

//Add a world to the game
Game.prototype.addWorld = function(world) {
    this.worlds.push(world);
    return this;
}
//Load in a world (=select this world to play)
Game.prototype.load = Game.prototype.loadWorld = function(world) {
    this.stop();
    this.worldLoadTime = new Date();
//The index of the world is given
    if(typeof world === "number") {
        this.world = this.worlds[world];
    }
//The world itself is given
    else {
        this.worlds.push(world);
//Select the world that was just added
        this.world = this.worlds.slice(-1)[0];
    }
    return this;
}
//Load in a level (=select this level to play)
Game.prototype.loadLevel = function(level) {
//Stop the game to avoid corruption
    this.stop();
    this.levelLoadTime = new Date();
//The index of the game in the world is given
    if(typeof level === "number") {
        this.level = this.world[level];
    }
//The level object itself is given
    else {
        this.world.push(level);
//Select the level that was just added
        this.level = level;
    }
    return this;
}
//Add a level to the current world
Game.prototype.addLevel = function(level) {
    this.world.push(level);
    return this;
}

/* UTILS
 * ===== */

//Get the average draw time (of all frames)
Game.prototype.getAverageDrawTime = function() {
    for(var i = 0, len = this.drawTime.length; i < len; i++) {
        sum += this.drawTimes[i];
    }
    return (sum / len);
}

//The main Draw object
//An instance of this object is passed to the block functions
//Draw reflects the Canvas API, but x/y-coordinates are mapped to their right square on the canvas
//This means that every block should draw inside a 20x20 block (which is placed correctly on the canvas by Draw)
var Draw = function(context, offsetX, offsetY) {
    var self     = this;
    this.context = context;
    this.offsetX = offsetX || 0;
    this.offsetY = offsetY || 0;

    return self;
}

Draw.prototype.beginPath = function() {
    this.context.beginPath();
    return this;
}
Draw.prototype.closePath = function() {
    this.context.closePath();
    return this;
}
Draw.prototype.moveTo = function(x, y) {
    this.context.moveTo(x, y);
    return this;
}
Draw.prototype.save = function() {
    this.context.save();
    return this;
}
Draw.prototype.restore = function() {
    this.context.restore();
    return this;
}
Draw.prototype.clip = function() {
    this.context.clip();
    return this;
}

Draw.prototype.fillStyle = function(style) {
    this.context.fillStyle = style;
    return this;
}
Draw.prototype.strokeStyle = function(style) {
    this.context.strokeStyle = style;
    return this;
}
Draw.prototype.strokeWidth = function(width) {
    this.context.strokeWidth = width;
    return this;
}
Draw.prototype.stroke = function() {
    this.context.stroke();
    return this;
}
Draw.prototype.fill = function() {
    this.context.fill();
    return this;
}
Draw.prototype.clearRect = function(x, y, w, h) {
    this.context.clearRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}

Draw.prototype.fillRect = function(x, y, w, h) {
    this.context.fillRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}
Draw.prototype.strokeRect = function(x, y, w, h) {
    this.context.strokeRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}
Draw.prototype.fullRect = function(x, y, w, h) {
    this.strokeRect(x+this.offsetX, y+this.offsetY, w, h);
    this.fillRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}

Draw.prototype.lineTo = function(x, y) {
    this.context.lineTo(x+this.offsetX, y+this.offsetY);
    return this;
}
Draw.prototype.rect = function(x, y, w, h) {
    this.context.rect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}
Draw.prototype.arc = function(x, y, r, sa, ea, ac) {
    this.context.arc(x+this.offsetX, y+this.offsetY, r, sa, ea, ac);
    return this;
}
Draw.prototype.arcTo = function(x1, y1, x2, y2, r) {
    this.context.arcTo(x1+this.offsetX, y1+this.offset.Y, x2+this.offsetX, y2+this.offsetY, r);
    return this;
}
Draw.prototype.bezierCurveTo = function(x1, y1, x2, y2, x3, y3) {
    this.context.bezierCurveTo(x1+this.offsetX, y1+this.offset.Y, x2+this.offsetX, y2+this.offsetY, x3+this.offsetX, y3+this.offsetY);
    return this;
}
Draw.prototype.quadraticCurveTo = function(x1, y1, x2, y2) {
    this.context.quadraticCurveTo(x1+this.offsetX, y1+this.offset.Y, x2+this.offsetX, y2+this.offsetY);
    return this;
}

Draw.prototype.fillText = function(text, x, y, maxw) {
    this.context.fillText(text, x+this.offsetX, y+this.offsetY, maxw);
    return this;
}
Draw.prototype.strokeText = function(text, x, y, maxw) {
    this.context.strokeText(text, x+this.offsetX, y+this.offsetY, maxw);
    return this;
}
Draw.prototype.fullText = function(text, x, y, maxw) {
    this.fillText(text, x+this.offsetX, y+this.offsetY, maxw);
    this.strokeText(text, x+this.offsetX, y+this.offsetY, maxw);
    return this;
}

/* NOT IMPLEMETED
 * ==============*/
 //->Security
     //.rotate()
     //.translate()
     //.scale()
     //.scrollPathIntoView()
 //->Too complex/not enough time --> look into them!
 //TODO
     //.createImageData()
     //.createLinearGradient()
     //.createRadialGradient()
     //.createPattern()
     //.drawCustomFocusRing()
     //.drawSystemFocusRing()
     //.drawImage()
     //.getImageData()
     //.getLineDash()
     //.isPointInPath()
     //.isPointInStroke()
     //.measureText()
     //.putImageData()
     //.setLineDash()
     //.setTransform()

/* EXTRA
 * =====*/

 //.fullRect()
 //.fullText()

//})();<commit_msg>Use requestAnimationFrame<commit_after>//Request AnimationFrame polyfill
    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
    // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

    // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel

    // MIT license

    (function() {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
            window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
                                       || window[vendors[x]+'CancelRequestAnimationFrame'];
        }

        if (!window.requestAnimationFrame)
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() { callback(currTime + timeToCall); },
                  timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };

        if (!window.cancelAnimationFrame)
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
    }());

/* Game2Go  **ITSELF**
 * =========================== */

var Game;

//(function() { //Mask everything but Game object

//The main Game constructor
Game = function(elem, options) {

//Pretty straightforward variables...
    var self     = this;
    self.options = options || {};
    self.canvas  = elem;
    self.context = self.canvas.getContext("2d");
    self.Draw    = new Draw(this.context);
    self.width   = self.canvas.offsetWidth;
    self.height  = self.canvas.offsetHeight;

    self.hasStarted      = false;
    self.frames          = 0;
    self.offset          = 0;
    self.refreshRate     = self.options.refreshRate || 16;
    self.speed           = self.options.speed || 5;
    self.playing         = false;

    self.drawBuffer      = [];
    self.worlds          = [];
    self.world           = [];
    self.worldNum        = 0;
    self.scene           = null;
    self.sceneNum        = 0;

    self.timer           = null;
    self.createTime      = new Date();
    self.initTime        = null;
    self.startTime       = null;
    self.lastStartTime   = null;
    self.stopTime        = null;
    this.worldLoadTime   = null;
    self.levelLoadTime   = null;
    self.drawTimes       = [];

//Register some events
    document.addEventListener("resize", function() {
        self.width = self.canvas.offsetWidth;
        self.height = self.canvas.offsetHeight;
    })

    return self;
}

/* LOOP
 * ==== */

//Start the game
Game.prototype.start = function(levelID) {
    this.lastStartTime = new Date();
    if(!this.hasStarted) {
        this.startTime = this.lastStartTime;
        this.init(levelID);
    }
    this.hasStarted = true;

    if(!this.timer) {
        this.playing = true;
        var game = this;
        this.timer = window.requestAnimationFrame(function() {
            game.Loop();
        })
    }

    return this;
}
//Stop the game
Game.prototype.stop = function() {
    this.stopTime = new Date();
    this.playing = false;
    return this;
}
//Initialization on first start()
Game.prototype.init = function(levelID) {
    this.initTime = new Date();
    this.levelNum = (levelID || 0);
    this.loadLevel(this.levelNum);
    return this;
}
//The main Game Loop
Game.prototype.Loop = function() {

//Update frame
    var START = new Date();
    this.frames++;
    this.offset += this.speed;
    this.clearCanvas();
    this.updateBuffer();
    this.drawTerrain();
    var END = new Date();
    this.drawTimes.push(END - START);

//Request next frame
    if(this.playing) {
        var game = this;
        this.timer = window.requestAnimationFrame(function() {
            game.Loop();
        });
    }

    return this;
}
//Draw the background (=blocks)
Game.prototype.drawTerrain = function() {
    var column, i, j, leni, lenj;
    var self = this;
    var h = game.height;

//Loop DrawBuffer
    for(i = this.drawBuffer.length - 1; i >= 0; i--) {
        column = self.drawBuffer[i];

        for(j = column.length - 1; j >= 0; j--) {
//Reuse offsetX and offsetY for memory efficiency
            this.Draw.offsetX = ((i)*20) - (this.offset % 20);
            this.Draw.offsetY = h-((j+1)*20);
            column[j](this.Draw);
        }
    }

    return this;
}
//Update the drawBuffer
Game.prototype.updateBuffer = function() {
    this.drawBuffer = this.level.slice(Math.floor(this.offset/20), Math.ceil((this.offset + this.width)/20));
    return this;
}
//Clear the whole canvas
Game.prototype.clearCanvas = function() {
    this.context.clearRect(0, 0, this.width, this.height);
    return this;
}

/* LEVELS
 * ====== */

//Add a world to the game
Game.prototype.addWorld = function(world) {
    this.worlds.push(world);
    return this;
}
//Load in a world (=select this world to play)
Game.prototype.load = Game.prototype.loadWorld = function(world) {
    this.stop();
    this.worldLoadTime = new Date();
//The index of the world is given
    if(typeof world === "number") {
        this.world = this.worlds[world];
    }
//The world itself is given
    else {
        this.worlds.push(world);
//Select the world that was just added
        this.world = this.worlds.slice(-1)[0];
    }
    return this;
}
//Load in a level (=select this level to play)
Game.prototype.loadLevel = function(level) {
//Stop the game to avoid corruption
    this.stop();
    this.levelLoadTime = new Date();
//The index of the game in the world is given
    if(typeof level === "number") {
        this.level = this.world[level];
    }
//The level object itself is given
    else {
        this.world.push(level);
//Select the level that was just added
        this.level = level;
    }
    return this;
}
//Add a level to the current world
Game.prototype.addLevel = function(level) {
    this.world.push(level);
    return this;
}

/* UTILS
 * ===== */

//Get the average draw time (of all frames)
Game.prototype.getAverageDrawTime = function() {
    var sum = 0, i;
    for(i = this.drawTimes.length - 1; i >= 0; i--) {
        sum += this.drawTimes[i];
    }
    return (sum / this.drawTimes.length);
}

//The main Draw object
//An instance of this object is passed to the block functions
//Draw reflects the Canvas API, but x/y-coordinates are mapped to their right square on the canvas
//This means that every block should draw inside a 20x20 block (which is placed correctly on the canvas by Draw)
var Draw = function(context, offsetX, offsetY) {
    var self     = this;
    this.context = context;
    this.offsetX = offsetX || 0;
    this.offsetY = offsetY || 0;

    return self;
}

Draw.prototype.beginPath = function() {
    this.context.beginPath();
    return this;
}
Draw.prototype.closePath = function() {
    this.context.closePath();
    return this;
}
Draw.prototype.moveTo = function(x, y) {
    this.context.moveTo(x, y);
    return this;
}
Draw.prototype.save = function() {
    this.context.save();
    return this;
}
Draw.prototype.restore = function() {
    this.context.restore();
    return this;
}
Draw.prototype.clip = function() {
    this.context.clip();
    return this;
}

Draw.prototype.fillStyle = function(style) {
    this.context.fillStyle = style;
    return this;
}
Draw.prototype.strokeStyle = function(style) {
    this.context.strokeStyle = style;
    return this;
}
Draw.prototype.strokeWidth = function(width) {
    this.context.strokeWidth = width;
    return this;
}
Draw.prototype.stroke = function() {
    this.context.stroke();
    return this;
}
Draw.prototype.fill = function() {
    this.context.fill();
    return this;
}
Draw.prototype.clearRect = function(x, y, w, h) {
    this.context.clearRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}

Draw.prototype.fillRect = function(x, y, w, h) {
    this.context.fillRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}
Draw.prototype.strokeRect = function(x, y, w, h) {
    this.context.strokeRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}
Draw.prototype.fullRect = function(x, y, w, h) {
    this.strokeRect(x+this.offsetX, y+this.offsetY, w, h);
    this.fillRect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}

Draw.prototype.lineTo = function(x, y) {
    this.context.lineTo(x+this.offsetX, y+this.offsetY);
    return this;
}
Draw.prototype.rect = function(x, y, w, h) {
    this.context.rect(x+this.offsetX, y+this.offsetY, w, h);
    return this;
}
Draw.prototype.arc = function(x, y, r, sa, ea, ac) {
    this.context.arc(x+this.offsetX, y+this.offsetY, r, sa, ea, ac);
    return this;
}
Draw.prototype.arcTo = function(x1, y1, x2, y2, r) {
    this.context.arcTo(x1+this.offsetX, y1+this.offset.Y, x2+this.offsetX, y2+this.offsetY, r);
    return this;
}
Draw.prototype.bezierCurveTo = function(x1, y1, x2, y2, x3, y3) {
    this.context.bezierCurveTo(x1+this.offsetX, y1+this.offset.Y, x2+this.offsetX, y2+this.offsetY, x3+this.offsetX, y3+this.offsetY);
    return this;
}
Draw.prototype.quadraticCurveTo = function(x1, y1, x2, y2) {
    this.context.quadraticCurveTo(x1+this.offsetX, y1+this.offset.Y, x2+this.offsetX, y2+this.offsetY);
    return this;
}

Draw.prototype.fillText = function(text, x, y, maxw) {
    this.context.fillText(text, x+this.offsetX, y+this.offsetY, maxw);
    return this;
}
Draw.prototype.strokeText = function(text, x, y, maxw) {
    this.context.strokeText(text, x+this.offsetX, y+this.offsetY, maxw);
    return this;
}
Draw.prototype.fullText = function(text, x, y, maxw) {
    this.fillText(text, x+this.offsetX, y+this.offsetY, maxw);
    this.strokeText(text, x+this.offsetX, y+this.offsetY, maxw);
    return this;
}

/* NOT IMPLEMETED
 * ==============*/
 //->Security
     //.rotate()
     //.translate()
     //.scale()
     //.scrollPathIntoView()
 //->Too complex/not enough time --> look into them!
 //TODO
     //.createImageData()
     //.createLinearGradient()
     //.createRadialGradient()
     //.createPattern()
     //.drawCustomFocusRing()
     //.drawSystemFocusRing()
     //.drawImage()
     //.getImageData()
     //.getLineDash()
     //.isPointInPath()
     //.isPointInStroke()
     //.measureText()
     //.putImageData()
     //.setLineDash()
     //.setTransform()

/* EXTRA
 * =====*/

 //.fullRect()
 //.fullText()

//})();<|endoftext|>


<commit_before>const { app, shell, clipboard, BrowserWindow, Tray, Menu } = require('electron')
const path = require('path')
const log = require('debug')(`app:master:electron [${process.pid}]`)
const project = require('../project.config')
const isDev = (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath))
const ICON_PATH = path.join(project.basePath, isDev ? 'public' : 'dist')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
let tray = null
const status = {
  url: '',
}

// NODE_ENV won't automatically pass to (non-Electron) forked processes;
// set it for them now based on detected dev/prod state
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = isDev ? 'development' : 'production'
}

// pass user data path to (non-Electron) forked processes
process.env.USER_DATA_PATH = app.getPath('userData')

// event handlers
app.on('ready', createWindow)
app.on('quit', (e, code) => { log(`quitting (exit code ${code})`) })

function createWindow () {
  win = new BrowserWindow({
    show: false,
    skipTaskbar: true, // windows
  })

  // macOS
  if (app.dock) {
    app.dock.hide()
  }

  if (process.platform === 'win32') {
    // white 32x32
    tray = new Tray(path.join(ICON_PATH, 'mic-white@2x.png'))
  } else {
    // blackish 32x32 (template works in light and dark macOS modes)
    tray = new Tray(path.join(ICON_PATH, 'mic-blackTemplate.png'))
    tray.setPressedImage(path.join(ICON_PATH, 'mic-white.png'))
  }

  tray.setToolTip('Karaoke Forever Server')
  tray.on('double-click', launchBrowser)
  updateMenu()

  // Emitted when the window is closed.
  win.on('closed', () => {
    win = null
    tray = null
  })
}

function launchBrowser () {
  if (status.url) {
    shell.openExternal(status.url)
  }
}

function setStatus (key, val) {
  status[key] = val
  updateMenu()
}

function updateMenu () {
  if (!tray) return

  const menu = [
    { label: `Karaoke Forever Server`, enabled: false },
    { label: status.url, enabled: false },
    { type: 'separator' },
    { label: 'Open in browser', click: launchBrowser },
    { label: 'Copy URL', click: () => clipboard.writeText(status.url) },
    { type: 'separator' },
    { label: 'Quit Karaoke Forever Server', role: 'quit' },
  ]

  tray.setContextMenu(Menu.buildFromTemplate(menu))
}

module.exports = { setStatus }
<commit_msg>Fix database path on Windows<commit_after>const { app, shell, clipboard, BrowserWindow, Tray, Menu } = require('electron')
const path = require('path')
const log = require('debug')(`app:master:electron [${process.pid}]`)
const project = require('../project.config')
const isDev = (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath))
const ICON_PATH = path.join(project.basePath, isDev ? 'public' : 'dist')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
let tray = null
const status = {
  url: '',
}

// NODE_ENV won't automatically pass to (non-Electron) forked processes;
// set it for them now based on detected dev/prod state
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = isDev ? 'development' : 'production'
}

// pass user data path to (non-Electron) forked processes
process.env.USER_DATA_PATH = app.getPath('appData')

// event handlers
app.on('ready', createWindow)
app.on('quit', (e, code) => { log(`quitting (exit code ${code})`) })

function createWindow () {
  win = new BrowserWindow({
    show: false,
    skipTaskbar: true, // windows
  })

  // macOS
  if (app.dock) {
    app.dock.hide()
  }

  if (process.platform === 'win32') {
    // white 32x32
    tray = new Tray(path.join(ICON_PATH, 'mic-white@2x.png'))
  } else {
    // blackish 32x32 (template works in light and dark macOS modes)
    tray = new Tray(path.join(ICON_PATH, 'mic-blackTemplate.png'))
    tray.setPressedImage(path.join(ICON_PATH, 'mic-white.png'))
  }

  tray.setToolTip('Karaoke Forever Server')
  tray.on('double-click', launchBrowser)
  updateMenu()

  // Emitted when the window is closed.
  win.on('closed', () => {
    win = null
    tray = null
  })
}

function launchBrowser () {
  if (status.url) {
    shell.openExternal(status.url)
  }
}

function setStatus (key, val) {
  status[key] = val
  updateMenu()
}

function updateMenu () {
  if (!tray) return

  const menu = [
    { label: `Karaoke Forever Server`, enabled: false },
    { label: status.url, enabled: false },
    { type: 'separator' },
    { label: 'Open in browser', click: launchBrowser },
    { label: 'Copy URL', click: () => clipboard.writeText(status.url) },
    { type: 'separator' },
    { label: 'Quit Karaoke Forever Server', role: 'quit' },
  ]

  tray.setContextMenu(Menu.buildFromTemplate(menu))
}

module.exports = { setStatus }
<|endoftext|>


<commit_before>function MapConfigBufferSizeAdapter() {
    this.formats = ['png', 'png32', 'mvt', 'grid.json'];
}

module.exports = MapConfigBufferSizeAdapter;

MapConfigBufferSizeAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
    if (!context.templateParams || !context.templateParams.buffersize) {
        return callback(null, requestMapConfig);
    }

    this.formats.forEach(function (format) {
        if (Number.isFinite(context.templateParams.buffersize[format])) {
            requestMapConfig.buffersize[format] = context.templateParams.buffersize[format];
        }
    });

    process.nextTick(function () {
        callback(null, requestMapConfig);
    });
};
<commit_msg>Use setInmmediate vs process.nextTick<commit_after>function MapConfigBufferSizeAdapter() {
    this.formats = ['png', 'png32', 'mvt', 'grid.json'];
}

module.exports = MapConfigBufferSizeAdapter;

MapConfigBufferSizeAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
    if (!context.templateParams || !context.templateParams.buffersize) {
        return callback(null, requestMapConfig);
    }

    this.formats.forEach(function (format) {
        if (Number.isFinite(context.templateParams.buffersize[format])) {
            requestMapConfig.buffersize[format] = context.templateParams.buffersize[format];
        }
    });

    setImmediate(function () {
        callback(null, requestMapConfig);
    });
};
<|endoftext|>


<commit_before>export default {
  "refs": {
    "element": {
      "type": "string"
    }
  },
  "defs": {
    "bind": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "input": {"enum": ["checkbox"]},
            "element": {"$ref": "#/refs/element"}
          },
          "required": ["input"]
        },
        {
          "type": "object",
          "properties": {
            "input": {"enum": ["radio", "select"]},
            "element": {"$ref": "#/refs/element"},
            "options": {
              "type": "array"
            }
          },
          "additionalProperties": false,
          "required": ["input", "options"]
        },
        {
          "type": "object",
          "properties": {
            "input": {"enum": ["range"]},
            "element": {"$ref": "#/refs/element"},
            "min": {"type": "number"},
            "max": {"type": "number"},
            "step": {"type": "number"}
          },
          "additionalProperties": false,
          "required": ["input"]
        },
        {
          "type": "object",
          "properties": {
            "input": {
              "not": {"enum": ["checkbox", "radio", "range", "select"]}
            },
            "element": {"$ref": "#/refs/element"}
          },
          "additionalProperties": true
        },
      ]
    }
  }
};
<commit_msg>Update schema to include DOM bind debounce.<commit_after>export default {
  "refs": {
    "element": {
      "type": "string"
    }
  },
  "defs": {
    "bind": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "input": {"enum": ["checkbox"]},
            "element": {"$ref": "#/refs/element"},
            "debounce": {"type": "number"}
          },
          "required": ["input"]
        },
        {
          "type": "object",
          "properties": {
            "input": {"enum": ["radio", "select"]},
            "element": {"$ref": "#/refs/element"},
            "options": {"type": "array"},
            "debounce": {"type": "number"}
          },
          "additionalProperties": false,
          "required": ["input", "options"]
        },
        {
          "type": "object",
          "properties": {
            "input": {"enum": ["range"]},
            "element": {"$ref": "#/refs/element"},
            "min": {"type": "number"},
            "max": {"type": "number"},
            "step": {"type": "number"},
            "debounce": {"type": "number"}
          },
          "additionalProperties": false,
          "required": ["input"]
        },
        {
          "type": "object",
          "properties": {
            "input": {
              "not": {"enum": ["checkbox", "radio", "range", "select"]}
            },
            "element": {"$ref": "#/refs/element"},
            "debounce": {"type": "number"}
          },
          "additionalProperties": true
        },
      ]
    }
  }
};
<|endoftext|>


<commit_before>/*
 * IDBWrapper - A cross-browser wrapper for IndexedDB
 * Copyright (c) 2011 Jens Arps
 * http://jensarps.de/
 *
 * Licensed under the MIT (X11) license
 */

(function (name, definition, global) {
  if (typeof define === 'function') {
    define(definition);
  } else {
    global[name] = definition();
  }
})('IDBStore', function () {

  var IDBStore;

  var defaults = {
    storeName: 'Store',
    dbVersion: 1,
    keyPath: 'id',
    autoIncrement: true,
    onStoreReady: function () {
    }
  };

  IDBStore = function (kwArgs, onStoreReady) {

    function fixupConstants (object, constants) {
      for (var prop in constants) {
        if (!(prop in object))
          object[prop] = constants[prop];
      }
    }

    mixin(this, defaults);
    mixin(this, kwArgs);
    this.dbName = 'IDBWrapper-' + this.storeName;

    onStoreReady && (this.onStoreReady = onStoreReady);

    this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
    this.consts = window.IDBTransaction || window.webkitIDBTransaction;
    fixupConstants(this.consts, {
      'READ_ONLY': 'readonly',
      'READ_WRITE': 'readwrite',
      'VERSION_CHANGE': 'versionchange'
    });
    this.cursor = window.IDBCursor || window.webkitIDBCursor;
    fixupConstants(this.cursor, {
      'NEXT': 'next',
      'NEXT_NO_DUPLICATE': 'nextunique',
      'PREV': 'prev',
      'PREV_NO_DUPLICATE': 'prevunique'
    });
    this.openDB();
  };

  IDBStore.prototype = {

    db: null,

    dbName: null,

    dbDescription: null,

    dbVersion: null,

    store: null,

    storeName: null,

    keyPath: null,

    autoIncrement: null,

    features: null,

    onStoreReady: null,

    openDB: function () {

      this.newVersionAPI = typeof this.idb.setVersion == 'undefined';

      if(!this.newVersionAPI){
        throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
      }

      var features = this.features = {};
      features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?

      var openRequest;

      this.dbVersion = parseInt(this.dbVersion, 10);
      openRequest = this.idb.open(this.dbName, this.dbVersion);

      openRequest.onerror = hitch(this, function (error) {
        var gotVersionErr = false;
        if ('error' in error.target) {
          gotVersionErr = error.target.error.name == "VersionError";
        } else if ('errorCode' in error.target) {
          gotVersionErr = error.target.errorCode == 12; // TODO: Use const
        }

        if (gotVersionErr) {
          console.log('Version error');
          //setTimeout(hitch(this, 'openDB'));
        } else {
          console.error('Could not open database, error', error);
        }
      });

      openRequest.onsuccess = hitch(this, function (event) {
        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          console.log('object store found');
          this.openExistingObjectStore(this.onStoreReady);
        } else {
          console.log('object store NOT found', this.storeName);
          throw new Error('Cannot create a new store in this db for the current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
        }
      });

      openRequest.onupgradeneeded = hitch(this, function(/* IDBVersionChangeEvent */ event){
        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          console.log('object store found');
        } else {
          console.log('object store NOT found', this.storeName);

          this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
        }
      });
    },

    deleteDatabase: function () {
      if (this.idb.deleteDatabase) {
        this.idb.deleteDatabase(this.dbName);
      }
    },

    /*************************
     * object store handling *
     *************************/

    getObjectStore: function (onSuccess, onError) {
      if (this.hasObjectStore()) {
        this.openExistingObjectStore(onSuccess, onError);
      } else {
        this.createNewObjectStore(onSuccess, onError);
      }
    },

    hasObjectStore: function () {
      return this.db.objectStoreNames.contains(this.storeName);
    },

    createNewObjectStore: function (onSuccess, onError) {
      this.enterMutationState(hitch(this, function () {
        this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
        onSuccess && onSuccess(this.store);
      }), onError);
    },

    openExistingObjectStore: function (onSuccess, onError) {
      var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      this.store = emptyTransaction.objectStore(this.storeName);
      emptyTransaction.abort();
      onSuccess && onSuccess(this.store);
    },

    deleteObjectStore: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Failed to delete objectStore.', error);
      });
      this.enterMutationState(hitch(this, function (evt) {
        var db = evt.target.result;
        db.deleteObjectStore(this.storeName);
        var success = !this.hasObjectStore();
        onSuccess && success && onSuccess();
        onError && !success && onError();
      }), onError);
    },

    /*********************
     * data manipulation *
     *********************/

    put: function (dataObj, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not write data.', error);
      });
      onSuccess || (onSuccess = noop);
      if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
        dataObj[this.keyPath] = this._getUID();
      }
      var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
      putRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      putRequest.onerror = onError;
    },

    get: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var getRequest = getTransaction.objectStore(this.storeName).get(key);
      getRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      getRequest.onerror = onError;
    },

    remove: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not remove data.', error);
      });
      onSuccess || (onSuccess = noop);
      var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
      deleteRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      deleteRequest.onerror = onError;
    },

    getAll: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var store = getAllTransaction.objectStore(this.storeName);
      if (store.getAll) {
        var getAllRequest = store.getAll();
        getAllRequest.onsuccess = function (event) {
          onSuccess(event.target.result);
        };
        getAllRequest.onerror = onError;
      } else {
        this._getAllCursor(getAllTransaction, onSuccess, onError);
      }
    },

    _getAllCursor: function (tr, onSuccess, onError) {
      var all = [];
      var store = tr.objectStore(this.storeName);
      var cursorRequest = store.openCursor();

      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          all.push(cursor.value);
          cursor['continue']();
        }
        else {
          onSuccess(all);
        }
      };
      cursorRequest.onError = onError;
    },

    clear: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not clear store.', error);
      });
      onSuccess || (onSuccess = noop);
      var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var clearRequest = clearTransaction.objectStore(this.storeName).clear();
      clearRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      clearRequest.onerror = onError;
    },

    _getUID: function () {
      // FF bails at times on non-numeric ids. So we take an even
      // worse approach now, using current time as id. Sigh.
      return +new Date();
    },

    /************
     * indexing *
     ************/

    createIndex: function (indexName, propertyName, isUnique, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not create index.', error);
      });
      onSuccess || (onSuccess = noop);
      propertyName || (propertyName = indexName);

      var that = this;

      this.enterMutationState(hitch(this, function (evt) {
        var result = evt.target.result;
        var index;
        if (result.objectStore) { // transaction
          index = db.objectStore(this.storeName).createIndex(indexName, propertyName, { unique: !!isUnique });
        } else { // db
          var putTransaction = result.transaction([that.storeName] /* , this.consts.READ_WRITE */);
          var store = putTransaction.objectStore(that.storeName);
          index = store.createIndex(indexName, propertyName, { unique: !!isUnique });
        }
        onSuccess(index);
      }), onError);
    },

    getIndex: function (indexName) {
      return this.store.index(indexName);
    },

    getIndexList: function () {
      return this.store.indexNames;
    },

    hasIndex: function (indexName) {
      return this.store.indexNames.contains(indexName);
    },

    removeIndex: function (indexName, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not remove index.', error);
      });
      onSuccess || (onSuccess = noop);
      this.enterMutationState(hitch(this, function (evt) {
        evt.target.result.objectStore(this.storeName).deleteIndex(indexName);
        onSuccess();
      }), onError);
    },

    /**********
     * cursor *
     **********/

    iterate: function (callback, options) {
      options = mixin({
        index: null,
        order: 'ASC',
        filterDuplicates: false,
        keyRange: null,
        writeAccess: false,
        onEnd: null,
        onError: function (error) {
          console.error('Could not open cursor.', error);
        }
      }, options || {});

      var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
      if (options.filterDuplicates) {
        directionType += '_NO_DUPLICATE';
      }

      var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
      var cursorTarget = cursorTransaction.objectStore(this.storeName);
      if (options.index) {
        cursorTarget = cursorTarget.index(options.index);
      }

      var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
      cursorRequest.onerror = options.onError;
      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          callback(cursor.value, cursor, cursorTransaction);
          cursor['continue']();
        } else {
          options.onEnd && options.onEnd() || callback(null, cursor, cursorTransaction)
        }
      };
    }

    /* key ranges */
    // TODO: implement

  };

  /** helpers **/

  var noop = function () {
  };
  var empty = {};
  var mixin = function (target, source) {
    var name, s;
    for (name in source) {
      s = source[name];
      if (s !== empty[name] && s !== target[name]) {
        target[name] = s;
      }
    }
    return target;
  };
  var hitch = function (scope, method) {
    if (!method) {
      method = scope;
      scope = null;
    }
    if (typeof method == "string") {
      scope = scope || window;
      if (!scope[method]) {
        throw(['method not found']);
      }
      return function () {
        return scope[method].apply(scope, arguments || []);
      };
    }
    return !scope ? method : function () {
      return method.apply(scope, arguments || []);
    };
  };

  return IDBStore;

}, this);
<commit_msg>Remove getObjectStore() method<commit_after>/*
 * IDBWrapper - A cross-browser wrapper for IndexedDB
 * Copyright (c) 2011 Jens Arps
 * http://jensarps.de/
 *
 * Licensed under the MIT (X11) license
 */

(function (name, definition, global) {
  if (typeof define === 'function') {
    define(definition);
  } else {
    global[name] = definition();
  }
})('IDBStore', function () {

  var IDBStore;

  var defaults = {
    storeName: 'Store',
    dbVersion: 1,
    keyPath: 'id',
    autoIncrement: true,
    onStoreReady: function () {
    }
  };

  IDBStore = function (kwArgs, onStoreReady) {

    function fixupConstants (object, constants) {
      for (var prop in constants) {
        if (!(prop in object))
          object[prop] = constants[prop];
      }
    }

    mixin(this, defaults);
    mixin(this, kwArgs);
    this.dbName = 'IDBWrapper-' + this.storeName;

    onStoreReady && (this.onStoreReady = onStoreReady);

    this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
    this.consts = window.IDBTransaction || window.webkitIDBTransaction;
    fixupConstants(this.consts, {
      'READ_ONLY': 'readonly',
      'READ_WRITE': 'readwrite',
      'VERSION_CHANGE': 'versionchange'
    });
    this.cursor = window.IDBCursor || window.webkitIDBCursor;
    fixupConstants(this.cursor, {
      'NEXT': 'next',
      'NEXT_NO_DUPLICATE': 'nextunique',
      'PREV': 'prev',
      'PREV_NO_DUPLICATE': 'prevunique'
    });
    this.openDB();
  };

  IDBStore.prototype = {

    db: null,

    dbName: null,

    dbDescription: null,

    dbVersion: null,

    store: null,

    storeName: null,

    keyPath: null,

    autoIncrement: null,

    features: null,

    onStoreReady: null,

    openDB: function () {

      this.newVersionAPI = typeof this.idb.setVersion == 'undefined';

      if(!this.newVersionAPI){
        throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
      }

      var features = this.features = {};
      features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?

      var openRequest;

      this.dbVersion = parseInt(this.dbVersion, 10);
      openRequest = this.idb.open(this.dbName, this.dbVersion);

      openRequest.onerror = hitch(this, function (error) {
        var gotVersionErr = false;
        if ('error' in error.target) {
          gotVersionErr = error.target.error.name == "VersionError";
        } else if ('errorCode' in error.target) {
          gotVersionErr = error.target.errorCode == 12; // TODO: Use const
        }

        if (gotVersionErr) {
          console.log('Version error');
          //setTimeout(hitch(this, 'openDB'));
        } else {
          console.error('Could not open database, error', error);
        }
      });

      openRequest.onsuccess = hitch(this, function (event) {
        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          console.log('object store found');
          this.openExistingObjectStore(this.onStoreReady);
        } else {
          console.log('object store NOT found', this.storeName);
          throw new Error('Cannot create a new store in this db for the current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
        }
      });

      openRequest.onupgradeneeded = hitch(this, function(/* IDBVersionChangeEvent */ event){
        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          console.log('object store found');
        } else {
          console.log('object store NOT found', this.storeName);

          this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
        }
      });
    },

    deleteDatabase: function () {
      if (this.idb.deleteDatabase) {
        this.idb.deleteDatabase(this.dbName);
      }
    },

    /*************************
     * object store handling *
     *************************/

    hasObjectStore: function () {
      return this.db.objectStoreNames.contains(this.storeName);
    },

    createNewObjectStore: function (onSuccess, onError) {
      this.enterMutationState(hitch(this, function () {
        this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
        onSuccess && onSuccess(this.store);
      }), onError);
    },

    openExistingObjectStore: function (onSuccess, onError) {
      var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      this.store = emptyTransaction.objectStore(this.storeName);
      emptyTransaction.abort();
      onSuccess && onSuccess(this.store);
    },

    deleteObjectStore: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Failed to delete objectStore.', error);
      });
      this.enterMutationState(hitch(this, function (evt) {
        var db = evt.target.result;
        db.deleteObjectStore(this.storeName);
        var success = !this.hasObjectStore();
        onSuccess && success && onSuccess();
        onError && !success && onError();
      }), onError);
    },

    /*********************
     * data manipulation *
     *********************/

    put: function (dataObj, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not write data.', error);
      });
      onSuccess || (onSuccess = noop);
      if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
        dataObj[this.keyPath] = this._getUID();
      }
      var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
      putRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      putRequest.onerror = onError;
    },

    get: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var getRequest = getTransaction.objectStore(this.storeName).get(key);
      getRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      getRequest.onerror = onError;
    },

    remove: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not remove data.', error);
      });
      onSuccess || (onSuccess = noop);
      var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
      deleteRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      deleteRequest.onerror = onError;
    },

    getAll: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var store = getAllTransaction.objectStore(this.storeName);
      if (store.getAll) {
        var getAllRequest = store.getAll();
        getAllRequest.onsuccess = function (event) {
          onSuccess(event.target.result);
        };
        getAllRequest.onerror = onError;
      } else {
        this._getAllCursor(getAllTransaction, onSuccess, onError);
      }
    },

    _getAllCursor: function (tr, onSuccess, onError) {
      var all = [];
      var store = tr.objectStore(this.storeName);
      var cursorRequest = store.openCursor();

      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          all.push(cursor.value);
          cursor['continue']();
        }
        else {
          onSuccess(all);
        }
      };
      cursorRequest.onError = onError;
    },

    clear: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not clear store.', error);
      });
      onSuccess || (onSuccess = noop);
      var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var clearRequest = clearTransaction.objectStore(this.storeName).clear();
      clearRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      clearRequest.onerror = onError;
    },

    _getUID: function () {
      // FF bails at times on non-numeric ids. So we take an even
      // worse approach now, using current time as id. Sigh.
      return +new Date();
    },

    /************
     * indexing *
     ************/

    createIndex: function (indexName, propertyName, isUnique, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not create index.', error);
      });
      onSuccess || (onSuccess = noop);
      propertyName || (propertyName = indexName);

      var that = this;

      this.enterMutationState(hitch(this, function (evt) {
        var result = evt.target.result;
        var index;
        if (result.objectStore) { // transaction
          index = db.objectStore(this.storeName).createIndex(indexName, propertyName, { unique: !!isUnique });
        } else { // db
          var putTransaction = result.transaction([that.storeName] /* , this.consts.READ_WRITE */);
          var store = putTransaction.objectStore(that.storeName);
          index = store.createIndex(indexName, propertyName, { unique: !!isUnique });
        }
        onSuccess(index);
      }), onError);
    },

    getIndex: function (indexName) {
      return this.store.index(indexName);
    },

    getIndexList: function () {
      return this.store.indexNames;
    },

    hasIndex: function (indexName) {
      return this.store.indexNames.contains(indexName);
    },

    removeIndex: function (indexName, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not remove index.', error);
      });
      onSuccess || (onSuccess = noop);
      this.enterMutationState(hitch(this, function (evt) {
        evt.target.result.objectStore(this.storeName).deleteIndex(indexName);
        onSuccess();
      }), onError);
    },

    /**********
     * cursor *
     **********/

    iterate: function (callback, options) {
      options = mixin({
        index: null,
        order: 'ASC',
        filterDuplicates: false,
        keyRange: null,
        writeAccess: false,
        onEnd: null,
        onError: function (error) {
          console.error('Could not open cursor.', error);
        }
      }, options || {});

      var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
      if (options.filterDuplicates) {
        directionType += '_NO_DUPLICATE';
      }

      var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
      var cursorTarget = cursorTransaction.objectStore(this.storeName);
      if (options.index) {
        cursorTarget = cursorTarget.index(options.index);
      }

      var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
      cursorRequest.onerror = options.onError;
      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          callback(cursor.value, cursor, cursorTransaction);
          cursor['continue']();
        } else {
          options.onEnd && options.onEnd() || callback(null, cursor, cursorTransaction)
        }
      };
    }

    /* key ranges */
    // TODO: implement

  };

  /** helpers **/

  var noop = function () {
  };
  var empty = {};
  var mixin = function (target, source) {
    var name, s;
    for (name in source) {
      s = source[name];
      if (s !== empty[name] && s !== target[name]) {
        target[name] = s;
      }
    }
    return target;
  };
  var hitch = function (scope, method) {
    if (!method) {
      method = scope;
      scope = null;
    }
    if (typeof method == "string") {
      scope = scope || window;
      if (!scope[method]) {
        throw(['method not found']);
      }
      return function () {
        return scope[method].apply(scope, arguments || []);
      };
    }
    return !scope ? method : function () {
      return method.apply(scope, arguments || []);
    };
  };

  return IDBStore;

}, this);
<|endoftext|>


<commit_before>import { RichEmbed } from 'discord.js';
import { sample } from 'lodash/collection';
import { isArray, isFunction, isString } from 'lodash/lang';
import { escapeRegExp } from 'lodash/string';
import ArgumentParser from '../../command/parsers/ArgumentParser';
import ClosureFilter from './ClosureFilter';
import CommandParser from '../../command/parsers/CommandParser';
import MarkdownFormatter from '../../utils/MarkdownFormatter';
import RegexFilter from './RegexFilter';
import Response from '../../command/responses/Response';

/**
 * Response type strings.
 * @type {Object}
 * @const
 * @ignore
 */
const RESPONSE_TYPES = {
  STRING: 'STRING',
  ARRAY: 'ARRAY',
  EMBED: 'EMBED',
  CUSTOM_RESPONSE: 'CUSTOM_RESPONSE',
  NO_RESPONSE: 'NO_RESPONSE',
};

/**
 * @desc Receives and dispatches messages.
 * @ignore
 */
export default class Dispatcher {
  /**
   * Constructor.
   * @param {Object} options - The configuration values for the dispatcher.
   * @param {Ghastly} options.client - The client this dispatcher is attached to.
   * @param {(string|function)} options.prefix - The prefix for this dispatcher.
   */
  constructor({ client, prefix }) {
    /**
     * The client this dispatcher is attached to.
     * @type {Ghastly}
     */
    this.client = client;

    /**
     * The raw prefix given as an option.
     * @type {(string|function)}
     * @private
     */
    this.prefix = isString(prefix) ? escapeRegExp(prefix.trim()) : prefix;

    /**
     * The prefix filter constructed from the raw prefix.
     * @type {?PrefixFilter}
     * @private
     */
    this.prefixFilter = null;

    client.once('ready', () => {
      this.prefixFilter = this.createPrefixFilter(this.prefix);
    });
    client.on('message', this.dispatch.bind(this));
    client.on('messageUpdate', this.dispatch.bind(this));
  }

  /**
   * Resolves a given response value to a type.
   * @param {*} response - The response to resolve.
   * @returns {?string} The type of the response, or `null` if not recognized.
   * @private
   */
  static resolveResponseType(response) {
    if (!response) {
      return RESPONSE_TYPES.NO_RESPONSE;
    } else if (isString(response)) {
      return RESPONSE_TYPES.STRING;
    } else if (isArray(response)) {
      return RESPONSE_TYPES.ARRAY;
    } else if (response instanceof RichEmbed) {
      return RESPONSE_TYPES.EMBED;
    } else if (response instanceof Response) {
      return RESPONSE_TYPES.CUSTOM_RESPONSE;
    }

    return null;
  }

  /**
   * @external {Message} https://discord.js.org/#/docs/main/stable/class/Message
   */

  /**
   * @external {Client#message} https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=message
   */

  /**
   * @external {Client#messageUpdate} https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=messageUpdate
   */

  /**
   * Emitted when a response could not be dispatched.
   * @event Ghastly#dispatchFail
   * @param {string} type - The type of failure encountered.
   * @param {Object} payload - An object containing context on the failure encountered.
   *   Contents vary depending on the type of the failure.
   */

  /**
   * Receives message update events and dispatches commands found in the messages.
   * @param {Message} message - A Discord.js `Message` object.
   * @param {Message} [newMessage] - A Discord.js `Message` object. Should be
   *   received only when the message event was an update.
   * @returns {Promise.<Dispatcher>} Promise resolving to the instance this method
   *   was called on.
   * @emits {Ghastly#dispatchFail} Emitted when a response could not be dispatched.
   * @listens {Client#message}
   * @listens {Client#messageUpdate}
   */
  async dispatch(message, newMessage) {
    if (this.shouldFilterEvent(message, newMessage)) {
      return this.client.emit('dispatchFail', 'eventFilter', { message, newMessage });
    }

    const contentMessage = newMessage || message;

    if (await this.shouldFilterPrefix(contentMessage)) {
      return this.client.emit('dispatchFail', 'prefixFilter', { message: contentMessage });
    }

    let parsedCommand;

    try {
      parsedCommand = CommandParser.parse(contentMessage, this.prefixFilter);
    } catch (error) {
      return this.client.emit('dispatchFail', 'parseCommand', { message: contentMessage, error });
    }

    const command = this.client.commands.get(parsedCommand.identifier);

    if (!command) {
      return this.client.emit('dispatchFail', 'unknownCommand', {
        message: contentMessage,
        command: parsedCommand.identifier,
      });
    }

    const context = {
      message: contentMessage,
      client: this.client,
      commands: this.client.commands,
      dispatch: response => this.dispatchResponse(contentMessage.channel, response),
      formatter: MarkdownFormatter,
      services: this.client.services,
    };

    let args;

    try {
      args = ArgumentParser.parse(command.parameters, parsedCommand.rawArgs);
    } catch (error) {
      return this.client.emit('dispatchFail', 'parseArguments', {
        message: contentMessage,
        command: parsedCommand.identifier,
        error,
      });
    }

    let response;

    try {
      const injectedServices = [...command.dependencies].reduce(
        (accumulated, [serviceName, contextName]) => {
          if (!this.client.services.has(serviceName)) {
            throw new Error(`Attempting to inject a non-existent service: ${serviceName}.`);
          }

          return {
            ...accumulated,
            [contextName]: this.client.services.get(serviceName),
          };
        },
        {},
      );

      response = await command.handle({ ...context, ...injectedServices, args });
    } catch (error) {
      return this.client.emit('dispatchFail', 'handlerError', {
        message: contentMessage,
        command: command.name,
        args,
        error,
      });
    }

    if (!response) {
      return this.client.emit('dispatchFail', 'middlewareFilter', {
        message: contentMessage,
        command: command.name,
        args,
      });
    }

    try {
      await this.dispatchResponse(contentMessage.channel, response);

      return this;
    } catch (error) {
      return this.client.emit('dispatchFail', 'dispatch', {
        message: contentMessage,
        command: command.name,
        args,
        response,
        error,
      });
    }
  }

  /**
   * Generates a prefix filter.
   * @param {(string|function)} prefix - The prefix.
   * @returns {PrefixFilter} The prefix filter.
   * @throws {TypeError} Thrown if the prefix is not a `string` or `function`.
   * @private
   */
  createPrefixFilter(prefix) {
    if (isString(prefix)) {
      if (/^@client$/i.test(prefix)) {
        return new RegexFilter(`^<@!?${this.client.user.id}>`);
      } else if (/^@me:.+/i.test(prefix)) {
        const prefixString = prefix.match(/@me:(.+)/)[1];
        const prefixRegex = new RegExp(`^${prefixString}`);

        return new ClosureFilter(({ author, content }) => (
          author.id === this.client.user.id && content.test(prefixRegex)
        ));
      }

      return new RegexFilter(`^${prefix}`);
    } else if (isFunction(prefix)) {
      return new ClosureFilter(prefix);
    }

    throw new TypeError('Prefix should be a string or function.');
  }

  /**
   * Determines if a message event should be filtered from the handler.
   * @param {Message} message - A Discord.js `Message` object.
   * @param {Message} [newMessage] - A Discord.js `Message` object. Should be
   *   received only when the message event was an update.
   * @returns {boolean} `true` if the message should be filtered, else `false`.
   * @private
   */
  shouldFilterEvent(message, newMessage) { // eslint-disable-line class-methods-use-this
    return newMessage && message.content === newMessage.content;
  }

  /**
   * Determines if a message should be filtered from the handler based on the
   *   dispatcher prefix.
   * @param {Message} message - The message contents.
   * @returns {boolean} `true` if the message should be filtered, else `false`.
   * @private
   */
  shouldFilterPrefix(message) {
    return !this.prefixFilter.test(message);
  }

  /**
   * @external {Channel} https://discord.js.org/#/docs/main/stable/class/Channel
   */

  /**
   * Dispatches the given response value.
   * @param {Channel} channel - The channel to send the response to.
   * @param {*} response - The response value.
   * @returns {Promise.<*, Error>} Promise resolving to a Discord.js `Message`
   *   object if a message was dispatched directly. If a custom response is
   *   dispatched, resolves to whatever the custom response handler returns.
   *   Resolves to `null` if no response is dispatched.
   * Rejects with an `Error` when the given response type is not recognized, or
   *   when an error is encountered while dispatching a value.
   * @private
   */
  async dispatchResponse(channel, response) {
    switch (this.constructor.resolveResponseType(response)) {
      case RESPONSE_TYPES.STRING:
        return channel.send(response);
      case RESPONSE_TYPES.ARRAY: {
        const choice = sample(response);

        return this.dispatchResponse(channel, choice);
      }
      case RESPONSE_TYPES.EMBED:
        return channel.send(response);
      case RESPONSE_TYPES.CUSTOM_RESPONSE:
        return response.respond();
      case RESPONSE_TYPES.NO_RESPONSE:
        return null;
      default:
        throw new Error('Returned value from command handler is not of a recognized type.');
    }
  }
}
<commit_msg>Require all prefixes to specify a RegExp prefix.<commit_after>import { RichEmbed } from 'discord.js';
import { sample } from 'lodash/collection';
import { isArray, isFunction, isRegExp, isString } from 'lodash/lang';
import { escapeRegExp } from 'lodash/string';
import ArgumentParser from '../../command/parsers/ArgumentParser';
import DeferredFilter from './DeferredFilter';
import CommandParser from '../../command/parsers/CommandParser';
import MarkdownFormatter from '../../utils/MarkdownFormatter';
import RegexFilter from './RegexFilter';
import Response from '../../command/responses/Response';

/**
 * One of the types that a prefix filter can be generated from.
 * @typedef {(string|RegExp|function)} PrefixType
 */

/**
 * Response type strings.
 * @type {Object}
 * @const
 * @ignore
 */
const RESPONSE_TYPES = {
  STRING: 'STRING',
  ARRAY: 'ARRAY',
  EMBED: 'EMBED',
  CUSTOM_RESPONSE: 'CUSTOM_RESPONSE',
  NO_RESPONSE: 'NO_RESPONSE',
};

/**
 * @desc Receives and dispatches messages.
 * @ignore
 */
export default class Dispatcher {
  /**
   * Constructor.
   * @param {Object} options - The configuration values for the dispatcher.
   * @param {Ghastly} options.client - The client this dispatcher is attached to.
   * @param {PrefixType} options.prefix - The prefix for this dispatcher.
   */
  constructor({ client, prefix }) {
    /**
     * The client this dispatcher is attached to.
     * @type {Ghastly}
     */
    this.client = client;

    /**
     * The raw prefix given as an option.
     * @type {PrefixType}
     * @private
     */
    this.prefix = isString(prefix) ? escapeRegExp(prefix.trim()) : prefix;

    /**
     * The prefix filter constructed from the raw prefix.
     * @type {?PrefixFilter}
     * @private
     */
    this.prefixFilter = null;

    client.once('ready', () => {
      this.prefixFilter = this.createPrefixFilter(this.prefix);
    });
    client.on('message', this.dispatch.bind(this));
    client.on('messageUpdate', this.dispatch.bind(this));
  }

  /**
   * Resolves a given response value to a type.
   * @param {*} response - The response to resolve.
   * @returns {?string} The type of the response, or `null` if not recognized.
   * @private
   */
  static resolveResponseType(response) {
    if (!response) {
      return RESPONSE_TYPES.NO_RESPONSE;
    } else if (isString(response)) {
      return RESPONSE_TYPES.STRING;
    } else if (isArray(response)) {
      return RESPONSE_TYPES.ARRAY;
    } else if (response instanceof RichEmbed) {
      return RESPONSE_TYPES.EMBED;
    } else if (response instanceof Response) {
      return RESPONSE_TYPES.CUSTOM_RESPONSE;
    }

    return null;
  }

  /**
   * @external {Message} https://discord.js.org/#/docs/main/stable/class/Message
   */

  /**
   * @external {Client#message} https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=message
   */

  /**
   * @external {Client#messageUpdate} https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=messageUpdate
   */

  /**
   * Emitted when a response could not be dispatched.
   * @event Ghastly#dispatchFail
   * @param {string} type - The type of failure encountered.
   * @param {Object} payload - An object containing context on the failure encountered.
   *   Contents vary depending on the type of the failure.
   */

  /**
   * Receives message update events and dispatches commands found in the messages.
   * @param {Message} message - A Discord.js `Message` object.
   * @param {Message} [newMessage] - A Discord.js `Message` object. Should be
   *   received only when the message event was an update.
   * @returns {Promise.<Dispatcher>} Promise resolving to the instance this method
   *   was called on.
   * @emits {Ghastly#dispatchFail} Emitted when a response could not be dispatched.
   * @listens {Client#message}
   * @listens {Client#messageUpdate}
   */
  async dispatch(message, newMessage) {
    if (this.shouldFilterEvent(message, newMessage)) {
      return this.client.emit('dispatchFail', 'eventFilter', { message, newMessage });
    }

    const contentMessage = newMessage || message;
    const prefixFilterResult = await this.prefixFilter.test(contentMessage);

    if (!prefixFilterResult) {
      return this.client.emit('dispatchFail', 'prefixFilter', { message: contentMessage });
    }

    let parsedCommand;

    try {
      // DeferredFilter produces a RegExp if message is valid command
      const prefix = this.prefixFilter instanceof DeferredFilter
        ? prefixFilterResult
        : this.prefixFilter.filter;

      parsedCommand = CommandParser.parse(contentMessage, prefix);
    } catch (error) {
      return this.client.emit('dispatchFail', 'parseCommand', { message: contentMessage, error });
    }

    const command = this.client.commands.get(parsedCommand.identifier);

    if (!command) {
      return this.client.emit('dispatchFail', 'unknownCommand', {
        message: contentMessage,
        command: parsedCommand.identifier,
      });
    }

    const context = {
      message: contentMessage,
      client: this.client,
      commands: this.client.commands,
      dispatch: response => this.dispatchResponse(contentMessage.channel, response),
      formatter: MarkdownFormatter,
      services: this.client.services,
    };

    let args;

    try {
      args = ArgumentParser.parse(command.parameters, parsedCommand.rawArgs);
    } catch (error) {
      return this.client.emit('dispatchFail', 'parseArguments', {
        message: contentMessage,
        command: parsedCommand.identifier,
        error,
      });
    }

    let response;

    try {
      const injectedServices = [...command.dependencies].reduce(
        (accumulated, [serviceName, contextName]) => {
          if (!this.client.services.has(serviceName)) {
            throw new Error(`Attempting to inject a non-existent service: ${serviceName}.`);
          }

          return {
            ...accumulated,
            [contextName]: this.client.services.get(serviceName),
          };
        },
        {},
      );

      response = await command.handle({ ...context, ...injectedServices, args });
    } catch (error) {
      return this.client.emit('dispatchFail', 'handlerError', {
        message: contentMessage,
        command: command.name,
        args,
        error,
      });
    }

    if (!response) {
      return this.client.emit('dispatchFail', 'middlewareFilter', {
        message: contentMessage,
        command: command.name,
        args,
      });
    }

    try {
      await this.dispatchResponse(contentMessage.channel, response);

      return this;
    } catch (error) {
      return this.client.emit('dispatchFail', 'dispatch', {
        message: contentMessage,
        command: command.name,
        args,
        response,
        error,
      });
    }
  }

  /**
   * Generates a prefix filter.
   * @param {PrefixType} prefix - The prefix.
   * @returns {PrefixFilter} The prefix filter.
   * @throws {TypeError} Thrown if the prefix is not a `string` or `function`.
   * @private
   */
  createPrefixFilter(prefix) {
    if (isString(prefix)) {
      if (/^@client$/i.test(prefix)) {
        return new RegexFilter(new RegExp(`^<@!?${this.client.user.id}>`));
      } else if (/^@me:.+/i.test(prefix)) {
        const prefixString = prefix.match(/@me:(.+)/)[1];
        const prefixRegex = new RegExp(`^${prefixString}`);

        return new DeferredFilter(({ author }) => (
          author.id === this.client.user.id && prefixRegex
        ));
      }

      return new RegexFilter(new RegExp(`^${prefix}`));
    } else if (isRegExp(prefix)) {
      const matches = prefix.toString().match(/^\/(.+)\/(\w+)?$/);

      return new RegexFilter(new RegExp(`^${matches[1]}`, matches[2]));
    } else if (isFunction(prefix)) {
      return new DeferredFilter(prefix);
    }

    throw new TypeError('Prefix should be a string or function.');
  }

  /**
   * Determines if a message event should be filtered from the handler.
   * @param {Message} message - A Discord.js `Message` object.
   * @param {Message} [newMessage] - A Discord.js `Message` object. Should be
   *   received only when the message event was an update.
   * @returns {boolean} `true` if the message should be filtered, else `false`.
   * @private
   */
  shouldFilterEvent(message, newMessage) { // eslint-disable-line class-methods-use-this
    return newMessage && message.content === newMessage.content;
  }

  /**
   * @external {Channel} https://discord.js.org/#/docs/main/stable/class/Channel
   */

  /**
   * Dispatches the given response value.
   * @param {Channel} channel - The channel to send the response to.
   * @param {*} response - The response value.
   * @returns {Promise.<*, Error>} Promise resolving to a Discord.js `Message`
   *   object if a message was dispatched directly. If a custom response is
   *   dispatched, resolves to whatever the custom response handler returns.
   *   Resolves to `null` if no response is dispatched.
   * Rejects with an `Error` when the given response type is not recognized, or
   *   when an error is encountered while dispatching a value.
   * @private
   */
  async dispatchResponse(channel, response) {
    switch (this.constructor.resolveResponseType(response)) {
      case RESPONSE_TYPES.STRING:
        return channel.send(response);
      case RESPONSE_TYPES.ARRAY: {
        const choice = sample(response);

        return this.dispatchResponse(channel, choice);
      }
      case RESPONSE_TYPES.EMBED:
        return channel.send(response);
      case RESPONSE_TYPES.CUSTOM_RESPONSE:
        return response.respond();
      case RESPONSE_TYPES.NO_RESPONSE:
        return null;
      default:
        throw new Error('Returned value from command handler is not of a recognized type.');
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');

// Scroll to the element, so it would be positioned in the viewport
//
//  - el    - Element to scroll
//  - ratio - 0...1 offset (1..100%) of element center from viewport top
//            e.g. 0.5 means it should position element to the middle of the screen
//
function scrollIntoView(el, coef) {
  // 1. The top line of the element should always be lower than navbar
  // 2. The middle line of the element should be located at coef*viewport_height (if possible)
  //
  var el_top = el.offset().top;
  var el_h   = el.height();
  var win_h  = $(window).height();
  var nav_h  = $('.navbar').height();

  $(window).scrollTop(Math.min(
    el_top - nav_h,
    (el_top + el_h / 2) - nav_h - (win_h - nav_h) * coef
  ));
}

/////////////////////////////////////////////////////////////////////
// init on page load
//
N.wire.once('navigate.done:' + module.apiPath, function page_setup(data) {

  // Exclude click
  //
  N.wire.on(module.apiPath + ':exclude', function exclude() {
    let params = {};

    return Promise.resolve()
      .then(() => N.io.rpc('forum.index.exclude.sections', {}))
      .then(res => {
        _.assign(params, res);
        return N.wire.emit('forum.index.sections_exclude_dlg', params);
      })
      .then(() => N.io.rpc('forum.index.exclude.update', { sections_ids: params.selected }))
      .then(() => N.wire.emit('navigate.reload'));
  });

  var anchor = data.anchor || '';

  if (anchor.match(/^#cat\d+$/)) {
    var el = $('.forum-section' + anchor);

    if (el.length) {
      // override automatic scroll to an anchor in the navigator
      data.no_scroll = true;

      // It's a section (user clicks on a levelup button in it), so it
      // should be positioned 30% from top of the screen and highlighed,
      // so user could see where exactly did she navigate from.
      //
      scrollIntoView(el, 0.3);
      el.addClass('forum-section__m-highlight');

      return;
    }
  }
});
<commit_msg>Fix anchor positioning on index page<commit_after>'use strict';

const _ = require('lodash');

// Scroll to the element, so it would be positioned in the viewport
//
//  - el    - Element to scroll
//  - ratio - 0...1 offset (1..100%) of element center from viewport top
//            e.g. 0.5 means it should position element to the middle of the screen
//
function scrollIntoView(el, coef) {
  // 1. The top line of the element should always be lower than navbar
  // 2. The middle line of the element should be located at coef*viewport_height (if possible)
  //
  var el_top = el.offset().top;
  var el_h   = el.height();
  var win_h  = $(window).height();
  var nav_h  = $('.navbar').height();

  $(window).scrollTop(Math.min(
    el_top - nav_h,
    (el_top + el_h / 2) - nav_h - (win_h - nav_h) * coef
  ));
}

/////////////////////////////////////////////////////////////////////
// init on page load
//
N.wire.on('navigate.done:' + module.apiPath, function page_setup(data) {
  var anchor = data.anchor || '';

  if (anchor.match(/^#cat\d+$/)) {
    var el = $('.forum-section' + anchor);

    if (el.length) {
      // override automatic scroll to an anchor in the navigator
      data.no_scroll = true;

      // It's a section (user clicks on a levelup button in it), so it
      // should be positioned 30% from top of the screen and highlighed,
      // so user could see where exactly did she navigate from.
      //
      scrollIntoView(el, 0.3);
      el.addClass('forum-section__m-highlight');

      return;
    }
  }
});

N.wire.once('navigate.done:' + module.apiPath, function page_once() {

  // Exclude click
  //
  N.wire.on(module.apiPath + ':exclude', function exclude() {
    let params = {};

    return Promise.resolve()
      .then(() => N.io.rpc('forum.index.exclude.sections', {}))
      .then(res => {
        _.assign(params, res);
        return N.wire.emit('forum.index.sections_exclude_dlg', params);
      })
      .then(() => N.io.rpc('forum.index.exclude.update', { sections_ids: params.selected }))
      .then(() => N.wire.emit('navigate.reload'));
  });
});
<|endoftext|>


<commit_before>(function (root, factory) {
  if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.BackboneTurboWhere = factory();
  }
}(this, function () {

  /**
   * TurboWhere library
   *
   * @param {Backbone.Collection} collection
   */
  function TurboWhere(collection) {
    this.collection = collection;
    this.indexes = {};
  }

  /**
   * Groups the models by the attribute
   *
   * @param  {Backbone.Collection} collection
   * @param  {String} attr
   * @return {Object}
   */
  function grouper(collection, attr) {
    return collection.reduce(function (memo, model) {
      var val = model.get(attr);
      memo[val] = memo[val] || [];
      memo[val].push(model.id);
      return memo;
    }, {});
  }

  /**
   * Remove the element from the array
   *
   * @param  {Array} array
   * @param  {*}     value
   * @return {Array}
   */
  function removeFromArray(array, value) {
    var position = array.indexOf(value);
    if (~position) array.splice(position, 1);
    return array;
  }

  /**
   * Index setter
   *
   * @param {String} attr
   * @param {Object} index {1: [22, 23, 24], 2: [25, 26, 27]}
   */
  TurboWhere.prototype.setIndex = function (attr, index) {
    this.indexes[attr] = index;
  };

  /**
   * Index getter
   *
   * @param  {String} attr
   * @return {Object} index {1: [22, 23, 24], 2: [25, 26, 27]}
   */
  TurboWhere.prototype.getIndex = function (attr) {
    return this.indexes[attr] || {};
  };

  /**
   * Return true if the index exists
   *
   * @param  {String}  attr
   * @return {Boolean}
   */
  TurboWhere.prototype.hasIndex = function (attr) {
    return !!this.indexes[attr];
  };

  /**
   * Returns a function that builds the index for the given attributes
   *
   * @param  {Array<String>} attrs
   * @return {Function}
   */
  TurboWhere.prototype.indexBuilder = function (attrs) {
    var self = this;

    return function () {
      attrs.forEach(function (attr) {
        self.setIndex(attr, grouper(self.collection, attr));
      });
    };
  };

  /**
   * Return the models using the index
   *
   * @param  {String} attr
   * @param  {*}      value
   * @param  {Boolean} first
   * @return {Array<Backbone.Model>}
   */
  TurboWhere.prototype.getModels = function (attr, value, first) {
    var self = this;
    var ids = this.getIndex(attr)[value] || [];

    if (first) return ids[0] ? self.collection.get(ids[0]) : void 0;
    return ids.map(function (id) {
      return self.collection.get(id);
    });
  };

  /**
   * Like Backbone.Collection.prototype.where but uses the index if available
   *
   * @param  {Object}  filter {project_id: 1}
   * @param  {Boolean} first
   * @return {Array<Backbone.Model>}
   */
  TurboWhere.prototype.turboWhere = function (filter, first) {
    var keys = filter === Object(filter) ? Object.keys(filter) : [];

    if (keys.length === 1 && this.hasIndex(keys[0])) {
      return this.getModels(keys[0], filter[keys[0]], first);
    } else {
      return this.collection.constructor.prototype.where.call(this.collection, filter, first);
    }
  };

  /**
   * On collection add event
   *
   * @param  {Backbone.Model} model
   */
  TurboWhere.prototype._onAdd = function (model) {
    var self = this;

    Object.keys(this.indexes).forEach(function (key) {
      var ids = self.indexes[key][model.get(key)];
      ids = ids || [];
      ids.push(model.id);
      self.indexes[key][model.get(key)] = ids;
    });
  };

  /**
   * On collection remove event
   *
   * @param  {Backbone.Model} model
   */
  TurboWhere.prototype._onRemove = function (model) {
    var self = this;

    Object.keys(this.indexes).forEach(function (key) {
      var ids = self.indexes[key][model.get(key)];
      ids = removeFromArray(ids, model.id);
      self.indexes[key][model.get(key)] = ids;
    });
  };

  /**
   * On collection change event
   *
   * @param  {Backbone.Model} model
   */
  TurboWhere.prototype._onChange = function (model) {
    var self = this;

    Object.keys(model.changed).forEach(function (key) {
      if (self.hasIndex(key)) {
        var previous = model.previous(key);
        var tmp;

        tmp = self.indexes[key][previous];
        tmp = removeFromArray(tmp, model.id);
        self.indexes[key][previous] = tmp;

        var current = model.get(key);

        tmp = self.indexes[key][current];
        tmp = tmp || [];
        tmp.push(model.id);
        self.indexes[key][current] = tmp;
      }
    });
  };

  /**
   * TurboWhere setup API
   * Call it on initialize
   *
   * @param  {Backbone.Collection} collection
   * @param  {Array<String>} indexes ['user_id', 'project_id']
   */
  function setupIndexes(collection, indexes) {
    var tw = new TurboWhere(collection);

    collection.listenTo(collection, 'reset', tw.indexBuilder(indexes));
    collection.listenTo(collection, 'add', tw._onAdd.bind(tw));
    collection.listenTo(collection, 'remove', tw._onRemove.bind(tw));
    collection.listenTo(collection, 'change', tw._onChange.bind(tw));

    collection.__tw__ = tw;
    collection.where = function (attrs, first) {
      return tw.turboWhere(attrs, first);
    };
  }

  return {setupIndexes: setupIndexes};

}));
<commit_msg>Fixed case of change event without model<commit_after>(function (root, factory) {
  if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.BackboneTurboWhere = factory();
  }
}(this, function () {

  /**
   * TurboWhere library
   *
   * @param {Backbone.Collection} collection
   */
  function TurboWhere(collection) {
    this.collection = collection;
    this.indexes = {};
  }

  /**
   * Groups the models by the attribute
   *
   * @param  {Backbone.Collection} collection
   * @param  {String} attr
   * @return {Object}
   */
  function grouper(collection, attr) {
    return collection.reduce(function (memo, model) {
      var val = model.get(attr);
      memo[val] = memo[val] || [];
      memo[val].push(model.id);
      return memo;
    }, {});
  }

  /**
   * Remove the element from the array
   *
   * @param  {Array} array
   * @param  {*}     value
   * @return {Array}
   */
  function removeFromArray(array, value) {
    var position = array.indexOf(value);
    if (~position) array.splice(position, 1);
    return array;
  }

  /**
   * Index setter
   *
   * @param {String} attr
   * @param {Object} index {1: [22, 23, 24], 2: [25, 26, 27]}
   */
  TurboWhere.prototype.setIndex = function (attr, index) {
    this.indexes[attr] = index;
  };

  /**
   * Index getter
   *
   * @param  {String} attr
   * @return {Object} index {1: [22, 23, 24], 2: [25, 26, 27]}
   */
  TurboWhere.prototype.getIndex = function (attr) {
    return this.indexes[attr] || {};
  };

  /**
   * Return true if the index exists
   *
   * @param  {String}  attr
   * @return {Boolean}
   */
  TurboWhere.prototype.hasIndex = function (attr) {
    return !!this.indexes[attr];
  };

  /**
   * Returns a function that builds the index for the given attributes
   *
   * @param  {Array<String>} attrs
   * @return {Function}
   */
  TurboWhere.prototype.indexBuilder = function (attrs) {
    var self = this;

    return function () {
      attrs.forEach(function (attr) {
        self.setIndex(attr, grouper(self.collection, attr));
      });
    };
  };

  /**
   * Return the models using the index
   *
   * @param  {String} attr
   * @param  {*}      value
   * @param  {Boolean} first
   * @return {Array<Backbone.Model>}
   */
  TurboWhere.prototype.getModels = function (attr, value, first) {
    var self = this;
    var ids = this.getIndex(attr)[value] || [];

    if (first) return ids[0] ? self.collection.get(ids[0]) : void 0;
    return ids.map(function (id) {
      return self.collection.get(id);
    });
  };

  /**
   * Like Backbone.Collection.prototype.where but uses the index if available
   *
   * @param  {Object}  filter {project_id: 1}
   * @param  {Boolean} first
   * @return {Array<Backbone.Model>}
   */
  TurboWhere.prototype.turboWhere = function (filter, first) {
    var keys = filter === Object(filter) ? Object.keys(filter) : [];

    if (keys.length === 1 && this.hasIndex(keys[0])) {
      return this.getModels(keys[0], filter[keys[0]], first);
    } else {
      return this.collection.constructor.prototype.where.call(this.collection, filter, first);
    }
  };

  /**
   * On collection add event
   *
   * @param  {Backbone.Model} model
   */
  TurboWhere.prototype._onAdd = function (model) {
    var self = this;

    Object.keys(this.indexes).forEach(function (key) {
      var ids = self.indexes[key][model.get(key)];
      ids = ids || [];
      ids.push(model.id);
      self.indexes[key][model.get(key)] = ids;
    });
  };

  /**
   * On collection remove event
   *
   * @param  {Backbone.Model} model
   */
  TurboWhere.prototype._onRemove = function (model) {
    var self = this;

    Object.keys(this.indexes).forEach(function (key) {
      var ids = self.indexes[key][model.get(key)];
      ids = removeFromArray(ids, model.id);
      self.indexes[key][model.get(key)] = ids;
    });
  };

  /**
   * On collection change event
   *
   * @param  {Backbone.Model} model
   */
  TurboWhere.prototype._onChange = function (model) {
    var self = this;

    if (!model) return;

    Object.keys(model.changed).forEach(function (key) {
      if (self.hasIndex(key)) {
        var previous = model.previous(key);
        var tmp;

        tmp = self.indexes[key][previous];
        tmp = removeFromArray(tmp, model.id);
        self.indexes[key][previous] = tmp;

        var current = model.get(key);

        tmp = self.indexes[key][current];
        tmp = tmp || [];
        tmp.push(model.id);
        self.indexes[key][current] = tmp;
      }
    });
  };

  /**
   * TurboWhere setup API
   * Call it on initialize
   *
   * @param  {Backbone.Collection} collection
   * @param  {Array<String>} indexes ['user_id', 'project_id']
   */
  function setupIndexes(collection, indexes) {
    var tw = new TurboWhere(collection);

    collection.listenTo(collection, 'reset', tw.indexBuilder(indexes));
    collection.listenTo(collection, 'add', tw._onAdd.bind(tw));
    collection.listenTo(collection, 'remove', tw._onRemove.bind(tw));
    collection.listenTo(collection, 'change', tw._onChange.bind(tw));

    collection.__tw__ = tw;
    collection.where = function (attrs, first) {
      return tw.turboWhere(attrs, first);
    };
  }

  return {setupIndexes: setupIndexes};
}));
<|endoftext|>