<commit_before>/* @flow */

const async = require('async')
const autoBind = require('auto-bind')
const fs = require('fs-extra')
const path = require('path')
const trash = require('trash')

const bluebird = require('bluebird')

const { TMP_DIR_NAME } = require('./constants')
const logger = require('../logger')
const { isUpToDate } = require('../metadata')
const { hideOnWindows } = require('../utils/fs')
const Watcher = require('./watcher')
const measureTime = require('../perftools')
const { withContentLength } = require('../file_stream_provider')

bluebird.promisifyAll(fs)

/*::
import type EventEmitter from 'events'
import type Config from '../config'
import type { FileStreamProvider, ReadableWithContentLength } from '../file_stream_provider'
import type { Metadata } from '../metadata'
import type Pouch from '../pouch'
import type Prep from '../prep'
import type { Side } from '../side' // eslint-disable-line
import type { Callback } from '../utils/func'
*/

const log = logger({
  component: 'LocalWriter'
})
// Local is the class that interfaces cozy-desktop with the local filesystem.
// It uses a watcher, based on chokidar, to listen for file and folder changes.
// It also applied changes from the remote cozy on the local filesystem.
module.exports = class Local /*:: implements Side */ {
  /*::
  prep: Prep
  pouch: Pouch
  events: EventEmitter
  syncPath: string
  tmpPath: string
  watcher: Watcher
  other: FileStreamProvider
  _trash: (Array<string>) => Promise<void>
  */

  constructor (config /*: Config */, prep /*: Prep */, pouch /*: Pouch */, events /*: EventEmitter */) {
    this.prep = prep
    this.pouch = pouch
    this.events = events
    this.syncPath = config.syncPath
    this.tmpPath = path.join(this.syncPath, TMP_DIR_NAME)
    this.watcher = new Watcher(this.syncPath, this.prep, this.pouch, events)
    // $FlowFixMe
    this.other = null
    this._trash = trash

    autoBind(this)
    bluebird.promisifyAll(this)
  }

  /*::
  addFileAsync: (Metadata) => Promise<*>
  addFolderAsync: (Metadata) => Promise<*>
  updateFileMetadataAsync: (Metadata, Metadata) => Promise<*>
  renameConflictingDocAsync: (doc: Metadata, newPath: string) => Promise<void>
  */

  // Start initial replication + watching changes in live
  start () {
    this.watcher.ensureDirSync()
    return this.watcher.start()
  }

  // Stop watching the file system
  stop () {
    return this.watcher.stop()
  }

  // Create a readable stream for the given doc
  // adds a contentLength property to be used
  async createReadStreamAsync (doc /*: Metadata */) /*: Promise<ReadableWithContentLength> */ {
    try {
      let filePath = path.resolve(this.syncPath, doc.path)
      let pStats = fs.statAsync(filePath)
      let pStream = new Promise((resolve, reject) => {
        let stream = fs.createReadStream(filePath)
        stream.on('open', () => resolve(stream))
        stream.on('error', err => reject(err))
      })
      const [
        stream /*: ReadableWithContentLength */,
        stat /*: fs.Stat */
      ] = await Promise.all([pStream, pStats])
      return withContentLength(stream, stat.size)
    } catch (err) {
      return Promise.reject(err)
    }
  }

  /* Helpers */

  // Return a function that will update last modification date
  // and does a chmod +x if the file is executable
  //
  // Note: UNIX has 3 timestamps for a file/folder:
  // - atime for last access
  // - ctime for change (metadata or content)
  // - utime for update (content only)
  // This function updates utime and ctime according to the last
  // modification date.
  metadataUpdater (doc /*: Metadata */) {
    let filePath = path.resolve(this.syncPath, doc.path)
    return function (callback /*: Callback */) {
      let next = function (err) {
        if (doc.executable) {
          fs.chmod(filePath, '755', callback)
        } else {
          callback(err)
        }
      }
      if (doc.updated_at) {
        let updated = new Date(doc.updated_at)
        fs.utimes(filePath, updated, updated, () =>
          // Ignore errors
          next()
        )
      } else {
        next()
      }
    }
  }

  async updateMetadataAsync (doc /*: Metadata */) {
    let filePath = path.resolve(this.syncPath, doc.path)
    if (doc.executable) await fs.chmod(filePath, '755')
    if (doc.updated_at) {
      let updated = new Date(doc.updated_at)
      try {
        await fs.utimes(filePath, updated, updated)
      } catch (_) {
        // Ignore errors
      }
    }
  }

  inodeSetter (doc /*: Metadata */) {
    let abspath = path.resolve(this.syncPath, doc.path)
    return (callback /*: Callback */) => {
      fs.stat(abspath, (err, stats) => {
        if (err) {
          callback(err)
        } else {
          doc.ino = stats.ino
          callback(null)
        }
      })
    }
  }

  // Check if a file corresponding to given checksum already exists
  fileExistsLocally (checksum /*: string */, callback /*: Callback */) {
    this.pouch.byChecksum(checksum, (err, docs) => {
      if (err) {
        callback(err)
      } else if ((docs == null) || (docs.length === 0)) {
        callback(null, false)
      } else {
        let paths = Array.from(docs)
          .filter((doc) => isUpToDate('local', doc))
          .map((doc) => path.resolve(this.syncPath, doc.path))
        async.detect(paths,
            (filePath, next) => fs.exists(filePath, found => next(null, found)),
            callback)
      }
    })
  }

  /* Write operations */

  // Add a new file, or replace an existing one
  //
  // Steps to create a file:
  //   * Try to find a similar file based on his checksum
  //     (in that case, it just requires a local copy)
  //   * Or download the linked binary from remote
  //   * Write to a temporary file
  //   * Ensure parent folder exists
  //   * Move the temporay file to its final destination
  //   * Update creation and last modification dates
  //
  // Note: if no checksum was available for this file, we download the file
  // from the remote document. Later, chokidar will fire an event for this new
  // file. The checksum will then be computed and added to the document, and
  // then pushed to CouchDB.
  addFile (doc /*: Metadata */, callback /*: Callback */) {
    let tmpFile = path.resolve(this.tmpPath, `${path.basename(doc.path)}.tmp`)
    let filePath = path.resolve(this.syncPath, doc.path)
    let parent = path.resolve(this.syncPath, path.dirname(doc.path))
    const stopMeasure = measureTime('LocalWriter#addFile')

    log.info({path: doc.path}, 'Put file')

    async.waterfall([
      next => {
        if (doc.md5sum != null) {
          this.fileExistsLocally(doc.md5sum, next)
        } else {
          next(null, false)
        }
      },

      (existingFilePath, next) => {
        fs.ensureDir(this.tmpPath, () => {
          hideOnWindows(this.tmpPath)
          if (existingFilePath) {
            log.info({path: filePath}, `Recopy ${existingFilePath} -> ${filePath}`)
            this.events.emit('transfer-copy', doc)
            fs.copy(existingFilePath, tmpFile, next)
          } else {
            this.other.createReadStreamAsync(doc).then(
              (stream) => {
                // Don't use async callback here!
                // Async does some magic and the stream can throw an
                // 'error' event before the next async is called...
                let target = fs.createWriteStream(tmpFile)
                stream.pipe(target)
                target.on('finish', next)
                target.on('error', next)
              },
              (err) => { next(err) }
            )
          }
        })
      },

      next => {
        if (doc.md5sum != null) {
          // TODO: Share checksumer instead of chaining properties
          this.watcher.checksumer.push(tmpFile).asCallback(function (err, md5sum) {
            if (err) {
              next(err)
            } else if (md5sum === doc.md5sum) {
              next()
            } else {
              next(new Error('Invalid checksum'))
            }
          })
        } else {
          next()
        }
      },

      next => fs.ensureDir(parent, () => fs.rename(tmpFile, filePath, next)),

      this.inodeSetter(doc),
      this.metadataUpdater(doc)

    ], function (err) {
      stopMeasure()
      if (err) { log.warn({path: doc.path}, 'addFile failed:', err, doc) }
      fs.unlink(tmpFile, () => callback(err))
    })
  }

  // Create a new folder
  addFolder (doc /*: Metadata */, callback /*: Callback */) {
    let folderPath = path.join(this.syncPath, doc.path)
    log.info({path: doc.path}, 'Put folder')
    async.series([
      cb => fs.ensureDir(folderPath, cb),
      this.inodeSetter(doc),
      this.metadataUpdater(doc)
    ], callback)
  }

  // Overwrite a file
  overwriteFileAsync (doc /*: Metadata */, old /*: ?Metadata */) /*: Promise<*> */ {
    return this.addFileAsync(doc)
  }

  // Update the metadata of a file
  updateFileMetadata (doc /*: Metadata */, old /*: Metadata */, callback /*: Callback */) {
    log.info({path: doc.path}, 'Updating file metadata...')
    this.metadataUpdater(doc)(callback)
  }

  // Update a folder
  updateFolderAsync (doc /*: Metadata */, old /*: Metadata */) /*: Promise<*> */ {
    return this.addFolderAsync(doc)
  }

  async assignNewRev (doc /*: Metadata */) /*: Promise<*> */ {
    log.info({path: doc.path}, 'Local assignNewRev = noop')
  }

  // Move a file from one place to another
  async moveFileAsync (doc /*: Metadata */, old /*: Metadata */) {
    log.info({path: doc.path}, `Moving from ${old.path}`)
    let oldPath = path.join(this.syncPath, old.path)
    let newPath = path.join(this.syncPath, doc.path)
    let parent = path.join(this.syncPath, path.dirname(doc.path))

    const oldPathExists = await fs.exists(oldPath)
    try {
      if (oldPathExists) {
        await fs.ensureDir(parent)
        await fs.rename(oldPath, newPath)
      } else {
        const newPathExists = await fs.exists(newPath)
        if (!newPathExists) {
          const msg = `File ${oldPath} not found`
          log.error({path: newPath}, msg)
          throw new Error(msg)
        }
      }
      await this.updateMetadataAsync(doc)
    } catch (err) {
      log.error({path: newPath, doc, old, err}, 'File move failed! Falling back to file download...')
      await this.addFileAsync(doc)
      return
    }

    if (doc.md5sum !== old.md5sum) {
      await this.overwriteFileAsync(doc)
    }
  }

  // Move a folder
  async moveFolderAsync (doc /*: Metadata */, old /*: Metadata */) {
    log.info({path: doc.path}, `Move folder from ${old.path}`)
    let oldPath = path.join(this.syncPath, old.path)
    let newPath = path.join(this.syncPath, doc.path)
    let parent = path.join(this.syncPath, path.dirname(doc.path))

    const oldPathExists = await fs.exists(oldPath)
    const newPathExists = await fs.exists(newPath)

    try {
      if (oldPathExists && newPathExists) {
        await fs.rmdir(oldPath)
      } else if (oldPathExists) {
        await fs.ensureDir(parent)
        await fs.rename(oldPath, newPath)
      } else if (!newPathExists) {
        const msg = `Folder ${oldPath} not found`
        log.error({path: newPath}, msg)
        throw new Error(msg)
      }
      await this.updateMetadataAsync(doc)
    } catch (err) {
      log.error({path: newPath, doc, old, err}, 'Folder move failed! Falling back to folder creation...')
      await this.addFolderAsync(doc)
    }
  }

  async trashAsync (doc /*: Metadata */) /*: Promise<*> */ {
    log.info({path: doc.path}, 'Moving to the OS trash...')
    this.events.emit('delete-file', doc)
    let fullpath = path.join(this.syncPath, doc.path)
    try {
      await this._trash([fullpath])
    } catch (err) {
      throw err
    }
  }

  async deleteFolderAsync (doc /*: Metadata */) /*: Promise<*> */ {
    if (doc.docType !== 'folder') throw new Error(`Not folder metadata: ${doc.path}`)
    const fullpath = path.join(this.syncPath, doc.path)

    try {
      log.info({path: doc.path}, 'Deleting empty folder...')
      await fs.rmdirAsync(fullpath)
      this.events.emit('delete-file', doc)
      return
    } catch (err) {
      if (err.code !== 'ENOTEMPTY') throw err
    }
    log.warn({path: doc.path}, 'Folder is not empty!')
    await this.trashAsync(doc)
  }

  // Rename a file/folder to resolve a conflict
  renameConflictingDoc (doc /*: Metadata */, newPath /*: string */, callback /*: Callback */) {
    log.info({path: doc.path}, `Resolve a conflict: ${doc.path} → ${newPath}`)
    let srcPath = path.join(this.syncPath, doc.path)
    let dstPath = path.join(this.syncPath, newPath)
    fs.rename(srcPath, dstPath, callback)
    // TODO: Don't fire an event for the deleted file?
  }
}
<commit_msg>core/local: Reuse #updateMetadataAsync() in #metadataUpdater()<commit_after>/* @flow */

