<commit_before>/**
 *
 *    Copyright (c) 2020 Silicon Labs
 *
 *    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
 *
 *        http://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 { dialog, Menu } from 'electron'
import { getSessionInfoFromWindowId } from '../db/query-session.js'
import {
  compileTemplate,
  generateDataToFile,
  getGenerationProperties,
  groupInfoIntoDbRow,
  infoFromDb,
  mapDatabase,
  resolveHelper,
  resolveTemplateDirectory,
} from '../generator/static-generator.js'
import { appDirectory, logError, logInfo, mainDatabase } from '../util/env.js'
import { showErrorMessage } from './ui.js'
import { windowCreate } from './window.js'
import { selectFileLocation, insertFileLocation } from '../db/query-generic.js'
import { updateKeyValue, getSessionKeyValue } from '../db/query-config.js'
import { exportDataIntoFile } from '../importexport/export.js'
import {
  readDataFromFile,
  writeStateToDatabase,
} from '../importexport/import.js'

var httpPort
var generationDirectory = appDirectory() + '/generation-output'
var handlebarTemplateDirectory = __dirname + '/../../test/gen-template'
var generationOptionsFile =
  __dirname + '/../../test/gen-template/generation-options.json'

const template = [
  {
    role: 'fileMenu',
    submenu: [
      {
        label: 'Generate Code',
        click(menuItem, browserWindow, event) {
          generateInDir(browserWindow)
        },
      },
      {
        label: 'Handlebar Template Directory',
        click(menuItem, browserWindow, event) {
          setHandlebarTemplateDirectory(browserWindow)
        },
      },
      {
        label: 'Open File...',
        accelerator: 'CmdOrCtrl+O',
        click(menuItem, browserWindow, event) {
          doOpen(menuItem, browserWindow, event)
        },
      },
      {
        type: 'separator',
      },
      {
        label: 'Session Information...',
        click(menuItem, browserWindow, event) {
          let winId = browserWindow.id
          getSessionInfoFromWindowId(mainDatabase(), winId)
            .then((row) => {
              dialog.showMessageBox(browserWindow, {
                title: 'Information',
                message: `Window id: ${winId}\nZap session id: ${
                  row.sessionId
                }\nSession key: ${row.sessionKey}\nTime: ${new Date(
                  row.creationTime
                )}`,
                buttons: ['Dismiss'],
              })
            })
            .catch((err) => showErrorMessage('Session info', err))
        },
      },
      {
        label: 'Save',
        accelerator: 'CmdOrCtrl+S',
        click(menuItem, browserWindow, event) {
          doSave(menuItem, browserWindow, event)
        },
      },
      {
        label: 'Save As...',
        click(menuItem, browserWindow, event) {
          doSaveAs(menuItem, browserWindow, event)
        },
      },
      {
        type: 'separator',
      },
      {
        role: 'close',
      },
      {
        type: 'separator',
      },
      {
        role: 'quit',
      },
    ],
  },
  {
    role: 'editMenu',
  },
  {
    role: 'viewMenu',
  },
  {
    role: 'windowMenu',
  },
  {
    label: 'Help',
    submenu: [
      {
        role: 'about',
      },
    ],
  },
]
/**
 * Perform a file->open operation.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doOpen(menuItem, browserWindow, event) {
  selectFileLocation(mainDatabase(), 'save')
    .then((filePath) => {
      var opts = {
        properties: ['openFile', 'multiSelections'],
      }
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showOpenDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        fileOpen(mainDatabase(), browserWindow.id, result.filePaths)
      }
    })
    .catch((err) => showErrorMessage('Open file', err))
}

/**
 * Perform a save, defering to save as if file is not yet selected.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSave(menuItem, browserWindow, event) {
  getSessionInfoFromWindowId(mainDatabase(), browserWindow.id)
    .then((row) =>
      getSessionKeyValue(mainDatabase(), row.sessionId, 'filePath')
    )
    .then((filePath) => {
      if (filePath == null) {
        doSaveAs(menuItem, browserWindow, event)
      } else {
        return fileSave(mainDatabase(), browserWindow.id, filePath)
      }
    })
}

/**
 * Perform save as.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSaveAs(menuItem, browserWindow, event) {
  selectFileLocation(mainDatabase(), 'save')
    .then((filePath) => {
      var opts = {}
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showSaveDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        return fileSave(mainDatabase(), browserWindow.id, result.filePath)
      } else {
        return null
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        insertFileLocation(mainDatabase(), filePath, 'save')
        browserWindow.setTitle(filePath)
        dialog.showMessageBox(browserWindow, {
          title: 'Save',
          message: `Save done. Output: ${filePath}`,
          buttons: ['Ok'],
        })
      }
    })
    .catch((err) => showErrorMessage('Save file', err))
}

/**
 * This function gets the directory where user wants the output and
 * calls generateCode function which generates the code in the user selected
 * output.
 *
 * @param {*} browserWindow
 */
function generateInDir(browserWindow) {
  dialog
    .showOpenDialog({ properties: ['openDirectory'] })
    .then((result) => {
      if (!result.canceled) {
        return Promise.resolve(result.filePaths[0])
      } else {
        return Promise.resolve(null)
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        generationDirectory = filePath
        getGenerationProperties(
          generationOptionsFile
        ).then((generationOptions) =>
          generateCode(mainDatabase(), generationOptions)
        )
        dialog.showMessageBox(browserWindow, {
          title: 'Generation',
          message: `Generation Output: ${filePath}`,
          buttons: ['Ok'],
        })
      }
    })
    .catch((err) => showErrorMessage('Save file', err))
}

/**
 *
 *
 * @export
 * @param {*} generationDir
 */
export function generateCodeViaCli(generationDir) {
  generationDirectory = generationDir
  return getGenerationProperties(
    generationOptionsFile
  ).then((generationOptions) => generateCode(mainDatabase(), generationOptions))
}

/**
 *
 *
 * @export
 * @param {*} handlebarTemplateDir
 */
export function setHandlebarTemplateDirForCli(handlebarTemplateDir) {
  return new Promise((resolve, reject) => {
    handlebarTemplateDirectory = handlebarTemplateDir
    resolve(handlebarTemplateDir)
  })
}

/**
 * This function gets the directory where user wants the output and calls
 * generateCode function which generates the code in the user selected output.
 *
 * @param {*} browserWindow
 */
function setHandlebarTemplateDirectory(browserWindow) {
  dialog
    .showOpenDialog({ properties: ['openDirectory'] })
    .then((result) => {
      if (!result.canceled) {
        return Promise.resolve(result.filePaths[0])
      } else {
        return Promise.resolve(null)
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        handlebarTemplateDirectory = filePath
        dialog.showMessageBox(browserWindow, {
          title: 'Handlebar Templates',
          message: `Handlebar Template Directory: ${filePath}`,
          buttons: ['Ok'],
        })
      }
    })
    .catch((err) => showErrorMessage('Save file', err))
}

/**
 * This function generates the code into the user defined directory using promises
 *
 * @param {*} db
 */
function generateCode(db, generationOptions) {
  //Keeping track of each generation promise
  let generatedCodeMap = []

  // The template file which provides meta data information on generation
  var currentGenerationOptions = generationOptions['generation-options']

  // Going through each of the generation options and performing generation
  let generationOptionIndex = 0
  for (
    generationOptionIndex = 0;
    generationOptionIndex < currentGenerationOptions.length;
    generationOptionIndex++
  ) {
    let i = 0
    let templateArray = new Set()
    let dbRowTypeArray = new Set()
    let groupInfoToDb =
      currentGenerationOptions[generationOptionIndex][
        'group-info-into-db-row-type'
      ]
    let helperApis =
      currentGenerationOptions[generationOptionIndex]['helper-api-name']
    let filename = currentGenerationOptions[generationOptionIndex]['filename']
    let handlebarTemplatePerDataRow =
      currentGenerationOptions[generationOptionIndex][
        'handlebar-templates-per-data-row'
      ]

    for (i = 0; i < handlebarTemplatePerDataRow.length; i++) {
      templateArray.add(handlebarTemplatePerDataRow[i]['hTemplateFile'])
      dbRowTypeArray.add(handlebarTemplatePerDataRow[i]['dbRowType'])
    }

    for (i = 0; i < groupInfoToDb.length; i++) {
      let dbType = groupInfoToDb[i].dbType
      if (!dbRowTypeArray.has(dbType)) {
        dbRowTypeArray.add(dbType)
      }
    }

    generatedCodeMap[generationOptionIndex] = mapDatabase(db)
      .then((templateDir) =>
        resolveTemplateDirectory(templateDir, handlebarTemplateDirectory)
      )
      .then((templates) => compileTemplate(templates, templateArray))
      .then((databaseRows) => infoFromDb(databaseRows, dbRowTypeArray))
      .then((databaseRowsWithMoreInfo) =>
        groupInfoIntoDbRow(databaseRowsWithMoreInfo, groupInfoToDb)
      )
      .then((helperResolution) => resolveHelper(helperResolution, helperApis))
      .then((directoryResolution) =>
        resolveGenerationDirectory(directoryResolution)
      )
      .then((resultToFile) =>
        generateDataToFile(resultToFile, filename, handlebarTemplatePerDataRow)
      )
      .catch((err) => logError(err))
  }

  return Promise.all(generatedCodeMap).catch((error) => {
    logError(error)
  })
}
/**
 * perform the save.
 *
 * @param {*} db
 * @param {*} winId
 * @param {*} filePath
 * @returns Promise of saving.
 */
function fileSave(db, winId, filePath) {
  return getSessionInfoFromWindowId(db, winId)
    .then((row) => {
      return updateKeyValue(db, row.sessionId, 'filePath', filePath).then(
        () => row
      )
    })
    .then((row) => exportDataIntoFile(db, row.sessionId, filePath))
    .catch((err) => showErrorMessage('File save', err))
}

/**
 * Perform the do open action, possibly reading in multiple files.
 *
 * @param {*} db
 * @param {*} winId
 * @param {*} filePaths
 */
function fileOpen(db, winId, filePaths) {
  filePaths.forEach((filePath, index) => {
    readAndProcessFile(db, filePath)
  })
}

/**
 * Process a single file, parsing it in as JSON and then possibly opening
 * a new window if all is good.
 *
 * @param {*} db
 * @param {*} filePath
 */
function readAndProcessFile(db, filePath) {
  logInfo(`Read and process: ${filePath}`)
  readDataFromFile(filePath)
    .then((state) => writeStateToDatabase(mainDatabase(), state))
    .then((sessionId) => {
      windowCreate(httpPort, filePath, sessionId)
      return true
    })
    .catch((err) => {
      showErrorMessage(filePath, err)
    })
}

/**
 * Description: Resolve the generation directory to be able to generate to the
 * correct directory.
 * @export
 * @param {*} map
 * @returns promise that resolves into a map.
 */
export function resolveGenerationDirectory(map) {
  return new Promise((resolve, reject) => {
    map.generationDirectory = generationDirectory
    resolve(map)
  })
}

/**
 * Initialize a menu.
 *
 * @export
 * @param {*} port
 */
