<commit_before>(function() {
  //functions managing & updating the game state data, consisting of the `store` object and
  //visualization in the browser.

  var Diversibee = {};
  var paintCellType;

  /**
   * Private methods
   */
  function setUpBoardState(cellsWide, cellsHigh) {
    // initializes board state

    var cells = [],
        distanceFromSeedCell,
        isForest,
        i,

        //The likelihood a cell contains a 'seed'; probability bound 0 <= seedRate <= 1
        seedRate = 0.02,

        //Cells containing an initial 'seed'. Seeds are used to determine the inital forest growth
        seededCells = generateUniqueRandomCells(cellsHigh, cellsWide, (cellsHigh * cellsWide * seedRate));

    for (i = 0; i < cellsWide * cellsHigh; i++) {
      distanceFromSeedCell = distancefromSeed(i, cellsWide, seededCells);

      //Does this cell initially become a forest based on the cell's seed proximity
      isForest = shouldGrowForest(distanceFromSeedCell);

      if (isForest) {
        setCellType(i, cells, 'forest');
      } else {
        setCellType(i, cells, 'grass');
      }
    }

    return cells;
  }

  function setUpBoardStateLv1(cellsWide, cellsHigh) {
    //Initializes board state for level one

    var cells = [];

    for (i = 0; i < cellsWide * cellsHigh; i++) {
      addToCell(i, cells, 'forest');
    }

    return cells;
  }

  function setCellType(i, cells, typeName)
  {
    var currentTypeName = cells[ i ];

    addToTypeCount(Diversibee.store.typeCount[ currentTypeName ], -1);
    addToTypeCount(Diversibee.store.typeCount[ typeName ], 1);

    cells[ i ] = { type: typeName };
  }

  // Add quantity to counter to typeName in store
  // Maintains bounds on type quantities
  function addToTypeCount(typeName, quantity)
  {
    var currentQuantity = Diversibee.store.typeCount[ typeName ];

    if (!currentQuantity) {
      console.log(
              'addToTypeCount(): ' +
                  typeName +
              ' not recognized');
      return;
    }

    currentQuantity += quantity;
    if (currentQuantity < 0)
    {
      currentQuantity = 0;
    }

    Diversibee.store.typeCount[ typeName ] = currentQuantity;
  }

  function setAnimation(i) {
    //set up the animation for cell i

    Diversibee.store.mapCell[i] = new createjs.Sprite(Diversibee.store.spriteSheet, Diversibee.store.state[i].type);
    Diversibee.store.mapCell[i].x = 20 * (i % Diversibee.store.width);
    Diversibee.store.mapCell[i].y = 20 * Math.floor(i / Diversibee.store.width);

    Diversibee.store.mapCell[i].addEventListener('mousedown', handleCellClick(i));
    Diversibee.store.mapCell[i].addEventListener('mouseover', handleCellMouseOver(i));

    Diversibee.store.stage.addChild(Diversibee.store.mapCell[i]);
    Diversibee.store.mapCell[i].play(Diversibee.store.state[i].type);
  }

  function handleCellClick(i) {
    return function(e) {
      paintCellType = Diversibee.store.state[i].type === 'blueberries' ? 'forest' : 'blueberries';
      paintCell(i);
    };
  }

  function handleCellMouseOver(i) {
    return function(e) {
      if (e.nativeEvent.buttons === 1 || e.nativeEvent.buttons === 3) {
        paintCell(i);
      }
    };
  }

  function paintCell(i) {
    setCellType(i, Diversibee.store.state, paintCellType);
    setAnimation(i);
    repaintBoard();
  }

  function repaintBoard() {
    //re-paints the board state

    if (Diversibee.store.animationLoop) {
      clearInterval(Diversibee.store.animationLoop);
    }

    Diversibee.store.stage.update();
    Diversibee.store.animationLoop = setInterval(function() {Diversibee.store.stage.update();}, 300);
  }

  function addToProfits(income) {
    //update the farmer's bank account

    Diversibee.store.profit += income;
    document.getElementById('profit-value').innerHTML = '$' + Diversibee.store.cash;
  }

  function addCellProfitsToProfits(i, neighbours) {
    //collect profits from cell i, modified by its nearest neghbours

    addToProfits(Profits.basicProfits(neighbours));
  }

  function calculateLevelOneProfit()
  {
    var blueberryCount = Diversibee.store.typeCount.blueberries;
    var treeCount = Diversibee.store.typeCount.forest;

    return blueberryCount * treeCount;
  }

  function updateProfitLv1()
  {
    var profits = calculateLevelOneProfit();

    Diversibee.store.profit = profits;
    document.getElementById('profit-value').innerHTML = '$' + profits;
  }

  function distancefromSeed(cell, width, seededCells) {
    //Returns the distance from a cell to the closest seed (seeds are given as an array of cell locations)
    var distance = Number.MAX_SAFE_INTEGER,
        i,
        temp_dist;

    for (i = 0; i < seededCells.length; i++) {
      temp_dist = distanceBetweenCells(seededCells[i], cell, width);
      distance = Math.min(distance, temp_dist);
    }

    return distance;
  }

  function distanceBetweenCells(cellA, cellB, boardWidth) {
    //returns the distance between cell A and cell B

    var xDiff = (cellA % boardWidth) - (cellB % boardWidth),
        yDiff = (Math.floor(cellA / boardWidth)) - (Math.floor(cellB / boardWidth));
    return Math.abs(xDiff) + Math.abs(yDiff);
  }

  function shouldGrowForest(distanceFromSeed) {
    //A simple forest growth algorithm which clusters around seed locations

    return distanceFromSeed + distanceFromSeed * Math.random() < 5;
  }

  function generateUniqueRandomCells(height, width, numberOfCells) {
    //Returns an array of unique random cells

    var uniqueRandomCells = [],
        i,

        //we can safely choose the first unique random cell number
        cellNumber = Math.floor(Math.random() * (height * width));

    for (i = 0; i < numberOfCells; i++) {
      while (Utils.inArray(cellNumber, uniqueRandomCells)) {
        cellNumber = Math.floor(Math.random() * (height * width));
      }

      uniqueRandomCells.push(cellNumber);
    }

    return uniqueRandomCells;
  }

  /**
   * Public methods
   */
  Diversibee.init = function(width, height) {
    // Initialize the play area on load

    var i;

    //declare global store with default values
    Diversibee.store.width = width;
    Diversibee.store.height = height;

    // Stores current total number of each type, updated by addTypeToCell()
    Diversibee.store.typeCount = {
      grass: 0,
      forest: 0,
      blueberries: 0
    };
    Diversibee.store.state = setUpBoardState(Diversibee.store.width, Diversibee.store.height);
    Diversibee.store.animationData = {
      images: ['img/spriteSheet.png'],
      frames: {width:20, height:20},
      animations: {
        grass: [0, 3],
        forest: [4, 7],
        blueberries: [8, 11]
      }
    };

    Diversibee.store.stage = new createjs.Stage('board');
    Diversibee.store.stage.enableMouseOver(10);
    Diversibee.store.mapCell = [];
    Diversibee.store.spriteSheet = new createjs.SpriteSheet(Diversibee.store.animationData);
    Diversibee.store.w = Diversibee.store.stage.canvas.width;
    Diversibee.store.h = Diversibee.store.stage.canvas.height;
    Diversibee.store.profit = 1000;
    Diversibee.store.blueberryBuildPrice = 100;

    //set up initial cell animationsL
    for (i = 0; i < Diversibee.store.width * Diversibee.store.height; i++) {
      setAnimation(i);
    }

    //paint the initial state of the board
    repaintBoard();
  };

  Diversibee.store = {};

  window.Diversibee = Diversibee;
})();
<commit_msg>Update profit tied to mouse input<commit_after>(function() {
  //functions managing & updating the game state data, consisting of the `store` object and
  //visualization in the browser.

  var Diversibee = {};
  var paintCellType;

  /**
   * Private methods
   */
  function setUpBoardState(cellsWide, cellsHigh) {
    // initializes board state

    var cells = [],
        distanceFromSeedCell,
        isForest,
        i,

        //The likelihood a cell contains a 'seed'; probability bound 0 <= seedRate <= 1
        seedRate = 0.02,

        //Cells containing an initial 'seed'. Seeds are used to determine the inital forest growth
        seededCells = generateUniqueRandomCells(cellsHigh, cellsWide, (cellsHigh * cellsWide * seedRate));

    for (i = 0; i < cellsWide * cellsHigh; i++) {
      distanceFromSeedCell = distancefromSeed(i, cellsWide, seededCells);

      //Does this cell initially become a forest based on the cell's seed proximity
      isForest = shouldGrowForest(distanceFromSeedCell);

      if (isForest) {
        setCellType(i, cells, 'forest');
      } else {
        setCellType(i, cells, 'grass');
      }
    }

    return cells;
  }

  function setUpBoardStateLv1(cellsWide, cellsHigh) {
    //Initializes board state for level one

    var cells = [];

    for (i = 0; i < cellsWide * cellsHigh; i++) {
      addToCell(i, cells, 'forest');
    }

    return cells;
  }

  function setCellType(i, cells, typeName)
  {
    var currentTypeName = cells[ i ];

    addToTypeCount(Diversibee.store.typeCount[ currentTypeName ], -1);
    addToTypeCount(Diversibee.store.typeCount[ typeName ], 1);

    cells[ i ] = { type: typeName };
  }

  // Add quantity to counter to typeName in store
  // Maintains bounds on type quantities
  function addToTypeCount(typeName, quantity)
  {
    var currentQuantity = Diversibee.store.typeCount[ typeName ];

    if (!currentQuantity) {
      console.log(
              'addToTypeCount(): ' +
                  typeName +
              ' not recognized');
      return;
    }

    currentQuantity += quantity;
    if (currentQuantity < 0)
    {
      currentQuantity = 0;
    }

    Diversibee.store.typeCount[ typeName ] = currentQuantity;
  }

  function setAnimation(i) {
    //set up the animation for cell i

    Diversibee.store.mapCell[i] = new createjs.Sprite(Diversibee.store.spriteSheet, Diversibee.store.state[i].type);
    Diversibee.store.mapCell[i].x = 20 * (i % Diversibee.store.width);
    Diversibee.store.mapCell[i].y = 20 * Math.floor(i / Diversibee.store.width);

    Diversibee.store.mapCell[i].addEventListener('mousedown', handleCellClick(i));
    Diversibee.store.mapCell[i].addEventListener('mouseover', handleCellMouseOver(i));

    Diversibee.store.stage.addChild(Diversibee.store.mapCell[i]);
    Diversibee.store.mapCell[i].play(Diversibee.store.state[i].type);
  }

  function handleCellClick(i) {
    return function(e) {
      paintCellType = Diversibee.store.state[i].type === 'blueberries' ? 'forest' : 'blueberries';
      paintCell(i);
      UpdateProfitLv1();
    };
  }

  function handleCellMouseOver(i) {
    return function(e) {
      if (e.nativeEvent.buttons === 1 || e.nativeEvent.buttons === 3) {
        paintCell(i);
      }
      UpdateProfitLv1();
    };
  }

  function paintCell(i) {
    setCellType(i, Diversibee.store.state, paintCellType);
    setAnimation(i);
    repaintBoard();
  }

  function repaintBoard() {
    //re-paints the board state

    if (Diversibee.store.animationLoop) {
      clearInterval(Diversibee.store.animationLoop);
    }

    Diversibee.store.stage.update();
    Diversibee.store.animationLoop = setInterval(function() {Diversibee.store.stage.update();}, 300);
  }

  function addToProfits(income) {
    //update the farmer's bank account

    Diversibee.store.profit += income;
    document.getElementById('profit-value').innerHTML = '$' + Diversibee.store.cash;
  }

  function addCellProfitsToProfits(i, neighbours) {
    //collect profits from cell i, modified by its nearest neghbours

    addToProfits(Profits.basicProfits(neighbours));
  }

  function calculateLevelOneProfit()
  {
    var blueberryCount = Diversibee.store.typeCount.blueberries;
    var treeCount = Diversibee.store.typeCount.forest;

    return blueberryCount * treeCount;
  }

  function updateProfitLv1()
  {
    var profits = calculateLevelOneProfit();

    Diversibee.store.profit = profits;
    document.getElementById('profit-value').innerHTML = '$' + profits;
  }

  function distancefromSeed(cell, width, seededCells) {
    //Returns the distance from a cell to the closest seed (seeds are given as an array of cell locations)
    var distance = Number.MAX_SAFE_INTEGER,
        i,
        temp_dist;

    for (i = 0; i < seededCells.length; i++) {
      temp_dist = distanceBetweenCells(seededCells[i], cell, width);
      distance = Math.min(distance, temp_dist);
    }

    return distance;
  }

  function distanceBetweenCells(cellA, cellB, boardWidth) {
    //returns the distance between cell A and cell B

    var xDiff = (cellA % boardWidth) - (cellB % boardWidth),
        yDiff = (Math.floor(cellA / boardWidth)) - (Math.floor(cellB / boardWidth));
    return Math.abs(xDiff) + Math.abs(yDiff);
  }

  function shouldGrowForest(distanceFromSeed) {
    //A simple forest growth algorithm which clusters around seed locations

    return distanceFromSeed + distanceFromSeed * Math.random() < 5;
  }

  function generateUniqueRandomCells(height, width, numberOfCells) {
    //Returns an array of unique random cells

    var uniqueRandomCells = [],
        i,

        //we can safely choose the first unique random cell number
        cellNumber = Math.floor(Math.random() * (height * width));

    for (i = 0; i < numberOfCells; i++) {
      while (Utils.inArray(cellNumber, uniqueRandomCells)) {
        cellNumber = Math.floor(Math.random() * (height * width));
      }

      uniqueRandomCells.push(cellNumber);
    }

    return uniqueRandomCells;
  }

  /**
   * Public methods
   */
  Diversibee.init = function(width, height) {
    // Initialize the play area on load

    var i;

    //declare global store with default values
    Diversibee.store.width = width;
    Diversibee.store.height = height;

    // Stores current total number of each type, updated by addTypeToCell()
    Diversibee.store.typeCount = {
      grass: 0,
      forest: 0,
      blueberries: 0
    };
    Diversibee.store.state = setUpBoardState(Diversibee.store.width, Diversibee.store.height);
    Diversibee.store.animationData = {
      images: ['img/spriteSheet.png'],
      frames: {width:20, height:20},
      animations: {
        grass: [0, 3],
        forest: [4, 7],
        blueberries: [8, 11]
      }
    };

    Diversibee.store.stage = new createjs.Stage('board');
    Diversibee.store.stage.enableMouseOver(10);
    Diversibee.store.mapCell = [];
    Diversibee.store.spriteSheet = new createjs.SpriteSheet(Diversibee.store.animationData);
    Diversibee.store.w = Diversibee.store.stage.canvas.width;
    Diversibee.store.h = Diversibee.store.stage.canvas.height;
    Diversibee.store.profit = 1000;
    Diversibee.store.blueberryBuildPrice = 100;

    //set up initial cell animationsL
    for (i = 0; i < Diversibee.store.width * Diversibee.store.height; i++) {
      setAnimation(i);
    }

    //paint the initial state of the board
    repaintBoard();
  };

  Diversibee.store = {};

  window.Diversibee = Diversibee;
})();
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'
const AppConstants = require('../constants/appConstants')
const SiteUtil = require('../state/siteUtil')
const electron = require('electron')
const ipcMain = electron.ipcMain
const messages = require('../constants/messages')
const BrowserWindow = electron.BrowserWindow
const LocalShortcuts = require('../../app/localShortcuts')
const AppActions = require('../actions/appActions')
const firstDefinedValue = require('../lib/functional').firstDefinedValue
const Serializer = require('../dispatcher/serializer')