const async = require('async')
const autoBind = require('auto-bind')
const fs = require('fs-extra')
const path = require('path')
const trash = require('trash')

const bluebird = require('bluebird')

const { TMP_DIR_NAME } = require('./constants')
const logger = require('../logger')
const { isUpToDate } = require('../metadata')
const { hideOnWindows } = require('../utils/fs')
const Watcher = require('./watcher')
const measureTime = require('../perftools')
const { withContentLength } = require('../file_stream_provider')

bluebird.promisifyAll(fs)

/*::
import type EventEmitter from 'events'
import type Config from '../config'
import type { FileStreamProvider, ReadableWithContentLength } from '../file_stream_provider'
import type { Metadata } from '../metadata'
import type Pouch from '../pouch'
import type Prep from '../prep'
import type { Side } from '../side' // eslint-disable-line
import type { Callback } from '../utils/func'
*/

const log = logger({
  component: 'LocalWriter'
})
// Local is the class that interfaces cozy-desktop with the local filesystem.
// It uses a watcher, based on chokidar, to listen for file and folder changes.
// It also applied changes from the remote cozy on the local filesystem.
module.exports = class Local /*:: implements Side */ {
  /*::
  prep: Prep
  pouch: Pouch
  events: EventEmitter
  syncPath: string
  tmpPath: string
  watcher: Watcher
  other: FileStreamProvider
  _trash: (Array<string>) => Promise<void>
  */

  constructor (config /*: Config */, prep /*: Prep */, pouch /*: Pouch */, events /*: EventEmitter */) {
    this.prep = prep
    this.pouch = pouch
    this.events = events
    this.syncPath = config.syncPath
    this.tmpPath = path.join(this.syncPath, TMP_DIR_NAME)
    this.watcher = new Watcher(this.syncPath, this.prep, this.pouch, events)
    // $FlowFixMe
    this.other = null
    this._trash = trash

    autoBind(this)
    bluebird.promisifyAll(this)
  }

  /*::
  addFileAsync: (Metadata) => Promise<*>
  addFolderAsync: (Metadata) => Promise<*>
  updateFileMetadataAsync: (Metadata, Metadata) => Promise<*>
  renameConflictingDocAsync: (doc: Metadata, newPath: string) => Promise<void>
  */

  // Start initial replication + watching changes in live
  start () {
    this.watcher.ensureDirSync()
    return this.watcher.start()
  }

  // Stop watching the file system
  stop () {
    return this.watcher.stop()
  }

  // Create a readable stream for the given doc
  // adds a contentLength property to be used
  async createReadStreamAsync (doc /*: Metadata */) /*: Promise<ReadableWithContentLength> */ {
    try {
      let filePath = path.resolve(this.syncPath, doc.path)
      let pStats = fs.statAsync(filePath)
      let pStream = new Promise((resolve, reject) => {
        let stream = fs.createReadStream(filePath)
        stream.on('open', () => resolve(stream))
        stream.on('error', err => reject(err))
      })
      const [
        stream /*: ReadableWithContentLength */,
        stat /*: fs.Stat */
      ] = await Promise.all([pStream, pStats])
      return withContentLength(stream, stat.size)
    } catch (err) {
      return Promise.reject(err)
    }
  }

  /* Helpers */

  // Return a function that will update last modification date
  // and does a chmod +x if the file is executable
  //
  // Note: UNIX has 3 timestamps for a file/folder:
  // - atime for last access
  // - ctime for change (metadata or content)
  // - utime for update (content only)
  // This function updates utime and ctime according to the last
  // modification date.
  metadataUpdater (doc /*: Metadata */) {
    return (callback /*: Callback */) => {
      this.updateMetadataAsync(doc)
        .then(() => { callback() })
        .catch(callback)
    }
  }

  async updateMetadataAsync (doc /*: Metadata */) {
    let filePath = path.resolve(this.syncPath, doc.path)
    if (doc.executable) await fs.chmod(filePath, '755')
    if (doc.updated_at) {
      let updated = new Date(doc.updated_at)
      try {
        await fs.utimes(filePath, updated, updated)
      } catch (_) {
        // Ignore errors
      }
    }
  }

  inodeSetter (doc /*: Metadata */) {
    let abspath = path.resolve(this.syncPath, doc.path)
    return (callback /*: Callback */) => {
      fs.stat(abspath, (err, stats) => {
        if (err) {
          callback(err)
        } else {
          doc.ino = stats.ino
          callback(null)
        }
      })
    }
  }

  // Check if a file corresponding to given checksum already exists
  fileExistsLocally (checksum /*: string */, callback /*: Callback */) {
    this.pouch.byChecksum(checksum, (err, docs) => {
      if (err) {
        callback(err)
      } else if ((docs == null) || (docs.length === 0)) {
        callback(null, false)
      } else {
        let paths = Array.from(docs)
          .filter((doc) => isUpToDate('local', doc))
          .map((doc) => path.resolve(this.syncPath, doc.path))
        async.detect(paths,
            (filePath, next) => fs.exists(filePath, found => next(null, found)),
            callback)
      }
    })
  }

  /* Write operations */

  // Add a new file, or replace an existing one
  //
  // Steps to create a file:
  //   * Try to find a similar file based on his checksum
  //     (in that case, it just requires a local copy)
  //   * Or download the linked binary from remote
  //   * Write to a temporary file
  //   * Ensure parent folder exists
  //   * Move the temporay file to its final destination
  //   * Update creation and last modification dates
  //
  // Note: if no checksum was available for this file, we download the file
  // from the remote document. Later, chokidar will fire an event for this new
  // file. The checksum will then be computed and added to the document, and
  // then pushed to CouchDB.
  addFile (doc /*: Metadata */, callback /*: Callback */) {
    let tmpFile = path.resolve(this.tmpPath, `${path.basename(doc.path)}.tmp`)
    let filePath = path.resolve(this.syncPath, doc.path)
    let parent = path.resolve(this.syncPath, path.dirname(doc.path))
    const stopMeasure = measureTime('LocalWriter#addFile')

    log.info({path: doc.path}, 'Put file')

    async.waterfall([
      next => {
        if (doc.md5sum != null) {
          this.fileExistsLocally(doc.md5sum, next)
        } else {
          next(null, false)
        }
      },

      (existingFilePath, next) => {
        fs.ensureDir(this.tmpPath, () => {
          hideOnWindows(this.tmpPath)
          if (existingFilePath) {
            log.info({path: filePath}, `Recopy ${existingFilePath} -> ${filePath}`)
            this.events.emit('transfer-copy', doc)
            fs.copy(existingFilePath, tmpFile, next)
          } else {
            this.other.createReadStreamAsync(doc).then(
              (stream) => {
                // Don't use async callback here!
                // Async does some magic and the stream can throw an
                // 'error' event before the next async is called...
                let target = fs.createWriteStream(tmpFile)
                stream.pipe(target)
                target.on('finish', next)
                target.on('error', next)
              },
              (err) => { next(err) }
            )
          }
        })
      },

      next => {
        if (doc.md5sum != null) {
          // TODO: Share checksumer instead of chaining properties
          this.watcher.checksumer.push(tmpFile).asCallback(function (err, md5sum) {
            if (err) {
              next(err)
            } else if (md5sum === doc.md5sum) {
              next()
            } else {
              next(new Error('Invalid checksum'))
            }
          })
        } else {
          next()
        }
      },

      next => fs.ensureDir(parent, () => fs.rename(tmpFile, filePath, next)),

      this.inodeSetter(doc),
      this.metadataUpdater(doc)

    ], function (err) {
      stopMeasure()
      if (err) { log.warn({path: doc.path}, 'addFile failed:', err, doc) }
      fs.unlink(tmpFile, () => callback(err))
    })
  }

  // Create a new folder
  addFolder (doc /*: Metadata */, callback /*: Callback */) {
    let folderPath = path.join(this.syncPath, doc.path)
    log.info({path: doc.path}, 'Put folder')
    async.series([
      cb => fs.ensureDir(folderPath, cb),
      this.inodeSetter(doc),
      this.metadataUpdater(doc)
    ], callback)
  }

  // Overwrite a file
  overwriteFileAsync (doc /*: Metadata */, old /*: ?Metadata */) /*: Promise<*> */ {
    return this.addFileAsync(doc)
  }

  // Update the metadata of a file
  updateFileMetadata (doc /*: Metadata */, old /*: Metadata */, callback /*: Callback */) {
    log.info({path: doc.path}, 'Updating file metadata...')
    this.metadataUpdater(doc)(callback)
  }

  // Update a folder
  updateFolderAsync (doc /*: Metadata */, old /*: Metadata */) /*: Promise<*> */ {
    return this.addFolderAsync(doc)
  }

  async assignNewRev (doc /*: Metadata */) /*: Promise<*> */ {
    log.info({path: doc.path}, 'Local assignNewRev = noop')
  }

  // Move a file from one place to another
  async moveFileAsync (doc /*: Metadata */, old /*: Metadata */) {
    log.info({path: doc.path}, `Moving from ${old.path}`)
    let oldPath = path.join(this.syncPath, old.path)
    let newPath = path.join(this.syncPath, doc.path)
    let parent = path.join(this.syncPath, path.dirname(doc.path))

    const oldPathExists = await fs.exists(oldPath)
    try {
      if (oldPathExists) {
        await fs.ensureDir(parent)
        await fs.rename(oldPath, newPath)
      } else {
        const newPathExists = await fs.exists(newPath)
        if (!newPathExists) {
          const msg = `File ${oldPath} not found`
          log.error({path: newPath}, msg)
          throw new Error(msg)
        }
      }
      await this.updateMetadataAsync(doc)
    } catch (err) {
      log.error({path: newPath, doc, old, err}, 'File move failed! Falling back to file download...')
      await this.addFileAsync(doc)
      return
    }

    if (doc.md5sum !== old.md5sum) {
      await this.overwriteFileAsync(doc)
    }
  }

  // Move a folder
  async moveFolderAsync (doc /*: Metadata */, old /*: Metadata */) {
    log.info({path: doc.path}, `Move folder from ${old.path}`)
    let oldPath = path.join(this.syncPath, old.path)
    let newPath = path.join(this.syncPath, doc.path)
    let parent = path.join(this.syncPath, path.dirname(doc.path))

    const oldPathExists = await fs.exists(oldPath)
    const newPathExists = await fs.exists(newPath)

    try {
      if (oldPathExists && newPathExists) {
        await fs.rmdir(oldPath)
      } else if (oldPathExists) {
        await fs.ensureDir(parent)
        await fs.rename(oldPath, newPath)
      } else if (!newPathExists) {
        const msg = `Folder ${oldPath} not found`
        log.error({path: newPath}, msg)
        throw new Error(msg)
      }
      await this.updateMetadataAsync(doc)
    } catch (err) {
      log.error({path: newPath, doc, old, err}, 'Folder move failed! Falling back to folder creation...')
      await this.addFolderAsync(doc)
    }
  }

  async trashAsync (doc /*: Metadata */) /*: Promise<*> */ {
    log.info({path: doc.path}, 'Moving to the OS trash...')
    this.events.emit('delete-file', doc)
    let fullpath = path.join(this.syncPath, doc.path)
    try {
      await this._trash([fullpath])
    } catch (err) {
      throw err
    }
  }

  async deleteFolderAsync (doc /*: Metadata */) /*: Promise<*> */ {
    if (doc.docType !== 'folder') throw new Error(`Not folder metadata: ${doc.path}`)
    const fullpath = path.join(this.syncPath, doc.path)

    try {
      log.info({path: doc.path}, 'Deleting empty folder...')
      await fs.rmdirAsync(fullpath)
      this.events.emit('delete-file', doc)
      return
    } catch (err) {
      if (err.code !== 'ENOTEMPTY') throw err
    }
    log.warn({path: doc.path}, 'Folder is not empty!')
    await this.trashAsync(doc)
  }

  // Rename a file/folder to resolve a conflict
  renameConflictingDoc (doc /*: Metadata */, newPath /*: string */, callback /*: Callback */) {
    log.info({path: doc.path}, `Resolve a conflict: ${doc.path} → ${newPath}`)
    let srcPath = path.join(this.syncPath, doc.path)
    let dstPath = path.join(this.syncPath, newPath)
    fs.rename(srcPath, dstPath, callback)
    // TODO: Don't fire an event for the deleted file?
  }
}
<|endoftext|>