export function initMenu(port) {
  httpPort = port
  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)
}
<commit_msg>Making changes such that generation-options.json file is present in the handlebar template directory<commit_after>/**
 *
 *    Copyright (c) 2020 Silicon Labs
 *
 *    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
 *
 *        http://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 { dialog, Menu } from 'electron'
import { getSessionInfoFromWindowId } from '../db/query-session.js'
import {
  compileTemplate,
  generateDataToFile,
  getGenerationProperties,
  groupInfoIntoDbRow,
  infoFromDb,
  mapDatabase,
  resolveHelper,
  resolveTemplateDirectory,
} from '../generator/static-generator.js'
import { appDirectory, logError, logInfo, mainDatabase } from '../util/env.js'
import { showErrorMessage } from './ui.js'
import { windowCreate } from './window.js'
import { selectFileLocation, insertFileLocation } from '../db/query-generic.js'
import { updateKeyValue, getSessionKeyValue } from '../db/query-config.js'
import { exportDataIntoFile } from '../importexport/export.js'
import {
  readDataFromFile,
  writeStateToDatabase,
} from '../importexport/import.js'

var httpPort
var generationDirectory = appDirectory() + '/generation-output'
var handlebarTemplateDirectory = __dirname + '/../../test/gen-template'
var generationOptionsFile =
  handlebarTemplateDirectory + '/generation-options.json'

const template = [
  {
    role: 'fileMenu',
    submenu: [
      {
        label: 'Generate Code',
        click(menuItem, browserWindow, event) {
          generateInDir(browserWindow)
        },
      },
      {
        label: 'Handlebar Template Directory',
        click(menuItem, browserWindow, event) {
          setHandlebarTemplateDirectory(browserWindow)
        },
      },
      {
        label: 'Open File...',
        accelerator: 'CmdOrCtrl+O',
        click(menuItem, browserWindow, event) {
          doOpen(menuItem, browserWindow, event)
        },
      },
      {
        type: 'separator',
      },
      {
        label: 'Session Information...',
        click(menuItem, browserWindow, event) {
          let winId = browserWindow.id
          getSessionInfoFromWindowId(mainDatabase(), winId)
            .then((row) => {
              dialog.showMessageBox(browserWindow, {
                title: 'Information',
                message: `Window id: ${winId}\nZap session id: ${
                  row.sessionId
                }\nSession key: ${row.sessionKey}\nTime: ${new Date(
                  row.creationTime
                )}`,
                buttons: ['Dismiss'],
              })
            })
            .catch((err) => showErrorMessage('Session info', err))
        },
      },
      {
        label: 'Save',
        accelerator: 'CmdOrCtrl+S',
        click(menuItem, browserWindow, event) {
          doSave(menuItem, browserWindow, event)
        },
      },
      {
        label: 'Save As...',
        click(menuItem, browserWindow, event) {
          doSaveAs(menuItem, browserWindow, event)
        },
      },
      {
        type: 'separator',
      },
      {
        role: 'close',
      },
      {
        type: 'separator',
      },
      {
        role: 'quit',
      },
    ],
  },
  {
    role: 'editMenu',
  },
  {
    role: 'viewMenu',
  },
  {
    role: 'windowMenu',
  },
  {
    label: 'Help',
    submenu: [
      {
        role: 'about',
      },
    ],
  },
]
/**
 * Perform a file->open operation.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doOpen(menuItem, browserWindow, event) {
  selectFileLocation(mainDatabase(), 'save')
    .then((filePath) => {
      var opts = {
        properties: ['openFile', 'multiSelections'],
      }
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showOpenDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        fileOpen(mainDatabase(), browserWindow.id, result.filePaths)
      }
    })
    .catch((err) => showErrorMessage('Open file', err))
}

/**
 * Perform a save, defering to save as if file is not yet selected.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSave(menuItem, browserWindow, event) {
  getSessionInfoFromWindowId(mainDatabase(), browserWindow.id)
    .then((row) =>
      getSessionKeyValue(mainDatabase(), row.sessionId, 'filePath')
    )
    .then((filePath) => {
      if (filePath == null) {
        doSaveAs(menuItem, browserWindow, event)
      } else {
        return fileSave(mainDatabase(), browserWindow.id, filePath)
      }
    })
}

/**
 * Perform save as.
 *
 * @param {*} menuItem
 * @param {*} browserWindow
 * @param {*} event
 */
function doSaveAs(menuItem, browserWindow, event) {
  selectFileLocation(mainDatabase(), 'save')
    .then((filePath) => {
      var opts = {}
      if (filePath != null) {
        opts.defaultPath = filePath
      }
      return dialog.showSaveDialog(opts)
    })
    .then((result) => {
      if (!result.canceled) {
        return fileSave(mainDatabase(), browserWindow.id, result.filePath)
      } else {
        return null
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        insertFileLocation(mainDatabase(), filePath, 'save')
        browserWindow.setTitle(filePath)
        dialog.showMessageBox(browserWindow, {
          title: 'Save',
          message: `Save done. Output: ${filePath}`,
          buttons: ['Ok'],
        })
      }
    })
    .catch((err) => showErrorMessage('Save file', err))
}

/**
 * This function gets the directory where user wants the output and
 * calls generateCode function which generates the code in the user selected
 * output.
 *
 * @param {*} browserWindow
 */
function generateInDir(browserWindow) {
  dialog
    .showOpenDialog({ properties: ['openDirectory'] })
    .then((result) => {
      if (!result.canceled) {
        return Promise.resolve(result.filePaths[0])
      } else {
        return Promise.resolve(null)
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        generationDirectory = filePath
        getGenerationProperties(
          generationOptionsFile
        ).then((generationOptions) =>
          generateCode(mainDatabase(), generationOptions)
        )
        dialog.showMessageBox(browserWindow, {
          title: 'Generation',
          message: `Generation Output: ${filePath}`,
          buttons: ['Ok'],
        })
      }
    })
    .catch((err) => showErrorMessage('Save file', err))
}

/**
 *
 *
 * @export
 * @param {*} generationDir
 */
export function generateCodeViaCli(generationDir) {
  generationDirectory = generationDir
  return getGenerationProperties(
    generationOptionsFile
  ).then((generationOptions) => generateCode(mainDatabase(), generationOptions))
}

/**
 *
 *
 * @export
 * @param {*} handlebarTemplateDir
 */
export function setHandlebarTemplateDirForCli(handlebarTemplateDir) {
  return new Promise((resolve, reject) => {
    handlebarTemplateDirectory = handlebarTemplateDir
    generationOptionsFile =
      handlebarTemplateDirectory + '/generation-options.json'
    resolve(handlebarTemplateDir)
  })
}

/**
 * This function gets the directory where user wants the output and calls
 * generateCode function which generates the code in the user selected output.
 *
 * @param {*} browserWindow
 */
function setHandlebarTemplateDirectory(browserWindow) {
  dialog
    .showOpenDialog({ properties: ['openDirectory'] })
    .then((result) => {
      if (!result.canceled) {
        return Promise.resolve(result.filePaths[0])
      } else {
        return Promise.resolve(null)
      }
    })
    .then((filePath) => {
      if (filePath != null) {
        handlebarTemplateDirectory = filePath
        dialog.showMessageBox(browserWindow, {
          title: 'Handlebar Templates',
          message: `Handlebar Template Directory: ${filePath}`,
          buttons: ['Ok'],
        })
      }
    })
    .catch((err) => showErrorMessage('Save file', err))
}

/**
 * This function generates the code into the user defined directory using promises
 *
 * @param {*} db
 */
function generateCode(db, generationOptions) {
  //Keeping track of each generation promise
  let generatedCodeMap = []

  // The template file which provides meta data information on generation
  var currentGenerationOptions = generationOptions['generation-options']

  // Going through each of the generation options and performing generation
  let generationOptionIndex = 0
  for (
    generationOptionIndex = 0;
    generationOptionIndex < currentGenerationOptions.length;
    generationOptionIndex++
  ) {
    let i = 0
    let templateArray = new Set()
    let dbRowTypeArray = new Set()
    let groupInfoToDb =
      currentGenerationOptions[generationOptionIndex][
        'group-info-into-db-row-type'
      ]
    let helperApis =
      currentGenerationOptions[generationOptionIndex]['helper-api-name']
    let filename = currentGenerationOptions[generationOptionIndex]['filename']
    let handlebarTemplatePerDataRow =
      currentGenerationOptions[generationOptionIndex][
        'handlebar-templates-per-data-row'
      ]

    for (i = 0; i < handlebarTemplatePerDataRow.length; i++) {
      templateArray.add(handlebarTemplatePerDataRow[i]['hTemplateFile'])
      dbRowTypeArray.add(handlebarTemplatePerDataRow[i]['dbRowType'])
    }

    for (i = 0; i < groupInfoToDb.length; i++) {
      let dbType = groupInfoToDb[i].dbType
      if (!dbRowTypeArray.has(dbType)) {
        dbRowTypeArray.add(dbType)
      }
    }

    generatedCodeMap[generationOptionIndex] = mapDatabase(db)
      .then((templateDir) =>
        resolveTemplateDirectory(templateDir, handlebarTemplateDirectory)
      )
      .then((templates) => compileTemplate(templates, templateArray))
      .then((databaseRows) => infoFromDb(databaseRows, dbRowTypeArray))
      .then((databaseRowsWithMoreInfo) =>
        groupInfoIntoDbRow(databaseRowsWithMoreInfo, groupInfoToDb)
      )
      .then((helperResolution) => resolveHelper(helperResolution, helperApis))
      .then((directoryResolution) =>
        resolveGenerationDirectory(directoryResolution)
      )
      .then((resultToFile) =>
        generateDataToFile(resultToFile, filename, handlebarTemplatePerDataRow)
      )
      .catch((err) => logError(err))
  }

  return Promise.all(generatedCodeMap).catch((error) => {
    logError(error)
  })
}
/**
 * perform the save.
 *
 * @param {*} db
 * @param {*} winId
 * @param {*} filePath
 * @returns Promise of saving.
 */
function fileSave(db, winId, filePath) {
  return getSessionInfoFromWindowId(db, winId)
    .then((row) => {
      return updateKeyValue(db, row.sessionId, 'filePath', filePath).then(
        () => row
      )
    })
    .then((row) => exportDataIntoFile(db, row.sessionId, filePath))
    .catch((err) => showErrorMessage('File save', err))
}

/**
 * Perform the do open action, possibly reading in multiple files.
 *
 * @param {*} db
 * @param {*} winId
 * @param {*} filePaths
 */
function fileOpen(db, winId, filePaths) {
  filePaths.forEach((filePath, index) => {
    readAndProcessFile(db, filePath)
  })
}

/**
 * Process a single file, parsing it in as JSON and then possibly opening
 * a new window if all is good.
 *
 * @param {*} db
 * @param {*} filePath
 */
function readAndProcessFile(db, filePath) {
  logInfo(`Read and process: ${filePath}`)
  readDataFromFile(filePath)
    .then((state) => writeStateToDatabase(mainDatabase(), state))
    .then((sessionId) => {
      windowCreate(httpPort, filePath, sessionId)
      return true
    })
    .catch((err) => {
      showErrorMessage(filePath, err)
    })
}

/**
 * Description: Resolve the generation directory to be able to generate to the
 * correct directory.
 * @export
 * @param {*} map
 * @returns promise that resolves into a map.
 */
export function resolveGenerationDirectory(map) {
  return new Promise((resolve, reject) => {
    map.generationDirectory = generationDirectory
    resolve(map)
  })
}

/**
 * Initialize a menu.
 *
 * @export
 * @param {*} port
 */
export function initMenu(port) {
  httpPort = port
  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)
}
<|endoftext|>