let appState

// TODO cleanup all this createWindow crap
function isModal (browserOpts) {
  // this needs some better checks
  return browserOpts.scrollbars === false
}

function navbarHeight () {
  // TODO there has to be a better way to get this or at least add a test
  return 75
}

const createWindow = (browserOpts, defaults) => {
  const parentWindowKey = browserOpts.parentWindowKey

  browserOpts.width = firstDefinedValue(browserOpts.width, browserOpts.innerWidth, defaults.width)
  // height and innerHeight are the frame webview size
  browserOpts.height = firstDefinedValue(browserOpts.height, browserOpts.innerHeight)
  if (isNaN(browserOpts.height)) {
    // no inner height so check outer height or use default
    browserOpts.height = firstDefinedValue(browserOpts.outerHeight, defaults.height)
  } else {
    // BrowserWindow height is window height so add navbar height
    browserOpts.height = browserOpts.height + navbarHeight()
  }

  browserOpts.x = firstDefinedValue(browserOpts.x, browserOpts.left, browserOpts.screenX)
  browserOpts.y = firstDefinedValue(browserOpts.y, browserOpts.top, browserOpts.screenY)
  delete browserOpts.left
  delete browserOpts.top

  const screen = electron.screen
  const primaryDisplay = screen.getPrimaryDisplay()
  const parentWindow = parentWindowKey ? BrowserWindow.fromId(parentWindowKey) : BrowserWindow.getFocusedWindow()
  const bounds = parentWindow ? parentWindow.getBounds() : primaryDisplay.bounds

  // position on screen should be relative to focused window
  // or the primary display if there is no focused window
  const display = screen.getDisplayNearestPoint(bounds)

  // if no parentWindow, x, y or center is defined then go ahead
  // and center it if it's smaller than the display width
  // typeof and isNaN are used because 0 is falsy
  if (!(parentWindow ||
      browserOpts.center === false ||
      browserOpts.x > 0 ||
      browserOpts.y > 0) &&
      browserOpts.width < display.bounds.width) {
    browserOpts.center = true
  } else {
    browserOpts.center = false
    // don't offset if focused window is at least as big as the screen it's on
    if (bounds.width >= display.bounds.width && bounds.height >= display.bounds.height) {
      browserOpts.x = firstDefinedValue(browserOpts.x, display.bounds.x)
      browserOpts.y = firstDefinedValue(browserOpts.y, display.bounds.y)
    } else {
      browserOpts.x = firstDefinedValue(browserOpts.x, bounds.x + defaults.windowOffset)
      browserOpts.y = firstDefinedValue(browserOpts.y, bounds.y + defaults.windowOffset)
    }

    // make sure the browser won't be outside the viewable area of any display
    // negative numbers aren't allowed so we don't need to worry about that
    const displays = screen.getAllDisplays()
    const maxX = Math.max(...displays.map((display) => { return display.bounds.x + display.bounds.width }))
    const maxY = Math.max(...displays.map((display) => { return display.bounds.y + display.bounds.height }))

    browserOpts.x = Math.min(browserOpts.x, maxX - defaults.windowOffset)
    browserOpts.y = Math.min(browserOpts.y, maxY - defaults.windowOffset)
  }

  const minWidth = isModal(browserOpts) ? defaults.minModalWidth : defaults.minWidth
  const minHeight = isModal(browserOpts) ? defaults.minModalHeight : defaults.minHeight

  // min width and height don't seem to work when the window is first created
  browserOpts.width = browserOpts.width < minWidth ? minWidth : browserOpts.width
  browserOpts.height = browserOpts.height < minHeight ? minHeight : browserOpts.height

  let mainWindow = new BrowserWindow(Object.assign({
    // smaller min size for "modal" windows
    minWidth,
    minHeight,
    // Neither a frame nor a titlebar
    // frame: false,
    // A frame but no title bar and windows buttons in titlebar 10.10 OSX and up only?
    titleBarStyle: 'hidden',
    autoHideMenuBar: true,
    webPreferences: defaults.webPreferences
  }, browserOpts))

  mainWindow.on('resize', function (evt) {
    // the default window size is whatever the last window resize was
    AppActions.setDefaultWindowSize(evt.sender.getSize())
  })

  mainWindow.on('close', function () {
    LocalShortcuts.unregister(mainWindow)
  })

  mainWindow.on('closed', function () {
    mainWindow = null
  })

  LocalShortcuts.register(mainWindow)
  return mainWindow
}

class AppStore {
  getState () {
    return appState
  }

  emitChange () {
    const stateJS = this.getState().toJS()
    BrowserWindow.getAllWindows().forEach(wnd =>
      wnd.webContents.send(messages.APP_STATE_CHANGE, stateJS))
  }
}

function windowDefaults () {
  setDefaultWindowSize()

  return {
    show: false,
    width: appState.get('defaultWindowWidth'),
    height: appState.get('defaultWindowHeight'),
    minWidth: 500,
    minHeight: 300,
    minModalHeight: 100,
    minModalWidth: 100,
    windowOffset: 20,
    webPreferences: {
      sharedWorker: true,
      partition: 'main-1'
    }
  }
}

/**
 * set the default width and height if they
 * haven't been initialized yet
 */
function setDefaultWindowSize () {
  const screen = electron.screen
  const primaryDisplay = screen.getPrimaryDisplay()
  if (!appState.get('defaultWindowWidth') && !appState.get('defaultWindowHeight')) {
    appState = appState.set('defaultWindowWidth', Math.floor(primaryDisplay.bounds.width / 2))
    appState = appState.set('defaultWindowHeight', Math.floor(primaryDisplay.bounds.height / 2))
  }
}

const appStore = new AppStore()

const handleAppAction = (action) => {
  switch (action.actionType) {
    case AppConstants.APP_SET_STATE:
      appState = action.appState
      appStore.emitChange()
      break
    case AppConstants.APP_NEW_WINDOW:
      const frameOpts = action.frameOpts && action.frameOpts.toJS()
      const browserOpts = (action.browserOpts && action.browserOpts.toJS()) || {}

      const mainWindow = createWindow(browserOpts, windowDefaults())
      if (action.restoredState) {
        mainWindow.webContents.once('dom-ready', () => {
          mainWindow.webContents.send('restore-state', action.restoredState)
        })
      }

      const currentWindows = appState.get('windows')
      appState = appState.set('windows', currentWindows.push(mainWindow.id))

      // initialize frames state
      let frames = []
      if (frameOpts) {
        if (frameOpts.forEach) {
          frames = frameOpts
        } else {
          frames.push(frameOpts)
        }
      }

      // pass the appState and frames into the query string for initialization
      // This seems kind of hacky, maybe there is a better way to make
      // sure that the Window has the app state before it renders?
      const queryString =
        'appState=' + encodeURIComponent(JSON.stringify(appState.toJS())) +
        '&frames=' + encodeURIComponent(JSON.stringify(frames))

      if (process.env.NODE_ENV === 'development') {
        mainWindow.loadURL('file://' + __dirname + '/../../app/index-dev.html?' + queryString)
      } else {
        mainWindow.loadURL('file://' + __dirname + '/../../app/index.html?' + queryString)
      }
      appStore.emitChange()

      mainWindow.show()
      break
    case AppConstants.APP_CLOSE_WINDOW:
      const appWindow = BrowserWindow.fromId(action.appWindowId)
      appWindow.close()

      const windows = appState.get('windows')
      appState = appState.set('windows', windows.delete(action.appWindowId))
      appStore.emitChange()
      break
    case AppConstants.APP_ADD_SITE:
      appState = appState.set('sites', SiteUtil.addSite(appState.get('sites'), action.frameProps, action.tag))
      appStore.emitChange()
      break
    case AppConstants.APP_REMOVE_SITE:
      appState = appState.set('sites', SiteUtil.removeSite(appState.get('sites'), action.frameProps, action.tag))
      appStore.emitChange()
      break
    case AppConstants.APP_SET_DEFAULT_WINDOW_SIZE:
      appState = appState.set('defaultWindowWidth', action.size[0])
      appState = appState.set('defaultWindowHeight', action.size[1])
      appStore.emitChange()
      break
    case AppConstants.APP_SET_DATA_FILE_ETAG:
      appState = appState.setIn([action.resourceName, 'etag'], action.etag)
      appStore.emitChange()
      break
    case AppConstants.APP_UPDATE_LAST_CHECK:
      appState = appState.setIn(['updates', 'lastCheckTimestamp'], (new Date()).getTime())
      appStore.emitChange()
      break
    case AppConstants.APP_SET_UPDATE_STATUS:
      if (action.status !== undefined) {
        appState = appState.setIn(['updates', 'status'], action.status)
      }
      // Auto reset back to false because it'll be set to true on each new check
      if (action.verbose !== undefined) {
        appState = appState.setIn(['updates', 'verbose'], action.verbose)
      }
      if (action.metadata !== undefined) {
        appState = appState.setIn(['updates', 'metadata'], action.metadata)
      }
      appStore.emitChange()
      break
    case AppConstants.APP_SET_RESOURCE_ENABLED:
      appState = appState.setIn([action.resourceName, 'enabled'], action.enabled)
      appStore.emitChange()
      break
    case AppConstants.APP_SET_DATA_FILE_LAST_CHECK:
      appState = appState.mergeIn([action.resourceName], {
        lastCheckVersion: action.lastCheckVersion,
        lastCheckDate: action.lastCheckDate
      })
      appStore.emitChange()
      break
    default:
  }
}