<commit_before>var messageSpace;
var svgImg;
var pointsList = [];
var lineWidth = 2;
var circleRadius = 5;
var strokeWidth = 5;

var polygons = [];

var currenPolygon = null;
var selectedPolygon = null;

function initSvg(ms) {
    messageSpace = document.getElementsByClassName('message-space')[0];
    svgImg = document.getElementsByClassName('svg-img')[0];
    initCoordinates(svgImg);
    pointsList = [];
}

function svgImgOnClick(event) {
    var point = getPoint(event);

    if (currenPolygon != null) {
        if (currenPolygon.shouldClose(point.x, point.y)) {
            closePolygon(currenPolygon);
        } else {
            currenPolygon.addPoint(point.x, point.y);
        }
    } else {
        currenPolygon = new Polygon(point.x, point.y);
        svgImg.append(currenPolygon.node);
        console.log(currenPolygon.node);
    }
}

function svgImgOnClickSelect(event) {
    if (selectedPolygon != null) {
        selectedPolygon.setSelected(false);
        selectedPolygon = null;
    }
}

function closePolygon() {
    currenPolygon.close();
    currenPolygon.onPolygonClick = onPolygonClick;
    polygons.push(currenPolygon);

    if (selectedPolygon != null) {
        selectedPolygon.setSelected(false);
    }

    selectedPolygon = currenPolygon;
    selectedPolygon.setSelected(true);
    showPolygonSelectedMessage();

    currenPolygon = null;
}

function onPolygonClick(polygon) {

    if (selectedPolygon != null) {
        selectedPolygon.setSelected(false);
    }

    polygon.setSelected(true);
    selectedPolygon = polygon;
    showPolygonSelectedMessage();
    // Bring it to top
    svgImg.append(selectedPolygon.node);
}

function showPolygonSelectedMessage() {
    showMessage("Please, characterize the selected object in the right menu.",
        MessageTypeEnum.WARNING);
    setTimeout(function () {
        showMessage(
            "Please, markup the image displayed below using the tools from the block on the left.",
            MessageTypeEnum.INFO);
    }, 5000);
}
<commit_msg>Refactored code in svg.js<commit_after>let svgImg;
let pointsList = [];

let polygons = [];

let currentPolygon = null;
let selectedPolygon = null;

function initSvg(ms) {
    svgImg = document.getElementsByClassName('svg-img')[0];
    initCoordinates(svgImg);
    pointsList = [];
}

function svgImgOnClick(event) {
    let point = getPoint(event);

    if (currentPolygon !== null) {
        if (currentPolygon.shouldClose(point.x, point.y)) {
            closePolygon(currentPolygon);
        } else {
            currentPolygon.addPoint(point.x, point.y);
        }
    } else {
        currentPolygon = new Polygon(point.x, point.y);
        svgImg.append(currentPolygon.node);
        console.log(currentPolygon.node);
    }
}

function svgImgOnClickSelect(event) {
    if (selectedPolygon !== null) {
        selectedPolygon.setSelected(false);
        selectedPolygon = null;
    }
}

function closePolygon() {
    currentPolygon.close();
    currentPolygon.onPolygonClick = onPolygonClick;
    polygons.push(currentPolygon);

    if (selectedPolygon !== null) {
        selectedPolygon.setSelected(false);
    }

    selectedPolygon = currentPolygon;
    selectedPolygon.setSelected(true);
    showPolygonSelectedMessage();

    currentPolygon = null;
}