<commit_before>Meteor.methods({
  "insert_comment": function(comment, type){
    //validate
    if(!this.userId){
      throw new Meteor.Error("403", "Not logged in.");
    } else if(comment.text.match(/<((?!(a|strong|blockquote|code|h1|h2|h3|i|li|ol|p|pre|ul|br|hr|s|em|u)).)*>/gi)){
      throw new Meteor.Error("400", "Invalid HTML.");
    } else if(comment.text.replace(/<\/?(\w|\d)+>/gi, "").length == 0 || /^ *$/gi.test(comment.text)){
      throw new Meteor.Error("400", "Empty HTML content.");
    }

    comment.userId = this.userId;
    comment.createdAt = new Date();
    const comment_id = Comments.insert(comment);

    //send notification
    const target_comment = Comments.findOne(comment.target_comment);
    if(comment.target_comment && target_comment.userId !== comment.userId){
      const notification = {
        sender: comment.userId,
        userId: target_comment.userId,
        read: false,
        comment_id: comment.target_comment,
        from_comment: comment_id,
        document_id: target_comment.document_id,
        createdAt: comment.createdAt,
        type: type
      }

      Notifications.insert(notification);
    }
  },
  "delete_comment": function(comment_id){
    //validate
    if(!this.userId){
      throw new Meteor.Error("403", "Not logged in.");
    } else if (!Comments.findOne(comment_id)){
      throw new Meteor.Error("400", "No such comment.")
    } else if (Comments.findOne(comment_id).userId !== this.userId){
      throw new Meteor.Error("400", "No the same user.")
    }

    //remove all related comments
    const comment_document = Comments.findOne(comment_id);
    if(Comments.findOne({parent_comment: comment_id})){
      const user_list = new Set();
      var related_comments = Comments.find({parent_comment: comment_id}).fetch();

      while(related_comments.length != 0){
        const current_doc = related_comments.splice(0, 1)[0];
        related_comments = related_comments.concat(Comments.find({parent_comment: current_doc._id}).fetch());
        Comments.update(current_doc._id, {$set: {
          document_id: "deleted_" + current_doc.document_id,
          target_comment: "deleted_" + current_doc.target_comment,
          parent_comment: "deleted_" + current_doc.parent_comment
        }});
        if(current_doc.userId !== this.userId){
          user_list.add(current_doc.userId);
        }
      }

      //send Notifications
      const date = new Date();
      for(var userId of user_list){
        const notification = {
          userId: userId,
          sender: this.userId,
          createdAt: date,
          read: false,
          from_comment: "deleted_" + comment_id,
          document_id: comment_document.document_id,
          type: Posts.findOne(comment_document.document_id) ? "posts" : "works"
        }

        Notifications.insert(notification);
      }
    }

    //delete notifications for documents that do not exist
    Notifications.remove({
      sender: comment_document.userId,
      comment_id: comment_document.target_comment,
      from_comment: comment_id,
      document_id: comment_document.document_id,
      type: Posts.findOne(comment_document.document_id) ? "posts" : "works",
      userId: Comments.findOne(comment_document.target_comment).userId
    });

    Comments.remove(comment_id);
  }
})
<commit_msg>Check user before delete notifications<commit_after>Meteor.methods({
  "insert_comment": function(comment, type){
    //validate
    if(!this.userId){
      throw new Meteor.Error("403", "Not logged in.");
    } else if(comment.text.match(/<((?!(a|strong|blockquote|code|h1|h2|h3|i|li|ol|p|pre|ul|br|hr|s|em|u)).)*>/gi)){
      throw new Meteor.Error("400", "Invalid HTML.");
    } else if(comment.text.replace(/<\/?(\w|\d)+>/gi, "").length == 0 || /^ *$/gi.test(comment.text)){
      throw new Meteor.Error("400", "Empty HTML content.");
    }

    comment.userId = this.userId;
    comment.createdAt = new Date();
    const comment_id = Comments.insert(comment);

    //send notification
    const target_comment = Comments.findOne(comment.target_comment);
    if(comment.target_comment && target_comment.userId !== comment.userId){
      const notification = {
        sender: comment.userId,
        userId: target_comment.userId,
        read: false,
        comment_id: comment.target_comment,
        from_comment: comment_id,
        document_id: target_comment.document_id,
        createdAt: comment.createdAt,
        type: type
      }

      Notifications.insert(notification);
    }
  },
  "delete_comment": function(comment_id){
    //validate
    if(!this.userId){
      throw new Meteor.Error("403", "Not logged in.");
    } else if (!Comments.findOne(comment_id)){
      throw new Meteor.Error("400", "No such comment.")
    } else if (Comments.findOne(comment_id).userId !== this.userId){
      throw new Meteor.Error("400", "No the same user.")
    }

    //remove all related comments
    const comment_document = Comments.findOne(comment_id);
    if(Comments.findOne({parent_comment: comment_id})){
      const user_list = new Set();
      var related_comments = Comments.find({parent_comment: comment_id}).fetch();

      while(related_comments.length != 0){
        const current_doc = related_comments.splice(0, 1)[0];
        related_comments = related_comments.concat(Comments.find({parent_comment: current_doc._id}).fetch());
        Comments.update(current_doc._id, {$set: {
          document_id: "deleted_" + current_doc.document_id,
          target_comment: "deleted_" + current_doc.target_comment,
          parent_comment: "deleted_" + current_doc.parent_comment
        }});
        if(current_doc.userId !== this.userId){
          user_list.add(current_doc.userId);
        }
      }

      //send Notifications
      const date = new Date();
      for(var userId of user_list){
        const notification = {
          userId: userId,
          sender: this.userId,
          createdAt: date,
          read: false,
          from_comment: "deleted_" + comment_id,
          document_id: comment_document.document_id,
          type: Posts.findOne(comment_document.document_id) ? "posts" : "works"
        }

        Notifications.insert(notification);
      }
    }

    //delete notifications for documents that do not exist
    if(comment_document.userId !== this.userId){//ignore notifications for the same user
      Notifications.remove({
        sender: comment_document.userId,
        comment_id: comment_document.target_comment,
        from_comment: comment_id,
        document_id: comment_document.document_id,
        type: Posts.findOne(comment_document.document_id) ? "posts" : "works",
        userId: Comments.findOne(comment_document.target_comment).userId
      });
    }

    Comments.remove(comment_id);
  }
})
<|endoftext|>


<commit_before>// @flow

import path from 'path';
import {
  Compiler,
  DllPlugin
} from 'webpack';
import createDebug from 'debug';
import findInstance from '../utilities/findInstance';
import createResourceMap from './createResourceMap';

const debug = createDebug('isomorphic-webpack');

export default (compiler: Compiler, callback: Function): Function => {
  const dllPlugin = findInstance(compiler.options.plugins, DllPlugin);
  const manifestPath = dllPlugin.options.path;

  debug('manifestPath', manifestPath);

  const outputFileSystem = compiler.outputFileSystem;

  return (error: Object, stats: Object) => {
    if (error) {
      debug('compiler error:', error);

      return;
    }

    if (stats.compilation.errors.length) {
      debug('compilation error', stats.compilation.errors);

      return;
    }

    if (stats.compilation.missingDependencies.length) {
      debug('aborting compilation; missing dependencies', stats.compilation.missingDependencies);

      return;
    }

    if (!outputFileSystem.existsSync(manifestPath)) {
      throw new Error('Manifest file does not exist.');
    }

    const manifest = JSON.parse(outputFileSystem.readFileSync(manifestPath));

    debug('manifest', manifest);

    const requestMap = createResourceMap(manifest.content);

    debug('requestMap', requestMap);

    const files = stats.compilation.chunks[0].files.find(file => {
      return /\.js$/.test(file)
    });
    const bundleName = files[files.length - 1];

    debug('bundleName', bundleName);

    const absoluteEntryBundleName = path.resolve(compiler.options.output.path, bundleName);

    if (!outputFileSystem.existsSync(absoluteEntryBundleName)) {
      throw new Error('Bundle file does not exist.');
    }

    if (!outputFileSystem.existsSync(absoluteEntryBundleName + '.map')) {
      throw new Error('Bundle map file does not exist.');
    }

    const bundleCode = outputFileSystem.readFileSync(absoluteEntryBundleName, 'utf-8');

    const bundleSourceMap = JSON.parse(outputFileSystem.readFileSync(absoluteEntryBundleName + '.map', 'utf-8'));

    callback({
      bundleCode,
      bundleSourceMap,
      requestMap
    });
  };
};
<commit_msg>fixing Lint issues<commit_after>// @flow

import path from 'path';
import {
  Compiler,
  DllPlugin
} from 'webpack';
import createDebug from 'debug';
import findInstance from '../utilities/findInstance';
import createResourceMap from './createResourceMap';

const debug = createDebug('isomorphic-webpack');

export default (compiler: Compiler, callback: Function): Function => {
  const dllPlugin = findInstance(compiler.options.plugins, DllPlugin);
  const manifestPath = dllPlugin.options.path;

  debug('manifestPath', manifestPath);

  const outputFileSystem = compiler.outputFileSystem;

  return (error: Object, stats: Object) => {
    if (error) {
      debug('compiler error:', error);

      return;
    }

    if (stats.compilation.errors.length) {
      debug('compilation error', stats.compilation.errors);

      return;
    }

    if (stats.compilation.missingDependencies.length) {
      debug('aborting compilation; missing dependencies', stats.compilation.missingDependencies);

      return;
    }

    if (!outputFileSystem.existsSync(manifestPath)) {
      throw new Error('Manifest file does not exist.');
    }

    const manifest = JSON.parse(outputFileSystem.readFileSync(manifestPath));

    debug('manifest', manifest);

    const requestMap = createResourceMap(manifest.content);

    debug('requestMap', requestMap);

    const files = stats.compilation.chunks[0].files.find((file) => {
      return /\.js$/.test(file);
    });
    const bundleName = files[files.length - 1];

    debug('bundleName', bundleName);

    const absoluteEntryBundleName = path.resolve(compiler.options.output.path, bundleName);

    if (!outputFileSystem.existsSync(absoluteEntryBundleName)) {
      throw new Error('Bundle file does not exist.');
    }

    if (!outputFileSystem.existsSync(absoluteEntryBundleName + '.map')) {
      throw new Error('Bundle map file does not exist.');
    }

    const bundleCode = outputFileSystem.readFileSync(absoluteEntryBundleName, 'utf-8');

    const bundleSourceMap = JSON.parse(outputFileSystem.readFileSync(absoluteEntryBundleName + '.map', 'utf-8'));

    callback({
      bundleCode,
      bundleSourceMap,
      requestMap
    });
  };
};
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react';
import * as libs from '../libs';
import uuid from 'node-uuid';
import type {DOMEvent, FormActions, FormConfig,
  FormErrors, FormField, FormFields, ListRow} from '../types';

let lib, fields, FormControl, layouts, Button;

type Props = {
  actions: FormActions,
  className?: string,
  config: {
    view: string,
    form: FormConfig
  },
  data: ListRow,
  errors: FormErrors,
  formUpdate: Function,
  layout: string,
  lib?: string,
  onSubmit: Function
};

/**
 * Create a form
 */
class UiForm extends Component {
  props: Props

  state: {
    form: Object,
    data: Object,
    state: Object
  }
  fields: FormFields
  actions: FormActions
  handleChange: (e: DOMEvent, name: string) => {}
  handleBlur: (name: string) => {}
  onSubmit: (e: DOMEvent, data: Object) => {}