// Register callback to handle all updates
ipcMain.on(messages.APP_ACTION, (event, action) => {
  handleAppAction(Serializer.deserialize(action))
})

process.on(messages.APP_ACTION, handleAppAction)

module.exports = appStore
<commit_msg>Fix race condition on loading previous window state<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'
const AppConstants = require('../constants/appConstants')
const SiteUtil = require('../state/siteUtil')
const electron = require('electron')
const ipcMain = electron.ipcMain
const messages = require('../constants/messages')
const BrowserWindow = electron.BrowserWindow
const LocalShortcuts = require('../../app/localShortcuts')
const AppActions = require('../actions/appActions')
const firstDefinedValue = require('../lib/functional').firstDefinedValue
const Serializer = require('../dispatcher/serializer')

let appState

// TODO cleanup all this createWindow crap
function isModal (browserOpts) {
  // this needs some better checks
  return browserOpts.scrollbars === false
}

function navbarHeight () {
  // TODO there has to be a better way to get this or at least add a test
  return 75
}

const createWindow = (browserOpts, defaults) => {
  const parentWindowKey = browserOpts.parentWindowKey

  browserOpts.width = firstDefinedValue(browserOpts.width, browserOpts.innerWidth, defaults.width)
  // height and innerHeight are the frame webview size
  browserOpts.height = firstDefinedValue(browserOpts.height, browserOpts.innerHeight)
  if (isNaN(browserOpts.height)) {
    // no inner height so check outer height or use default
    browserOpts.height = firstDefinedValue(browserOpts.outerHeight, defaults.height)
  } else {
    // BrowserWindow height is window height so add navbar height
    browserOpts.height = browserOpts.height + navbarHeight()
  }

  browserOpts.x = firstDefinedValue(browserOpts.x, browserOpts.left, browserOpts.screenX)
  browserOpts.y = firstDefinedValue(browserOpts.y, browserOpts.top, browserOpts.screenY)
  delete browserOpts.left
  delete browserOpts.top

  const screen = electron.screen
  const primaryDisplay = screen.getPrimaryDisplay()
  const parentWindow = parentWindowKey ? BrowserWindow.fromId(parentWindowKey) : BrowserWindow.getFocusedWindow()
  const bounds = parentWindow ? parentWindow.getBounds() : primaryDisplay.bounds

  // position on screen should be relative to focused window
  // or the primary display if there is no focused window
  const display = screen.getDisplayNearestPoint(bounds)

  // if no parentWindow, x, y or center is defined then go ahead
  // and center it if it's smaller than the display width
  // typeof and isNaN are used because 0 is falsy
  if (!(parentWindow ||
      browserOpts.center === false ||
      browserOpts.x > 0 ||
      browserOpts.y > 0) &&
      browserOpts.width < display.bounds.width) {
    browserOpts.center = true
  } else {
    browserOpts.center = false
    // don't offset if focused window is at least as big as the screen it's on
    if (bounds.width >= display.bounds.width && bounds.height >= display.bounds.height) {
      browserOpts.x = firstDefinedValue(browserOpts.x, display.bounds.x)
      browserOpts.y = firstDefinedValue(browserOpts.y, display.bounds.y)
    } else {
      browserOpts.x = firstDefinedValue(browserOpts.x, bounds.x + defaults.windowOffset)
      browserOpts.y = firstDefinedValue(browserOpts.y, bounds.y + defaults.windowOffset)
    }

    // make sure the browser won't be outside the viewable area of any display
    // negative numbers aren't allowed so we don't need to worry about that
    const displays = screen.getAllDisplays()
    const maxX = Math.max(...displays.map((display) => { return display.bounds.x + display.bounds.width }))
    const maxY = Math.max(...displays.map((display) => { return display.bounds.y + display.bounds.height }))

    browserOpts.x = Math.min(browserOpts.x, maxX - defaults.windowOffset)
    browserOpts.y = Math.min(browserOpts.y, maxY - defaults.windowOffset)
  }

  const minWidth = isModal(browserOpts) ? defaults.minModalWidth : defaults.minWidth
  const minHeight = isModal(browserOpts) ? defaults.minModalHeight : defaults.minHeight

  // min width and height don't seem to work when the window is first created
  browserOpts.width = browserOpts.width < minWidth ? minWidth : browserOpts.width
  browserOpts.height = browserOpts.height < minHeight ? minHeight : browserOpts.height

  let mainWindow = new BrowserWindow(Object.assign({
    // smaller min size for "modal" windows
    minWidth,
    minHeight,
    // Neither a frame nor a titlebar
    // frame: false,
    // A frame but no title bar and windows buttons in titlebar 10.10 OSX and up only?
    titleBarStyle: 'hidden',
    autoHideMenuBar: true,
    webPreferences: defaults.webPreferences
  }, browserOpts))

  mainWindow.on('resize', function (evt) {
    // the default window size is whatever the last window resize was
    AppActions.setDefaultWindowSize(evt.sender.getSize())
  })

  mainWindow.on('close', function () {
    LocalShortcuts.unregister(mainWindow)
  })

  mainWindow.on('closed', function () {
    mainWindow = null
  })

  LocalShortcuts.register(mainWindow)
  return mainWindow
}

class AppStore {
  getState () {
    return appState
  }

  emitChange () {
    const stateJS = this.getState().toJS()
    BrowserWindow.getAllWindows().forEach(wnd =>
      wnd.webContents.send(messages.APP_STATE_CHANGE, stateJS))
  }
}

function windowDefaults () {
  setDefaultWindowSize()

  return {
    show: false,
    width: appState.get('defaultWindowWidth'),
    height: appState.get('defaultWindowHeight'),
    minWidth: 500,
    minHeight: 300,
    minModalHeight: 100,
    minModalWidth: 100,
    windowOffset: 20,
    webPreferences: {
      sharedWorker: true,
      partition: 'main-1'
    }
  }
}

/**
 * set the default width and height if they
 * haven't been initialized yet
 */
function setDefaultWindowSize () {
  const screen = electron.screen
  const primaryDisplay = screen.getPrimaryDisplay()
  if (!appState.get('defaultWindowWidth') && !appState.get('defaultWindowHeight')) {
    appState = appState.set('defaultWindowWidth', Math.floor(primaryDisplay.bounds.width / 2))
    appState = appState.set('defaultWindowHeight', Math.floor(primaryDisplay.bounds.height / 2))
  }
}

const appStore = new AppStore()

const handleAppAction = (action) => {
  switch (action.actionType) {
    case AppConstants.APP_SET_STATE:
      appState = action.appState
      appStore.emitChange()
      break
    case AppConstants.APP_NEW_WINDOW:
      const frameOpts = action.frameOpts && action.frameOpts.toJS()
      const browserOpts = (action.browserOpts && action.browserOpts.toJS()) || {}

      const mainWindow = createWindow(browserOpts, windowDefaults())
      if (action.restoredState) {
        mainWindow.webContents.once('did-finish-load', () => {
          mainWindow.webContents.send('restore-state', action.restoredState)
        })
      }

      const currentWindows = appState.get('windows')
      appState = appState.set('windows', currentWindows.push(mainWindow.id))

      // initialize frames state
      let frames = []
      if (frameOpts) {
        if (frameOpts.forEach) {
          frames = frameOpts
        } else {
          frames.push(frameOpts)
        }
      }

      // pass the appState and frames into the query string for initialization
      // This seems kind of hacky, maybe there is a better way to make
      // sure that the Window has the app state before it renders?
      const queryString =
        'appState=' + encodeURIComponent(JSON.stringify(appState.toJS())) +
        '&frames=' + encodeURIComponent(JSON.stringify(frames))

      if (process.env.NODE_ENV === 'development') {
        mainWindow.loadURL('file://' + __dirname + '/../../app/index-dev.html?' + queryString)
      } else {
        mainWindow.loadURL('file://' + __dirname + '/../../app/index.html?' + queryString)
      }
      appStore.emitChange()

      mainWindow.show()
      break
    case AppConstants.APP_CLOSE_WINDOW:
      const appWindow = BrowserWindow.fromId(action.appWindowId)
      appWindow.close()

      const windows = appState.get('windows')
      appState = appState.set('windows', windows.delete(action.appWindowId))
      appStore.emitChange()
      break
    case AppConstants.APP_ADD_SITE:
      appState = appState.set('sites', SiteUtil.addSite(appState.get('sites'), action.frameProps, action.tag))
      appStore.emitChange()
      break
    case AppConstants.APP_REMOVE_SITE:
      appState = appState.set('sites', SiteUtil.removeSite(appState.get('sites'), action.frameProps, action.tag))
      appStore.emitChange()
      break
    case AppConstants.APP_SET_DEFAULT_WINDOW_SIZE:
      appState = appState.set('defaultWindowWidth', action.size[0])
      appState = appState.set('defaultWindowHeight', action.size[1])
      appStore.emitChange()
      break
    case AppConstants.APP_SET_DATA_FILE_ETAG:
      appState = appState.setIn([action.resourceName, 'etag'], action.etag)
      appStore.emitChange()
      break
    case AppConstants.APP_UPDATE_LAST_CHECK:
      appState = appState.setIn(['updates', 'lastCheckTimestamp'], (new Date()).getTime())
      appStore.emitChange()
      break
    case AppConstants.APP_SET_UPDATE_STATUS:
      if (action.status !== undefined) {
        appState = appState.setIn(['updates', 'status'], action.status)
      }
      // Auto reset back to false because it'll be set to true on each new check
      if (action.verbose !== undefined) {
        appState = appState.setIn(['updates', 'verbose'], action.verbose)
      }
      if (action.metadata !== undefined) {
        appState = appState.setIn(['updates', 'metadata'], action.metadata)
      }
      appStore.emitChange()
      break
    case AppConstants.APP_SET_RESOURCE_ENABLED:
      appState = appState.setIn([action.resourceName, 'enabled'], action.enabled)
      appStore.emitChange()
      break
    case AppConstants.APP_SET_DATA_FILE_LAST_CHECK:
      appState = appState.mergeIn([action.resourceName], {
        lastCheckVersion: action.lastCheckVersion,
        lastCheckDate: action.lastCheckDate
      })
      appStore.emitChange()
      break
    default:
  }
}

// Register callback to handle all updates
ipcMain.on(messages.APP_ACTION, (event, action) => {
  handleAppAction(Serializer.deserialize(action))
})

process.on(messages.APP_ACTION, handleAppAction)

module.exports = appStore
<|endoftext|>