function onPolygonClick(polygon) {

    if (selectedPolygon !== null) {
        selectedPolygon.setSelected(false);
    }

    polygon.setSelected(true);
    selectedPolygon = polygon;
    showPolygonSelectedMessage();
    // Bring it to top
    svgImg.append(selectedPolygon.node);
}

function showPolygonSelectedMessage() {
    showMessage("Please, characterize the selected object in the right menu.",
        MessageTypeEnum.WARNING);
    setTimeout(function () {
        showMessage(
            "Please, markup the image displayed below using the tools from the block on the left.",
            MessageTypeEnum.INFO);
    }, 5000);
}
<|endoftext|>


<commit_before>import './index.module';
import { setupRoutes } from './routes.js';
import { MCAppController } from './mc-app.controller';
import './global.services/index.module';
import './global.filters/index.module';
import './global.components/index.module';
import './login/index.module';
import './join/index.module';
import './model/index.module';
import './project/index.module';
import './projects/index.module';
import './user/index.module';
import './util/util';

angular.module('materialscommons')
    .constant('mcglobals', setupMCGlobals())
    .config(appConfig)
    .run(appRun)
    .controller('MCAppController', MCAppController);

appConfig.$inject = ['$stateProvider', '$urlRouterProvider', '$mdThemingProvider', '$ariaProvider', '$compileProvider'];
function appConfig($stateProvider, $urlRouterProvider, $mdThemingProvider, $ariaProvider, $compileProvider) {
    setupMaterialsTheme($mdThemingProvider);
    setupRoutes($stateProvider, $urlRouterProvider);
    $ariaProvider.config({ariaChecked: false, ariaInvalid: false});
    $compileProvider.debugInfoEnabled(false);
}

appRun.$inject = ['$rootScope', 'User', 'Restangular', '$state', 'mcglobals', 'searchQueryText'];
function appRun($rootScope, User, Restangular, $state, mcglobals, searchQueryText) {
    Restangular.setBaseUrl(mcglobals.apihost);

    // appRun will run when the application starts up and before any controllers have run.
    // This means it will run on a refresh. We check if the user is already authenticated
    // during the run. If they are then the browser has been refreshed and we need to
    // set the apikey param for Restangular. This param is set in the login-controller.
    // However on a refresh the login-controller isn't run so we need to explicitly set
    // the apikey param in Restangular.
    if (User.isAuthenticated()) {
        Restangular.setDefaultRequestParams({apikey: User.apikey()});
    }

    var unregister = $rootScope.$on('$stateChangeStart', function(event, toState) {
        $rootScope.navbarSearchText = toState.name.startsWith('projects') ? 'SEARCH PROJECTS...' : 'SEARCH PROJECT...';
        if (!User.isAuthenticated()
            && (toState.name !== 'login' && toState.name !== 'join' && toState.name !== 'validate')) {
            event.preventDefault();
            $state.go('login');
        }

        if (!toState.name.startsWith('project.search')) {
            searchQueryText.set("");
        }
    });

    $rootScope.$on('$destroy', function() { unregister(); });
}

function setupMCGlobals() {
    var mcglobals = {};
    if (window.location.hostname === 'localhost') {
        mcglobals.apihost = window.location.protocol + '//localhost:5002';
    } else {
        mcglobals.apihost = window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/api';
    }

    return mcglobals;
}

function setupMaterialsTheme($mdThemingProvider) {
    $mdThemingProvider.theme('default').primaryPalette('blue').accentPalette('red');
}
<commit_msg>highlightjs convert tabs to spaces.<commit_after>import './index.module';
import { setupRoutes } from './routes.js';
import { MCAppController } from './mc-app.controller';
import './global.services/index.module';
import './global.filters/index.module';
import './global.components/index.module';
import './login/index.module';
import './join/index.module';
import './model/index.module';
import './project/index.module';
import './projects/index.module';
import './user/index.module';
import './util/util';

angular.module('materialscommons')
    .constant('mcglobals', setupMCGlobals())
    .config(appConfig)
    .run(appRun)
    .controller('MCAppController', MCAppController);

appConfig.$inject = ['$stateProvider', '$urlRouterProvider', '$mdThemingProvider', '$ariaProvider', '$compileProvider', 'hljsServiceProvider'];
function appConfig($stateProvider, $urlRouterProvider, $mdThemingProvider, $ariaProvider, $compileProvider, hljsServiceProvider) {
    setupMaterialsTheme($mdThemingProvider);
    setupRoutes($stateProvider, $urlRouterProvider);
    $ariaProvider.config({ariaChecked: false, ariaInvalid: false});
    $compileProvider.debugInfoEnabled(false);
    hljsServiceProvider.setOptions({
        // replace tab with 4 spaces
        tabReplace: '    '
    });
}

/*
 myApp.config(function (hljsServiceProvider) {
 hljsServiceProvider.setOptions({
 // replace tab with 4 spaces
 tabReplace: '    '
 });
 });
 */

appRun.$inject = ['$rootScope', 'User', 'Restangular', '$state', 'mcglobals', 'searchQueryText'];
function appRun($rootScope, User, Restangular, $state, mcglobals, searchQueryText) {
    Restangular.setBaseUrl(mcglobals.apihost);

    // appRun will run when the application starts up and before any controllers have run.
    // This means it will run on a refresh. We check if the user is already authenticated
    // during the run. If they are then the browser has been refreshed and we need to
    // set the apikey param for Restangular. This param is set in the login-controller.
    // However on a refresh the login-controller isn't run so we need to explicitly set
    // the apikey param in Restangular.
    if (User.isAuthenticated()) {
        Restangular.setDefaultRequestParams({apikey: User.apikey()});
    }

    var unregister = $rootScope.$on('$stateChangeStart', function(event, toState) {
        $rootScope.navbarSearchText = toState.name.startsWith('projects') ? 'SEARCH PROJECTS...' : 'SEARCH PROJECT...';
        if (!User.isAuthenticated()
            && (toState.name !== 'login' && toState.name !== 'join' && toState.name !== 'validate')) {
            event.preventDefault();
            $state.go('login');
        }

        if (!toState.name.startsWith('project.search')) {
            searchQueryText.set("");
        }
    });

    $rootScope.$on('$destroy', function() { unregister(); });
}

function setupMCGlobals() {
    var mcglobals = {};
    if (window.location.hostname === 'localhost') {
        mcglobals.apihost = window.location.protocol + '//localhost:5002';
    } else {
        mcglobals.apihost = window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/api';
    }

    return mcglobals;
}

function setupMaterialsTheme($mdThemingProvider) {
    $mdThemingProvider.theme('default').primaryPalette('blue').accentPalette('red');
}
<|endoftext|>


<commit_before>/* eslint key-spacing:0 spaced-comment:0 */
import _debug from 'debug'
import path from 'path'
import { argv } from 'yargs'

const debug = _debug('app:config:_base')
const config = {
  env : process.env.NODE_ENV || 'development',

  // ----------------------------------
  // Project Structure
  // ----------------------------------
  path_base  : path.resolve(__dirname, '../'),
  dir_client : 'src',
  dir_dist   : 'dist',
  dir_server : 'server',
  dir_test   : 'tests',

  // ----------------------------------
  // Server Configuration
  // ----------------------------------
  server_host : 'localhost',
  server_port : process.env.PORT || 3000,

  // ----------------------------------
  // Compiler Configuration
  // ----------------------------------
  compiler_css_modules     : true,
  compiler_devtool         : 'source-map',
  compiler_hash_type       : 'hash',
  compiler_fail_on_warning : false,
  compiler_quiet           : false,
  compiler_public_path     : '',
  compiler_stats           : {
    chunks : false,
    chunkModules : false,
    colors : true
  },
  compiler_vendor : [
    'history',
    'react',
    'react-redux',
    'react-router',
    'redux',
    'redux-actions',
    'react-router-redux',
    'material-ui',
    'react-tap-event-plugin'
  ],

  // ----------------------------------
  // Test Configuration
  // ----------------------------------
  coverage_enabled   : !argv.watch,
  coverage_reporters : [
    { type : 'text-summary' },
    { type : 'html', dir : 'coverage' }
  ]
}

/************************************************
-------------------------------------------------

All Internal Configuration Below
Edit at Your Own Risk

-------------------------------------------------
************************************************/

// ------------------------------------
// Environment
// ------------------------------------
// N.B.: globals added here must _also_ be added to .eslintrc
config.globals = {
  'process.env'  : {
    'NODE_ENV' : JSON.stringify(config.env)
  },
  'NODE_ENV'     : config.env,
  '__DEV__'      : config.env === 'development',
  '__PROD__'     : config.env === 'production',
  '__TEST__'     : config.env === 'test',
  '__DEBUG__'    : config.env === 'development' && !argv.no_debug,
  '__DEBUG_NEW_WINDOW__' : !!argv.nw,
  '__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
}

// ------------------------------------
// Validate Vendor Dependencies
// ------------------------------------
const pkg = require('../package.json')

config.compiler_vendor = config.compiler_vendor
  .filter(dep => {
    if (pkg.dependencies[dep]) return true

    debug(
      `Package "${dep}" was not found as an npm dependency in package.json; ` +
      `it won't be included in the webpack vendor bundle.\n` +
      `Consider removing it from vendor_dependencies in ~/config/index.js`
    )
  })