  /**
   * Constructor
   * @param {Object} props props
   */
  constructor(props: Props) {
    super(props);
    const {config, onSubmit} = this.props;
    this.state = {
      form: config.form,
      data: {},
      state: {}
    };

    let libType = config.lib || 'reactBootstrap';
    lib = libs[libType];
    fields = lib.fields;
    layouts = lib.layouts;
    FormControl = lib.FormControl;
    Button = lib.Button;

    this.fields = config.form.fields;
    this.actions = config.form.actions;

    this.makeState();

    this.handleChange = this.handleChange.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.onSubmit = onSubmit.bind(this);
    this.applyFieldFunctions();
    this.applyDataToForm(this.state.data);
  }

  componentWillReceiveProps(newProps: Props) {
    const {config} = newProps;
    this.fields = config.form.fields;
    this.applyFieldFunctions();
  }

  /**
   * Build the form state, using this.props.data, then field value
   * for data.
   */
  makeState() {
    const {data} = this.props;
    let name;
    for (name in this.fields) {
      if (data && data[name]) {
        this.state.data[name] = data[name];
      } else if (this.fields[name].value === undefined) {
        this.state.data[name] = '';
      } else {
        this.state.data[name] = this.fields[name].value;
      }
    }
  }

  /**
   * Update the form's properties so that any config value that is a function is
   * run and the current record's data applied.
   * @param {Object} row Form state
   */
  applyDataToForm(row: ListRow) {
    let name,
      form = this.state.form;
    if (typeof (form._title) === 'function') {
      this.state.form.title = form._title(row);
    }
    for (name in form.actions) {
      if (typeof (form.actions[name]._label) === 'function') {
        this.state.form.actions[name].label = form.actions[name]._label(row);
      }
    }
  }

  /**
   * Get validation state
   * @param {string} name column name
   * @return {string | voild}
   */
  getValidationState(name: string): string | void {
    let field = this.fields[name],
      {errors} = this.props,
      state = this.state.state,
      i = 0,
      value = this.state.data[name],
      res = [],
      serverError = (errors[name] && errors[name].length > 0),
      serverSuccess = (errors[name] && errors[name].length === 0);

    if (field.validate !== undefined) {
      res = field.validate.map(v => v(value))
    }
    if (!field.pristine) {
      if (res.indexOf('error') !== -1 || serverError) {
         state[name] = 'error';
      }
      else if (res.indexOf('warning') !== -1) {
        state[name] = 'warning';
      } else {
        state[name] = 'success';
      }
    } else {
       if (serverError) {
      state[name] = 'error';
    }
      if (serverSuccess) {
        state[name] = 'success';
      }
    }
    console.log('state', state);
    this.setState(state);
  }

  /**
   * Check field access, if no access property then return true
   * Otherwise it should be an object with functions keyed on edit or new
   * @param {Object} field To render
   * @return {Bool} Can access field
   */
  access(field: FormField): boolean {
    const mode = this.state.data.id === '' ? 'new' : 'edit';

    if (!field.access) {
      return true;
    }

    if (field.access[mode]) {
      return field.access[mode](field, this.state.data);
    }

    return true;
  }

  /**
   * Update the form's state
   * @param {String} name Field name
   * @param {String|number} value Field value
   */
  handleChange(name: string, value: string | number) {
    const {formUpdate, config} = this.props,
      field = this.fields[name];
    this.fields[name].pristine = false;
    if (typeof formUpdate === 'function') {
      formUpdate(config.view, field, name, value);
    }
    let data = this.state.data;
    data[name] = value;
    this.setState({data});

    if (field.onChange) {
      field.onChange(this);
    }
  }

  /**
   * Get the form's layout
   * @return {Dom} Dom node
   */
  formLayout() {
    const {layout} = this.props;
    if (typeof layout === 'function') {
      return layout;
    }
    let layoutName = layout && layout[0].toUpperCase() + layout.slice(1);
    return layouts[layoutName] ? layouts[layoutName] : layouts.Default;
  }

  /**
   * Handle field blur - check validation state
   */
  handleBlur(name: string) {
    this.fields[name].pristine = false;
    this.getValidationState(name);
  }

  /**
   * Build a field
   * @param {String} name Field name
   * @param {Object} field Field
   * @return {Node} FormGroup - field, help and label
   */
  makeField(name: string, field: FormField): React$Element<any> | null {
    // Ucase first the name
    let {errors} = this.props,
      error = errors[name] || [],
      FormGroup = lib.FormGroup,
      type = field.type && field.type[0].toUpperCase()
        + field.type.slice(1);

    if (!fields[type]) {
      return null;
    }
    this.getValidationState(name);
    return <FormGroup
      errors={error}
      FieldComponent={fields[type]}
      field={field}
      name={name}
      row={this.state.data}
      onBlur={this.handleBlur}
      onChange={(name, value) => this.handleChange(name, value)}
      value={this.state.data[name]}
      validationState={this.state.state[name]} />
  }

  /**
   * If a field type is a function then its a react element
   * Create a uuid for it and assign it to the list of field
   * renderers
   */
  applyFieldFunctions() {
    Object.keys(this.fields).forEach(name => {
        let type,
          field = this.fields[name];
        if (typeof field.type === 'function') {
          // React component....
          type = uuid.v4();
          this.fields[type] = field.type;
        }
    });
  }

  /**
   * Render
   * @return {Node} Dom node
   */
  render(): React$Element<any> {
    const {errors} = this.props,
      FormActions = lib.FormActions,
      buttons = <FormActions
                  actions={this.actions}
                  onSubmit={e => {
                    debugger;
                    e.preventDefault();
                    console.log('data', this.state.data);
                    this.onSubmit(e, this.state.data)}
                  }/>,
      FormLayout = this.formLayout();

    let fields = {};

    Object.keys(this.fields)
      .filter(name => this.access(this.fields[name]))
      .forEach(name => {
        let field = this.fields[name];
        fields[name] = this.makeField(name, field);
    });

    return (<FormLayout
              actions={buttons}
              errors={errors}
              fields={fields}
              form={this.state.form}
              onSubmit={this.onSubmit} />);
  }
}

export default UiForm;
<commit_msg>set validation state only in handleblur<commit_after>// @flow
import React, {Component} from 'react';
import * as libs from '../libs';
import uuid from 'node-uuid';
import type {DOMEvent, FormActions, FormConfig,
  FormErrors, FormField, FormFields, ListRow} from '../types';

let lib, fields, FormControl, layouts, Button;

type Props = {
  actions: FormActions,
  className?: string,
  config: {
    view: string,
    form: FormConfig
  },
  data: ListRow,
  errors: FormErrors,
  formUpdate: Function,
  layout: string,
  lib?: string,
  onSubmit: Function
};

/**
 * Create a form
 */
class UiForm extends Component {
  props: Props

  state: {
    form: Object,
    data: Object,
    state: Object
  }
  fields: FormFields
  actions: FormActions
  handleChange: (e: DOMEvent, name: string) => {}
  handleBlur: (name: string) => {}
  onSubmit: (e: DOMEvent, data: Object) => {}

  /**
   * Constructor
   * @param {Object} props props
   */
  constructor(props: Props) {
    super(props);
    const {config, onSubmit} = this.props;
    this.state = {
      form: config.form,
      data: {},
      state: {}
    };

    let libType = config.lib || 'reactBootstrap';
    lib = libs[libType];
    fields = lib.fields;
    layouts = lib.layouts;
    FormControl = lib.FormControl;
    Button = lib.Button;

    this.fields = config.form.fields;
    this.actions = config.form.actions;

    this.makeState();

    this.handleChange = this.handleChange.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.onSubmit = onSubmit.bind(this);
    this.applyFieldFunctions();
    this.applyDataToForm(this.state.data);
  }

  componentWillReceiveProps(newProps: Props) {
    const {config} = newProps;
    this.fields = config.form.fields;
    this.applyFieldFunctions();
  }

  /**
   * Build the form state, using this.props.data, then field value
   * for data.
   */
  makeState() {
    const {data} = this.props;
    let name;
    for (name in this.fields) {
      if (data && data[name]) {
        this.state.data[name] = data[name];
      } else if (this.fields[name].value === undefined) {
        this.state.data[name] = '';
      } else {
        this.state.data[name] = this.fields[name].value;
      }
    }
  }

  /**
   * Update the form's properties so that any config value that is a function is
   * run and the current record's data applied.
   * @param {Object} row Form state
   */
  applyDataToForm(row: ListRow) {
    let name,
      form = this.state.form;
    if (typeof (form._title) === 'function') {
      this.state.form.title = form._title(row);
    }
    for (name in form.actions) {
      if (typeof (form.actions[name]._label) === 'function') {
        this.state.form.actions[name].label = form.actions[name]._label(row);
      }
    }
  }

  /**
   * Get validation state
   * @param {string} name column name
   * @return {Object} validation state
   */
  getValidationState(name: string): Object {
    let field = this.fields[name],
      {errors} = this.props,
      state = this.state.state,
      i = 0,
      value = this.state.data[name],
      res = [],
      serverError = (errors[name] && errors[name].length > 0),
      serverSuccess = (errors[name] && errors[name].length === 0);

    if (field.validate !== undefined) {
      res = field.validate.map(v => v(value))
    }
    if (!field.pristine) {
      if (res.indexOf('error') !== -1 || serverError) {
         state[name] = 'error';
      }
      else if (res.indexOf('warning') !== -1) {
        state[name] = 'warning';
      } else {
        state[name] = 'success';
      }
    } else {
       if (serverError) {
        state[name] = 'error';
      }
      if (serverSuccess) {
        state[name] = 'success';
      }
    }
    console.log('state', state);
    return state;
  }

  /**
   * Check field access, if no access property then return true
   * Otherwise it should be an object with functions keyed on edit or new
   * @param {Object} field To render
   * @return {Bool} Can access field
   */
  access(field: FormField): boolean {
    const mode = this.state.data.id === '' ? 'new' : 'edit';

    if (!field.access) {
      return true;
    }

    if (field.access[mode]) {
      return field.access[mode](field, this.state.data);
    }

    return true;
  }

  /**
   * Update the form's state
   * @param {String} name Field name
   * @param {String|number} value Field value
   */
  handleChange(name: string, value: string | number) {
    const {formUpdate, config} = this.props,
      field = this.fields[name];
    this.fields[name].pristine = false;
    if (typeof formUpdate === 'function') {
      formUpdate(config.view, field, name, value);
    }
    let data = this.state.data;
    data[name] = value;
    this.setState({data});

    if (field.onChange) {
      field.onChange(this);
    }
  }

  /**
   * Get the form's layout
   * @return {Dom} Dom node
   */
  formLayout() {
    const {layout} = this.props;
    if (typeof layout === 'function') {
      return layout;
    }
    let layoutName = layout && layout[0].toUpperCase() + layout.slice(1);
    return layouts[layoutName] ? layouts[layoutName] : layouts.Default;
  }

  /**
   * Handle field blur - check validation state
   */
  handleBlur(name: string) {
    this.fields[name].pristine = false;
    let state = this.getValidationState(name);
    this.setState(state);
  }

  /**
   * Build a field
   * @param {String} name Field name
   * @param {Object} field Field
   * @return {Node} FormGroup - field, help and label
   */
  makeField(name: string, field: FormField): React$Element<any> | null {
    // Ucase first the name
    let {errors} = this.props,
      error = errors[name] || [],
      FormGroup = lib.FormGroup,
      validationState = this.getValidationState(name)[name],
      type = field.type && field.type[0].toUpperCase()
        + field.type.slice(1);

    if (!fields[type]) {
      return null;
    }
    ;
    return <FormGroup
      errors={error}
      FieldComponent={fields[type]}
      field={field}
      name={name}
      row={this.state.data}
      onBlur={this.handleBlur}
      onChange={(name, value) => this.handleChange(name, value)}
      value={this.state.data[name]}
      validationState={this.state.state[name]} />
  }