<commit_before>!function(e,t){function n(){var e='

Title

Text
<button class="cancel" tabIndex="2">Cancel</button><button class="confirm" tabIndex="1">OK</button>
',n=t.createElement("div");n.innerHTML=e,t.body.appendChild(n)}function o(t){var n=m(),o=n.querySelector("h2"),r=n.querySelector("p"),a=n.querySelector("button.cancel"),c=n.querySelector("button.confirm");if(o.innerHTML=b(t.title).split("\n").join("
"),r.innerHTML=b(t.text||"").split("\n").join("
"),t.text&&w(r),x(n.querySelectorAll(".icon")),t.type){for(var l=!1,s=0;s<d.length;s++)if(t.type===d[s]){l=!0;break}if(!l)return e.console.error("Unknown alert type: "+t.type),!1;var u=n.querySelector(".icon."+t.type);switch(w(u),t.type){case"success":g(u,"animate"),g(u.querySelector(".tip"),"animateSuccessTip"),g(u.querySelector(".long"),"animateSuccessLong");break;case"error":g(u,"animateErrorIcon"),g(u.querySelector(".x-mark"),"animateXMark");break;case"warning":g(u,"pulseWarning"),g(u.querySelector(".body"),"pulseWarningIns"),g(u.querySelector(".dot"),"pulseWarningIns")}}if(t.imageUrl){var f=n.querySelector(".icon.custom");f.style.backgroundImage="url("+t.imageUrl+")",w(f);var y=80,p=80;if(t.imageSize){var v=t.imageSize.split("x")[0],h=t.imageSize.split("x")[1];v&&h?(y=v,p=h,f.css({width:v+"px",height:h+"px"})):e.console.error("Parameter imageSize expects value with format WIDTHxHEIGHT, got "+t.imageSize)}f.setAttribute("style",f.getAttribute("style")+"width:"+y+"px; height:"+p+"px")}n.setAttribute("data-has-cancel-button",t.showCancelButton),t.showCancelButton?a.style.display="inline-block":x(a),t.cancelButtonText&&(a.innerHTML=b(t.cancelButtonText)),t.confirmButtonText&&(c.innerHTML=b(t.confirmButtonText)),c.style.backgroundColor=t.confirmButtonColor,i(c,t.confirmButtonColor),n.setAttribute("data-allow-ouside-click",t.allowOutsideClick);var S=t.doneFunction?!0:!1;n.setAttribute("data-has-done-function",S)}function r(e,t){e=String(e).replace(/[^0-9a-f]/gi,""),e.length<6&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),t=t||0;var n="#",o,r;for(r=0;3>r;r++)o=parseInt(e.substr(2*r,2),16),o=Math.round(Math.min(Math.max(0,o+o*t),255)).toString(16),n+=("00"+o).substr(o.length);return n}function a(e){var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?parseInt(t[1],16)+", "+parseInt(t[2],16)+", "+parseInt(t[3],16):null}function i(e,t){var n=a(t);e.style.boxShadow="0 0 2px rgba("+n+", 0.8), inset 0 0 0 1px rgba(0, 0, 0, 0.05)"}function c(){var e=m();B(y(),10),w(e),g(e,"showSweetAlert"),v(e,"hideSweetAlert"),I=t.activeElement;var n=e.querySelector("button.confirm");n.focus(),setTimeout(function(){g(e,"visible")},500)}function l(){var n=m();T(y(),5),T(n,5),v(n,"showSweetAlert"),g(n,"hideSweetAlert"),v(n,"visible");var o=n.querySelector(".icon.success");v(o,"animate"),v(o.querySelector(".tip"),"animateSuccessTip"),v(o.querySelector(".long"),"animateSuccessLong");var r=n.querySelector(".icon.error");v(r,"animateErrorIcon"),v(r.querySelector(".x-mark"),"animateXMark");var a=n.querySelector(".icon.warning");v(a,"pulseWarning"),v(a.querySelector(".body"),"pulseWarningIns"),v(a.querySelector(".dot"),"pulseWarningIns"),e.onkeydown=A,t.onclick=O,I&&I.focus(),M=void 0}function s(){var e=m();e.style.marginTop=C(m())}var u=".sweet-alert",f=".sweet-overlay",d=["error","warning","info","success"],m=function(){return t.querySelector(u)},y=function(){return t.querySelector(f)},p=function(e,t){return new RegExp(" "+t+" ").test(" "+e.className+" ")},g=function(e,t){p(e,t)||(e.className+=" "+t)},v=function(e,t){var n=" "+e.className.replace(/[\t\r\n]/g," ")+" ";if(p(e,t)){for(;n.indexOf(" "+t+" ")>=0;)n=n.replace(" "+t+" "," ");e.className=n.replace(/^\s+|\s+$/g,"")}},b=function(e){var n=t.createElement("div");return n.appendChild(t.createTextNode(e)),n.innerHTML},h=function(e){e.style.opacity="",e.style.display="block"},w=function(e){if(e&&!e.length)return h(e);for(var t=0;t<e.length;++t)h(e[t])},S=function(e){e.style.opacity="",e.style.display="none"},x=function(e){if(e&&!e.length)return S(e);for(var t=0;t<e.length;++t)S(e[t])},k=function(e,t){for(var n=t.parentNode;null!==n;){if(n===e)return!0;n=n.parentNode}return!1},C=function(e){e.style.left="-9999px",e.style.display="block";var t=e.clientHeight,n=parseInt(getComputedStyle(e).getPropertyValue("padding"),10);return e.style.left="",e.style.display="none","-"+parseInt(t/2+n)+"px"},B=function(e,t){if(+e.style.opacity<1){t=t||16,e.style.opacity=0,e.style.display="block";var n=+new Date,o=function(){e.style.opacity=+e.style.opacity+(new Date-n)/100,n=+new Date,+e.style.opacity<1&&setTimeout(o,t)};o()}},T=function(e,t){t=t||16,e.style.opacity=1;var n=+new Date,o=function(){e.style.opacity=+e.style.opacity-(new Date-n)/100,n=+new Date,+e.style.opacity>0?setTimeout(o,t):e.style.display="none"};o()},E=function(n){if(MouseEvent){var o=new MouseEvent("click",{view:e,bubbles:!1,cancelable:!0});n.dispatchEvent(o)}else if(t.createEvent){var r=t.createEvent("MouseEvents");r.initEvent("click",!1,!1),n.dispatchEvent(r)}else t.createEventObject?n.fireEvent("onclick"):"function"==typeof n.onclick&&n.onclick()},q=function(t){"function"==typeof t.stopPropagation?(t.stopPropagation(),t.preventDefault()):e.event&&e.event.hasOwnProperty("cancelBubble")&&(e.event.cancelBubble=!0)},I,O,A,M;e.sweetAlert=e.swal=function(){function n(e){var t=e.keyCode||e.which;if(-1!==[9,13,32,27].indexOf(t)){for(var n=e.target||e.srcElement,o=-1,r=0;r<h.length;r++)if(n===h[r]){o=r;break}9===t?(n=-1===o?v:o===h.length-1?h[0]:h[o+1],q(e),n.focus(),i(n,u.confirmButtonColor)):(n=13===t||32===t?-1===o?v:void 0:27!==t||b.hidden||"none"===b.style.display?void 0:b,void 0!==n&&E(n,e))}}function a(e){var t=e.target||e.srcElement,n=e.relatedTarget,o=p(f,"visible");if(o){var r=-1;if(null!==n){for(var a=0;a<h.length;a++)if(n===h[a]){r=a;break}-1===r&&t.focus()}else M=t}}var u={title:"",text:"",type:null,allowOutsideClick:!1,showCancelButton:!1,closeOnConfirm:!0,confirmButtonText:"OK",confirmButtonColor:"#AEDEF4",cancelButtonText:"Cancel",imageUrl:null,imageSize:null};if(void 0===arguments[0])return e.console.error("sweetAlert expects at least 1 attribute!"),!1;switch(typeof arguments[0]){case"string":u.title=arguments[0],u.text=arguments[1]||"",u.type=arguments[2]||"";break;case"object":if(void 0===arguments[0].title)return e.console.error('Missing "title" argument!'),!1;u.title=arguments[0].title,u.text=arguments[0].text||u.text,u.type=arguments[0].type||u.type,u.allowOutsideClick=arguments[0].allowOutsideClick||u.allowOutsideClick,u.showCancelButton=void 0!==arguments[0].showCancelButton?arguments[0].showCancelButton:u.showCancelButton,u.closeOnConfirm=void 0!==arguments[0].closeOnConfirm?arguments[0].closeOnConfirm:u.closeOnConfirm,u.confirmButtonText=u.showCancelButton?"Confirm":u.confirmButtonText,u.confirmButtonText=arguments[0].confirmButtonText||u.confirmButtonText,u.confirmButtonColor=arguments[0].confirmButtonColor||u.confirmButtonColor,u.cancelButtonText=arguments[0].cancelButtonText||u.cancelButtonText,u.imageUrl=arguments[0].imageUrl||u.imageUrl,u.imageSize=arguments[0].imageSize||u.imageSize,u.doneFunction=arguments[1]||null;break;default:return e.console.error('Unexpected type of argument! Expected "string" or "object", got '+typeof arguments[0]),!1}o(u),s(),c();for(var f=m(),d=function(e){var t=e.target||e.srcElement,n="confirm"===t.className,o=p(f,"visible"),a=u.doneFunction&&"true"===f.getAttribute("data-has-done-function");switch(e.type){case"mouseover":n&&(e.target.style.backgroundColor=r(u.confirmButtonColor,-.04));break;case"mouseout":n&&(e.target.style.backgroundColor=u.confirmButtonColor);break;case"mousedown":n&&(e.target.style.backgroundColor=r(u.confirmButtonColor,-.14));break;case"mouseup":n&&(e.target.style.backgroundColor=r(u.confirmButtonColor,-.04));break;case"focus":var i=f.querySelector("button.confirm"),c=f.querySelector("button.cancel");n?c.style.boxShadow="none":i.style.boxShadow="none";break;case"click":n&&a&&o?(u.doneFunction(),u.closeOnConfirm&&l()):l()}},y=f.querySelectorAll("button"),g=0;g<y.length;g++)y[g].onclick=d,y[g].onmouseover=d,y[g].onmouseout=d,y[g].onmousedown=d,y[g].onfocus=d;O=t.onclick,t.onclick=function(e){var t=e.target||e.srcElement,n=f===t,o=k(f,e.target),r=p(f,"visible"),a="true"===f.getAttribute("data-allow-ouside-click");!n&&!o&&r&&a&&l()};var v=f.querySelector("button.confirm"),b=f.querySelector("button.cancel"),h=f.querySelectorAll("button:not([type=hidden])");A=e.onkeydown,e.onkeydown=n,v.onblur=a,b.onblur=a,e.onfocus=function(){e.setTimeout(function(){void 0!==M&&(M.focus(),M=void 0)},0)}},function(){"complete"===t.readyState||"interactive"===t.readyState?n():t.addEventListener?t.addEventListener("DOMContentLoaded",function e(){t.removeEventListener("DOMContentLoaded",arguments.callee,!1),n()},!1):t.attachEvent&&t.attachEvent("onreadystatechange",function(){"complete"===t.readyState&&(t.detachEvent("onreadystatechange",arguments.callee),n())})}()}(window,document);<commit_msg>update sweetalert<commit_after>!function(e,t){function n(t){var n=y(),o=n.querySelector("h2"),r=n.querySelector("p"),a=n.querySelector("button.cancel"),c=n.querySelector("button.confirm");if(o.innerHTML=w(t.title).split("\n").join("
"),r.innerHTML=w(t.text||"").split("\n").join("
"),t.text&&S(r),C(n.querySelectorAll(".icon")),t.type){for(var l=!1,s=0;s<d.length;s++)if(t.type===d[s]){l=!0;break}if(!l)return e.console.error("Unknown alert type: "+t.type),!1;var u=n.querySelector(".icon."+t.type);switch(S(u),t.type){case"success":v(u,"animate"),v(u.querySelector(".tip"),"animateSuccessTip"),v(u.querySelector(".long"),"animateSuccessLong");break;case"error":v(u,"animateErrorIcon"),v(u.querySelector(".x-mark"),"animateXMark");break;case"warning":v(u,"pulseWarning"),v(u.querySelector(".body"),"pulseWarningIns"),v(u.querySelector(".dot"),"pulseWarningIns")}}if(t.imageUrl){var f=n.querySelector(".icon.custom");f.style.backgroundImage="url("+t.imageUrl+")",S(f);var m=80,g=80;if(t.imageSize){var p=t.imageSize.split("x")[0],b=t.imageSize.split("x")[1];p&&b?(m=p,g=b,f.css({width:p+"px",height:b+"px"})):e.console.error("Parameter imageSize expects value with format WIDTHxHEIGHT, got "+t.imageSize)}f.setAttribute("style",f.getAttribute("style")+"width:"+m+"px; height:"+g+"px")}n.setAttribute("data-has-cancel-button",t.showCancelButton),t.showCancelButton?a.style.display="inline-block":C(a),t.cancelButtonText&&(a.innerHTML=w(t.cancelButtonText)),t.confirmButtonText&&(c.innerHTML=w(t.confirmButtonText)),c.style.backgroundColor=t.confirmButtonColor,i(c,t.confirmButtonColor),n.setAttribute("data-allow-ouside-click",t.allowOutsideClick);var h=t.doneFunction?!0:!1;n.setAttribute("data-has-done-function",h),n.setAttribute("data-timer",t.timer)}function o(e,t){e=String(e).replace(/[^0-9a-f]/gi,""),e.length<6&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),t=t||0;var n="#",o,r;for(r=0;3>r;r++)o=parseInt(e.substr(2*r,2),16),o=Math.round(Math.min(Math.max(0,o+o*t),255)).toString(16),n+=("00"+o).substr(o.length);return n}function r(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function a(e){var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?parseInt(t[1],16)+", "+parseInt(t[2],16)+", "+parseInt(t[3],16):null}function i(e,t){var n=a(t);e.style.boxShadow="0 0 2px rgba("+n+", 0.8), inset 0 0 0 1px rgba(0, 0, 0, 0.05)"}function c(){var e=y();T(g(),10),S(e),v(e,"showSweetAlert"),b(e,"hideSweetAlert"),A=t.activeElement;var n=e.querySelector("button.confirm");n.focus(),setTimeout(function(){v(e,"visible")},500);var o=e.getAttribute("data-timer");"null"!==o&&setTimeout(function(){l()},o)}function l(){var n=y();E(g(),5),E(n,5),b(n,"showSweetAlert"),v(n,"hideSweetAlert"),b(n,"visible");var o=n.querySelector(".icon.success");b(o,"animate"),b(o.querySelector(".tip"),"animateSuccessTip"),b(o.querySelector(".long"),"animateSuccessLong");var r=n.querySelector(".icon.error");b(r,"animateErrorIcon"),b(r.querySelector(".x-mark"),"animateXMark");var a=n.querySelector(".icon.warning");b(a,"pulseWarning"),b(a.querySelector(".body"),"pulseWarningIns"),b(a.querySelector(".dot"),"pulseWarningIns"),e.onkeydown=M,t.onclick=I,A&&A.focus(),z=void 0}function s(){var e=y();e.style.marginTop=B(y())}var u=".sweet-alert",f=".sweet-overlay",d=["error","warning","info","success"],m={title:"",text:"",type:null,allowOutsideClick:!1,showCancelButton:!1,closeOnConfirm:!0,closeOnCancel:!0,confirmButtonText:"OK",confirmButtonColor:"#AEDEF4",cancelButtonText:"Cancel",imageUrl:null,imageSize:null,timer:null},y=function(){return t.querySelector(u)},g=function(){return t.querySelector(f)},p=function(e,t){return new RegExp(" "+t+" ").test(" "+e.className+" ")},v=function(e,t){p(e,t)||(e.className+=" "+t)},b=function(e,t){var n=" "+e.className.replace(/[\t\r\n]/g," ")+" ";if(p(e,t)){for(;n.indexOf(" "+t+" ")>=0;)n=n.replace(" "+t+" "," ");e.className=n.replace(/^\s+|\s+$/g,"")}},w=function(e){var n=t.createElement("div");return n.appendChild(t.createTextNode(e)),n.innerHTML},h=function(e){e.style.opacity="",e.style.display="block"},S=function(e){if(e&&!e.length)return h(e);for(var t=0;t<e.length;++t)h(e[t])},x=function(e){e.style.opacity="",e.style.display="none"},C=function(e){if(e&&!e.length)return x(e);for(var t=0;t<e.length;++t)x(e[t])},k=function(e,t){for(var n=t.parentNode;null!==n;){if(n===e)return!0;n=n.parentNode}return!1},B=function(e){e.style.left="-9999px",e.style.display="block";var t=e.clientHeight,n=parseInt(getComputedStyle(e).getPropertyValue("padding"),10);return e.style.left="",e.style.display="none","-"+parseInt(t/2+n)+"px"},T=function(e,t){if(+e.style.opacity<1){t=t||16,e.style.opacity=0,e.style.display="block";var n=+new Date,o=function(){e.style.opacity=+e.style.opacity+(new Date-n)/100,n=+new Date,+e.style.opacity<1&&setTimeout(o,t)};o()}},E=function(e,t){t=t||16,e.style.opacity=1;var n=+new Date,o=function(){e.style.opacity=+e.style.opacity-(new Date-n)/100,n=+new Date,+e.style.opacity>0?setTimeout(o,t):e.style.display="none"};o()},q=function(n){if(MouseEvent){var o=new MouseEvent("click",{view:e,bubbles:!1,cancelable:!0});n.dispatchEvent(o)}else if(t.createEvent){var r=t.createEvent("MouseEvents");r.initEvent("click",!1,!1),n.dispatchEvent(r)}else t.createEventObject?n.fireEvent("onclick"):"function"==typeof n.onclick&&n.onclick()},O=function(t){"function"==typeof t.stopPropagation?(t.stopPropagation(),t.preventDefault()):e.event&&e.event.hasOwnProperty("cancelBubble")&&(e.event.cancelBubble=!0)},A,I,M,z;e.sweetAlertInitialize=function(){var e='

Title

Text
<button class="cancel" tabIndex="2">Cancel</button><button class="confirm" tabIndex="1">OK</button>
',n=t.createElement("div");n.innerHTML=e,t.body.appendChild(n)},e.sweetAlert=e.swal=function(){function a(e){var t=e.keyCode||e.which;if(-1!==[9,13,32,27].indexOf(t)){for(var n=e.target||e.srcElement,o=-1,r=0;r<S.length;r++)if(n===S[r]){o=r;break}9===t?(n=-1===o?w:o===S.length-1?S[0]:S[o+1],O(e),n.focus(),i(n,f.confirmButtonColor)):(n=13===t||32===t?-1===o?w:void 0:27!==t||h.hidden||"none"===h.style.display?void 0:h,void 0!==n&&q(n,e))}}function u(e){var t=e.target||e.srcElement,n=e.relatedTarget,o=p(d,"visible");if(o){var r=-1;if(null!==n){for(var a=0;a<S.length;a++)if(n===S[a]){r=a;break}-1===r&&t.focus()}else z=t}}if(void 0===arguments[0])return e.console.error("sweetAlert expects at least 1 attribute!"),!1;var f=r({},m);switch(typeof arguments[0]){case"string":f.title=arguments[0],f.text=arguments[1]||"",f.type=arguments[2]||"";break;case"object":if(void 0===arguments[0].title)return e.console.error('Missing "title" argument!'),!1;f.title=arguments[0].title,f.text=arguments[0].text||m.text,f.type=arguments[0].type||m.type,f.allowOutsideClick=arguments[0].allowOutsideClick||m.allowOutsideClick,f.showCancelButton=void 0!==arguments[0].showCancelButton?arguments[0].showCancelButton:m.showCancelButton,f.closeOnConfirm=void 0!==arguments[0].closeOnConfirm?arguments[0].closeOnConfirm:m.closeOnConfirm,f.closeOnCancel=void 0!==arguments[0].closeOnCancel?arguments[0].closeOnCancel:m.closeOnCancel,f.timer=arguments[0].timer||m.timer,f.confirmButtonText=m.showCancelButton?"Confirm":m.confirmButtonText,f.confirmButtonText=arguments[0].confirmButtonText||m.confirmButtonText,f.confirmButtonColor=arguments[0].confirmButtonColor||m.confirmButtonColor,f.cancelButtonText=arguments[0].cancelButtonText||m.cancelButtonText,f.imageUrl=arguments[0].imageUrl||m.imageUrl,f.imageSize=arguments[0].imageSize||m.imageSize,f.doneFunction=arguments[1]||null;break;default:return e.console.error('Unexpected type of argument! Expected "string" or "object", got '+typeof arguments[0]),!1}n(f),s(),c();for(var d=y(),g=function(e){var t=e.target||e.srcElement,n="confirm"===t.className,r=p(d,"visible"),a=f.doneFunction&&"true"===d.getAttribute("data-has-done-function");switch(e.type){case"mouseover":n&&(e.target.style.backgroundColor=o(f.confirmButtonColor,-.04));break;case"mouseout":n&&(e.target.style.backgroundColor=f.confirmButtonColor);break;case"mousedown":n&&(e.target.style.backgroundColor=o(f.confirmButtonColor,-.14));break;case"mouseup":n&&(e.target.style.backgroundColor=o(f.confirmButtonColor,-.04));break;case"focus":var i=d.querySelector("button.confirm"),c=d.querySelector("button.cancel");n?c.style.boxShadow="none":i.style.boxShadow="none";break;case"click":if(n&&a&&r)f.doneFunction(!0),f.closeOnConfirm&&l();else if(a&&r){var s=String(f.doneFunction).replace(/\s/g,""),u="function("===s.substring(0,9)&&")"!==s.substring(9,10);u&&f.doneFunction(!1),f.closeOnCancel&&l()}else l()}},v=d.querySelectorAll("button"),b=0;b<v.length;b++)v[b].onclick=g,v[b].onmouseover=g,v[b].onmouseout=g,v[b].onmousedown=g,v[b].onfocus=g;I=t.onclick,t.onclick=function(e){var t=e.target||e.srcElement,n=d===t,o=k(d,e.target),r=p(d,"visible"),a="true"===d.getAttribute("data-allow-ouside-click");!n&&!o&&r&&a&&l()};var w=d.querySelector("button.confirm"),h=d.querySelector("button.cancel"),S=d.querySelectorAll("button:not([type=hidden])");M=e.onkeydown,e.onkeydown=a,w.onblur=u,h.onblur=u,e.onfocus=function(){e.setTimeout(function(){void 0!==z&&(z.focus(),z=void 0)},0)}},e.swal.setDefaults=function(e){if(!e)throw new Error("userParams is required");if("object"!=typeof e)throw new Error("userParams has to be a object");r(m,e)},function(){"complete"===t.readyState||"interactive"===t.readyState&&t.body?sweetAlertInitialize():t.addEventListener?t.addEventListener("DOMContentLoaded",function e(){t.removeEventListener("DOMContentLoaded",arguments.callee,!1),sweetAlertInitialize()},!1):t.attachEvent&&t.attachEvent("onreadystatechange",function(){"complete"===t.readyState&&(t.detachEvent("onreadystatechange",arguments.callee),sweetAlertInitialize())})}()}(window,document);<|endoftext|>


<commit_before>//-----------------------------------------updating service list function-------------------------------------------------------------------------------------------
function updateServicelist(){
activeService=$('#serviceModal').attr('service');
	doFetch({ action: 'serviceList', data: activeService, user: localStorage.getItem("bits-user-name")}).then(
	function(e){
    if (e.status=="ok"){
    serviceList = e.data;
    for(var ii = 0; ii < serviceList.length; ++ii) {

		$('.serviceListHolder').append('
'+serviceList[ii].name+'

 Second Line

');

             };

           }else{

           }
        });
}
//-----------------------------------------------save to local storage------------------------------------------------------------------
function serviceOpener(){
if (getBitsWinOpt('s') != undefined )
 {
servicePageLoader();
         }else{
              $(".promoHome").removeClass("displayNone");
  $(".fixed-a").removeClass("displayNone");
	 updatePromos();
	 }
  }
//------------------------------------------end save--------------------------------------------------------------------------------------
//---------------------------------------- subscription function--------------------------------------------------------------------------
function doSubscribe(){
	$(".promoSubButton").bind( "touchstart click", function(event, ui) {
	checkanon();
	showLogin();
	starting();
     event.preventDefault();
	var pid = $(this).attr('pid');
	var dr = $(this).attr('dailyR');
     if (!flag) {
    flag = true;
    setTimeout(function(){ flag = false; }, 100);
	localConverter().then(function(e){
	var infiat = parseInt(localStorage.getItem('bitsoko-wallets-bal'))/100000000;
    var infiat=infiat*parseInt(e.xrate)*parseInt(e.rate);
        console.log(infiat);
		if($( ".promoSubButton-"+pid ).prop( "checked" )){
			var action='unsubscribe';
           // Materialize.toast('unsubscribing..', 1000);
		}else{
			var action='subscribe';
            //Materialize.toast('subscribing..', 1000);
		}

	doFetch({ action: 'doSubscription', todo: action, pid: pid, uid: localStorage.getItem('bits-user-name')}).then(function(e){
           if (e.status=="ok"){
           	    if(infiat<(dr*1.1)){
		 Materialize.toast(action+'d. Insufficient Funds', 6500);
	}else{
		Materialize.toast(action+'d successfully', 5000);
	}

//---------------------------------------send promo data to db-----------------------------------------------------------------------------
		   if(action=='subscribe'){
			   //START-TODO-remove support for individually listed promos in db, moved to "bits-mypromos-USERID"
			    getObjectStore('data', 'readwrite').put(JSON.stringify(e.prom), 'bits-promo-'+pid);
			   //END-TODO

    var walsvar = getObjectStore('data', 'readwrite').get('bits-mypromos-'+localStorage.getItem('bits-user-name'));
	walsvar.onsuccess = function (event) {

		try{var oold=JSON.parse(event.target.result);oold.push(e.prom);}catch(err){var oold=[];oold.push(e.prom);}
	  getObjectStore('data', 'readwrite').put(JSON.stringify(oold), 'bits-mypromos-'+localStorage.getItem('bits-user-name'));

		   $( ".promoSubButton-"+pid ).prop( "checked",true);
			   $(".promoSubState-"+pid).html("Subscribed");
	   }

	          }else{
		  $( ".promoSubButton-"+pid ).prop( "checked",false);
			  $(".promoSubState-"+pid).html("Not Subscribed");
	           }
		         }else{
		Materialize.toast('unable to subscribe '+e.msg, 3000);
           }
        })
         .catch(function(){
		Materialize.toast('temporary error. please try again', 3000);
        });
    //$( ".bitsoko-balance" ).html(infiat.toFixed(2));

    });

     }

});

}

//----------------------------------------------populateService function----------------------------------------------------------------------------------------------------------------------------------------
 function populateService(id){
 	var mDet = JSON.parse(localStorage.getItem('bits-merchant-id-'+id));
$(".resDisplay").html( mDet.name);
        document.querySelector('.serviceName').innerHTML = mDet.name;
        document.querySelector('.cardimage').src = 'https://bitsoko.io'+mDet.bannerPath;
         document.querySelector('.cardLogo').src = mDet.icon;
		document.querySelector('.serviceDescription').innerHTML = mDet.description;
		 $('.serviceListHolder').html("");
//-----------------------------------------------incase the user is the owner of this shop, then show POS button------------------------------------------------------------------------------------------------
	 if(mDet.owner==parseInt(localStorage.getItem('bitsoko-owner-id'))){
	 $('#manage-store').css("display","block");
	 }else{
	  $('#manage-store').css("display","none");
	 }
	 	callMerchant()
		 for(var ii = 0,subs=subs; ii < mDet.promotions.length; ++ii) {
		 var dailyCost=(parseInt(mDet.promotions[ii].discount)/100)*mDet.promotions[ii].promoPrice;
		 $('.serviceListHolder').prepend('
'+
						 ''+
						 ' '+mDet.promotions[ii].promoName+' '+
						 '
 '+Math.ceil(dailyCost)+' Ksh daily
'+
						 '
 Not Subscribed <input type="checkbox" dailyR="'+Math.ceil(dailyCost)+'" pid="'+mDet.promotions[ii].id+'" class="promoSubButton promoSubButton-'+mDet.promotions[ii].id+'" style="background: rgb(128, 210, 147);">

');
       	 subs=mDet.promotions[ii].promoSubs;
		 for(var iii = 0,subs=subs,mDet=mDet; iii < subs.length; ++iii) {
			 if(subs[iii]==localStorage.getItem('bitsoko-owner-id')){
			 console.log('im subscribed to ',mDet.promotions[ii]);
				 $( ".promoSubButton-"+mDet.promotions[ii].id ).prop( "checked", true );
				 $(".promoSubState-"+mDet.promotions[ii].id).html("Subscribed");
			 };
		 }
		};

	 doSubscribe();
	 checkPayments();
 for(var ii = 0; ii < mDet.list.length; ++ii) {
// -------------------------------------------------loads the shops product lists --------------------------------------------------------------------------------------------------------------------------
 $('.serviceListHolder').append('
 '+mDet.list[ii].name+'
 '+mDet.list[ii].price+'  '+mDet.list[ii].metric+'

 <input class="number bitsInputQty" price="'+mDet.list[ii].price+'" type="number" placeholder="0" min="0.25" max="10" id='+mDet.list[ii].name+'> '+mDet.list[ii].metric+' (s)

');};
 $('.materialboxed').materialbox();
var addproducts = document.querySelectorAll(".bitsInputQty");
for(var i = 0; i< addproducts.length; ++i){
	addproducts[i].addEventListener("change",tabulateTotals,false);
 }
 $( "body" ).scrollTop( 156 );
 }

//---------------------------------------------------end populateService function------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------  function handleModal---------------------------------------------------------------------------------------------------------------------------------------
function handleModal(){
      console.log(this);
      $('#serviceModal').attr('service',$(this).attr('service'));
      $('#serviceModal').attr('account',$(this).attr('account'));
      populateModal($(this).attr('service'),$(this).attr('account'));
  }
      var items =  [
      {dataModal:'#modal'}
      ]
        for(var ii = 0; ii < items.length; ++ii) {
             };
 var activeService;
          allServices =  [
      //{name:'Merchants',id:'3',desc:'Merchant desription',cardimage:'/bits/images/merchantsBanner',cardLogo:'/bits/images/merchants',image:'mdi-maps-store-mall-directory',list:[]},
       {name:'Contacts',id:'2',desc:'Contacts desription',cardimage:'/bits/images/contactsBanner',cardLogo:'/bits/images/contacts',image:'mdi-social-group',list:[]}
      ]
        for(var ii = 0; ii < allServices.length; ++ii) {
		$('.serviceButtonsHolder').append('
 '+allServices[ii].name+'
');
           };
 var bts = document.querySelectorAll(".serviceButtons");
    for(var ii = 0; ii < bts.length; ++ii) {
bts[ii].addEventListener('touchstart', function(event) {
 	console.log($(this).attr('service'));
if ($(this).attr('service')=='2'){
	contact();
}
 });
             };
     function populateModal(x,y){
         console.log(x,y)
    for(var ii = 0,x=x,activeService=activeService; ii < allServices.length; ++ii) {
        if(allServices[ii].id==x){
            activeService=allServices[ii];
        }
		};
             if ( activeService.list.length==0){
             	$('.serviceListHolder').html("");
             }
        document.querySelector('.serviceName').innerHTML = activeService.name;
        document.querySelector('.cardimage').src = "https://bitsoko.io"+activeService.cardimage+".png";
         document.querySelector('.cardLogo').src = "https://bitsoko.io"+activeService.cardLogo+".png";
		document.querySelector('.serviceDescription').innerHTML = activeService.desription;
//add button

updateServicelist();
    var el = document.querySelectorAll(".loadButtons");
for(var l=0, el=el; l < el.length; ++l){
  el[l].addEventListener("touchstart", handleModal, false);
}
     function populateModal(x,y){
         console.log(x,y)
    for(var ii = 0,x=x,activeService=activeService; ii < serviceList.length; ++ii) {
        if(serviceList[ii].id==x){
            activeService=serviceList[ii];
        }
			if (serviceList[ii].id==1){
			 document.querySelector('.service-banner').src = 'https://bitsoko.io/app/images/services/contacts.png';
		}else if(serviceList[ii].id==2){
			 document.querySelector('.service-banner').src = 'https://bitsoko.io/app/images/services/contacts.png';
		}
             };
        document.querySelector('.btnname').innerHTML = activeService.name;
             };
     }
//--------------------------function to hide and display nav-bar balance div  ----------------------------------------------------------------------------
// bits_contacts handles the contacts process and has other functions that depend on it
// sam@bitsoko.io
//----------------------------------- function getContacts-------------------------------------------------------------------------------------
function contact(){
 var ctFunc = getObjectStore('data', 'readwrite').get('bits-contacts-'+localStorage.getItem('bits-user-name'));
  ctFunc.onerror = function (event) {
   reqContacts();
  }
 ctFunc.onsuccess = function (event) {
        try{
//------------------------------------contacts found--------------------------------------------------------------------------------------------
        document.querySelector('.serviceName').innerHTML = "Contacts";
        document.querySelector('.cardimage').src = "https://bitsoko.io/app/images/contactsBanner.png";
         document.querySelector('.cardLogo').src = "https://bitsoko.io/app/images/services/contacts.png";
		document.querySelector('.serviceDescription').innerHTML = "All your contacts";
    var allContacts = JSON.parse(event.target.result);
  	for(var iii = 0 ;  iii < allContacts.length; ++iii) {
		console.log("contacts")
			//var dailyCost=(parseInt(mDet.promotions[ii].discount)/100)*mDet.promotions[ii].promoPrice;
			var id = allContacts[iii].uid ? allContacts[iii].uid : 'undefined';
		 $('.serviceListHolder').append('
 '+allContacts[iii].name+'
'+allContacts[iii].contact+'

');
            }
	}
catch(e) {  	console.log("no contacts")
       $('.serviceListHolder').append('
Empty
 Click to update your  list

');
       // $('.serviceListHolder').append('
Empty
 Click to update your '+activeService.name+' list

');
//----------------------------------------- no contacts------------------------------------------------------------------------------------------
           reqContacts();
            }
	}
}
//------------------------------------------cliked contact-----------------------------------------------------------------------------------------

function selectContact(id){
	console.log("you just clicked on a contact")
	history.pushState({page: 1}, "", "?s=2&a="+id)

}
//-----------------------------------------------------------------------------------------------------------------------------------------------         <commit_msg>Update update_services.js<commit_after>//-----------------------------------------updating service list function-------------------------------------------------------------------------------------------
function updateServicelist(){
activeService=$('#serviceModal').attr('service');
	doFetch({ action: 'serviceList', data: activeService, user: localStorage.getItem("bits-user-name")}).then(
	function(e){
    if (e.status=="ok"){
    serviceList = e.data;
    for(var ii = 0; ii < serviceList.length; ++ii) {

		$('.serviceListHolder').append('
'+serviceList[ii].name+'

 Second Line

');

             };

           }else{

           }
        });
}
//-----------------------------------------------save to local storage------------------------------------------------------------------
function serviceOpener(){
if (getBitsWinOpt('s') != undefined )
 {
servicePageLoader();
         }else{
              $(".promoHome").removeClass("displayNone");
  $(".fixed-a").removeClass("displayNone");
	 updatePromos();
	 }
  }
//------------------------------------------end save--------------------------------------------------------------------------------------
//---------------------------------------- subscription function--------------------------------------------------------------------------
function doSubscribe(){
	$(".promoSubButton").bind( "touchstart click", function(event, ui) {
	checkanon();
	showLogin();
	starting();
     event.preventDefault();
	var pid = $(this).attr('pid');
	var dr = $(this).attr('dailyR');
     if (!flag) {
    flag = true;
    setTimeout(function(){ flag = false; }, 100);
	localConverter().then(function(e){
	var infiat = parseInt(localStorage.getItem('bitsoko-wallets-bal'))/100000000;
    var infiat=infiat*parseInt(e.xrate)*parseInt(e.rate);
        console.log(infiat);
		if($( ".promoSubButton-"+pid ).prop( "checked" )){
			var action='unsubscribe';
           // Materialize.toast('unsubscribing..', 1000);
		}else{
			var action='subscribe';
            //Materialize.toast('subscribing..', 1000);
		}

	doFetch({ action: 'doSubscription', todo: action, pid: pid, uid: localStorage.getItem('bits-user-name')}).then(function(e){
           if (e.status=="ok"){
           	    if(infiat<(dr*1.1)){
		 Materialize.toast(action+'d. Insufficient Funds', 6500);
	}else{
		Materialize.toast(action+'d successfully', 5000);
	}

//---------------------------------------send promo data to db-----------------------------------------------------------------------------
		   if(action=='subscribe'){
			   //START-TODO-remove support for individually listed promos in db, moved to "bits-mypromos-USERID"
			    getObjectStore('data', 'readwrite').put(JSON.stringify(e.prom), 'bits-promo-'+pid);
			   //END-TODO

    var walsvar = getObjectStore('data', 'readwrite').get('bits-mypromos-'+localStorage.getItem('bits-user-name'));
	walsvar.onsuccess = function (event) {

		try{var oold=JSON.parse(event.target.result);oold.push(e.prom);}catch(err){var oold=[];oold.push(e.prom);}
	  getObjectStore('data', 'readwrite').put(JSON.stringify(oold), 'bits-mypromos-'+localStorage.getItem('bits-user-name'));

		   $( ".promoSubButton-"+pid ).prop( "checked",true);
			   $(".promoSubState-"+pid).html("Subscribed");
	   }

	          }else{
		  $( ".promoSubButton-"+pid ).prop( "checked",false);
			  $(".promoSubState-"+pid).html("Not Subscribed");
	           }
		         }else{
		Materialize.toast('unable to subscribe '+e.msg, 3000);
           }
        })
         .catch(function(){
		Materialize.toast('temporary error. please try again', 3000);
        });
    //$( ".bitsoko-balance" ).html(infiat.toFixed(2));

    });

     }

});

}

//----------------------------------------------populateService function----------------------------------------------------------------------------------------------------------------------------------------
 function populateService(id){
 	var mDet = JSON.parse(localStorage.getItem('bits-merchant-id-'+id));
$(".resDisplay").html( mDet.name);
        document.querySelector('.serviceName').innerHTML = mDet.name;
        document.querySelector('.cardimage').src = 'https://bitsoko.io'+mDet.bannerPath;
         document.querySelector('.cardLogo').src = mDet.icon;
		document.querySelector('.serviceDescription').innerHTML = mDet.description;
		 $('.serviceListHolder').html("");
//-----------------------------------------------incase the user is the owner of this shop, then show POS button------------------------------------------------------------------------------------------------
	 if(mDet.owner==parseInt(localStorage.getItem('bitsoko-owner-id'))){
	 $('#manage-store').css("display","block");
	 }else{
	  $('#manage-store').css("display","none");
	 }
	 	callMerchant()
		 for(var ii = 0,subs=subs; ii < mDet.promotions.length; ++ii) {
		 var dailyCost=(parseInt(mDet.promotions[ii].discount)/100)*mDet.promotions[ii].promoPrice;
		 $('.serviceListHolder').prepend('
'+
						 ''+
						 ' '+mDet.promotions[ii].promoName+' '+
						 '
 '+Math.ceil(dailyCost)+' Ksh daily
'+
						 '
 Not Subscribed <input type="checkbox" dailyR="'+Math.ceil(dailyCost)+'" pid="'+mDet.promotions[ii].id+'" class="promoSubButton promoSubButton-'+mDet.promotions[ii].id+'" style="background: rgb(128, 210, 147);">

');
       	 subs=mDet.promotions[ii].promoSubs;
		 for(var iii = 0,subs=subs,mDet=mDet; iii < subs.length; ++iii) {
			 if(subs[iii]==localStorage.getItem('bitsoko-owner-id')){
			 //console.log('im subscribed to ',mDet.promotions[ii]);
				  //START-TODO-remove support for individually listed promos in db, moved to "bits-mypromos-USERID"
			    getObjectStore('data', 'readwrite').put(JSON.stringify(mDet.promotions[ii]), 'bits-promo-'+mDet.promotions[ii].id);
			   //END-TODO

				 $( ".promoSubButton-"+mDet.promotions[ii].id ).prop( "checked", true );
				 $(".promoSubState-"+mDet.promotions[ii].id).html("Subscribed");
			 };
		 }
		};

	 doSubscribe();
	 checkPayments();
 for(var ii = 0; ii < mDet.list.length; ++ii) {
// -------------------------------------------------loads the shops product lists --------------------------------------------------------------------------------------------------------------------------
 $('.serviceListHolder').append('
 '+mDet.list[ii].name+'
 '+mDet.list[ii].price+'  '+mDet.list[ii].metric+'

 <input class="number bitsInputQty" price="'+mDet.list[ii].price+'" type="number" placeholder="0" min="0.25" max="10" id='+mDet.list[ii].name+'> '+mDet.list[ii].metric+' (s)

');};
 $('.materialboxed').materialbox();
var addproducts = document.querySelectorAll(".bitsInputQty");
for(var i = 0; i< addproducts.length; ++i){
	addproducts[i].addEventListener("change",tabulateTotals,false);
 }
 $( "body" ).scrollTop( 156 );
 }

//---------------------------------------------------end populateService function------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------  function handleModal---------------------------------------------------------------------------------------------------------------------------------------
function handleModal(){
      console.log(this);
      $('#serviceModal').attr('service',$(this).attr('service'));
      $('#serviceModal').attr('account',$(this).attr('account'));
      populateModal($(this).attr('service'),$(this).attr('account'));
  }
      var items =  [
      {dataModal:'#modal'}
      ]
        for(var ii = 0; ii < items.length; ++ii) {
             };
 var activeService;
          allServices =  [
      //{name:'Merchants',id:'3',desc:'Merchant desription',cardimage:'/bits/images/merchantsBanner',cardLogo:'/bits/images/merchants',image:'mdi-maps-store-mall-directory',list:[]},
       {name:'Contacts',id:'2',desc:'Contacts desription',cardimage:'/bits/images/contactsBanner',cardLogo:'/bits/images/contacts',image:'mdi-social-group',list:[]}
      ]
        for(var ii = 0; ii < allServices.length; ++ii) {
		$('.serviceButtonsHolder').append('
 '+allServices[ii].name+'
');
           };
 var bts = document.querySelectorAll(".serviceButtons");
    for(var ii = 0; ii < bts.length; ++ii) {
bts[ii].addEventListener('touchstart', function(event) {
 	console.log($(this).attr('service'));
if ($(this).attr('service')=='2'){
	contact();
}
 });
             };
     function populateModal(x,y){
         console.log(x,y)
    for(var ii = 0,x=x,activeService=activeService; ii < allServices.length; ++ii) {
        if(allServices[ii].id==x){
            activeService=allServices[ii];
        }
		};
             if ( activeService.list.length==0){
             	$('.serviceListHolder').html("");
             }
        document.querySelector('.serviceName').innerHTML = activeService.name;
        document.querySelector('.cardimage').src = "https://bitsoko.io"+activeService.cardimage+".png";
         document.querySelector('.cardLogo').src = "https://bitsoko.io"+activeService.cardLogo+".png";
		document.querySelector('.serviceDescription').innerHTML = activeService.desription;
//add button

updateServicelist();
    var el = document.querySelectorAll(".loadButtons");
for(var l=0, el=el; l < el.length; ++l){
  el[l].addEventListener("touchstart", handleModal, false);
}
     function populateModal(x,y){
         console.log(x,y)
    for(var ii = 0,x=x,activeService=activeService; ii < serviceList.length; ++ii) {
        if(serviceList[ii].id==x){
            activeService=serviceList[ii];
        }
			if (serviceList[ii].id==1){
			 document.querySelector('.service-banner').src = 'https://bitsoko.io/app/images/services/contacts.png';
		}else if(serviceList[ii].id==2){
			 document.querySelector('.service-banner').src = 'https://bitsoko.io/app/images/services/contacts.png';
		}
             };
        document.querySelector('.btnname').innerHTML = activeService.name;
             };
     }
//--------------------------function to hide and display nav-bar balance div  ----------------------------------------------------------------------------
// bits_contacts handles the contacts process and has other functions that depend on it
// sam@bitsoko.io
//----------------------------------- function getContacts-------------------------------------------------------------------------------------
function contact(){
 var ctFunc = getObjectStore('data', 'readwrite').get('bits-contacts-'+localStorage.getItem('bits-user-name'));
  ctFunc.onerror = function (event) {
   reqContacts();
  }
 ctFunc.onsuccess = function (event) {
        try{
//------------------------------------contacts found--------------------------------------------------------------------------------------------
        document.querySelector('.serviceName').innerHTML = "Contacts";
        document.querySelector('.cardimage').src = "https://bitsoko.io/app/images/contactsBanner.png";
         document.querySelector('.cardLogo').src = "https://bitsoko.io/app/images/services/contacts.png";
		document.querySelector('.serviceDescription').innerHTML = "All your contacts";
    var allContacts = JSON.parse(event.target.result);
  	for(var iii = 0 ;  iii < allContacts.length; ++iii) {
		console.log("contacts")
			//var dailyCost=(parseInt(mDet.promotions[ii].discount)/100)*mDet.promotions[ii].promoPrice;
			var id = allContacts[iii].uid ? allContacts[iii].uid : 'undefined';
		 $('.serviceListHolder').append('
 '+allContacts[iii].name+'
'+allContacts[iii].contact+'

');
            }
	}
catch(e) {  	console.log("no contacts")
       $('.serviceListHolder').append('
Empty
 Click to update your  list

');
       // $('.serviceListHolder').append('
Empty
 Click to update your '+activeService.name+' list

');
//----------------------------------------- no contacts------------------------------------------------------------------------------------------
           reqContacts();
            }
	}
}
//------------------------------------------cliked contact-----------------------------------------------------------------------------------------

function selectContact(id){
	console.log("you just clicked on a contact")
	history.pushState({page: 1}, "", "?s=2&a="+id)

}
//-----------------------------------------------------------------------------------------------------------------------------------------------
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado

/**
 * Live region is used with accessibility to read out changes in model state.
 * Should conform to the Fort.Model.property interface to make it easy to interchange.
 *
 * @author Sam Reid
 */

define( function( require ) {
  'use strict';

  var inherit = require( 'PHET_CORE/inherit' );
  var escapeHTML = require( 'PHET_CORE/escapeHTML' );

  var scenery = require( 'SCENERY/scenery' );

  var LiveRegion = scenery.LiveRegion = function LiveRegion( instance, property, options ) {
    var liveRegion = this;
    this.property = property;
    options = options || {};

    //Defaulting to 0 would mean using the document order, which can easily be incorrect for a PhET simulation.
    //For any of the nodes to use a nonzero tabindex, they must all use a nonzero tabindex, see #40
    options.tabIndex = options.tabIndex || 1;

    // TODO: if element is a DOM element, verify that no other accessibility liveRegion is using it! (add a flag, and remove on disposal)
//    this.element = '
';
    this.element = document.createElement( 'div' );
    this.element.setAttribute( 'aria-live', 'polite' );
    this.element.setAttribute( 'role', 'region' );
    this.textNode = document.createTextNode( '' );
    this.element.appendChild( this.textNode );

    //Just setting the text causes NVDA to read deltas
    this.listener = function( newText ) {
      liveRegion.element.removeChild( liveRegion.textNode );
      liveRegion.textNode = document.createTextNode( newText );
      liveRegion.element.appendChild( liveRegion.textNode );
    };
    property.link( this.listener );
  };

  LiveRegion.prototype = {
    constructor: scenery.LiveRegion,
    dispose: function() { this.property.unlink( this.listener ); }
  };

  return LiveRegion;
} );<commit_msg>updated docs<commit_after>// Copyright 2002-2013, University of Colorado

/**
 * Live region is used with accessibility to read out changes in model state.
 * Should conform to the Fort.Model.property interface to make it easy to interchange.
 *
 * @author Sam Reid
 */

define( function( require ) {
  'use strict';

  var inherit = require( 'PHET_CORE/inherit' );
  var escapeHTML = require( 'PHET_CORE/escapeHTML' );

  var scenery = require( 'SCENERY/scenery' );

  var LiveRegion = scenery.LiveRegion = function LiveRegion( instance, property, options ) {
    var liveRegion = this;
    this.property = property;
    options = options || {};

    //Defaulting to 0 would mean using the document order, which can easily be incorrect for a PhET simulation.
    //For any of the nodes to use a nonzero tabindex, they must all use a nonzero tabindex, see #40
    options.tabIndex = options.tabIndex || 1;

    // TODO: if element is a DOM element, verify that no other accessibility liveRegion is using it! (add a flag, and remove on disposal)
//    this.element = '
';
    this.element = document.createElement( 'div' );
    this.element.setAttribute( 'aria-live', 'polite' );
    this.element.setAttribute( 'role', 'region' );
    this.textNode = document.createTextNode( '' );
    this.element.appendChild( this.textNode );

    //Just setting the text causes NVDA to read deltas, you have to replace the node to have it read the text
    this.listener = function( newText ) {
      liveRegion.element.removeChild( liveRegion.textNode );
      liveRegion.textNode = document.createTextNode( newText );
      liveRegion.element.appendChild( liveRegion.textNode );
    };
    property.link( this.listener );
  };

  LiveRegion.prototype = {
    constructor: scenery.LiveRegion,
    dispose: function() { this.property.unlink( this.listener ); }
  };

  return LiveRegion;
} );<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import * as React from 'react';
import {BuilderDataTable} from './BuilderDataTable';
import {BuilderGrid} from './BuilderGrid';
import toggleIcon from './resources/play.svg';

/**
 * Component used to render Build Information.
 *
 * @param {Object[]} props.buildData - Aggregated data on latest commit build.
 * @param {string} props.buildData[].description - Commit message.
 * @param {Object[]} props.buildData[].builders - Bots that built this commit.
 * @param {string} props.buildData[].commitHash - Commit hash.
 * @param {string} props.buildData[].repository - URL to the GitHub repository.
 * @param {string} props.buildData[].status - The status of the build.
 *    Either 'passed', 'failed' or 'lost'.
 * @param {string} props.buildData[].timestamp - Timestamp of commit push.
 */
export const BuildSnapshot = (props) => {
  // State variable indicating whether the BuildSnapshot is open or closed.
  // isOpen === true indicates the tray should be visible
  // and the indicator arrow on the Header should be facing downwards.
  const [isOpen, setIsOpen] = React.useState(false);

  const {builders, commitHash, description,
      repository, status, timestamp} = props.buildData;

  const headerData = {commitHash, description, repository, status};
  const trayData = {builders, timestamp};

  // Set background based on build status.
  const snapshotClassName = 'build-snapshot ' + status;

  return (

      <Tray isOpen={isOpen} data={trayData}/>

  );
}

/**
 * Subcomponent that holds the content displayed
 * when the BuildSnapshot is collapsed.
 *
 * @param {boolean} props.isOpen - Indicates if the Tray should be open.
 * @param {Object} onClick - A callback function responsible
 * for toggling the visibility of the Tray via the onClick event.
 * @param {Object} props.data - Build information to be displayed on Header.
 * @param {string} props.data[].description - The commit message.
 * @param {string} props.data[].commitHash - The commit hash.
 * @param {string} props.data[].repository - URL to the GitHub repository.
 * @param {string} props.data[].status - The status of the build.
 */
const Header = (props) => {
  let headerClassName = 'snapshot-header';
  if (props.isOpen === true) {
    headerClassName = 'snapshot-header active';
  }

  return (

 props.onClick(!props.isOpen)}>

      {props.data.commitHash}
      {props.data.description}
      Group
      {props.data.status.toUpperCase()}

  );
}

/**
 * Subcomponent that holds the content revealed
 * when the BuildSnapshot is open (props.isOpen === true).
 *
 * @param {boolean} props.isOpen - Indicates if the Tray should be open.
 * @param {Object[]} props.data.builders - Bots that built this commit.
 * @param {string} props.data.builders[].name - The name of the given build bot.
 * @param {string} props.data.timestamp - The timestamp of the given build bot.
 */
const Tray = (props) => {
  // State variable indicating the builder whose data
  // should be rendered to the BuilderDataTable.
  // Represents the index of the builder in props.data.builders
  const [selectedBuilder, selectBuilder] = React.useState(0);
  const builder = props.data.builders[selectedBuilder];

  let builderName = "";
  if (builder !== undefined) {
    builderName = builder.name;
  }

  if (props.isOpen !== true) { return null };
  return (

      {props.data.timestamp}

        {builderName}

      <BuilderGrid onClick={selectBuilder} builders={props.data.builders}/>
      <BuilderDataTable buildSteps={builder.buildSteps}/>

  );
}
<commit_msg>Catch undefined error when accessing build steps #217<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import * as React from 'react';
import {BuilderDataTable} from './BuilderDataTable';
import {BuilderGrid} from './BuilderGrid';
import toggleIcon from './resources/play.svg';

/**
 * Component used to render Build Information.
 *
 * @param {Object[]} props.buildData - Aggregated data on latest commit build.
 * @param {string} props.buildData[].description - Commit message.
 * @param {Object[]} props.buildData[].builders - Bots that built this commit.
 * @param {string} props.buildData[].commitHash - Commit hash.
 * @param {string} props.buildData[].repository - URL to the GitHub repository.
 * @param {string} props.buildData[].status - The status of the build.
 *    Either 'passed', 'failed' or 'lost'.
 * @param {string} props.buildData[].timestamp - Timestamp of commit push.
 */
export const BuildSnapshot = (props) => {
  // State variable indicating whether the BuildSnapshot is open or closed.
  // isOpen === true indicates the tray should be visible
  // and the indicator arrow on the Header should be facing downwards.
  const [isOpen, setIsOpen] = React.useState(false);

  const {builders, commitHash, description,
      repository, status, timestamp} = props.buildData;

  const headerData = {commitHash, description, repository, status};
  const trayData = {builders, timestamp};

  // Set background based on build status.
  const snapshotClassName = 'build-snapshot ' + status;

  return (

      <Tray isOpen={isOpen} data={trayData}/>

  );
}

/**
 * Subcomponent that holds the content displayed
 * when the BuildSnapshot is collapsed.
 *
 * @param {boolean} props.isOpen - Indicates if the Tray should be open.
 * @param {Object} onClick - A callback function responsible
 * for toggling the visibility of the Tray via the onClick event.
 * @param {Object} props.data - Build information to be displayed on Header.
 * @param {string} props.data[].description - The commit message.
 * @param {string} props.data[].commitHash - The commit hash.
 * @param {string} props.data[].repository - URL to the GitHub repository.
 * @param {string} props.data[].status - The status of the build.
 */
const Header = (props) => {
  let headerClassName = 'snapshot-header';
  if (props.isOpen === true) {
    headerClassName = 'snapshot-header active';
  }

  return (

 props.onClick(!props.isOpen)}>

      {props.data.commitHash}
      {props.data.description}
      Group
      {props.data.status.toUpperCase()}

  );
}

/**
 * Subcomponent that holds the content revealed
 * when the BuildSnapshot is open (props.isOpen === true).
 *
 * @param {boolean} props.isOpen - Indicates if the Tray should be open.
 * @param {Object[]} props.data.builders - Bots that built this commit.
 * @param {string} props.data.builders[].name - The name of the given build bot.
 * @param {string} props.data.timestamp - The timestamp of the given build bot.
 */
const Tray = (props) => {
  // State variable indicating the builder whose data
  // should be rendered to the BuilderDataTable.
  // Represents the index of the builder in props.data.builders
  const [selectedBuilder, selectBuilder] = React.useState(0);
  const builder = props.data.builders[selectedBuilder];

  let builderName = "";
  let buildSteps = [];

  if (builder !== undefined) {
    builderName = builder.name;
    buildSteps = builder.buildSteps;
  }

  if (props.isOpen !== true) { return null };
  return (

      {props.data.timestamp}

        {builderName}

      <BuilderGrid onClick={selectBuilder} builders={props.data.builders}/>
      <BuilderDataTable buildSteps={buildSteps}/>

  );
}
<|endoftext|>


<commit_before>const commentItem = {
  display: 'flex',
  paddingBottom: '5px',
  WebkitFontSmoothing: 'antialiased',
  transition: 'opacity 0.5s',
}

export default {
  wrapper: {
    flex: 1,
    overflow: 'auto',
    padding: '7px 15px',
  },
  commentItem: {
    ...commentItem,
  },
  commentItemloading: {
    ...commentItem,
    opacity: 0.25,
  },
  commentAside: {
    margin: '5px 10px 0 0',
  },
  commentAvatar: {
    width: 32,
    height: 32,
    borderRadius: 5,
  },
  commentContent: {
    flex: 1,
  },
  commentHead: {
    //
  },
  commentUser: {
    fontFamily: 'sans-serif',
    fontSize: 12,
    lineHeight: 1,
    fontWeight: 'bold',
    marginRight: 5,
  },
  commentTime: {
    fontFamily: 'sans-serif',
    fontSize: 11,
    lineHeight: 1,
    color: 'rgb(150, 150, 150)',
  },
  commentText: {
    fontFamily: 'sans-serif',
    fontSize: 11,
    lineHeight: 1,
  },
}
<commit_msg>change font sizes on comments<commit_after>const commentItem = {
  display: 'flex',
  paddingBottom: '5px',
  WebkitFontSmoothing: 'antialiased',
  transition: 'opacity 0.5s',
}

export default {
  wrapper: {
    flex: 1,
    overflow: 'auto',
    padding: '7px 15px',
  },
  commentItem: {
    ...commentItem,
  },
  commentItemloading: {
    ...commentItem,
    opacity: 0.25,
  },
  commentAside: {
    margin: '5px 10px 0 0',
  },
  commentAvatar: {
    width: 32,
    height: 32,
    borderRadius: 5,
  },
  commentContent: {
    flex: 1,
  },
  commentHead: {
    //
  },
  commentUser: {
    fontFamily: 'sans-serif',
    fontSize: 13,
    lineHeight: 1,
    fontWeight: 'bold',
    marginRight: 5,
  },
  commentTime: {
    fontFamily: 'sans-serif',
    fontSize: 11,
    lineHeight: 1,
    color: 'rgb(150, 150, 150)',
  },
  commentText: {
    fontFamily: 'sans-serif',
    fontSize: 13,
    lineHeight: 1.7,
  },
}
<|endoftext|>