// ------------------------------------
// Utilities
// ------------------------------------
config.utils_paths = (() => {
  const resolve = path.resolve

  const base = (...args) =>
    resolve.apply(resolve, [config.path_base, ...args])

  return {
    base   : base,
    client : base.bind(null, config.dir_client),
    dist   : base.bind(null, config.dir_dist)
  }
})()

export default config
<commit_msg>Update _base.js<commit_after>/* eslint key-spacing:0 spaced-comment:0 */
import _debug from 'debug'
import path from 'path'
import { argv } from 'yargs'

const debug = _debug('app:config:_base')
const config = {
  env : process.env.NODE_ENV || 'development',

  // ----------------------------------
  // Project Structure
  // ----------------------------------
  path_base  : path.resolve(__dirname, '../'),
  dir_client : 'src',
  dir_dist   : 'dist',
  dir_server : 'server',
  dir_test   : 'tests',

  // ----------------------------------
  // Server Configuration
  // ----------------------------------
  server_host : '178.32.253.90',
  server_port : process.env.PORT || 80,

  // ----------------------------------
  // Compiler Configuration
  // ----------------------------------
  compiler_css_modules     : true,
  compiler_devtool         : 'source-map',
  compiler_hash_type       : 'hash',
  compiler_fail_on_warning : false,
  compiler_quiet           : false,
  compiler_public_path     : '',
  compiler_stats           : {
    chunks : false,
    chunkModules : false,
    colors : true
  },
  compiler_vendor : [
    'history',
    'react',
    'react-redux',
    'react-router',
    'redux',
    'redux-actions',
    'react-router-redux',
    'material-ui',
    'react-tap-event-plugin'
  ],

  // ----------------------------------
  // Test Configuration
  // ----------------------------------
  coverage_enabled   : !argv.watch,
  coverage_reporters : [
    { type : 'text-summary' },
    { type : 'html', dir : 'coverage' }
  ]
}

/************************************************
-------------------------------------------------

All Internal Configuration Below
Edit at Your Own Risk

-------------------------------------------------
************************************************/

// ------------------------------------
// Environment
// ------------------------------------
// N.B.: globals added here must _also_ be added to .eslintrc
config.globals = {
  'process.env'  : {
    'NODE_ENV' : JSON.stringify(config.env)
  },
  'NODE_ENV'     : config.env,
  '__DEV__'      : config.env === 'development',
  '__PROD__'     : config.env === 'production',
  '__TEST__'     : config.env === 'test',
  '__DEBUG__'    : config.env === 'development' && !argv.no_debug,
  '__DEBUG_NEW_WINDOW__' : !!argv.nw,
  '__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
}

// ------------------------------------
// Validate Vendor Dependencies
// ------------------------------------
const pkg = require('../package.json')

config.compiler_vendor = config.compiler_vendor
  .filter(dep => {
    if (pkg.dependencies[dep]) return true

    debug(
      `Package "${dep}" was not found as an npm dependency in package.json; ` +
      `it won't be included in the webpack vendor bundle.\n` +
      `Consider removing it from vendor_dependencies in ~/config/index.js`
    )
  })

// ------------------------------------
// Utilities
// ------------------------------------
config.utils_paths = (() => {
  const resolve = path.resolve

  const base = (...args) =>
    resolve.apply(resolve, [config.path_base, ...args])

  return {
    base   : base,
    client : base.bind(null, config.dir_client),
    dist   : base.bind(null, config.dir_dist)
  }
})()

export default config
<|endoftext|>


<commit_before>//TODO deal with update image assets without making children

include("assets/asset.js", true);
include("assets/assetmanager.js", true);

var floor = Math.floor;
var Vec = EN.Vector;

function SpriteAsset(sFileName)
{
    //Super constructor
    EN.Asset.call(this, sFileName);

    this.m_cBaseSprite = null;
    this.m_cImages = {};

    this.m_sAnimation = "";
    this.m_cCurrentAnimation = null;
    this.CurrentFrame = 0;
    this.m_nPreviousFramesElapsed = 0;
    this.m_bAnimationRunning = true;
};

inherits(SpriteAsset, EN.Asset);

SpriteAsset.prototype.Load = function(fOnLoad){
    var self = this;

    EN.AssetManager.LoadJSON("sprites/" + this.m_sFileName + ".json", function(cErr, cSprite){
        if (!cErr)
        {
            self.m_cBaseSprite = cSprite;

            var aDependencies = [];

            if (isset(cSprite.dependencies))
            {
                for (var sKey in cSprite.dependencies)
                {
                    switch (sKey)
                    {
                        case "images":

                            for (var sImageKey in cSprite.dependencies[sKey])
                            {
                                var cAnimation = cSprite.animations[sImageKey];

                                var cImage = new EN.ImageAsset(cSprite.dependencies[sKey][sImageKey], {
                                    visibleWidth: cAnimation.width,
                                    visibleHeight: cAnimation.height
                                });

                                self.m_cImages[sImageKey] = cImage;
                                aDependencies.push(cImage);
                            }

                            break;
                    }
                }
            }

            self.ChangeAnimation(cSprite.default);

            EN.Loader.Load(aDependencies, fOnLoad);
        }
        else
        {
            fOnLoad(cErr);
        }
    });
};

SpriteAsset.prototype.InitialUpdate = function(nDt){
    EN.Asset.prototype.InitialUpdate.call(this, nDt);

    if (this.m_bAnimationRunning)
    {
        this.m_nPreviousFramesElapsed += nDt / this.m_cCurrentAnimation.frameDelta;

        if (this.m_nPreviousFramesElapsed >= 1)
        {
            this.CurrentFrame = floor(this.CurrentFrame + this.m_nPreviousFramesElapsed) % this.m_cCurrentAnimation.frames;
            this.m_nPreviousFramesElapsed = this.m_nPreviousFramesElapsed - floor(this.m_nPreviousFramesElapsed);
        }

        this.m_cImages[this.m_sAnimation].Offset = new Vec(this.CurrentFrame * this.Width % this.m_cImages[this.m_sAnimation].ImageWidth, floor(this.CurrentFrame * this.Width / this.m_cImages[this.m_sAnimation].ImageWidth) * this.Height);
    }

    this.m_cImages[this.m_sAnimation].Pos = this.Pos;
};

SpriteAsset.prototype.Draw = function(cRenderer){
    EN.Asset.prototype.Draw.call(this, cRenderer);

    this.m_cImages[this.m_sAnimation].Draw(cRenderer);
};

SpriteAsset.prototype.ChangeAnimation = function(sAnimation){
    this.m_sAnimation = sAnimation;

    this.CurrentFrame = 0;
    this.m_nPreviousFramesElapsed = 0;

    var cCurrentAnimation = this.m_cBaseSprite.animations[this.m_sAnimation];

    cCurrentAnimation.frameDelta = 1000 / cCurrentAnimation.fps;
    this.m_cCurrentAnimation = cCurrentAnimation;

    this.m_cImages[this.m_sAnimation].CoordAlignment = this.CoordAlignment;

    this.Width = this.m_cCurrentAnimation.width;
    this.Height = this.m_cCurrentAnimation.height;
};

SpriteAsset.prototype.PauseAnimation = function(bPaused){
    this.m_bAnimationRunning = !bPaused;
};

SpriteAsset.prototype.CleanUp = function(){
    EN.Asset.prototype.CleanUp.call(this);
    EN.AssetManager.ReleaseJSON("sprites/" + this.m_sFileName + ".json");
};

EN.SpriteAsset = SpriteAsset;
//# sourceURL=engine/assets/spriteasset.js<commit_msg>refactor of sprite sheet to use base images<commit_after>//TODO deal with update image assets without making children

include("assets/asset.js", true);
include("assets/assetmanager.js", true);

var floor = Math.floor;
var Vec = EN.Vector;

function SpriteAsset(sFileName)
{
    //Super constructor
    EN.Asset.call(this, sFileName);

    this.Alpha = 1;

    this.m_cBaseSprite = null;
    this.m_cImages = {};
    this.m_cBaseImages = {};
    this.m_cCurrentImage = null;

    this.m_sAnimation = "";
    this.m_cCurrentAnimation = null;
    this.CurrentFrame = 0;
    this.m_nPreviousFramesElapsed = 0;
    this.m_bAnimationRunning = true;
};

inherits(SpriteAsset, EN.Asset);

SpriteAsset.prototype.Load = function(fOnLoad){
    var self = this;

    EN.AssetManager.LoadJSON("sprites/" + this.m_sFileName + ".json", function(cErr, cSprite){
        if (!cErr)
        {
            self.m_cBaseSprite = cSprite;

            var aDependencies = [];

            if (isset(cSprite.dependencies))
            {
                for (var sKey in cSprite.dependencies)
                {
                    switch (sKey)
                    {
                        case "images":

                            for (var sImageKey in cSprite.dependencies[sKey])
                            {
                                self.m_cImages[sImageKey] = {
                                    Offset: new Vec(0, 0)
                                };

                                var fLoadImage = (function(sImageKey, sFileName){
                                    return {
                                        Load: function(fOnLoad){
                                            EN.AssetManager.LoadImage(sFileName, function(cErr, cImage){
                                                if (!cErr)
                                                {
                                                    self.m_cImages[sImageKey].ImageWidth = cImage.width;
                                                    self.m_cImages[sImageKey].ImageHeight = cImage.height;
                                                    self.m_cBaseImages[sImageKey] = cImage;
                                                    fOnLoad();
                                                }
                                                else
                                                {
                                                    fOnload(cErr);
                                                }
                                            });
                                        }
                                    };
                                })(sImageKey, cSprite.dependencies[sKey][sImageKey]);

                                aDependencies.push(fLoadImage);
                            }

                            break;
                    }
                }
            }

            EN.Loader.Load(aDependencies, function(cErr){
                if (!cErr)
                {
                    self.ChangeAnimation(cSprite.default);
                }

                fOnLoad(cErr);
            });
        }
        else
        {
            fOnLoad(cErr);
        }
    });
};