  /**
   * If a field type is a function then its a react element
   * Create a uuid for it and assign it to the list of field
   * renderers
   */
  applyFieldFunctions() {
    Object.keys(this.fields).forEach(name => {
        let type,
          field = this.fields[name];
        if (typeof field.type === 'function') {
          // React component....
          type = uuid.v4();
          this.fields[type] = field.type;
        }
    });
  }

  /**
   * Render
   * @return {Node} Dom node
   */
  render(): React$Element<any> {
    const {errors} = this.props,
      FormActions = lib.FormActions,
      buttons = <FormActions
                  actions={this.actions}
                  onSubmit={e => {
                    debugger;
                    e.preventDefault();
                    console.log('data', this.state.data);
                    this.onSubmit(e, this.state.data)}
                  }/>,
      FormLayout = this.formLayout();

    let fields = {};

    Object.keys(this.fields)
      .filter(name => this.access(this.fields[name]))
      .forEach(name => {
        let field = this.fields[name];
        fields[name] = this.makeField(name, field);
    });

    return (<FormLayout
              actions={buttons}
              errors={errors}
              fields={fields}
              form={this.state.form}
              onSubmit={this.onSubmit} />);
  }
}

export default UiForm;
<|endoftext|>


<commit_before>'use strict';
Polymer({
  is: "paper-step",
  properties: {
    /**
     * Computed css classes based on the current `step` and the total
     * number value of `steps`.
     */
    _cssClass: {
      type: String,
      computed: '__cssClass(step, steps)'
    },
    /**
     * Computed for the `paper-step` image icon based on the supplied `icon`
     * attribute and the `completed` property.
     */
    _image: {
      type: String,
      computed: '_icon(editable)'
    },
    /**
     * Indicates if the current `paper-step` is selected and toggles the
     * active/inactive css style rules.
     */
    _selected: {
      type: Boolean,
      value: false
    },
    /**
     * If true, the submit buttons which are automatically
     * added will be hidden for this `paper-step` element.
     */
    actionsDisabled: {
      type: Boolean
    },
    /**
     * If true, the current step has been validated and marked as done.
     */
    completed: {
      type: Boolean,
      value: false,
      reflectToAttribute: true,
      observer: '_onComplete'
    },
    /**
     * Specify an alternate label to use for the `continue` button.
     */
    continueLabel: {
      type: String,
      value: 'Continue'
    },
    /**
     * A copy of the serialized form data for the last successful submit.
     */
     data: Object,
    /**
     * Used internally to debouce custom events:
     * `paper-step-complete`, and `paper-step-next`.
     */
    duplicate: {
      type: Boolean,
      value: false
    },
    /**
     *
     */
    editable: {
      type: Boolean,
      value: false,
      reflectToAttribute: true
    },
    /**
     * The text to display in the steps area next to the image icon.
     */
    label: {
      type: String
    },
    lastErrorResponse: Object,
    lastSuccessResponse: Object,
    /**
     * To indicate if the current step required or optional.
     */
    optional: {
      type: Boolean,
      value: false
    },
    /**
     * Specify an alternate label to use for the `reset` button.
     */
    resetLabel: {
      type: String,
      value: 'Reset'
    },
    selectable: {
      type: Boolean,
      computed: '_selectable(completed, editable)',
      reflectToAttribute: true
    },
    skipLabel: {
      type: String,
      value: 'Skip'
    },
    /**
     * The 1 based index value for the current `paper-step` in `paper-steps`.
     *
     * `Automatically calculated`.
     */
    step: {
      type: Number
    },
    /**
     * The value for the total number of `paper-step` elements (readonly).
     *
     * `Automatically calculated`.
     */
    steps: {
      type: Number
    }
  },
  behaviors: [],
  listeners: {
    //error after submit, IronRequestElement in event.detail object
    'iron-form-error': '_onResponse', //'_onError',

    //cannot submit, form is invalid

    // 'iron-form-invalid': '',
    'iron-form-presubmit': '_onPreSubmit',

    // //after form is reset
    // 'iron-form-reset': '_reset',

    //after form submitted, IronRequestElement inevent.detail object
    'iron-form-response': '_onResponse',
    'iron-form-submit': '_onSubmit' //after submitted
  },

  // Element Lifecycle
  ready: function() {},

  attached: function() {
    var
      parent = this.parentNode && this.parentNode.indexOf && this.parentNode || null,
      form = this._getForm()
    ;

    // @TODO: finish adding fallback for shadowDom, not yet ready.
    if (!Boolean(parent) && this.shadowRoot) {
      try {
        parent = this.parentNode.$.selector;
      } catch (e) {
        parent = null;
      }
    }

    //
    if (!parent || parent.hasOwnProperty('indexOf') || typeof parent.indexOf != 'function') {
      this.step = 1;
      this.steps = 1;
      try {
        console.log('Improperly declared paper-step element, ' +
          'it is missing the parent iterator.');
      } catch (e) {}
      return;
    }

    if (!Boolean(this.step)) {
      this.step = parent.indexOf(this) + 1;
    }

    if (!Boolean(this.steps)) {
      this.steps = parent.items.length;
    }

    this.listen(this.$.continue, 'tap', '_submit');
    this.listen(this.$.reset, 'tap', '_reset');
    this.listen(this.$.skip, 'tap', '_skip');

    if (form && Polymer.isInstance(form)) {
      var i, child, len,
        children = form.getEffectiveChildren()
      ;
      for (i=0, len=children.length; i<len; i++) {
        child = children[i];
        this.listen(child, 'keyup', '_onChange');
      }
    }
  },

  detached: function() {},

  /**
   * Computes `_cssClass` property.
   * @return {string}.
   */
  __cssClass: function(step, steps) {
    var cls = (this.optional ? 'optional' : 'required') + '-step ';

    if (step === 1) {
      cls += 'first ';
    }
    if (step === steps) {
      cls += 'last ';
    }

    return cls;
  },
  /**
   * Attempts to retrieve the `iron-form` element for the current `paper-step`.
   */
  _getForm: function() {
    var
      el = Polymer.dom(this.$.step_content),
      form = el && el.node &&
        el.node.querySelector('div form[is="iron-form"]')
    ;
    return form || false;
  },
  /**
   * Attempts to retrieve the `submit` button for the current `paper-step`.
   */
  _getPrimaryButton: function() {
    var el, button;
    if (this.actionsDisabled) {
      el = this._getForm();
      if (el) {
        el = Polymer.dom(el).node.querySelectorAll(
          'button,paper-button,input[type="submit"]');
        button = el && el.length && el[0];
        if (button) {
          return button;
        }
      }
    } else {
      return this.$.continue;
    }
    return undefined;
  },
  /**
   * Computes `image` property based on step `icon` and current `step`.
   */
  _icon: function(editable) {
    if (editable) {
      return 'icons:create';
    }
    return 'icons:check';
    // return 'image:brightness-1';
  },
  _onChange: function(e) {
    if (this.completed) {
      this.completed = false;
    }
  },
  /**
   *
   */
  _onComplete: function(_new, _old) {
    if (this.duplicate === false && _new === true) {
      this.fire('paper-step-complete', this.step);
      this.fire('paper-step-next', this.step);
    }
  },
  /**
   *
   */
  _onError: function(e) {
  },
  /**
   *
   */
  _onPreSubmit: function(e) {
    var
      form = this._getForm()
    ;

    if (this.completed) {
      e.preventDefault();
      this.fire('paper-step-already-complete', this);
    }
  },
  /**
   * Handles iron form events for `iron-form-error` and `iron-form-response`.
   */
  _onResponse: function(e) {
    var
      el = this._getPrimaryButton(),
      request = e && e.detail || false
    ;

    if (Polymer.isInstance(el) && el.disabled !== undefined) {
      el.disabled = false;
    }
    if (request && request.status === 200) {
      this.lastSuccessResponse = request;
      try {
        this.data = this._getForm().serialize();
      } catch (ex) {
        this.data = {}
      }
      this.completed = true;
    } else {
      this.lastErrorResponse = request;
    }
  },
  /**
   * Hooks into the `iron-form-submit` event to disable the form submit button.
   */
  _onSubmit: function(e) {
    var
      el = this._getPrimaryButton()
    ;

    if (Polymer.isInstance(el) && el.disabled !== undefined) {
      el.disabled = true;
    }
  },
  /**
   */
  _selectable: function(completed, editable) {
    return editable || !completed;
  },
  /**
   * When the `Reset` button is pressed, this restores the form elements
   * to their initial state by calling `form.reset()`.
   */
  _reset: function(e) {
    var
      event = e,
      that = this,
      form
    ;

    this.debounce('paper-step-reset', function() {
      form = that._getForm();
      // reset form if it exists.
      if (form) {
        //TODO: confirm?

        //then reset
        form.reset();
        that.completed = false;
      }
    }, 300);
  },
  /**
   * When the `Skip` button is pressed, this will the `_reset()` method,
   * and set the current `paper-step.completed` property to `true`, and then
   * advance to the next `paper-step` element.
   */
  _skip: function(e) {
    var
      event = e,
      that = this,
      form
    ;
    //reset form
    this._reset(e);

    this.debounce('paper-step-skip', function() {
      this.completed = true;
    }, 300);
  },
  /**
   * When the `Continue` button is pressed, check if form is valid
   * and then trigger the form `submit` event.
   */
  _submit: function(e) {
    var
      event = e,
      that = this,
      form
    ;
    // collapse multiple submissions and only trigger once.
    this.debounce('paper-steps-submit', function() {
      form = that._getForm();
      // submit form if it exists.
      if (form) {
        form.submit();
      }
    }, 300);
  }
});
<commit_msg>ignore iron-activate events from any element within the iron-form.<commit_after>'use strict';
Polymer({
  is: "paper-step",
  properties: {
    /**
     * Computed css classes based on the current `step` and the total
     * number value of `steps`.
     */
    _cssClass: {
      type: String,
      computed: '__cssClass(step, steps)'
    },
    /**
     * Computed for the `paper-step` image icon based on the supplied `icon`
     * attribute and the `completed` property.
     */
    _image: {
      type: String,
      computed: '_icon(editable)'
    },
    /**
     * Indicates if the current `paper-step` is selected and toggles the
     * active/inactive css style rules.
     */
    _selected: {
      type: Boolean,
      value: false
    },
    /**
     * If true, the submit buttons which are automatically
     * added will be hidden for this `paper-step` element.
     */
    actionsDisabled: {
      type: Boolean
    },
    /**
     * If true, the current step has been validated and marked as done.
     */
    completed: {
      type: Boolean,
      value: false,
      reflectToAttribute: true,
      observer: '_onComplete'
    },
    /**
     * Specify an alternate label to use for the `continue` button.
     */
    continueLabel: {
      type: String,
      value: 'Continue'
    },
    /**
     * A copy of the serialized form data for the last successful submit.
     */
     data: Object,
    /**
     * Used internally to debouce custom events:
     * `paper-step-complete`, and `paper-step-next`.
     */
    duplicate: {
      type: Boolean,
      value: false
    },
    /**
     *
     */
    editable: {
      type: Boolean,
      value: false,
      reflectToAttribute: true
    },
    /**
     * The text to display in the steps area next to the image icon.
     */
    label: {
      type: String
    },
    lastErrorResponse: Object,
    lastSuccessResponse: Object,
    /**
     * To indicate if the current step required or optional.
     */
    optional: {
      type: Boolean,
      value: false
    },
    /**
     * Specify an alternate label to use for the `reset` button.
     */
    resetLabel: {
      type: String,
      value: 'Reset'
    },
    selectable: {
      type: Boolean,
      computed: '_selectable(completed, editable)',
      reflectToAttribute: true
    },
    skipLabel: {
      type: String,
      value: 'Skip'
    },
    /**
     * The 1 based index value for the current `paper-step` in `paper-steps`.
     *
     * `Automatically calculated`.
     */
    step: {
      type: Number
    },
    /**
     * The value for the total number of `paper-step` elements (readonly).
     *
     * `Automatically calculated`.
     */
    steps: {
      type: Number
    }
  },
  behaviors: [],
  listeners: {
    //error after submit, IronRequestElement in event.detail object
    'iron-form-error': '_onResponse', //'_onError',

    //cannot submit, form is invalid

    // 'iron-form-invalid': '',
    'iron-form-presubmit': '_onPreSubmit',

    // //after form is reset
    // 'iron-form-reset': '_reset',

    //after form submitted, IronRequestElement inevent.detail object
    'iron-form-response': '_onResponse',
    'iron-form-submit': '_onSubmit' //after submitted
  },

  // Element Lifecycle
  ready: function() {},

  attached: function() {
    var
      parent = this.parentNode && this.parentNode.indexOf && this.parentNode || null,
      form = this._getForm()
    ;

    // @TODO: finish adding fallback for shadowDom, not yet ready.
    if (!Boolean(parent) && this.shadowRoot) {
      try {
        parent = this.parentNode.$.selector;
      } catch (e) {
        parent = null;
      }
    }

    //
    if (!parent || parent.hasOwnProperty('indexOf') || typeof parent.indexOf != 'function') {
      this.step = 1;
      this.steps = 1;
      try {
        console.log('Improperly declared paper-step element, ' +
          'it is missing the parent iterator.');
      } catch (e) {}
      return;
    }

    if (!Boolean(this.step)) {
      this.step = parent.indexOf(this) + 1;
    }

    if (!Boolean(this.steps)) {
      this.steps = parent.items.length;
    }

    this.listen(this.$.continue, 'tap', '_submit');
    this.listen(this.$.reset, 'tap', '_reset');
    this.listen(this.$.skip, 'tap', '_skip');

    if (form && Polymer.isInstance(form)) {
      var i, child, len,
        children = form.getEffectiveChildren()
      ;
      for (i=0, len=children.length; i<len; i++) {
        child = children[i];
        this.listen(child, 'keyup', '_onChange');
      }
      this.listen(form, 'iron-activate', '_onActivate');
    }
  },

  detached: function() {},

  /**
   * Computes `_cssClass` property.
   * @return {string}.
   */
  __cssClass: function(step, steps) {
    var cls = (this.optional ? 'optional' : 'required') + '-step ';

    if (step === 1) {
      cls += 'first ';
    }
    if (step === steps) {
      cls += 'last ';
    }

    return cls;
  },
  /**
   * Attempts to retrieve the `iron-form` element for the current `paper-step`.
   */
  _getForm: function() {
    var
      el = Polymer.dom(this.$.step_content),
      form = el && el.node &&
        el.node.querySelector('div form[is="iron-form"]')
    ;
    return form || false;
  },
  /**
   * Attempts to retrieve the `submit` button for the current `paper-step`.
   */
  _getPrimaryButton: function() {
    var el, button;
    if (this.actionsDisabled) {
      el = this._getForm();
      if (el) {
        el = Polymer.dom(el).node.querySelectorAll(
          'button,paper-button,input[type="submit"]');
        button = el && el.length && el[0];
        if (button) {
          return button;
        }
      }
    } else {
      return this.$.continue;
    }
    return undefined;
  },
  /**
   * Computes `image` property based on step `icon` and current `step`.
   */
  _icon: function(editable) {
    if (editable) {
      return 'icons:create';
    }
    return 'icons:check';
    // return 'image:brightness-1';
  },
  /**
   * Event handler to ignore form elements which trigger any iron-select
   * events.
   */
  _onActivate: function(e) {
    //ignore iron-selector events from form children
    e.stopPropagation();
  },
  /**
   * Event handler to change the completed state of a `paper-step` once
   * any form inputs are changed.
   */
  _onChange: function(e) {
    if (this.completed) {
      this.completed = false;
    }
  },
  /**
   *
   */
  _onComplete: function(_new, _old) {
    if (this.duplicate === false && _new === true) {
      this.fire('paper-step-complete', this.step);
      this.fire('paper-step-next', this.step);
    }
  },
  /**
   *
   */
  _onError: function(e) {
  },
  /**
   *
   */
  _onPreSubmit: function(e) {
    var
      form = this._getForm()
    ;

    if (this.completed) {
      e.preventDefault();
      this.fire('paper-step-already-complete', this);
    }
  },
  /**
   * Handles iron form events for `iron-form-error` and `iron-form-response`.
   */
  _onResponse: function(e) {
    var
      el = this._getPrimaryButton(),
      request = e && e.detail || false
    ;

    if (Polymer.isInstance(el) && el.disabled !== undefined) {
      el.disabled = false;
    }
    if (request && request.status === 200) {
      this.lastSuccessResponse = request;
      try {
        this.data = this._getForm().serialize();
      } catch (ex) {
        this.data = {}
      }
      this.completed = true;
    } else {
      this.lastErrorResponse = request;
    }
  },
  /**
   * Hooks into the `iron-form-submit` event to disable the form submit button.
   */
  _onSubmit: function(e) {
    var
      el = this._getPrimaryButton()
    ;

    if (Polymer.isInstance(el) && el.disabled !== undefined) {
      el.disabled = true;
    }
  },
  /**
   */
  _selectable: function(completed, editable) {
    return editable || !completed;
  },
  /**
   * When the `Reset` button is pressed, this restores the form elements
   * to their initial state by calling `form.reset()`.
   */
  _reset: function(e) {
    var
      event = e,
      that = this,
      form
    ;

    this.debounce('paper-step-reset', function() {
      form = that._getForm();
      // reset form if it exists.
      if (form) {
        //TODO: confirm?

        //then reset
        form.reset();
        that.completed = false;
      }
    }, 300);
  },
  /**
   * When the `Skip` button is pressed, this will the `_reset()` method,
   * and set the current `paper-step.completed` property to `true`, and then
   * advance to the next `paper-step` element.
   */
  _skip: function(e) {
    var
      event = e,
      that = this,
      form
    ;
    //reset form
    this._reset(e);

    this.debounce('paper-step-skip', function() {
      this.completed = true;
    }, 300);
  },
  /**
   * When the `Continue` button is pressed, check if form is valid
   * and then trigger the form `submit` event.
   */
  _submit: function(e) {
    var
      event = e,
      that = this,
      form
    ;
    // collapse multiple submissions and only trigger once.
    this.debounce('paper-steps-submit', function() {
      form = that._getForm();
      // submit form if it exists.
      if (form) {
        form.submit();
      }
    }, 300);
  }
});
<|endoftext|>