SpriteAsset.prototype.InitialUpdate = function(nDt){
    EN.Asset.prototype.InitialUpdate.call(this, nDt);

    if (this.m_bAnimationRunning)
    {
        this.m_nPreviousFramesElapsed += nDt / this.m_cCurrentAnimation.frameDelta;

        if (this.m_nPreviousFramesElapsed >= 1)
        {
            this.CurrentFrame = floor(this.CurrentFrame + this.m_nPreviousFramesElapsed) % this.m_cCurrentAnimation.frames;
            this.m_nPreviousFramesElapsed = this.m_nPreviousFramesElapsed - floor(this.m_nPreviousFramesElapsed);
        }

        var nOffsetX = (this.CurrentFrame * this.Width) % this.m_cImages[this.m_sAnimation].ImageWidth;
        var nOffsetY = floor((this.CurrentFrame * this.Width) / this.m_cImages[this.m_sAnimation].ImageWidth) * this.Height;

        this.m_cImages[this.m_sAnimation].Offset = new Vec(nOffsetX, nOffsetY);
    }

    this.m_cImages[this.m_sAnimation].Pos = this.Pos;
};

SpriteAsset.prototype.Draw = function(cRenderer){
    EN.Asset.prototype.Draw.call(this, cRenderer);

    cRenderer.DrawImage(this.m_cTransformMatrix, this.m_cCurrentImage, this.Width, this.Height, this.m_cImages[this.m_sAnimation].Offset, this.Alpha);
};

SpriteAsset.prototype.ChangeAnimation = function(sAnimation){
    this.m_sAnimation = sAnimation;

    this.CurrentFrame = 0;
    this.m_nPreviousFramesElapsed = 0;

    var cCurrentAnimation = this.m_cBaseSprite.animations[this.m_sAnimation];

    cCurrentAnimation.frameDelta = 1000 / cCurrentAnimation.fps;
    this.m_cCurrentAnimation = cCurrentAnimation;

    this.m_cCurrentImage = this.m_cBaseImages[this.m_sAnimation];

    this.Width = this.m_cCurrentAnimation.width;
    this.Height = this.m_cCurrentAnimation.height;
};

SpriteAsset.prototype.PauseAnimation = function(bPaused){
    this.m_bAnimationRunning = !bPaused;
};

SpriteAsset.prototype.CleanUp = function(){
    EN.Asset.prototype.CleanUp.call(this);
    EN.AssetManager.ReleaseJSON("sprites/" + this.m_sFileName + ".json");

    for (var sKey in this.m_cImages)
    {
        EN.AssetManager.ReleaseImage(cSprite.dependencies["images"][sKey]);
    }
};

EN.SpriteAsset = SpriteAsset;
//# sourceURL=engine/assets/spriteasset.js<|endoftext|>


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2019 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Utilities for element styles.
 * These methods are not specific to Blockly, and could be factored out into
 * a JavaScript framework such as Closure.
 * @author samelh@google.com (Sam El-Husseini)
 */
'use strict';

/**
 * @name Blockly.utils.style
 * @namespace
 */
goog.provide('Blockly.utils.style');

goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.Size');

/**
 * Gets the height and width of an element.
 * Similar to Closure's goog.style.getSize.
 * @param {Element} element Element to get size of.
 * @return {!Blockly.utils.Size} Object with width/height properties.
 */
Blockly.utils.style.getSize = function(element) {
  if (Blockly.utils.style.getStyle_(element, 'display') != 'none') {
    var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth;
    var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight;
    return new Blockly.utils.Size(offsetWidth, offsetHeight);
  }

  // Evaluate size with a temporary element.
  var style = element.style;
  var originalDisplay = style.display;
  var originalVisibility = style.visibility;
  var originalPosition = style.position;

  style.visibility = 'hidden';
  style.position = 'absolute';
  style.display = 'inline';

  var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth;
  var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight;

  style.display = originalDisplay;
  style.position = originalPosition;
  style.visibility = originalVisibility;

  return new Blockly.utils.Size(offsetWidth, offsetHeight);
};

/**
 * Cross-browser pseudo get computed style. It returns the computed style where
 * available. If not available it tries the cascaded style value (IE
 * currentStyle) and in worst case the inline style value.  It shouldn't be
 * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
 * discussion.
 *
 * Copied from Closure's goog.ui.style
 *
 * @param {Element} element Element to get style of.
 * @param {string} style Property to get (must be camelCase, not css-style.).
 * @return {string} Style value.
 * @private
 */
Blockly.utils.style.getStyle_ = function(element, style) {
  return Blockly.utils.style.getComputedStyle(element, style) ||
      Blockly.utils.style.getCascadedStyle(element, style) ||
      (element.style && element.style[style]);
};

/**
 * Retrieves a computed style value of a node. It returns empty string if the
 * value cannot be computed (which will be the case in Internet Explorer) or
 * "none" if the property requested is an SVG one and it has not been
 * explicitly set (firefox and webkit).
 *
 * Copied from Closure's goog.ui.style
 *
 * @param {Element} element Element to get style of.
 * @param {string} property Property to get (camel-case).
 * @return {string} Style value.
 */
Blockly.utils.style.getComputedStyle = function(element, property) {
  if (document.defaultView && document.defaultView.getComputedStyle) {
    var styles = document.defaultView.getComputedStyle(element, null);
    if (styles) {
      // element.style[..] is undefined for browser specific styles
      // as 'filter'.
      return styles[property] || styles.getPropertyValue(property) || '';
    }
  }

  return '';
};

/**
 * Gets the cascaded style value of a node, or null if the value cannot be
 * computed (only Internet Explorer can do this).
 *
 * Copied from Closure's goog.ui.style
 *
 * @param {Element} element Element to get style of.
 * @param {string} style Property to get (camel-case).
 * @return {string} Style value.
 */
Blockly.utils.style.getCascadedStyle = function(element, style) {
  return /** @type {string} */ (
      element.currentStyle ? element.currentStyle[style] : null);
};

/**
 * Returns a Coordinate object relative to the top-left of the HTML document.
 * Similar to Closure's goog.style.getPageOffset.
 * @param {Element} el Element to get the page offset for.
 * @return {!Blockly.utils.Coordinate} The page offset.
 */
Blockly.utils.style.getPageOffset = function(el) {
  var pos = new Blockly.utils.Coordinate(0, 0);
  var box = el.getBoundingClientRect();
  var documentElement = document.documentElement;
  // Must add the scroll coordinates in to get the absolute page offset
  // of element since getBoundingClientRect returns relative coordinates to
  // the viewport.
  var scrollCoord = new Blockly.utils.Coordinate(
      window.pageXOffset || documentElement.scrollLeft,
      window.pageYOffset || documentElement.scrollTop);
  pos.x = box.left + scrollCoord.x;
  pos.y = box.top + scrollCoord.y;

  return pos;
};

/**
 * Calculates the viewport coordinates relative to the document.
 * Similar to Closure's goog.style.getViewportPageOffset.
 * @return {!Blockly.utils.Coordinate} The page offset of the viewport.
 */
Blockly.utils.style.getViewportPageOffset = function() {
  var body = document.body;
  var documentElement = document.documentElement;
  var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
  var scrollTop = body.scrollTop || documentElement.scrollTop;
  return new Blockly.utils.Coordinate(scrollLeft, scrollTop);
};
<commit_msg>Add extra style methods used by closure components<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2019 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Utilities for element styles.
 * These methods are not specific to Blockly, and could be factored out into
 * a JavaScript framework such as Closure.
 * @author samelh@google.com (Sam El-Husseini)
 */
'use strict';

/**
 * @name Blockly.utils.style
 * @namespace
 */
goog.provide('Blockly.utils.style');

goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.Size');

/**
 * Gets the height and width of an element.
 * Similar to Closure's goog.style.getSize
 * @param {Element} element Element to get size of.
 * @return {!Blockly.utils.Size} Object with width/height properties.
 */
Blockly.utils.style.getSize = function(element) {
  if (Blockly.utils.style.getStyle_(element, 'display') != 'none') {
    return Blockly.utils.style.getSizeWithDisplay_(element);
  }

  // Evaluate size with a temporary element.
  var style = element.style;
  var originalDisplay = style.display;
  var originalVisibility = style.visibility;
  var originalPosition = style.position;

  style.visibility = 'hidden';
  style.position = 'absolute';
  style.display = 'inline';

  var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth;
  var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight;

  style.display = originalDisplay;
  style.position = originalPosition;
  style.visibility = originalVisibility;

  return new Blockly.utils.Size(offsetWidth, offsetHeight);
};

/**
 * Gets the height and width of an element when the display is not none.
 * @param {Element} element Element to get size of.
 * @return {!goog.math.Size} Object with width/height properties.
 * @private
 */
Blockly.utils.style.getSizeWithDisplay_ = function(element) {
  var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth;
  var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight;
  return new Blockly.utils.Size(offsetWidth, offsetHeight);
};

/**
 * Cross-browser pseudo get computed style. It returns the computed style where
 * available. If not available it tries the cascaded style value (IE
 * currentStyle) and in worst case the inline style value.  It shouldn't be
 * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
 * discussion.
 *
 * Copied from Closure's goog.style.getStyle_
 *
 * @param {Element} element Element to get style of.
 * @param {string} style Property to get (must be camelCase, not css-style.).
 * @return {string} Style value.
 * @private
 */