<commit_before>'use strict';

var ERROR_CODE = 'Terror codes collision detected in the %name%&.extendCodes call for code "%code%"';

var has = Object.prototype.hasOwnProperty;

/**
 * @constructor
 * @param {String|Number} [code=Terror.CODES.UNKNOWN_ERROR]
 * @param {String} [message]
 * @returns {Terror}
 * @example
 *      ```
 *      var MyError = Terror.create('MyError', {
 *          MY_FAULT: "It\'s my own fault, because %reason%"
 *      });
 *
 *      try {
 *          // dangerous code here
 *      } catch(err) {
 *          MyError.createError(MyError.CODES.MY_FAULT, err)
 *              .bind({ reason: 'something strange happens!' })
 *              .log();
 *      }
 *
 *      // try to use Terror#ensureError if catched error can be Terror successor or not
 *
 *      try {
 *          // dangerous code here
 *      } catch(err) {
 *          MyError.ensureError(err, MyError.CODES.MY_FAULT)
 *              .bind({ reason: 'i was sad when write code above' })
 *              .log();
 *      }
 *
 *      // or simply log and throw Terror
 *
 *      throw MyError.createError(MyError.CODES.MY_FAULT, { reason: 'i was too stupid!' })
 *          .log('warn');
 *      ```
 */
function Terror(code, message) {
    var msg;
    var original;

    this.code = code == null ? Terror.CODES.UNKNOWN_ERROR : code;

    if (isString(message)) {
        msg = message;
    } else {
        msg = this.constructor.MESSAGES[this.code];

        if (isError(message)) {
            original = message;
        }
    }

    this.data = {};
    this.message = msg;
    this.originalError = original;
    this._isTerror = true;
    this._isLogged = false;

    return captureStackTrace(this, this.constructor);
}

inherits(Terror, Error);

module.exports = Terror;

/**
 *
 * @type {String}
 * @default 'ERROR'
 */
Terror.DEFAULT_ERROR_LEVEL = 'ERROR';

/**
 *
 * @type {Object}
 */
Terror.CODES = {
    UNKNOWN_ERROR: 'UNKNOWN_ERROR'
};

/**
 *
 * @type {Object}
 */
Terror.MESSAGES = {
    UNKNOWN_ERROR: 'Unknown error'
};

/**
 *
 * @type {Number}
 * @default 10
 */
Terror.stackTraceLimit = 10;

/**
 * @param {String} name Error class name
 * @param {Object} [codes] hash { ERROR_CODE: 'error message', … }
 */
Terror.create = function(name, codes) {
    var Inheritor = function(code, message) {
        Terror.call(this, code, message);
    };

    inherits(Inheritor, this);

    // link contructors methods
    Inheritor.create = this.create;
    Inheritor.extendCodes = this.extendCodes;
    Inheritor.is = this.is;
    Inheritor.setLogger = this.setLogger;
    Inheritor.createError = this.createError;
    Inheritor.ensureError = this.ensureError;
    Inheritor.stackTraceLimit = this.stackTraceLimit;

    // copy static fields
    Inheritor.CODES = copyPlainObject(this.CODES);
    Inheritor.MESSAGES = copyPlainObject(this.MESSAGES);
    Inheritor.DEFAULT_ERROR_LEVEL = this.DEFAULT_ERROR_LEVEL;

    if (isObject(codes)) {
        Inheritor.extendCodes(codes);
    }

    Inheritor.prototype.name = name;

    return Inheritor;
};

/**
 * Extends existing CODE and MESSAGE hashes.
 * @param {Object} codes { CODE_NAME: 'error message', … }
 * @throws {Terror} TerrorError if collisions detected.
 * @returns {Terror} constructor
 */
Terror.extendCodes = function(codes) {
    for (var code in codes) {
        if (has.call(codes, code)) {
            if (has.call(this.CODES, code)) {
                throw Terror.createError(null, ERROR_CODE).bind({
                    name: this.prototype.name,
                    code: code
                });
            }

            this.CODES[code] = code;
            this.MESSAGES[code] = codes[code];
        }
    }

    return this;
};

/**
 * @param {String|Number} [code]
 * @param {String|Error|Object} [message] error message, Error or key-value hash for binding
 * @returns {Terror} new Terror instance
 */
Terror.createError = function(code, message) {
    var Const = this === Terror ? Terror : this;
    var msg;
    var data;

    if (isString(message) || isError(message)) {
        msg = message;
    } else {
        data = message;
    }

    var error = new Const(code, msg).bind(data);

    return captureStackTrace(error, this.createError);
};

/**
 * @param {Error|Terror} error
 * @param {String|Number} [code]
 * @returns {Terror}
 */
Terror.ensureError = function(error, code) {
    return error instanceof this ? error : this.createError(code, error);
};

/**
 * @param {*} error
 * @returns {Boolean}
 */
Terror.isTerror = function(error) {
    if ( ! error) {
        return false;
    } else {
        return error._isTerror === true;
    }
};

/**
 * @param {Function} logger (String message, String errorLevel)
 * @returns {Terror} constructor
 */
Terror.setLogger = function(logger) {
    this.prototype.logger = logger;

    return this;
};

/**
 * @default 'Terror'
 * @type {String}
 */
Terror.prototype.name = 'Terror';

/**
 * @param {*} [level] – error level, any value allowed by logger
 * @returns {Terror} self
 */
Terror.prototype.log = function(level) {
    if (this._isLogged || isNotFunction(this.logger)) {
        return this;
    }

    this.logger(this.getFullStack(), level);
    this._isLogged = true;

    return this;
};

/**
 * Default logger writes error message to console using logMultilineError
 * @param {String} message
 * @param {String} [level=ERROR]
 */
Terror.prototype.logger = function(message, level) {
    this.logMultilineError(message, level);
};

/**
 * Default multiine error formatiing for simple text loggers,
 * such as console.log or any one, which write message as line in the text file.
 * @param {String} message
 * @param {String} [level=ERROR]
 * @param {Function} [log=console.log] (String formattedMessageRow)
 * @returns {Terror}
 */
Terror.prototype.logMultilineError = function(message, level, log) {
    var _level = (level || this.constructor.DEFAULT_ERROR_LEVEL).toUpperCase();
    var _log = log;
    var arrows = '\n' + new Array(_level.length + 1).join('>') + ' ';

    if (isNotFunction(_log)) {
        _log = defaultLog;
    }

    _log(_level + ' ' + message.replace(/\n/g, arrows));

    return this;
};

/**
 *
 * @returns {String}
 */
Terror.prototype.getFullMessage = function() {
    var error = this;
    var result = '';
    var msg;

    do {
        msg = error.message;

        if (msg) {
            if (msg[msg.length - 1] !== '.') {
                msg += '.';
            }

            result += ' ' + msg;
        }

        error = error.originalError;
    } while (error);

    return this.code + result;
};

/**
 *
 * @returns {String}
 */
Terror.prototype.getFullStack = function() {
    var error = this;
    var result = new Array(0);
    var indent = '';
    var stack;

    do {
        if (error.stack) {
            stack = String(error.stack).replace(/\n/g, '\n' + indent) + '\n';
        } else {
            stack = error.name + ': ' + error.message;
        }

        result.push(indent + (has.call(error, 'code') ? error.code + ' ' : '') + stack);

        indent += '    ';
    } while ((error = error.originalError));

    return result.join('\n');
};

/**
 * Check is an error instanciated from `Ctor` and error code equals passed code.
 * @param {String} code
 * @param {Error} error
 * @returns {Boolean}
 */
Terror.is = function(code, error) {
    return (error instanceof this) && error.code === code;
};

/**
 * @param {Object} data
 * @returns {Terror} self
 */
Terror.prototype.bind = function(data) {
    if (data == null) {
        return this;
    }

    for (var propName in data) {
        if (has.call(data, propName)) {
            this.data[propName] = data[propName];
        }
    }

    return this.setMessage(this.message);
};

/**
 *
 * @param {String} message
 * @returns {Terror}
 */
Terror.prototype.setMessage = function(message) {
    this.message = isString(message) && message.indexOf('%') > -1 ? sPrintF(message, this.data) : message;

    return this;
};

function isError(value) {
    return value instanceof Error;
}

function isString(value) {
    return typeof value === 'string';
}

function isObject(value) {
    return typeof value === 'object' && value !== null;
}

function isNotFunction(fnc) {
    return typeof fnc !== 'function';
}

function defaultLog(msg) {
    /* eslint-disable no-console */
    console.log(msg);
    /* eslint-enable no-console */
}

function sPrintF(str, data) {
    return str.replace(/%(\S+?)%/g, function (match, variable) {
        return has.call(data, variable) ? data[variable] : match;
    });
}

function copyPlainObject(src) {
    var object = {};

    for (var key in src) {
        if (has.call(src, key)) {
            object[key] = src[key];
        }
    }

    return object;
}

function captureStackTrace(error, fnc) {
    if (Error.captureStackTrace) {
        var stackTraceLimit = Error.stackTraceLimit;

        Error.stackTraceLimit = error.constructor.stackTraceLimit;
        Error.captureStackTrace(error, fnc);
        Error.stackTraceLimit = stackTraceLimit;
    }

    return error;
}

function inherits(Ctor, SuperCtor) {
    if (Object.create) {
        Ctor.prototype = Object.create(SuperCtor.prototype, {
            constructor: {
                value: Ctor,
                enumerable: false,
                writable: true,
                configurable: true
            }
        });
    } else {
        Const.prototype = SuperCtor.prototype;
        new Const(Ctor);
    }
}

function Const(ctor) {
    this.constructor = ctor;
    ctor.prototype = this;

    Const.prototype = Object.prototype;
}
<commit_msg>Fix error.stack in nodejs@8<commit_after>'use strict';

var ERROR_CODE = 'Terror codes collision detected in the %name%&.extendCodes call for code "%code%"';

var has = Object.prototype.hasOwnProperty;

/**
 * @constructor
 * @param {String|Number} [code=Terror.CODES.UNKNOWN_ERROR]
 * @param {String} [message]
 * @returns {Terror}
 * @example
 *      ```
 *      var MyError = Terror.create('MyError', {
 *          MY_FAULT: "It\'s my own fault, because %reason%"
 *      });
 *
 *      try {
 *          // dangerous code here
 *      } catch(err) {
 *          MyError.createError(MyError.CODES.MY_FAULT, err)
 *              .bind({ reason: 'something strange happens!' })
 *              .log();
 *      }
 *
 *      // try to use Terror#ensureError if catched error can be Terror successor or not
 *
 *      try {
 *          // dangerous code here
 *      } catch(err) {
 *          MyError.ensureError(err, MyError.CODES.MY_FAULT)
 *              .bind({ reason: 'i was sad when write code above' })
 *              .log();
 *      }
 *
 *      // or simply log and throw Terror
 *
 *      throw MyError.createError(MyError.CODES.MY_FAULT, { reason: 'i was too stupid!' })
 *          .log('warn');
 *      ```
 */
function Terror(code, message) {
    var msg;
    var original;

    this.code = code == null ? Terror.CODES.UNKNOWN_ERROR : code;

    if (isString(message)) {
        msg = message;
    } else {
        msg = this.constructor.MESSAGES[this.code];

        if (isError(message)) {
            original = message;
        }
    }

    this.data = {};
    this.message = msg;
    this.originalError = original;
    this._isTerror = true;
    this._isLogged = false;

    return captureStackTrace(this, this.constructor);
}

inherits(Terror, Error);

module.exports = Terror;

/**
 *
 * @type {String}
 * @default 'ERROR'
 */
Terror.DEFAULT_ERROR_LEVEL = 'ERROR';

/**
 *
 * @type {Object}
 */
Terror.CODES = {
    UNKNOWN_ERROR: 'UNKNOWN_ERROR'
};

/**
 *
 * @type {Object}
 */
Terror.MESSAGES = {
    UNKNOWN_ERROR: 'Unknown error'
};

/**
 *
 * @type {Number}
 * @default 10
 */
Terror.stackTraceLimit = 10;

/**
 * @param {String} name Error class name
 * @param {Object} [codes] hash { ERROR_CODE: 'error message', … }
 */
Terror.create = function(name, codes) {
    var Inheritor = function(code, message) {
        Terror.call(this, code, message);
    };

    inherits(Inheritor, this);

    // link contructors methods
    Inheritor.create = this.create;
    Inheritor.extendCodes = this.extendCodes;
    Inheritor.is = this.is;
    Inheritor.setLogger = this.setLogger;
    Inheritor.createError = this.createError;
    Inheritor.ensureError = this.ensureError;
    Inheritor.stackTraceLimit = this.stackTraceLimit;

    // copy static fields
    Inheritor.CODES = copyPlainObject(this.CODES);
    Inheritor.MESSAGES = copyPlainObject(this.MESSAGES);
    Inheritor.DEFAULT_ERROR_LEVEL = this.DEFAULT_ERROR_LEVEL;

    if (isObject(codes)) {
        Inheritor.extendCodes(codes);
    }

    Inheritor.prototype.name = name;

    return Inheritor;
};

/**
 * Extends existing CODE and MESSAGE hashes.
 * @param {Object} codes { CODE_NAME: 'error message', … }
 * @throws {Terror} TerrorError if collisions detected.
 * @returns {Terror} constructor
 */
Terror.extendCodes = function(codes) {
    for (var code in codes) {
        if (has.call(codes, code)) {
            if (has.call(this.CODES, code)) {
                throw Terror.createError(null, ERROR_CODE).bind({
                    name: this.prototype.name,
                    code: code
                });
            }

            this.CODES[code] = code;
            this.MESSAGES[code] = codes[code];
        }
    }

    return this;
};

/**
 * @param {String|Number} [code]
 * @param {String|Error|Object} [message] error message, Error or key-value hash for binding
 * @returns {Terror} new Terror instance
 */
Terror.createError = function(code, message) {
    var Const = this === Terror ? Terror : this;
    var msg;
    var data;

    if (isString(message) || isError(message)) {
        msg = message;
    } else {
        data = message;
    }

    var error = new Const(code, msg).bind(data);

    return captureStackTrace(error, this.createError);
};

/**
 * @param {Error|Terror} error
 * @param {String|Number} [code]
 * @returns {Terror}
 */
Terror.ensureError = function(error, code) {
    return error instanceof this ? error : this.createError(code, error);
};

/**
 * @param {*} error
 * @returns {Boolean}
 */
Terror.isTerror = function(error) {
    if ( ! error) {
        return false;
    } else {
        return error._isTerror === true;
    }
};

/**
 * @param {Function} logger (String message, String errorLevel)
 * @returns {Terror} constructor
 */
Terror.setLogger = function(logger) {
    this.prototype.logger = logger;

    return this;
};

/**
 * @default 'Terror'
 * @type {String}
 */
Terror.prototype.name = 'Terror';

/**
 * @param {*} [level] – error level, any value allowed by logger
 * @returns {Terror} self
 */
Terror.prototype.log = function(level) {
    if (this._isLogged || isNotFunction(this.logger)) {
        return this;
    }

    this.logger(this.getFullStack(), level);
    this._isLogged = true;

    return this;
};

/**
 * Default logger writes error message to console using logMultilineError
 * @param {String} message
 * @param {String} [level=ERROR]
 */
Terror.prototype.logger = function(message, level) {
    this.logMultilineError(message, level);
};

/**
 * Default multiine error formatiing for simple text loggers,
 * such as console.log or any one, which write message as line in the text file.
 * @param {String} message
 * @param {String} [level=ERROR]
 * @param {Function} [log=console.log] (String formattedMessageRow)
 * @returns {Terror}
 */
Terror.prototype.logMultilineError = function(message, level, log) {
    var _level = (level || this.constructor.DEFAULT_ERROR_LEVEL).toUpperCase();
    var _log = log;
    var arrows = '\n' + new Array(_level.length + 1).join('>') + ' ';

    if (isNotFunction(_log)) {
        _log = defaultLog;
    }

    _log(_level + ' ' + message.replace(/\n/g, arrows));

    return this;
};