Blockly.utils.style.getStyle_ = function(element, style) {
  return Blockly.utils.style.getComputedStyle(element, style) ||
      Blockly.utils.style.getCascadedStyle(element, style) ||
      (element.style && element.style[style]);
};

/**
 * Retrieves a computed style value of a node. It returns empty string if the
 * value cannot be computed (which will be the case in Internet Explorer) or
 * "none" if the property requested is an SVG one and it has not been
 * explicitly set (firefox and webkit).
 *
 * Copied from Closure's goog.style.getComputedStyle
 *
 * @param {Element} element Element to get style of.
 * @param {string} property Property to get (camel-case).
 * @return {string} Style value.
 */
Blockly.utils.style.getComputedStyle = function(element, property) {
  if (document.defaultView && document.defaultView.getComputedStyle) {
    var styles = document.defaultView.getComputedStyle(element, null);
    if (styles) {
      // element.style[..] is undefined for browser specific styles
      // as 'filter'.
      return styles[property] || styles.getPropertyValue(property) || '';
    }
  }
  return '';
};

/**
 * Gets the cascaded style value of a node, or null if the value cannot be
 * computed (only Internet Explorer can do this).
 *
 * Copied from Closure's goog.style.getCascadedStyle
 *
 * @param {Element} element Element to get style of.
 * @param {string} style Property to get (camel-case).
 * @return {string} Style value.
 */
Blockly.utils.style.getCascadedStyle = function(element, style) {
  return /** @type {string} */ (
      element.currentStyle ? element.currentStyle[style] : null);
};

/**
 * Returns a Coordinate object relative to the top-left of the HTML document.
 * Similar to Closure's goog.style.getPageOffset
 * @param {Element} el Element to get the page offset for.
 * @return {!Blockly.utils.Coordinate} The page offset.
 */
Blockly.utils.style.getPageOffset = function(el) {
  var pos = new Blockly.utils.Coordinate(0, 0);
  var box = el.getBoundingClientRect();
  var documentElement = document.documentElement;
  // Must add the scroll coordinates in to get the absolute page offset
  // of element since getBoundingClientRect returns relative coordinates to
  // the viewport.
  var scrollCoord = new Blockly.utils.Coordinate(
      window.pageXOffset || documentElement.scrollLeft,
      window.pageYOffset || documentElement.scrollTop);
  pos.x = box.left + scrollCoord.x;
  pos.y = box.top + scrollCoord.y;

  return pos;
};

/**
 * Calculates the viewport coordinates relative to the document.
 * Similar to Closure's goog.style.getViewportPageOffset
 * @return {!Blockly.utils.Coordinate} The page offset of the viewport.
 */
Blockly.utils.style.getViewportPageOffset = function() {
  var body = document.body;
  var documentElement = document.documentElement;
  var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
  var scrollTop = body.scrollTop || documentElement.scrollTop;
  return new Blockly.utils.Coordinate(scrollLeft, scrollTop);
};

/**
 * Shows or hides an element from the page. Hiding the element is done by
 * setting the display property to "none", removing the element from the
 * rendering hierarchy so it takes up no space. To show the element, the default
 * inherited display property is restored (defined either in stylesheets or by
 * the browser's default style rules).
 * Copied from Closure's goog.style.getViewportPageOffset
 *
 * @param {Element} el Element to show or hide.
 * @param {*} isShown True to render the element in its default style,
 *     false to disable rendering the element.
 */
Blockly.utils.style.setElementShown = function(el, isShown) {
  el.style.display = isShown ? '' : 'none';
};

/**
 * Returns true if the element is using right to left (rtl) direction.
 * Copied from Closure's goog.style.isRightToLeft
 *
 * @param {Element} el  The element to test.
 * @return {boolean} True for right to left, false for left to right.
 */
Blockly.utils.style.isRightToLeft = function(el) {
  return 'rtl' == Blockly.utils.style.getStyle_(el, 'direction');
};

/**
 * Gets the computed border widths (on all sides) in pixels
 * Copied from Closure's goog.style.getBorderBox
 * @param {Element} element  The element to get the border widths for.
 * @return {!Object} The computed border widths.
 */
Blockly.utils.style.getBorderBox = function(element) {
  var left = Blockly.utils.style.getComputedStyle(element, 'borderLeftWidth');
  var right = Blockly.utils.style.getComputedStyle(element, 'borderRightWidth');
  var top = Blockly.utils.style.getComputedStyle(element, 'borderTopWidth');
  var bottom = Blockly.utils.style.getComputedStyle(element, 'borderBottomWidth');

  return {
    top: parseFloat(top),
    right: parseFloat(right),
    bottom: parseFloat(bottom),
    left: parseFloat(left)
  };
};

/**
 * Changes the scroll position of `container` with the minimum amount so
 * that the content and the borders of the given `element` become visible.
 * If the element is bigger than the container, its top left corner will be
 * aligned as close to the container's top left corner as possible.
 * Copied from Closure's goog.style.scrollIntoContainerView
 *
 * @param {Element} element The element to make visible.
 * @param {Element} container The container to scroll. If not set, then the
 *     document scroll element will be used.
 * @param {boolean=} opt_center Whether to center the element in the container.
 *     Defaults to false.
 */
Blockly.utils.style.scrollIntoContainerView = function(
    element, container, opt_center) {
  var offset =
      Blockly.utils.style.getContainerOffsetToScrollInto(element,
          container, opt_center);
  container.scrollLeft = offset.x;
  container.scrollTop = offset.y;
};

/**
 * Calculate the scroll position of `container` with the minimum amount so
 * that the content and the borders of the given `element` become visible.
 * If the element is bigger than the container, its top left corner will be
 * aligned as close to the container's top left corner as possible.
 * Copied from Closure's goog.style.getContainerOffsetToScrollInto
 *
 * @param {Element} element The element to make visible.
 * @param {Element} container The container to scroll. If not set, then the
 *     document scroll element will be used.
 * @param {boolean=} opt_center Whether to center the element in the container.
 *     Defaults to false.
 * @return {!Blockly.utils.Coordinate} The new scroll position of the container,
 *     in form of goog.math.Coordinate(scrollLeft, scrollTop).
 */
Blockly.utils.style.getContainerOffsetToScrollInto = function(
    element, container, opt_center) {
  // Absolute position of the element's border's top left corner.
  var elementPos = Blockly.utils.style.getPageOffset(element);
  // Absolute position of the container's border's top left corner.
  var containerPos = Blockly.utils.style.getPageOffset(container);
  var containerBorder = Blockly.utils.style.getBorderBox(container);
  // Relative pos. of the element's border box to the container's content box.
  var relX = elementPos.x - containerPos.x - containerBorder.left;
  var relY = elementPos.y - containerPos.y - containerBorder.top;
  // How much the element can move in the container, i.e. the difference between
  // the element's bottom-right-most and top-left-most position where it's
  // fully visible.
  var elementSize = Blockly.utils.style.getSizeWithDisplay_(element);
  var spaceX = container.clientWidth - elementSize.width;
  var spaceY = container.clientHeight - elementSize.height;
  var scrollLeft = container.scrollLeft;
  var scrollTop = container.scrollTop;
  if (opt_center) {
    // All browsers round non-integer scroll positions down.
    scrollLeft += relX - spaceX / 2;
    scrollTop += relY - spaceY / 2;
  } else {
    // This formula was designed to give the correct scroll values in the
    // following cases:
    // - element is higher than container (spaceY < 0) => scroll down by relY
    // - element is not higher that container (spaceY >= 0):
    //   - it is above container (relY < 0) => scroll up by abs(relY)
    //   - it is below container (relY > spaceY) => scroll down by relY - spaceY
    //   - it is in the container => don't scroll
    scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
    scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
  }
  return new Blockly.utils.Coordinate(scrollLeft, scrollTop);
};
<|endoftext|>


<commit_before>// Tally Votes in JavaScript Pairing Challenge.

// I worked on this challenge with: Yi Lu
// This challenge took me [#] hours.

// These are the votes cast by each student. Do not alter these objects here.
var votes = {
  "Alex": { president: "Bob", vicePresident: "Devin", secretary: "Gail", treasurer: "Kerry" },
  "Bob": { president: "Mary", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Cindy": { president: "Cindy", vicePresident: "Hermann", secretary: "Bob", treasurer: "Bob" },
  "Devin": { president: "Louise", vicePresident: "John", secretary: "Bob", treasurer: "Fred" },
  "Ernest": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Fred": { president: "Louise", vicePresident: "Alex", secretary: "Ivy", treasurer: "Ivy" },
  "Gail": { president: "Fred", vicePresident: "Alex", secretary: "Ivy", treasurer: "Bob" },
  "Hermann": { president: "Ivy", vicePresident: "Kerry", secretary: "Fred", treasurer: "Ivy" },
  "Ivy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Gail" },
  "John": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Kerry" },
  "Kerry": { president: "Fred", vicePresident: "Mary", secretary: "Fred", treasurer: "Ivy" },
  "Louise": { president: "Nate", vicePresident: "Alex", secretary: "Mary", treasurer: "Ivy" },
  "Mary": { president: "Louise", vicePresident: "Oscar", secretary: "Nate", treasurer: "Ivy" },
  "Nate": { president: "Oscar", vicePresident: "Hermann", secretary: "Fred", treasurer: "Tracy" },
  "Oscar": { president: "Paulina", vicePresident: "Nate", secretary: "Fred", treasurer: "Ivy" },
  "Paulina": { president: "Louise", vicePresident: "Bob", secretary: "Devin", treasurer: "Ivy" },
  "Quintin": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Bob" },
  "Romanda": { president: "Louise", vicePresident: "Steve", secretary: "Fred", treasurer: "Ivy" },
  "Steve": { president: "Tracy", vicePresident: "Kerry", secretary: "Oscar", treasurer: "Xavier" },
  "Tracy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Ullyses": { president: "Louise", vicePresident: "Hermann", secretary: "Ivy", treasurer: "Bob" },
  "Valorie": { president: "Wesley", vicePresident: "Bob", secretary: "Alex", treasurer: "Ivy" },
  "Wesley": { president: "Bob", vicePresident: "Yvonne", secretary: "Valorie", treasurer: "Ivy" },
  "Xavier": { president: "Steve", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Yvonne": { president: "Bob", vicePresident: "Zane", secretary: "Fred", treasurer: "Hermann" },
  "Zane": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Mary" }
}

// Tally the votes in voteCount.
var voteCount = {
  president: {},
  vicePresident: {},
  secretary: {},
  treasurer: {}
}

/* The name of each student receiving a vote for an office should become a property
of the respective office in voteCount.  After Alex's votes have been tallied,
voteCount would be ...
  var voteCount = {
    president: { Bob: 1 },
    vicePresident: { Devin: 1 },
    secretary: { Gail: 1 },
    treasurer: { Kerry: 1 }
  }
*/

/* Once the votes have been tallied, assign each officer position the name of the
student who received the most votes. */
var officers = {
  president: undefined,
  vicePresident: undefined,
  secretary: undefined,
  treasurer: undefined
}

// Pseudocode

// __________________________________________
// Initial Solution

// __________________________________________
// Refactored Solution

// __________________________________________
// Reflection

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (voteCount.president["Bob"] === 3),
  "Bob should receive three votes for President.",
  "1. "
)

assert(
  (voteCount.vicePresident["Bob"] === 2),
  "Bob should receive two votes for Vice President.",
  "2. "
)

assert(
  (voteCount.secretary["Bob"] === 2),
  "Bob should receive two votes for Secretary.",
  "3. "
)

assert(
  (voteCount.treasurer["Bob"] === 4),
  "Bob should receive four votes for Treasurer.",
  "4. "
)

assert(
  (officers.president === "Louise"),
  "Louise should be elected President.",
  "5. "
)

assert(
  (officers.vicePresident === "Hermann"),
  "Hermann should be elected Vice President.",
  "6. "
)

assert(
  (officers.secretary === "Fred"),
  "Fred should be elected Secretary.",
  "7. "
)

assert(
  (officers.treasurer === "Ivy"),
  "Ivy should be elected Treasurer.",
  "8. "
)<commit_msg>add and finish talley_votes<commit_after>// Tally Votes in JavaScript Pairing Challenge.

// I worked on this challenge with: Yi Lu
// This challenge took me [1] hours.

// These are the votes cast by each student. Do not alter these objects here.
var votes = {
  "Alex": { president: "Bob", vicePresident: "Devin", secretary: "Gail", treasurer: "Kerry" },
  "Bob": { president: "Mary", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Cindy": { president: "Cindy", vicePresident: "Hermann", secretary: "Bob", treasurer: "Bob" },
  "Devin": { president: "Louise", vicePresident: "John", secretary: "Bob", treasurer: "Fred" },
  "Ernest": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Fred": { president: "Louise", vicePresident: "Alex", secretary: "Ivy", treasurer: "Ivy" },
  "Gail": { president: "Fred", vicePresident: "Alex", secretary: "Ivy", treasurer: "Bob" },
  "Hermann": { president: "Ivy", vicePresident: "Kerry", secretary: "Fred", treasurer: "Ivy" },
  "Ivy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Gail" },
  "John": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Kerry" },
  "Kerry": { president: "Fred", vicePresident: "Mary", secretary: "Fred", treasurer: "Ivy" },
  "Louise": { president: "Nate", vicePresident: "Alex", secretary: "Mary", treasurer: "Ivy" },
  "Mary": { president: "Louise", vicePresident: "Oscar", secretary: "Nate", treasurer: "Ivy" },
  "Nate": { president: "Oscar", vicePresident: "Hermann", secretary: "Fred", treasurer: "Tracy" },
  "Oscar": { president: "Paulina", vicePresident: "Nate", secretary: "Fred", treasurer: "Ivy" },
  "Paulina": { president: "Louise", vicePresident: "Bob", secretary: "Devin", treasurer: "Ivy" },
  "Quintin": { president: "Fred", vicePresident: "Hermann", secretary: "Fred", treasurer: "Bob" },
  "Romanda": { president: "Louise", vicePresident: "Steve", secretary: "Fred", treasurer: "Ivy" },
  "Steve": { president: "Tracy", vicePresident: "Kerry", secretary: "Oscar", treasurer: "Xavier" },
  "Tracy": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Ullyses": { president: "Louise", vicePresident: "Hermann", secretary: "Ivy", treasurer: "Bob" },
  "Valorie": { president: "Wesley", vicePresident: "Bob", secretary: "Alex", treasurer: "Ivy" },
  "Wesley": { president: "Bob", vicePresident: "Yvonne", secretary: "Valorie", treasurer: "Ivy" },
  "Xavier": { president: "Steve", vicePresident: "Hermann", secretary: "Fred", treasurer: "Ivy" },
  "Yvonne": { president: "Bob", vicePresident: "Zane", secretary: "Fred", treasurer: "Hermann" },
  "Zane": { president: "Louise", vicePresident: "Hermann", secretary: "Fred", treasurer: "Mary" }
}

// Tally the votes in voteCount.
var voteCount = {
  president: {},
  vicePresident: {},
  secretary: {},
  treasurer: {}
}

/* The name of each student receiving a vote for an office should become a property
of the respective office in voteCount.  After Alex's votes have been tallied,
voteCount would be ...
  var voteCount = {
    president: { Bob: 1 },
    vicePresident: { Devin: 1 },
    secretary: { Gail: 1 },
    treasurer: { Kerry: 1 }
  }
*/

/* Once the votes have been tallied, assign each officer position the name of the
student who received the most votes. */
var officers = {
  president: undefined,
  vicePresident: undefined,
  secretary: undefined,
  treasurer: undefined
}

// Pseudocode
/*
1 iterate through votes, record votes into voteCount

  a. 1st to iterate through voters: e.g. alex, bob, ...
  b. 2nd to iterate through voters' final votes

  c. add the vote to voteCount
  d. IF the nominee does not exist in voteCount, then set the value for nominee to be 1
    OTHERWISE, increase the value by 1

2 onces recorded to voteCount, assign winner into officers
  a. iterate through voteCount and find the biggest value for each position and return the nominee's name/position

*/
// __________________________________________
// Initial Solution

// function check_nominee (position, nominee) {
//   if (voteCount[position].hasOwnProperty(nominee)){
//     voteCount[position][nominee] += 1;}
//   else {
//     voteCount[position][nominee] = 1;
//   }
// };

// for (var student in votes) {
//   for (var position in votes[student]) {
//     check_nominee (position, votes[student][position]);
//   }
// };

// function max (tally) {
//   var max_vote = null;
//   var winner = null;
//   for (var name in tally) {
//     if (max_vote === null) {
//       max_vote = tally[name];
//       winner = name;
//     }
//     else if (tally[name] > max_vote) {
//              max_vote = tally[name];
//              winner = name;
//     }
//   }
//   return winner;
// };

// for (var nominee in officers) {
//   officers[nominee] = max(voteCount[nominee]);
// };

//http://www.w3schools.com/jsref/jsref_max.asp

// __________________________________________
// Refactored Solution

// checks if nominee exists in voteCount
//  if it does add one, otherwise put it in there
function check_nominee (position, nominee) {
  if (voteCount[position].hasOwnProperty(nominee)){
    voteCount[position][nominee] += 1;}
  else {
    voteCount[position][nominee] = 1;
  }
};
// loop through voters and run check_nominee on their votes
for (var student in votes) {
  for (var position in votes[student]) {
    check_nominee (position, votes[student][position]);
  }
};

// loop through positions officers list
// assign positions to list of nominees sorted by vote count
for (var position in officers) {
  officers[position] = Object.keys(voteCount[position]).sort(function(a,b){
    return voteCount[position][b]-voteCount[position][a]
  })[0];
};

// __________________________________________
// Reflection

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (voteCount.president["Bob"] === 3),
  "Bob should receive three votes for President.",
  "1. "
)

assert(
  (voteCount.vicePresident["Bob"] === 2),
  "Bob should receive two votes for Vice President.",
  "2. "
)

assert(
  (voteCount.secretary["Bob"] === 2),
  "Bob should receive two votes for Secretary.",
  "3. "
)

assert(
  (voteCount.treasurer["Bob"] === 4),
  "Bob should receive four votes for Treasurer.",
  "4. "
)

assert(
  (officers.president === "Louise"),
  "Louise should be elected President.",
  "5. "
)

assert(
  (officers.vicePresident === "Hermann"),
  "Hermann should be elected Vice President.",
  "6. "
)

assert(
  (officers.secretary === "Fred"),
  "Fred should be elected Secretary.",
  "7. "
)

assert(
  (officers.treasurer === "Ivy"),
  "Ivy should be elected Treasurer.",
  "8. "
)<|endoftext|>