/**
 *
 * @returns {String}
 */
Terror.prototype.getFullMessage = function() {
    var error = this;
    var result = '';
    var msg;

    do {
        msg = error.message;

        if (msg) {
            if (msg[msg.length - 1] !== '.') {
                msg += '.';
            }

            result += ' ' + msg;
        }

        error = error.originalError;
    } while (error);

    return this.code + result;
};

/**
 *
 * @returns {String}
 */
Terror.prototype.getFullStack = function() {
    var error = this;
    var result = new Array(0);
    var indent = '';
    var stack;

    do {
        if (error.stack) {
            // @see https://nodejs.org/api/errors.html#errors_error_message
            // The message passed to the constructor
            // will also appear in the first line of the stack trace of the Error,
            // however changing this property after the Error object is created
            // may not change the first line of the stack trace
            // (for example, when error.stack is read before this property is changed).
            stack = String(error.stack);
            if (stack.indexOf('%') > -1) {
                // nodejs@8 is affected
                stack = sPrintF(stack, this.data);
            }

            stack = stack.replace(/\n/g, '\n' + indent) + '\n';
        } else {
            stack = error.name + ': ' + error.message;
        }

        result.push(indent + (has.call(error, 'code') ? error.code + ' ' : '') + stack);

        indent += '    ';
    } while ((error = error.originalError));

    return result.join('\n');
};

/**
 * Check is an error instanciated from `Ctor` and error code equals passed code.
 * @param {String} code
 * @param {Error} error
 * @returns {Boolean}
 */
Terror.is = function(code, error) {
    return (error instanceof this) && error.code === code;
};

/**
 * @param {Object} data
 * @returns {Terror} self
 */
Terror.prototype.bind = function(data) {
    if (data == null) {
        return this;
    }

    for (var propName in data) {
        if (has.call(data, propName)) {
            this.data[propName] = data[propName];
        }
    }

    return this.setMessage(this.message);
};

/**
 *
 * @param {String} message
 * @returns {Terror}
 */
Terror.prototype.setMessage = function(message) {
    this.message = isString(message) && message.indexOf('%') > -1 ? sPrintF(message, this.data) : message;

    return this;
};

function isError(value) {
    return value instanceof Error;
}

function isString(value) {
    return typeof value === 'string';
}

function isObject(value) {
    return typeof value === 'object' && value !== null;
}

function isNotFunction(fnc) {
    return typeof fnc !== 'function';
}

function defaultLog(msg) {
    /* eslint-disable no-console */
    console.log(msg);
    /* eslint-enable no-console */
}

function sPrintF(str, data) {
    return str.replace(/%(\S+?)%/g, function (match, variable) {
        return has.call(data, variable) ? data[variable] : match;
    });
}

function copyPlainObject(src) {
    var object = {};

    for (var key in src) {
        if (has.call(src, key)) {
            object[key] = src[key];
        }
    }

    return object;
}

function captureStackTrace(error, fnc) {
    if (Error.captureStackTrace) {
        var stackTraceLimit = Error.stackTraceLimit;

        Error.stackTraceLimit = error.constructor.stackTraceLimit;
        Error.captureStackTrace(error, fnc);
        Error.stackTraceLimit = stackTraceLimit;
    }

    return error;
}

function inherits(Ctor, SuperCtor) {
    if (Object.create) {
        Ctor.prototype = Object.create(SuperCtor.prototype, {
            constructor: {
                value: Ctor,
                enumerable: false,
                writable: true,
                configurable: true
            }
        });
    } else {
        Const.prototype = SuperCtor.prototype;
        new Const(Ctor);
    }
}

function Const(ctor) {
    this.constructor = ctor;
    ctor.prototype = this;

    Const.prototype = Object.prototype;
}
<|endoftext|>


<commit_before>/** @constructor */
GameTitle.Game = function( game )
{
  this.pointer = null;

  this.circleSprite = null;

  this.bell = null;
};

GameTitle.Game.prototype.init = function()
{

};

GameTitle.Game.prototype.create = function()
{
  this.stage.backgroundColor = 0x171642;

  // Buttons.
  var exitButton = GameTitle.createTextButton( 0, 32,
                                               "Exit", this.returnToMainMenu, this );

  exitButton.position.x = this.game.world.width - exitButton.width - 16;
  exitButton.input.priorityID = 1;

  var buttonGroup = this.game.add.group();
  buttonGroup.add( exitButton );

  // All text.
  var allTextGroup = this.game.add.group();
  allTextGroup.add( buttonGroup );
  allTextGroup.alpha = 0.0;

  this.game.add.tween( allTextGroup ).to( { alpha: 1 }, 500, Phaser.Easing.Linear.None, true );

  this.circleSprite = this.createCircleSprite();

  this.game.world.bringToTop( allTextGroup );

  this.bell = this.game.add.audio( "bell2" );

  var background = this.game.add.sprite( 0, 0 );
  background.fixedToCamera = true;
  background.scale.setTo( this.game.width, this.game.height );
  background.inputEnabled = true;
  background.input.priorityID = 0;
  background.events.onInputDown.add( this.pointerDown, this );

  this.game.world.sendToBack( background );
};

GameTitle.Game.prototype.update = function()
{

};

GameTitle.Game.prototype.pointerDown = function( sprite, pointer )
{
  this.pointer = pointer;
  this.makeImpact( pointer.position );
};

GameTitle.Game.prototype.returnToMainMenu = function()
{
  this.state.start( "MainMenu" );
};

GameTitle.Game.prototype.makeImpact = function( position )
{
  if( !!this.bell._sound )
  {
    this.adjustBellPitch();
  }
  else
  {
    this.bell.onPlay.add( this.adjustBellPitch, this );
  }

  this.bell.play();

  this.resetCircleSprite( this.circleSprite, position );
};

GameTitle.Game.prototype.createCircleSprite = function()
{
  var bmd = this.game.add.bitmapData( 128, 128 );

  bmd.ctx.fillStyle = "#999999";
  bmd.ctx.beginPath();
  bmd.ctx.arc( 64, 64, 64, 0, Math.PI * 2, true );
  bmd.ctx.closePath();
  bmd.ctx.fill();

  var sprite = this.game.add.sprite( 0, 0, bmd );
  sprite.anchor.set( 0.5 );

  sprite.alpha = 0.0;

  return sprite;
};

GameTitle.Game.prototype.adjustBellPitch = function()
{
  var verticalScale = 4.0 * ( 1.0 - ( this.pointer.position.y / this.game.world.height ) );
  this.bell._sound.playbackRate.value = verticalScale;
};

GameTitle.Game.prototype.resetCircleSprite = function( circleSprite, position )
{
  circleSprite.position.set( position.x, position.y );

  circleSprite.scale.set( 0.5 );
  circleSprite.alpha = 1.0;

  var verticalScale = ( 1.0 - ( this.pointer.position.y / this.game.world.height ) );
  var colorAdjustment = ( verticalScale * 255 ) | 0;
  var r = 255 - colorAdjustment;
  var g = this.game.rnd.integerInRange( 64, 255 );
  var b = 0 + colorAdjustment;
  circleSprite.tint = ( r << 16 ) + ( g << 8 ) + b;

  this.game.add.tween( circleSprite.scale ).to( { x: 4.0, y: 4.0 }, 500, Phaser.Easing.Sinusoidal.InOut, true );
  this.game.add.tween( circleSprite ).to( { alpha: 0.0 }, 500, Phaser.Easing.Sinusoidal.InOut, true );
};
<commit_msg>Adjusted color of pointer down visualization to go through the color spectrum rather than have a random green componement.<commit_after>/** @constructor */
GameTitle.Game = function( game )
{
  this.pointer = null;

  this.circleSprite = null;

  this.bell = null;
};

GameTitle.Game.prototype.init = function()
{

};

GameTitle.Game.prototype.create = function()
{
  this.stage.backgroundColor = 0x171642;

  // Buttons.
  var exitButton = GameTitle.createTextButton( 0, 32,
                                               "Exit", this.returnToMainMenu, this );

  exitButton.position.x = this.game.world.width - exitButton.width - 16;
  exitButton.input.priorityID = 1;

  var buttonGroup = this.game.add.group();
  buttonGroup.add( exitButton );

  // All text.
  var allTextGroup = this.game.add.group();
  allTextGroup.add( buttonGroup );
  allTextGroup.alpha = 0.0;

  this.game.add.tween( allTextGroup ).to( { alpha: 1 }, 500, Phaser.Easing.Linear.None, true );

  this.circleSprite = this.createCircleSprite();

  this.game.world.bringToTop( allTextGroup );

  this.bell = this.game.add.audio( "bell2" );

  var background = this.game.add.sprite( 0, 0 );
  background.fixedToCamera = true;
  background.scale.setTo( this.game.width, this.game.height );
  background.inputEnabled = true;
  background.input.priorityID = 0;
  background.events.onInputDown.add( this.pointerDown, this );

  this.game.world.sendToBack( background );
};

GameTitle.Game.prototype.update = function()
{

};

GameTitle.Game.prototype.pointerDown = function( sprite, pointer )
{
  this.pointer = pointer;
  this.makeImpact( pointer.position );
};

GameTitle.Game.prototype.returnToMainMenu = function()
{
  this.state.start( "MainMenu" );
};

GameTitle.Game.prototype.makeImpact = function( position )
{
  if( !!this.bell._sound )
  {
    this.adjustBellPitch();
  }
  else
  {
    this.bell.onPlay.add( this.adjustBellPitch, this );
  }

  this.bell.play();

  this.resetCircleSprite( this.circleSprite, position );
};

GameTitle.Game.prototype.createCircleSprite = function()
{
  var bmd = this.game.add.bitmapData( 128, 128 );

  bmd.ctx.fillStyle = "#999999";
  bmd.ctx.beginPath();
  bmd.ctx.arc( 64, 64, 64, 0, Math.PI * 2, true );
  bmd.ctx.closePath();
  bmd.ctx.fill();

  var sprite = this.game.add.sprite( 0, 0, bmd );
  sprite.anchor.set( 0.5 );

  sprite.alpha = 0.0;

  return sprite;
};

GameTitle.Game.prototype.adjustBellPitch = function()
{
  var verticalScale = 4.0 * ( 1.0 - ( this.pointer.position.y / this.game.world.height ) );
  this.bell._sound.playbackRate.value = verticalScale;
};

GameTitle.Game.prototype.resetCircleSprite = function( circleSprite, position )
{
  circleSprite.position.set( position.x, position.y );

  circleSprite.scale.set( 0.5 );
  circleSprite.alpha = 1.0;

  var verticalScale = ( 1.0 - ( this.pointer.position.y / this.game.world.height ) );
  var colorAdjustment = ( verticalScale * 255 ) | 0;

  var r = 255 - colorAdjustment;
  var g = 63;
  var b = 0 + colorAdjustment;

  if( colorAdjustment < 128 )
  {
    g += b;
  }
  else
  {
    g += r;
  }

  circleSprite.tint = ( r << 16 ) + ( g << 8 ) + b;

  this.game.add.tween( circleSprite.scale ).to( { x: 4.0, y: 4.0 }, 500, Phaser.Easing.Sinusoidal.InOut, true );
  this.game.add.tween( circleSprite ).to( { alpha: 0.0 }, 500, Phaser.Easing.Sinusoidal.InOut, true );
};
<|endoftext|>