<commit_before>import * as d3 from "d3";
import { _interpolateGreys } from "d3-scale-chromatic";

// A fake scale that uses only the first digits of a text to compute the color.
// Creates a list of all the possible first digits and uses a sequential scale to color based on such index
export function scaleText(digits = 1) {
  const DEBUG = false;

  const interpolateGreys =
    "interpolateGreys" in d3 ? d3.interpolateGreys : _interpolateGreys; // Hack to keep it working with d3.v4
  let scale = d3.scaleSequential(interpolateGreys).domain([32, 90]), // initialize with ascii
    dRepresentativesCounts = d3.map(), // Contains the counts for each letter/substrg
    dRepresentativesIndexes = d3.map();

  // Computes the actual value, based on the index of the first digits in the domain
  function compute(d) {
    let ci = dRepresentativesIndexes.get(
      d.slice(0, digits)
    );
    if (ci === undefined) {
      console.log(
        `scaleText Couldn't find index for ${d.slice(0, digits)} did you call domain? Using ascii of first letter`
      );
      ci = d
        .slice(0, digits)
        .charCodeAt(0);
    }
    return scale(ci) || "white";
  }

  function computeRepresentatives(data, doIndex = true) {
    dRepresentativesCounts = d3.map();
    for (let v of data) {
      //Initialize
      if (!dRepresentativesCounts.has(v)) dRepresentativesCounts.set(v, 0);

      //count+=1
      dRepresentativesCounts.set(v, dRepresentativesCounts.get(v) + 1);
    }
    if (DEBUG)
      console.log(
        `scaleText Compute representatives found ${dRepresentativesCounts.keys()} categories `
      );

    const ret = {
      counts: dRepresentativesCounts
    };

    if (doIndex) {
      // Compute the indexes of each representative
      dRepresentativesIndexes = d3.map();
      let i = 0;
      for (let r of dRepresentativesCounts.keys().sort()) {
        dRepresentativesIndexes.set(r, i++);
      }
      ret.indexes = dRepresentativesIndexes;
    }

    return ret;
  }

  compute.digits = function(_) {
    return arguments.length ? ((digits = _), compute) : digits;
  };

  compute.scale = function(_) {
    return arguments.length ? ((scale = _), compute) : scale;
  };

  compute.domain = function(data) {
    if (DEBUG) console.log(`scaleText domain data.lengt=${data.length}`);
    if (arguments.length) {
      // Compute representatives for letters/substrings
      computeRepresentatives(
        data
          .filter(d => d !== undefined && d !== null)
          .map(d => d.slice(0, digits))
      );
      scale.domain([0, dRepresentativesCounts.keys().length]);
      return compute;
    } else {
      return scale.domain();
    }
  };

  compute.computeRepresentatives = computeRepresentatives;
  compute.__type = "text";

  return compute;
}<commit_msg>Bugfix colorinterpolator on observable<commit_after>import * as d3 from "d3";
import { interpolateGreys  } from "d3-scale-chromatic";

// A fake scale that uses only the first digits of a text to compute the color.
// Creates a list of all the possible first digits and uses a sequential scale to color based on such index
export function scaleText(digits = 1) {
  const DEBUG = false;

  const defaultColorInterpolator = "interpolateGreys" in d3 ? d3.interpolateGreys : interpolateGreys; // Hack to keep it working with d3.v4
  let scale = d3.scaleSequential(defaultColorInterpolator).domain([32, 90]), // initialize with ascii
    dRepresentativesCounts = d3.map(), // Contains the counts for each letter/substrg
    dRepresentativesIndexes = d3.map();

  // Computes the actual value, based on the index of the first digits in the domain
  function compute(d) {
    let ci = dRepresentativesIndexes.get(
      d.slice(0, digits)
    );
    if (ci === undefined) {
      console.log(
        `scaleText Couldn't find index for ${d.slice(0, digits)} did you call domain? Using ascii of first letter`
      );
      ci = d
        .slice(0, digits)
        .charCodeAt(0);
    }
    return scale(ci) || "white";
  }

  function computeRepresentatives(data, doIndex = true) {
    dRepresentativesCounts = d3.map();
    for (let v of data) {
      //Initialize
      if (!dRepresentativesCounts.has(v)) dRepresentativesCounts.set(v, 0);

      //count+=1
      dRepresentativesCounts.set(v, dRepresentativesCounts.get(v) + 1);
    }
    if (DEBUG)
      console.log(
        `scaleText Compute representatives found ${dRepresentativesCounts.keys()} categories `
      );

    const ret = {
      counts: dRepresentativesCounts
    };

    if (doIndex) {
      // Compute the indexes of each representative
      dRepresentativesIndexes = d3.map();
      let i = 0;
      for (let r of dRepresentativesCounts.keys().sort()) {
        dRepresentativesIndexes.set(r, i++);
      }
      ret.indexes = dRepresentativesIndexes;
    }

    return ret;
  }

  compute.digits = function(_) {
    return arguments.length ? ((digits = _), compute) : digits;
  };

  compute.scale = function(_) {
    return arguments.length ? ((scale = _), compute) : scale;
  };

  compute.domain = function(data) {
    if (DEBUG) console.log(`scaleText domain data.lengt=${data.length}`);
    if (arguments.length) {
      // Compute representatives for letters/substrings
      computeRepresentatives(
        data
          .filter(d => d !== undefined && d !== null)
          .map(d => d.slice(0, digits))
      );
      scale.domain([0, dRepresentativesCounts.keys().length]);
      return compute;
    } else {
      return scale.domain();
    }
  };

  compute.computeRepresentatives = computeRepresentatives;
  compute.__type = "text";

  return compute;
}<|endoftext|>


<commit_before>function initScenes(Q) {
  Q.scene("mainMenu", function(stage) {
    stage.insert(new Q.UI.Button(
    {
      asset: "play_button.png",
      x: Q.width / 2 - 55,
      y: 500
    },
    function() {
      Q.stageScene('game');
    }));

    stage.insert(new Q.HoverSprite({
      asset: "title.png",
      cx: 0,
      cy: 0,
      x: Q.width / 2 - 210,
      y: 16,
    }));
  });

  Q.scene('endGame',function(stage) {
    var box = stage.insert(new Q.UI.Container({
      x: Q.width/2, y: Q.height/2, fill: "rgba(0,0,0,0.25)"
    }));

    var button = box.insert(new Q.UI.Button({ x: 0, y: 0, fill: "#CCCCCC", label: "Main Menu" }))
    var label = box.insert(new Q.UI.Text({x:10, y: -10 - button.p.h, label: stage.options.label }));
    button.on("click",function() {
      Q.clearStages();
      Q.stageScene('mainMenu');
    });
    box.fit(20);
  });

  Q.scene("game",function(stage) {
    Q.stageTMX('stage1.tmx', stage);

    var player = stage.detect(function() { return this.p.team === 'players' });
    stage.add('viewport').follow(player);
  });
}<commit_msg>Change "Main Menu" button to an "Agree" button<commit_after>function initScenes(Q) {
  Q.scene("mainMenu", function(stage) {
    stage.insert(new Q.UI.Button(
    {
      asset: "play_button.png",
      x: Q.width / 2 - 55,
      y: 500
    },
    function() {
      Q.stageScene('game');
    }));

    stage.insert(new Q.HoverSprite({
      asset: "title.png",
      cx: 0,
      cy: 0,
      x: Q.width / 2 - 210,
      y: 16,
    }));
  });

  Q.scene('endGame',function(stage) {
    var box = stage.insert(new Q.UI.Container({
      x: Q.width/2, y: Q.height/2, fill: "rgba(0,0,0,0.25)"
    }));

    var button = box.insert(new Q.UI.Button({ x: 0, y: 0, fill: "#CCCCCC", label: "Agree" }))
    var label = box.insert(new Q.UI.Text({x:10, y: -10 - button.p.h, label: stage.options.label }));
    button.on("click",function() {
      Q.clearStages();
      Q.stageScene('mainMenu');
    });
    box.fit(20);
  });

  Q.scene("game",function(stage) {
    Q.stageTMX('stage1.tmx', stage);

    var player = stage.detect(function() { return this.p.team === 'players' });
    stage.add('viewport').follow(player);
  });
}
<|endoftext|>


<commit_before>import React from 'react';
import {Tabs, Tab} from 'material-ui/Tabs';
import SwipeableViews from 'react-swipeable-views';
import ReferralDashboard from './ReferralDashboard'
import Profile from './Profile'

const styles = {
  headline: {
    fontSize: 24,
    paddingTop: 16,
    marginBottom: 12,
    fontWeight: 400,
  },
  slide: {
    padding: 10,
  },
};

export default class PageTab extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      slideIndex: 0,
    };
  }

  handleChange = (value) => {
    this.setState({
      slideIndex: value,
    });
  };

  render() {
    return (

        <Tabs
          onChange={this.handleChange}
          value={this.state.slideIndex}
        >
          <Tab label="Referral" value={0} />
          <Tab label="My Account" value={1} />
        </Tabs>
        <SwipeableViews
          index={this.state.slideIndex}
          onChangeIndex={this.handleChange}
        >

          <ReferralDashboard
            referrals={this.props.referrals}
            _addReferral={this.props._addReferral}
            _updateReferral={this.props._updateReferral}

          />

          <Profile />

        </SwipeableViews>

    );
  }
}
<commit_msg>removed styles from pagetab.js<commit_after>import React from 'react';
import {Tabs, Tab} from 'material-ui/Tabs';
import SwipeableViews from 'react-swipeable-views';
import ReferralDashboard from './ReferralDashboard'
import Profile from './Profile'

export default class PageTab extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      slideIndex: 0,
    };
  }

  handleChange = (value) => {
    this.setState({
      slideIndex: value,
    });
  };

  render() {
    return (

        <Tabs
          onChange={this.handleChange}
          value={this.state.slideIndex}
        >
          <Tab label="Referral" value={0} />
          <Tab label="My Account" value={1} />
        </Tabs>
        <SwipeableViews
          index={this.state.slideIndex}
          onChangeIndex={this.handleChange}
        >

          <ReferralDashboard
            referrals={this.props.referrals}
            _addReferral={this.props._addReferral}
            _updateReferral={this.props._updateReferral}

          />

          <Profile />

        </SwipeableViews>

    );
  }
}
<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);
const sem = require('../sensor/SensorEventManager.js').getInstance();
const Sensor = require('./Sensor.js').Sensor;
const HostTool = require('../net2/HostTool.js');
const hostTool = new HostTool();

class DeviceOfflineSensor extends Sensor {
  constructor() {
    super();
  }

  run() {
    this.interval = 60; // interval default to 60 seconds
    if (this.config && this.config.interval) {
      this.interval = this.config.interval;
    }
    this.idle = 1800; // idle time default to 30 minutes
    if (this.config && this.config.idle) {
      this.idle = this.config.idle;
    }
    setInterval(() => {
      this.checkDeviceActivity();
    }, this.interval * 1000); // every minute
  }

  async checkDeviceActivity() {

    log.debug("Start to check device activity...");
    const hostEntries = await hostTool.getAllMACEntries();
    hostEntries.forEach((host) => {
      if (host) {
        const lastActiveTimestamp = Number(host.lastActiveTimestamp);
        const now = new Date() / 1000;
        const idleTime = now - lastActiveTimestamp;
        if (idleTime > this.idle && idleTime < this.idle + 2 * this.interval) {
          // ensure that device offline message will be emitted at most twice
          log.info(`Device ${host.mac} is offline, last seen at ${lastActiveTimestamp}.`);
          try {
            if (host.ipv6Addr && host.ipv6Addr.length > 0) {
              host.ipv6Addr = JSON.parse(host.ipv6Addr);
            }
          } catch (err) {}
          sem.emitEvent({
            type: "DeviceOffline",
            message: "A deivce was offline",
            host: host,
            lastSeen: lastActiveTimestamp
          });
        }
      }
    });
  }
}

module.exports = DeviceOfflineSensor;
<commit_msg>customize the threshold to trigger device offline alarm<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);
const sem = require('../sensor/SensorEventManager.js').getInstance();
const Sensor = require('./Sensor.js').Sensor;
const HostTool = require('../net2/HostTool.js');
const hostTool = new HostTool();

class DeviceOfflineSensor extends Sensor {
  constructor() {
    super();
  }

  run() {
    this.interval = 60; // interval default to 60 seconds
    if (this.config && this.config.interval) {
      this.interval = this.config.interval;
    }
    this.idle = 1800; // idle time default to 30 minutes
    if (this.config && this.config.idle) {
      this.idle = this.config.idle;
    }
    setInterval(() => {
      this.checkDeviceActivity();
    }, this.interval * 1000); // every minute
  }

  async checkDeviceActivity() {

    log.debug("Start to check device activity...");
    const hostEntries = await hostTool.getAllMACEntries();
    hostEntries.forEach(async (host) => {
      if (host) {
        let customizedOfflineIdle;
        const policy = await hostTool.loadDevicePolicyByMAC(host.mac);
        if (policy && policy["customizedOfflineIdle"]) {
          customizedOfflineIdle = Number(policy["customizedOfflineIdle"]);
        } else {
          customizedOfflineIdle = this.idle;
        }
        const lastActiveTimestamp = Number(host.lastActiveTimestamp);
        const now = new Date() / 1000;
        const idleTime = now - lastActiveTimestamp;
        if (idleTime > customizedOfflineIdle && idleTime < customizedOfflineIdle + 2 * this.interval) {
          // ensure that device offline message will be emitted at most twice
          log.info(`Device ${host.mac} is offline, last seen at ${lastActiveTimestamp}.`);
          try {
            if (host.ipv6Addr && host.ipv6Addr.length > 0) {
              host.ipv6Addr = JSON.parse(host.ipv6Addr);
            }
          } catch (err) {}
          sem.emitEvent({
            type: "DeviceOffline",
            message: "A deivce was offline",
            host: host,
            lastSeen: lastActiveTimestamp
          });
        }
      }
    });
  }
}

module.exports = DeviceOfflineSensor;
<|endoftext|>


<commit_before>exports.uploader = {
  name: 'uploader',
  description: 'uploader',
  middleware: [ 'auth' ],
  inputs: {
    file: { required: true }
  },
  run: function (api, data, next) {
    api.log('received', 'info', data.params.file)
    api.filestorage.push(data.params.file, {
      userId: data.session.userId
    }, function (err, id, stat) {
      if (err) return next(err)
      api.log('saved as #' + id, 'info', stat)
      data.response.data = { id: id }
      next()
    })
  }
}

exports.download = {
  name: 'downloader',
  description: 'downloader',
  middleware: [ 'auth' ],
  inputs: {
    id: { required: true }
  },
  run: function (api, data, next) {
    api.log('serving', 'info', data.params.id)
    api.filestorage.get(data.params.id, function (err, stream, stat) {
      if (err) {
        data.connection.rawConnection.responseHttpCode = 404
        return next(err)
      }

      if (stat.custom &&
        stat.custom.userId &&
        !data.session.user.isAdmin &&
        !data.session.user.isModerator &&
        stat.custom.userId !== data.session.userId) {
        data.connection.rawConnection.responseHttpCode = 403
        return next(new Error(api.config.errors.sessionNoPermission(data.connection)))
      }

      api.log('sending', 'info', stat)
      if (stat.name) {
        data.connection.rawConnection.responseHeaders.push([ 'Content-Disposition', `attachment; filename="${stat.name}"` ])
      }
      api.servers.servers.web.sendFile(data.connection, null, stream, stat.type, stat.length)
      data.toRender = false
      next()
    })
  }
}
<commit_msg>Remove content-length header when serving files - fixes broken images in chrome<commit_after>exports.uploader = {
  name: 'uploader',
  description: 'uploader',
  middleware: [ 'auth' ],
  inputs: {
    file: { required: true }
  },
  run: function (api, data, next) {
    api.log('received', 'info', data.params.file)
    api.filestorage.push(data.params.file, {
      userId: data.session.userId
    }, function (err, id, stat) {
      if (err) return next(err)
      api.log('saved as #' + id, 'info', stat)
      data.response.data = { id: id }
      next()
    })
  }
}

exports.download = {
  name: 'downloader',
  description: 'downloader',
  middleware: [ 'auth' ],
  inputs: {
    id: { required: true }
  },
  run: function (api, data, next) {
    api.log('serving', 'info', data.params.id)
    api.filestorage.get(data.params.id, function (err, stream, stat) {
      if (err) {
        data.connection.rawConnection.responseHttpCode = 404
        return next(err)
      }

      if (stat.custom &&
        stat.custom.userId &&
        !data.session.user.isAdmin &&
        !data.session.user.isModerator &&
        stat.custom.userId !== data.session.userId) {
        data.connection.rawConnection.responseHttpCode = 403
        return next(new Error(api.config.errors.sessionNoPermission(data.connection)))
      }

      api.log('sending', 'info', stat)
      if (stat.name) {
        data.connection.rawConnection.responseHeaders.push([ 'Content-Disposition', `attachment; filename="${stat.name}"` ])
      }
      api.servers.servers.web.sendFile(data.connection, null, stream, stat.type)
      data.toRender = false
      next()
    })
  }
}
<|endoftext|>


<commit_before>var FacebookStrategy = require('passport-facebook').Strategy;
var db = require('../db');
var localServices = require('../config/environment/thirdPartyServices');
// var client = require('./facebookAuthInfo.js');

module.exports = function(passport){

  //The Facebook strategy allows users to log in to a web
  //application using their Facebook account.
  passport.use(new FacebookStrategy({

  //clientID and clientSecret need to remain private
  //we need to add this information to heroku
  clientID: localServices.facebook.id,
  clientSecret: localServices.facebook.secret,
  callbackURL: client.facebookAuth.callbackURL

  }, function(token, refreshToken, profile, done){
    console.log('im in authController');

    //function looks to see if the user id is in the system
    User.findOne({'facebook.id' : profile.id}, function (err, user){
      if(err){
        console.error (err);
        return done (err);
      } else if (user) {
        //if it is in the system, we return the user information
        return done(null, user);
      } else {
        //if it is not in the system, we create user information
        //in the system
        db.User.create({
          username: profile.id,
          facebook_id: profile.name.givenName
        })
        //return user information when done
        .then(function(results){
          return done(null, results)
        //return error if user is not found
        .catch(function (error){
          next(new Error("couldn't add in user", error));
        });
      });
      }
   });
  }));
};
<commit_msg>Auth Controller can use passport<commit_after>var FacebookStrategy = require('passport-facebook').Strategy;
var db = require('../db');
var localServices = require('../config/environment/thirdPartyServices');
// var client = require('./facebookAuthInfo.js');

module.exports = function(passport){

  //The Facebook strategy allows users to log in to a web
  //application using their Facebook account.
  passport.use(new FacebookStrategy({

  //clientID and clientSecret need to remain private
  //we need to add this information to heroku
  clientID: localServices.facebook.id,
  clientSecret: localServices.facebook.secret,
  callbackURL: client.facebookAuth.callbackURL

   }, function(token, refreshToken, profile, done){
    console.log(profile.id);

    db.User.find({where: {'facebook_id' : profile.id}})
      .then(function(user){
        if(!user){
          console.log('creating new user');
          db.User.create({
            username: profile.name.givenName,
            facebook_id: profile.id
          })
          .then(function(newUser){
            console.log(newUser, '----------------------');
            return done(null, newUser);
          });
        } else {
          var currentUser = {
            username: user.username,
            table_id: user.id
          };
          return done(null, currentUser);
        }
      });
    }
  ));
};<|endoftext|>


<commit_before>/**
 * Server-side Backbone implementation for TileMill. This module should be
 * required instead of directly requiring Backbone.
 */
var _ = require('underscore')._,
    Backbone = require('../modules/backbone/backbone.js'),
    settings = require('settings'),
    rmrf = require('rm-rf'),
    fs = require('fs'),
    Step = require('step'),
    path = require('path');

/**
 * Override Backbone.sync() for the server-side context. Uses TileMill-specific
 * file storage methods for model CRUD operations.
 */
Backbone.sync = function(method, model, success, error) {
    switch (method) {
    case 'read':
        if (model.id) {
            load(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        } else {
            loadAll(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        }
        break;
    case 'create':
        create(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    case 'update':
        update(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    case 'delete':
        destroy(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    }
};

/**
 * Load a single model. Requires that model.id be populated.
 */
function load(model, callback) {
    var modelPath = path.join(settings.files, model.type);
    modelPath = (model.type == 'project') ? path.join(modelPath, model.id) : modelPath;
    var extension = (model.type == 'project') ? 'mml' : 'json';
    fs.readFile(path.join(modelPath, model.id + '.' + extension), 'utf-8',
    function(err, data) {
        if (err || !data) {
            return callback(new Error('Error reading model file.'));
        }
        var object = JSON.parse(data);
        // Set the object ID explicitly for multiple-load scenarios where
        // model parse()/set() is bypassed.
        object.id = model.id;
        if (model.type === 'project' && object.Stylesheet && object.Stylesheet.length > 0) {
            Step(
                function() {
                    var group = this.group();
                    _.each(object.Stylesheet, function(filename, index) {
                        fs.readFile(
                            path.join(modelPath, filename),
                            'utf-8',
                            group()
                        );
                    });
                },
                function(err, files) {
                    object.Stylesheet = _.reduce(
                        object.Stylesheet,
                        function(memo, filename, index) {
                            if (typeof files[index] !== 'undefined') {
                                memo.push({
                                    id: filename,
                                    data: files[index]
                                });
                            }
                            return memo;
                        },
                        []
                    );
                    return callback(null, object);
                }
            );
        }
        else {
            return callback(null, object);
        }
    });
};

/**
 * Load an array of all models.
 */
function loadAll(model, callback) {
    var basepath = path.join(settings.files, model.type);
    Step(
        function() {
            path.exists(basepath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basepath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(basepath, this);
        },
        function(err, files) {
            if (err) {
                return this(new Error('Error reading model directory.'));
            }
            else if (files.length === 0) {
                return this();
            }
            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                if (model.type === 'project') {
                    var id = files[i];
                } else {
                    var id = path.basename(files[i], '.json');
                }
                load({ id: id, type: model.type }, group());
            }
        },
        function(err, models) {
            // Ignore errors from loading individual models (e.g.
            // don't let one bad apple spoil the collection).
            models = _.select(models, function(model) {
                return (typeof model === 'object');
            });
            return callback(null, models);
        }
    );
};

/**
 * Create a new model.
 * @TODO assign a model id if not present.
 */
function create(model, callback) {
    if (model.type === 'project') {
        saveProject(model, callback);
    } else {
        save(model, callback);
    }
};

/**
 * Update an existing model.
 */
function update(model, callback) {
    if (model.type === 'project') {
        saveProject(model, callback);
    } else {
        save(model, callback);
    }
};

/**
 * Destroy (delete, remove, etc.) a model.
 * - Project: rm rf the project directory.
 * - Export: track and and kill the export file in addition to the model.
 * - All others: rm the model json file.
 */
function destroy(model, callback) {
    switch (model.type) {
    case 'project':
        var modelPath = path.join(settings.files, model.type, model.id);
        rmrf(modelPath, function() { return callback(null, model) });
        break;
    case 'export':
        var filepath;
        Step(
            function() {
                load(model, this);
            },
            function(err, data) {
                if (data && data.filename) {
                    filepath = path.join(settings.export_dir, data.filename);
                    path.exists(filepath, this);
                }
                else {
                    this(false);
                }
            },
            function(remove) {
                if (remove) {
                    fs.unlink(filepath, this);
                }
                else {
                    this();
                }
            },
            function() {
                var modelPath = path.join(settings.files, model.type, model.id + '.json');
                fs.unlink(modelPath, function() { return callback(null, model) });
            }
        );
        break;
    default:
        var modelPath = path.join(settings.files, model.type, model.id + '.json');
        fs.unlink(modelPath, function() { return callback(null, model) });
        break;
    }
};

/**
 * Save a Project. Called by create/update.
 *
 * Special case for projects creates a subdirectory per project and does
 * additional splitting out of subproperties (stylesheets) into separate files.
 */
function saveProject(model, callback) {
    var basePath = path.join(settings.files, model.type);
    var modelPath = path.join(settings.files, model.type, model.id);
    Step(
        function() {
            path.exists(basePath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basePath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            path.exists(modelPath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(modelPath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(modelPath, this);
        },
        function(err, files) {
            // Remove any stale files in the project directory.
            var group = this.group();
            var stylesheets = model.get('Stylesheet') || [];
            var stale = _.select(files, function(filename) {
                if (filename === (model.id + '.mml')) {
                    return false;
                } else if (_.pluck(stylesheets, 'id').indexOf(filename) !== -1) {
                    return false;
                }
                return true;
            });
            if (stale.length) {
                for (var i = 0; i < stale.length; i++) {
                    fs.unlink(path.join(modelPath, stale[i]), group());
                }
            }
            else {
                group()();
            }
        },
        function() {
            // Hard clone the model JSON before doing adjustments to the data
            // based on writing separate stylesheets.
            var data = JSON.parse(JSON.stringify(model.toJSON()));
            var files = [];
            if (data.id) {
                delete data.id;
            }
            if (data.Stylesheet) {
                _.each(data.Stylesheet, function(stylesheet, key) {
                    if (stylesheet.id) {
                        files.push({
                            filename: stylesheet.id,
                            data: stylesheet.data
                        });
                        data.Stylesheet[key] = stylesheet.id;
                    }
                });
            }
            files.push({
                filename: model.id + '.mml',
                data: JSON.stringify(data)
            });

            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                fs.writeFile(
                    path.join(modelPath, files[i].filename),
                    files[i].data,
                    group()
                );
            }
        },
        function() {
            callback(null, model);
        }
    );
}

/**
 * Save a model. Called by create/update.
 */
function save(model, callback) {
    var basePath = path.join(settings.files, model.type);
    Step(
        function() {
            path.exists(basePath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basePath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.writeFile(
                path.join(basePath, model.id + '.json'),
                JSON.stringify(model.toJSON()),
                this
            );
        },
        function() {
            callback(null, model);
        }
    );
}

module.exports = Backbone;

<commit_msg>Fix payload for errors.<commit_after>/**
 * Server-side Backbone implementation for TileMill. This module should be
 * required instead of directly requiring Backbone.
 */
var _ = require('underscore')._,
    Backbone = require('../modules/backbone/backbone.js'),
    settings = require('settings'),
    rmrf = require('rm-rf'),
    fs = require('fs'),
    Step = require('step'),
    path = require('path');

/**
 * Override Backbone.sync() for the server-side context. Uses TileMill-specific
 * file storage methods for model CRUD operations.
 */
Backbone.sync = function(method, model, success, error) {
    switch (method) {
    case 'read':
        if (model.id) {
            load(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        } else {
            loadAll(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        }
        break;
    case 'create':
        create(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    case 'update':
        update(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    case 'delete':
        destroy(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    }
};

/**
 * Load a single model. Requires that model.id be populated.
 */
function load(model, callback) {
    var modelPath = path.join(settings.files, model.type);
    modelPath = (model.type == 'project') ? path.join(modelPath, model.id) : modelPath;
    var extension = (model.type == 'project') ? 'mml' : 'json';
    fs.readFile(path.join(modelPath, model.id + '.' + extension), 'utf-8',
    function(err, data) {
        if (err || !data) {
            return callback('Error reading model file.');
        }
        var object = JSON.parse(data);
        // Set the object ID explicitly for multiple-load scenarios where
        // model parse()/set() is bypassed.
        object.id = model.id;
        if (model.type === 'project' && object.Stylesheet && object.Stylesheet.length > 0) {
            Step(
                function() {
                    var group = this.group();
                    _.each(object.Stylesheet, function(filename, index) {
                        fs.readFile(
                            path.join(modelPath, filename),
                            'utf-8',
                            group()
                        );
                    });
                },
                function(err, files) {
                    object.Stylesheet = _.reduce(
                        object.Stylesheet,
                        function(memo, filename, index) {
                            if (typeof files[index] !== 'undefined') {
                                memo.push({
                                    id: filename,
                                    data: files[index]
                                });
                            }
                            return memo;
                        },
                        []
                    );
                    return callback(null, object);
                }
            );
        }
        else {
            return callback(null, object);
        }
    });
};

/**
 * Load an array of all models.
 */
function loadAll(model, callback) {
    var basepath = path.join(settings.files, model.type);
    Step(
        function() {
            path.exists(basepath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basepath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(basepath, this);
        },
        function(err, files) {
            if (err) {
                return this('Error reading model directory.');
            }
            else if (files.length === 0) {
                return this();
            }
            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                if (model.type === 'project') {
                    var id = files[i];
                } else {
                    var id = path.basename(files[i], '.json');
                }
                load({ id: id, type: model.type }, group());
            }
        },
        function(err, models) {
            // Ignore errors from loading individual models (e.g.
            // don't let one bad apple spoil the collection).
            models = _.select(models, function(model) {
                return (typeof model === 'object');
            });
            return callback(null, models);
        }
    );
};

/**
 * Create a new model.
 * @TODO assign a model id if not present.
 */
function create(model, callback) {
    if (model.type === 'project') {
        saveProject(model, callback);
    } else {
        save(model, callback);
    }
};

/**
 * Update an existing model.
 */
function update(model, callback) {
    if (model.type === 'project') {
        saveProject(model, callback);
    } else {
        save(model, callback);
    }
};

/**
 * Destroy (delete, remove, etc.) a model.
 * - Project: rm rf the project directory.
 * - Export: track and and kill the export file in addition to the model.
 * - All others: rm the model json file.
 */
function destroy(model, callback) {
    switch (model.type) {
    case 'project':
        var modelPath = path.join(settings.files, model.type, model.id);
        rmrf(modelPath, function() { return callback(null, model) });
        break;
    case 'export':
        var filepath;
        Step(
            function() {
                load(model, this);
            },
            function(err, data) {
                if (data && data.filename) {
                    filepath = path.join(settings.export_dir, data.filename);
                    path.exists(filepath, this);
                }
                else {
                    this(false);
                }
            },
            function(remove) {
                if (remove) {
                    fs.unlink(filepath, this);
                }
                else {
                    this();
                }
            },
            function() {
                var modelPath = path.join(settings.files, model.type, model.id + '.json');
                fs.unlink(modelPath, function() { return callback(null, model) });
            }
        );
        break;
    default:
        var modelPath = path.join(settings.files, model.type, model.id + '.json');
        fs.unlink(modelPath, function() { return callback(null, model) });
        break;
    }
};

/**
 * Save a Project. Called by create/update.
 *
 * Special case for projects creates a subdirectory per project and does
 * additional splitting out of subproperties (stylesheets) into separate files.
 */
function saveProject(model, callback) {
    var basePath = path.join(settings.files, model.type);
    var modelPath = path.join(settings.files, model.type, model.id);
    Step(
        function() {
            path.exists(basePath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basePath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            path.exists(modelPath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(modelPath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(modelPath, this);
        },
        function(err, files) {
            // Remove any stale files in the project directory.
            var group = this.group();
            var stylesheets = model.get('Stylesheet') || [];
            var stale = _.select(files, function(filename) {
                if (filename === (model.id + '.mml')) {
                    return false;
                } else if (_.pluck(stylesheets, 'id').indexOf(filename) !== -1) {
                    return false;
                }
                return true;
            });
            if (stale.length) {
                for (var i = 0; i < stale.length; i++) {
                    fs.unlink(path.join(modelPath, stale[i]), group());
                }
            }
            else {
                group()();
            }
        },
        function() {
            // Hard clone the model JSON before doing adjustments to the data
            // based on writing separate stylesheets.
            var data = JSON.parse(JSON.stringify(model.toJSON()));
            var files = [];
            if (data.id) {
                delete data.id;
            }
            if (data.Stylesheet) {
                _.each(data.Stylesheet, function(stylesheet, key) {
                    if (stylesheet.id) {
                        files.push({
                            filename: stylesheet.id,
                            data: stylesheet.data
                        });
                        data.Stylesheet[key] = stylesheet.id;
                    }
                });
            }
            files.push({
                filename: model.id + '.mml',
                data: JSON.stringify(data)
            });

            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                fs.writeFile(
                    path.join(modelPath, files[i].filename),
                    files[i].data,
                    group()
                );
            }
        },
        function() {
            callback(null, model);
        }
    );
}

/**
 * Save a model. Called by create/update.
 */
function save(model, callback) {
    var basePath = path.join(settings.files, model.type);
    Step(
        function() {
            path.exists(basePath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basePath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.writeFile(
                path.join(basePath, model.id + '.json'),
                JSON.stringify(model.toJSON()),
                this
            );
        },
        function() {
            callback(null, model);
        }
    );
}

module.exports = Backbone;

<|endoftext|>


<commit_before>// Load in dependencies
var assert = require('assert');

// Default page
exports.index = function (config) {
  return [
    function indexFn(req, res) {
      res.render('contact.jade');
    }
  ];
};
<commit_msg>Fixed lint error<commit_after>// Default page
exports.index = function (config) {
  return [
    function indexFn(req, res) {
      res.render('contact.jade');
    }
  ];
};
<|endoftext|>


<commit_before>import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';

var people, containerView, view;

moduleFor('view:ivy-sortable', 'view:ivy-sortable', {
  setup: function() {
    people = Ember.A([
      { name: 'Tyrion' },
      { name: 'Cersei' }
    ]);

    view = this.subject({
      content: people,

      moved: 'moved',

      itemViewClass: Ember.View.extend({
        template: Ember.Handlebars.compile('{{view.content.name}}')
      })
    });

    containerView = Ember.ContainerView.create();

    Ember.run(function() {
      containerView.pushObject(view);
      containerView.appendTo('#ember-testing');
    });
  },

  teardown: function() {
    Ember.run(containerView, 'destroy');
  }
});

test('should render the template for each item in an array', function() {
  var items = view.$('li.ui-sortable-handle');
  equal(items.length, 2);
  equal(items.text(), 'TyrionCersei');
});

test('should update the view if content is replaced', function() {
  Ember.run(function() {
    view.set('content', Ember.A([
      { name: 'Joffrey' },
      { name: 'Sansa' }
    ]));
  });

  var items = view.$('li.ui-sortable-handle');
  equal(items.length, 2);
  equal(items.text(), 'JoffreySansa');
});

test('should update the view if an item is added', function() {
  Ember.run(function() {
    people.pushObject({ name: 'Tywin' });
  });

  equal(view.$().text(), 'TyrionCerseiTywin');
});

test('should update the view if an item is removed', function() {
  Ember.run(function() {
    people.removeAt(0);
  });

  equal(view.$().text(), 'Cersei');
});

test('should update the view if an item is replaced', function() {
  Ember.run(function() {
    people.removeAt(0);
    people.insertAt(0, { name: 'Jaime' });
  });

  equal(view.$().text(), 'JaimeCersei');
});

test('should not cause Morph errors if an item is dragged and then moved', function() {
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'CerseiTyrion');

  Ember.run(function() {
    var tyrion = people.objectAt(1);
    people.removeAt(1);
    people.insertAt(0, tyrion);
  });

  equal(view.$().text(), 'TyrionCersei');
});

test('should update the view if an item is dragged', function() {
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'CerseiTyrion');
});

test('should update the content if an item is dragged', function() {
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  deepEqual(people.mapBy('name'), ['Cersei', 'Tyrion']);
});

test('should become disabled if the disabled attribute is true', function() {
  Ember.run(function() { view.set('disabled', true); });
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'TyrionCersei', 'setting disabled to true disables dragging');

  Ember.run(function() { view.set('disabled', false); });
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'CerseiTyrion', 'setting disabled to false enables dragging');
});

test('should trigger moved action after successful drag', function() {
  expect(3);

  Ember.run(function() {
    containerView.set('controller', Ember.Object.create({
      moved: function(item, oldIndex, newIndex) {
        strictEqual(item, people.objectAt(1));
        equal(oldIndex, 0);
        equal(newIndex, 1);
      }
    }));
  });

  view.$('li:eq(0)').simulate('drag', { dy: 22 });
});

test('should not refresh after destruction', function() {
  expect(1);

  Ember.run(function() {
    people.pushObject({ name: 'Arya' });
    containerView.destroy();
  });

  ok(true, 'no error was thrown');
});

function optionTest(key, beforeValue, afterValue) {
  test('should update jQuery UI ' + key + ' option when ' + key + ' property changes', function() {
    equal(view.$().sortable('option', key), beforeValue,
          'precond - initial value of ' + key + ' option is correct');

    Ember.run(function() {
      view.set(key, afterValue);
    });

    equal(view.$().sortable('option', key), afterValue,
          key + ' option is updated after ' + key + ' property is changed');
  });
}

optionTest('axis', false, 'x');
optionTest('containment', false, 'parent');
optionTest('cursor', 'auto', 'move');
optionTest('cursorAt', false, { left: 5 });
optionTest('delay', 0, 150);
optionTest('disabled', false, true);
optionTest('distance', 1, 5);
optionTest('forceHelperSize', false, true);
optionTest('forcePlaceholderSize', false, true);
optionTest('grid', false, [20, 10]);
optionTest('helper', 'original', 'clone');
optionTest('opacity', false, 0.5);
optionTest('placeholder', false, 'sortable-placeholder');
optionTest('revert', false, true);
optionTest('scroll', true, false);
optionTest('scrollSensitivity', 20, 10);
optionTest('scrollSpeed', 20, 40);
optionTest('tolerance', 'intersect', 'pointer');
optionTest('zIndex', 1000, 9999);
<commit_msg>Update test for handle option<commit_after>import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';

var people, containerView, view;

moduleFor('view:ivy-sortable', 'view:ivy-sortable', {
  setup: function() {
    people = Ember.A([
      { name: 'Tyrion' },
      { name: 'Cersei' }
    ]);

    view = this.subject({
      content: people,

      moved: 'moved',

      itemViewClass: Ember.View.extend({
        template: Ember.Handlebars.compile('{{view.content.name}}')
      })
    });

    containerView = Ember.ContainerView.create();

    Ember.run(function() {
      containerView.pushObject(view);
      containerView.appendTo('#ember-testing');
    });
  },

  teardown: function() {
    Ember.run(containerView, 'destroy');
  }
});

test('should render the template for each item in an array', function() {
  var items = view.$('li.ui-sortable-handle');
  equal(items.length, 2);
  equal(items.text(), 'TyrionCersei');
});

test('should update the view if content is replaced', function() {
  Ember.run(function() {
    view.set('content', Ember.A([
      { name: 'Joffrey' },
      { name: 'Sansa' }
    ]));
  });

  var items = view.$('li.ui-sortable-handle');
  equal(items.length, 2);
  equal(items.text(), 'JoffreySansa');
});

test('should update the view if an item is added', function() {
  Ember.run(function() {
    people.pushObject({ name: 'Tywin' });
  });

  equal(view.$().text(), 'TyrionCerseiTywin');
});

test('should update the view if an item is removed', function() {
  Ember.run(function() {
    people.removeAt(0);
  });

  equal(view.$().text(), 'Cersei');
});

test('should update the view if an item is replaced', function() {
  Ember.run(function() {
    people.removeAt(0);
    people.insertAt(0, { name: 'Jaime' });
  });

  equal(view.$().text(), 'JaimeCersei');
});

test('should not cause Morph errors if an item is dragged and then moved', function() {
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'CerseiTyrion');

  Ember.run(function() {
    var tyrion = people.objectAt(1);
    people.removeAt(1);
    people.insertAt(0, tyrion);
  });

  equal(view.$().text(), 'TyrionCersei');
});

test('should update the view if an item is dragged', function() {
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'CerseiTyrion');
});

test('should update the content if an item is dragged', function() {
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  deepEqual(people.mapBy('name'), ['Cersei', 'Tyrion']);
});

test('should become disabled if the disabled attribute is true', function() {
  Ember.run(function() { view.set('disabled', true); });
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'TyrionCersei', 'setting disabled to true disables dragging');

  Ember.run(function() { view.set('disabled', false); });
  view.$('li:eq(0)').simulate('drag', { dy: 22 });

  equal(view.$().text(), 'CerseiTyrion', 'setting disabled to false enables dragging');
});

test('should trigger moved action after successful drag', function() {
  expect(3);

  Ember.run(function() {
    containerView.set('controller', Ember.Object.create({
      moved: function(item, oldIndex, newIndex) {
        strictEqual(item, people.objectAt(1));
        equal(oldIndex, 0);
        equal(newIndex, 1);
      }
    }));
  });

  view.$('li:eq(0)').simulate('drag', { dy: 22 });
});

test('should not refresh after destruction', function() {
  expect(1);

  Ember.run(function() {
    people.pushObject({ name: 'Arya' });
    containerView.destroy();
  });

  ok(true, 'no error was thrown');
});

function optionTest(key, beforeValue, afterValue) {
  test('should update jQuery UI ' + key + ' option when ' + key + ' property changes', function() {
    equal(view.$().sortable('option', key), beforeValue,
          'precond - initial value of ' + key + ' option is correct');

    Ember.run(function() {
      view.set(key, afterValue);
    });

    equal(view.$().sortable('option', key), afterValue,
          key + ' option is updated after ' + key + ' property is changed');
  });
}

optionTest('axis', false, 'x');
optionTest('containment', false, 'parent');
optionTest('cursor', 'auto', 'move');
optionTest('cursorAt', false, { left: 5 });
optionTest('delay', 0, 150);
optionTest('disabled', false, true);
optionTest('distance', 1, 5);
optionTest('forceHelperSize', false, true);
optionTest('forcePlaceholderSize', false, true);
optionTest('grid', false, [20, 10]);
optionTest('handle', false, '.handle');
optionTest('helper', 'original', 'clone');
optionTest('opacity', false, 0.5);
optionTest('placeholder', false, 'sortable-placeholder');
optionTest('revert', false, true);
optionTest('scroll', true, false);
optionTest('scrollSensitivity', 20, 10);
optionTest('scrollSpeed', 20, 40);
optionTest('tolerance', 'intersect', 'pointer');
optionTest('zIndex', 1000, 9999);
<|endoftext|>


<commit_before>var co = require('co');
var extend = require('extend-merge').extend;
var merge = require('extend-merge').merge;
var BaseSchema = require('chaos-orm').Schema;
var Collection = require('chaos-orm').Collection;
var Relationship = require('chaos-orm').Relationship;
var BelongsTo = require('chaos-orm').BelongsTo;
var HasOne = require('chaos-orm').HasOne;
var HasMany = require('chaos-orm').HasMany;
var HasManyThrough = require('chaos-orm').HasManyThrough;
var Query = require('./query');
var Payload = require('./payload');

function arrayDiff(a, b) {
  var len = a.length;
  var arr = [];

  for (var i = 0; i < len; i++) {
    if (b.indexOf(a[i]) === -1) {
      arr.push(a[i]);
    }
  }
  return arr;
}

function path() {
  var result = '';
  for(var i = 0, length = arguments.length; i < length; i++) {
    if (result.substr(-1) === '/' && arguments[i][0] === '/') {
      result = result.substr(0, result.length - 1);
    }
    result += arguments[i];
  }
  return result;
}

class Schema extends BaseSchema {

    /**
   * Configures the meta for use.
   *
   * @param Object config Possible options are:
   *                      - `'connection'`  _Function_ : The connection instance (defaults to `undefined`).
   */
  constructor(config) {
    var defaults = {
      connection: undefined,
    };

    config = merge({}, defaults, config);
    super(config);

    /**
     * The connection instance.
     *
     * @var Object
     */
    this._connection = undefined;

    if (config.connection) {
      this.connection(config.connection);
    }
  }

  /**
   * Return a query to retrieve data from the connected data source.
   *
   * @param  Object options Query options.
   * @return Object         An instance of `Query`.
   */
  query(options) {
    var defaults = {
      connection: this.connection(),
      model:      this.reference(),
      path:       '/' + this.source()
    };
    options = extend({}, defaults, options);

    var query = this.constructor.classes().query;

    if (!options.model) {
      throw new Error("Missing model for this schema, can't create a query.");
    }
    return new query(options);
  }

  /**
   * Gets/sets the connection object to which this class is bound.
   *
   * @param  Object connection The connection instance to set or `null` to get the current one.
   * @return mixed             Returns the connection instance on get or `this` on set.
   */
  connection(connection) {
    if (arguments.length) {
      this._connection = connection;
      this._formatters = merge({}, this._connection.formatters(), this._formatters);
      return this;
    }
    if (!this._connection) {
      throw new Error("Error, missing connection for this schema.");
    }
    return this._connection;
  }

  /**
   * Create the schema.
   *
   * @param  Object  options An object of options.
   * @return Boolean
   */
  create(options) {
    throw new Error("Creating schemas are not supported by JSON API.");
  }

  /**
   * Drop the schema
   *
   * @param  array   options An array of options.
   * @return boolean
   * @throws DatabaseException If no connection is defined or the schema name is missing.
   */
  drop(options) {
    throw new Error("Dropping schemas are not supported by JSON API.");
  }

  /**
   * Save data related to relations.
   *
   * @param  Object  entity  The entity instance.
   * @param  Array   types   Type of relations to save.
   * @param  Object  options Options array.
   * @return Promise         Returns a promise.
   */
  saveRelation(instance, types, options) {
    return co(function*() {
      return true;
    }.bind(this));
  }

  /**
   * Bulk inserts
   *
   * @param  Array    inserts An array of entities to insert.
   * @param  Function filter  The filter handler for which extract entities values for the insertion.
   * @return Promise          Returns `true` if insert operations succeeded, `false` otherwise.
   */
  bulkInsert(inserts, filter) {
    return co(function*() {
      if (!inserts || !inserts.length) {
        return true;
      }
      var payload = new Payload();
      payload.set(new Collection({ data: inserts }));
      try {
        var json = yield this.connection().post('/' + this.source(), payload.serialize());
        this._amendCollection(inserts, Payload.parse(extend({ data:[] }, json)).export(), { exists: true });
      } catch (response) {
        this._manageErrors(inserts, response);
      }
    }.bind(this));
  }

  /**
   * Bulk updates
   *
   * @param  Array    updates An array of entities to update.
   * @param  Function filter  The filter handler for which extract entities values to update.
   * @return Promise          Returns `true` if update operations succeeded, `false` otherwise.
   */
  bulkUpdate(updates, filter) {
    return co(function*() {
      if (!updates || !updates.length) {
        return true;
      }
      var payload = new Payload();
      payload.set(new Collection({ data: updates }));
      try {
        var json = yield this.connection().patch('/' + this.source(), payload.serialize());
        this._amendCollection(updates, Payload.parse(extend({ data:[] }, json)).export(), { exists: true });
      } catch (response) {
        this._manageErrors(updates, response);
      }
    }.bind(this));
  }

  /**
   * Sync data from the response payload.
   *
   * @param Array  collection The sent collection
   * @param Object response   The JSON-API response payload
   * @param Object options    Some additionnal amend options
   */
  _amendCollection(collection, data, options) {
    options = options || {};
    if (collection.length !== data.length) {
      throw new Error('Error, received data must have the same length as sent data.');
    }
    for (var i = 0, len = data.length; i < len; i++) {
      var entity = collection[i];
      entity.amend(data[i], options);
    }
  }

  /**
   * Manage errors as well as validation errors.
   *
   * @param Array  collection The sent collection
   * @param Object response   The JSON-API error response payload
   */
  _manageErrors(collection, response) {
    if (response.data && response.data.errors) {
      var errors = response.data.errors;
      for (var error of errors) {
        if (error.code !== 0) {
          throw new Error(error.title);
        }
        var meta = error.meta;
        if (collection.length !== meta.length) {
          throw new Error('Error, received errors must have the same length as sent data.');
        }
        for (var i = 0, len = meta.length; i < len; i++) {
          collection[i].invalidate(meta[i]);
        }
      }
    }
    throw response;
  }

  /**
   * Delete a record or document.
   *
   * @param  mixed    conditions The conditions with key/value pairs representing the ID of the records or
   *                             documents to be deleted.
   * @return Promise             Returns a promise.
   */
  delete(instance) {
    return co(function*() {
      var payload = new Payload();
      payload.delete(instance);
      try {
        yield this.connection().delete('/' + this.source(), payload.serialize());
        return true;
      } catch (response) {
        return false;
      }
    }.bind(this));
  }

  /**
   * Return the last insert id from the database.
   *
   * @return mixed Returns the last insert id.
   */
  lastInsertId() {
    var key = this.key();
    var data = this.connection().lastInsert();
    return data ? data[key] : undefined;
  }

  /**
   * Return the last request.
   *
   * @return Object Returns the last request.
   */
  lastRequest() {
    return this.connection().lastRequest();
  }

  /**
   * Return the last response.
   *
   * @return Object Returns the last response.
   */
  lastResponse() {
    return this.connection().lastResponse();
  }
}

/**
 * Class dependencies.
 *
 * @var array
 */
Schema._classes = {
  relationship: Relationship,
  belongsTo: BelongsTo,
  hasOne: HasOne,
  hasMany: HasMany,
  hasManyThrough: HasManyThrough,
  query: Query
};

module.exports = Schema;
<commit_msg>Fix delete error management<commit_after>var co = require('co');
var extend = require('extend-merge').extend;
var merge = require('extend-merge').merge;
var BaseSchema = require('chaos-orm').Schema;
var Collection = require('chaos-orm').Collection;
var Relationship = require('chaos-orm').Relationship;
var BelongsTo = require('chaos-orm').BelongsTo;
var HasOne = require('chaos-orm').HasOne;
var HasMany = require('chaos-orm').HasMany;
var HasManyThrough = require('chaos-orm').HasManyThrough;
var Query = require('./query');
var Payload = require('./payload');

function arrayDiff(a, b) {
  var len = a.length;
  var arr = [];

  for (var i = 0; i < len; i++) {
    if (b.indexOf(a[i]) === -1) {
      arr.push(a[i]);
    }
  }
  return arr;
}

function path() {
  var result = '';
  for(var i = 0, length = arguments.length; i < length; i++) {
    if (result.substr(-1) === '/' && arguments[i][0] === '/') {
      result = result.substr(0, result.length - 1);
    }
    result += arguments[i];
  }
  return result;
}

class Schema extends BaseSchema {

    /**
   * Configures the meta for use.
   *
   * @param Object config Possible options are:
   *                      - `'connection'`  _Function_ : The connection instance (defaults to `undefined`).
   */
  constructor(config) {
    var defaults = {
      connection: undefined,
    };

    config = merge({}, defaults, config);
    super(config);

    /**
     * The connection instance.
     *
     * @var Object
     */
    this._connection = undefined;

    if (config.connection) {
      this.connection(config.connection);
    }
  }

  /**
   * Return a query to retrieve data from the connected data source.
   *
   * @param  Object options Query options.
   * @return Object         An instance of `Query`.
   */
  query(options) {
    var defaults = {
      connection: this.connection(),
      model:      this.reference(),
      path:       '/' + this.source()
    };
    options = extend({}, defaults, options);

    var query = this.constructor.classes().query;

    if (!options.model) {
      throw new Error("Missing model for this schema, can't create a query.");
    }
    return new query(options);
  }

  /**
   * Gets/sets the connection object to which this class is bound.
   *
   * @param  Object connection The connection instance to set or `null` to get the current one.
   * @return mixed             Returns the connection instance on get or `this` on set.
   */
  connection(connection) {
    if (arguments.length) {
      this._connection = connection;
      this._formatters = merge({}, this._connection.formatters(), this._formatters);
      return this;
    }
    if (!this._connection) {
      throw new Error("Error, missing connection for this schema.");
    }
    return this._connection;
  }

  /**
   * Create the schema.
   *
   * @param  Object  options An object of options.
   * @return Boolean
   */
  create(options) {
    throw new Error("Creating schemas are not supported by JSON API.");
  }

  /**
   * Drop the schema
   *
   * @param  array   options An array of options.
   * @return boolean
   * @throws DatabaseException If no connection is defined or the schema name is missing.
   */
  drop(options) {
    throw new Error("Dropping schemas are not supported by JSON API.");
  }

  /**
   * Save data related to relations.
   *
   * @param  Object  entity  The entity instance.
   * @param  Array   types   Type of relations to save.
   * @param  Object  options Options array.
   * @return Promise         Returns a promise.
   */
  saveRelation(instance, types, options) {
    return co(function*() {
      return true;
    }.bind(this));
  }

  /**
   * Bulk inserts
   *
   * @param  Array    inserts An array of entities to insert.
   * @param  Function filter  The filter handler for which extract entities values for the insertion.
   * @return Promise          Returns `true` if insert operations succeeded, `false` otherwise.
   */
  bulkInsert(inserts, filter) {
    return co(function*() {
      if (!inserts || !inserts.length) {
        return true;
      }
      var payload = new Payload();
      payload.set(new Collection({ data: inserts }));
      try {
        var json = yield this.connection().post('/' + this.source(), payload.serialize());
        this._amendCollection(inserts, Payload.parse(extend({ data:[] }, json)).export(), { exists: true });
      } catch (response) {
        this._manageErrors(inserts, response);
      }
    }.bind(this));
  }

  /**
   * Bulk updates
   *
   * @param  Array    updates An array of entities to update.
   * @param  Function filter  The filter handler for which extract entities values to update.
   * @return Promise          Returns `true` if update operations succeeded, `false` otherwise.
   */
  bulkUpdate(updates, filter) {
    return co(function*() {
      if (!updates || !updates.length) {
        return true;
      }
      var payload = new Payload();
      payload.set(new Collection({ data: updates }));
      try {
        var json = yield this.connection().patch('/' + this.source(), payload.serialize());
        this._amendCollection(updates, Payload.parse(extend({ data:[] }, json)).export(), { exists: true });
      } catch (response) {
        this._manageErrors(updates, response);
      }
    }.bind(this));
  }

  /**
   * Sync data from the response payload.
   *
   * @param Array  collection The sent collection
   * @param Object response   The JSON-API response payload
   * @param Object options    Some additionnal amend options
   */
  _amendCollection(collection, data, options) {
    options = options || {};
    if (collection.length !== data.length) {
      throw new Error('Error, received data must have the same length as sent data.');
    }
    for (var i = 0, len = data.length; i < len; i++) {
      var entity = collection[i];
      entity.amend(data[i], options);
    }
  }

  /**
   * Manage errors as well as validation errors.
   *
   * @param Array  collection The sent collection
   * @param Object response   The JSON-API error response payload
   */
  _manageErrors(collection, response) {
    if (response.data && response.data.errors) {
      var errors = response.data.errors;
      for (var error of errors) {
        if (error.code !== 0) {
          throw new Error(error.title);
        }
        var meta = error.meta;
        if (collection.length !== meta.length) {
          throw new Error('Error, received errors must have the same length as sent data.');
        }
        for (var i = 0, len = meta.length; i < len; i++) {
          collection[i].invalidate(meta[i]);
        }
      }
    }
    throw response;
  }

  /**
   * Delete a record or document.
   *
   * @param  mixed    conditions The conditions with key/value pairs representing the ID of the records or
   *                             documents to be deleted.
   * @return Promise             Returns a promise.
   */
  delete(instance) {
    return co(function*() {
      var payload = new Payload();
      payload.delete(instance);
      try {
        yield this.connection().delete('/' + this.source(), payload.serialize());
      } catch (response) {
        if (response.data && response.data.errors) {
          var errors = response.data.errors;
          for (var error of errors) {
            throw new Error(error.title);
          }
        }
        throw new Error('Unexpected error');
      }
    }.bind(this));
  }

  /**
   * Return the last insert id from the database.
   *
   * @return mixed Returns the last insert id.
   */
  lastInsertId() {
    var key = this.key();
    var data = this.connection().lastInsert();
    return data ? data[key] : undefined;
  }

  /**
   * Return the last request.
   *
   * @return Object Returns the last request.
   */
  lastRequest() {
    return this.connection().lastRequest();
  }

  /**
   * Return the last response.
   *
   * @return Object Returns the last response.
   */
  lastResponse() {
    return this.connection().lastResponse();
  }
}

/**
 * Class dependencies.
 *
 * @var array
 */
Schema._classes = {
  relationship: Relationship,
  belongsTo: BelongsTo,
  hasOne: HasOne,
  hasMany: HasMany,
  hasManyThrough: HasManyThrough,
  query: Query
};

module.exports = Schema;
<|endoftext|>


<commit_before>// Class Screen defines canvas part of YouTyping.
// One YouTyping have only one Screen as child, and vice versa.
var Screen = function (canvas, youTyping) {
	var screen = this;

	var FPS = 0;

	this.canvas = canvas;

	// All notes and lines will be stored in this variable and managed
	// in key which represents index.
	this.items = {};

	this.setup = function (deferred) {
		paper.setup(screen.canvas);

		screen.cover = new paper.Path.Rectangle(paper.view.bounds);
		screen.cover.fillColor = 'black';
		screen.cover.fillColor.alpha = 0.7;

		screen.debugTexts = [];
		for (var i = 0; i < 5; i++) {
			var index = screen.debugTexts.push(new paper.PointText([20, 20 * (i + 1)]));
			screen.debugText = screen.debugTexts[index - 1];
			screen.debugText.justification = 'left';
			screen.debugText.fillColor = 'white';
		}

		screen.bufferText = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.bufferTextPosition),
			content: '',
			fillColor: 'white',
			justification: 'left',
			fontSize: 24
		});

		screen.currentLyric = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.currentLyricPosition),
			content: '',
			fillColor: 'white',
			justification: 'center',
			fontSize: 36
		});

		screen.nextLyric = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.nextLyricPosition),
			content: '',
			fillColor: 'white',
			justification: 'center',
			fontSize: 18
		});

		screen.kanaLyric = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.kanaLyricPosition),
			content: '',
			fillColor: 'white',
			justification: 'center',
			fontSize: 24
		});

		screen.judgeEffects = new paper.Group();

		setInterval(function () {
			screen.debugTexts[0].content = 'FPS: ' + FPS;
			FPS = 0;
			screen.debugTexts[2].content = 'Zerocall FPS: ' + youTyping.zeroCallFPS;
			youTyping.zeroCallFPS = 0; // not good
		}, 1000);

		logTrace('Screen is Set.');
		deferred.resolve();
	};

	this.load = function (deffered) {
		var settings = youTyping.settings;
		var now = youTyping.now;

		var paddingRight = settings.width * (1 - settings.hitPosition) + settings.noteSize + settings.screenPadding; // distance from hit line to right edge
		var paddingLeft = settings.width * settings.hitPosition + settings.noteSize + settings.screenPadding; // distance from hit line to left edge

		try {
			// Computes emerge time and vanishing time of item.
			// This is yet a very simple way without regards for speed changes.
			youTyping.roll.forEach(function (item, index) {
				item.emergeTime = (settings.speed * item.time - paddingRight) / settings.speed;
				item.vanishTime = (settings.speed * item.time + paddingLeft) / settings.speed;
			});

			logTrace('Computed roll Parameters.');
		} catch (error) {
			logTrace('ERROR: Computing roll Parameters Faild: ' + error);
			return -1;
		}

		youTyping.zeroTime = now;
		screen.update();

		screen.hitCircle = new paper.Path.Circle({
			center: paper.view.bounds.bottomRight.multiply([settings.hitPosition, settings.rollYpos]),
			radius: settings.noteSize,
			strokeWidth: 1,
			strokeColor: 'white'
		});
	};

	this.ready = function () {
		screen.pressEnter = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply([0.5, 0.8]),
			content: 'Press enter or click here.',
			justification: 'center',
			fontSize: 45,
			fillColor: 'white'
		});
		var triggerStartScreen = function (event) {
			if ((event.type === 'keydown' && event.key === 'enter') || event.type === 'mousedown') {
				screen.pressEnter.remove();
				paper.tool.onKeyDown = null; // unbind
				screen.start();
			}
		};
		paper.tool.onKeyDown = triggerStartScreen;
		screen.pressEnter.onMouseDown = triggerStartScreen;

		logTrace('Screen is Ready.');
	};

	this.start = function () {
		logTrace('Starting game.');

		// register onFrame event
		paper.view.onFrame = screen.onFrame;

		youTyping.play();

		var triggerHitNote = function (event) {
			if (youTyping.player.getPlayerState() === 1 && event.type === 'keydown') {
				// suspend default operation on browser by keydown
				event.preventDefault();
				youTyping.hit(event.key);
			}
		};
		paper.tool.onKeyDown = triggerHitNote;
	};

	// layout notes and lines fitting to current time
	this.update = function () {
		var setting = youTyping.settings;
		var items = screen.items;

		var now = youTyping.now;
		var runTime = now - youTyping.zeroTime;

		youTyping.roll.forEach(function (item, index) {
			// X position of the item
			var position = (item.time - runTime) * setting.speed + setting.width * setting.hitPosition;

			// if index-th item doesn't exists in screen
			if (!(index in items)) {
				if (item.emergeTime <= runTime && runTime <= item.vanishTime) {
					// create item
					items[index] = new paper.Group();

					// long line which devides roll to measures
					if (item.type === 'longline') {
						items[index].longLine = items[index].addChild(new paper.Path.Line({
							from: [position, setting.rollYpos * setting.height - setting.longLineHeight / 2],
							to: [position, setting.rollYpos * setting.height + setting.longLineHeight / 2],
							strokeColor: 'white',
							strokeWidth: 2
						}));
					}
					// small line
					if (item.type === 'line') {
						items[index].smallLine = items[index].addChild(new paper.Path.Line({
							from: [position, setting.rollYpos * setting.height - setting.lineHeight / 2],
							to: [position, setting.rollYpos * setting.height + setting.lineHeight / 2],
							strokeColor: 'white',
							strokeWidth: 1
						}));
					}
					if (item.type === 'note') {
						// note
						items[index].note = items[index].addChild(new paper.Path.Circle({
							center: [position, setting.rollYpos * setting.height],
							radius: setting.noteSize,
							strokeWidth: 1,
							strokeColor: '#aaa'
						}));
						// lyric
						items[index].lyric = items[index].addChild(new paper.PointText({
							point: [position, setting.rollYpos * setting.height + setting.noteSize + 50],
							content: item.remainingText,
							fillColor: 'white',
							justification: 'center',
							fontSize: 20,
							fontFamily: 'sans-serif'
						}));
					}
					// order stop mark
					if (item.type === 'stop') {
						items[index].orderStop = items[index].addChild(new paper.Path({
							segments: [[position, setting.rollYpos * setting.height - setting.noteSize - 30]],
							fillColor: 'white'
						}));
						items[index].orderStop.lineBy([10, -10]);
						items[index].orderStop.lineBy([-20, 0]);
						items[index].orderStop.closed = true;
					}
				} else {
					return;
				}
			} else { // if index-th item exists in screen
				if (runTime < item.emergeTime || item.vanishTime < runTime) {
					items[index].remove();
					delete items[index];
					return;
				}
			}

			// update item style
			if (item.type === 'longline') {
				items[index].position.x = position;
			}
			if (item.type === 'line') {
				items[index].position.x = position;
			}
			if (item.type === 'stop') {
				items[index].position.x = position;
			}
			if (item.type === 'note') {
				items[index].position.x = position;
				if (item.state === youTyping.noteState.CLEARED) {
					items[index].note.visible = false;
					items[index].lyric.visible = false;
				} else {
					// note
					items[index].note.style = {
						fillColor: (
							item.state === youTyping.noteState.WAITING ||
							item.state === youTyping.noteState.HITTING
						) ? 'red' : '#aaa'
					};
					items[index].note.opacity = (
						item.state === youTyping.noteState.FAILED ||
						item.state === youTyping.noteState.WAITING
					) ? 1 : 0.5;
					// lyric
					items[index].lyric.content = item.remainingText;
				}
			}
		});
	};

	this.onFrame = function (event) {
		var kanaLyric;

		if (youTyping.player.getPlayerState() === 1) {
			screen.update();
		}

		screen.debugTexts[1].content = 'Measured Zero: ' + youTyping.estimatedZero.toFixed(2);
		screen.debugTexts[3].content = 'Active Objects: ' + paper.project.activeLayer.children.length;
		screen.debugTexts[4].content = 'Zero Time: ' + youTyping.zeroTime.toFixed(2);
		screen.bufferText.content = youTyping.inputBuffer;
		screen.currentLyric.content = youTyping.currentLyricIndex ? youTyping.roll[youTyping.currentLyricIndex].text : '';
		screen.nextLyric.content = youTyping.nextLyricIndex ? youTyping.roll[youTyping.nextLyricIndex].text : '';
		screen.kanaLyric.content = (kanaLyric = youTyping.getKanaLyric()) ? kanaLyric : '';

		screen.judgeEffects.children.forEach(function (judgeEffect) {
			judgeEffect.controller.onFrame();
		});

		FPS++;
	};

	// YouTube onStateChange event supplied from YouTyping
	this.onPlayerStateChange = function (event) {
		// hide mouse cursor when playing
		if (event.data === YT.PlayerState.PLAYING) {
			youTyping.DOM.screen.css({
				cursor: 'none'
			});
		} else {
			youTyping.DOM.screen.css({
				cursor: 'auto'
			});
		}
	};

	this.onJudgement = function (event) {
		var judgeEffect = new JudgeEffect(event.judgement);
		judgeEffect.item.controller = judgeEffect;
		screen.judgeEffects.addChild(judgeEffect.item);
	};

	// judge effect object
	var JudgeEffect = function (judgement) {
		var judgeEffect = this;
		var settings = youTyping.settings;

		this.item = new paper.Group();

		this.judgeColor = '';
		switch (judgement.judge) {
		case 'perfect':
			this.judgeColor = 'yellow'; break;
		case 'great':
			this.judgeColor = '#2d1'; break;
		case 'good':
			this.judgeColor = '#19a'; break;
		case 'bad':
			this.judgeColor = '#aaa'; break;
		}

		this.judge = this.item.addChild(new paper.PointText({
			point: screen.hitCircle.position.add([0, -settings.noteSize - 24]),
			content: judgement.judge,
			fillColor: this.judgeColor,
			justification: 'center',
			fontSize: 24,
			fontFamily: 'sans-serif'
		}));

		this.combo = this.item.addChild(new paper.PointText({
			point: screen.hitCircle.position.add([0, -settings.noteSize]),
			content: judgement.combo,
			fillColor: 'white',
			justification: 'center',
			fontSize: 15,
			fontFamily: 'sans-serif'
		}));

		this.onFrame = function (event) {
			this.item.translate([0, -3]);
			this.item.opacity -= 0.02;

			if (this.item.opacity < 0) {
				this.item.remove();
			}
		};
	};
};
<commit_msg>Hide kanaLyric<commit_after>// Class Screen defines canvas part of YouTyping.
// One YouTyping have only one Screen as child, and vice versa.
var Screen = function (canvas, youTyping) {
	var screen = this;

	var FPS = 0;

	this.canvas = canvas;

	// All notes and lines will be stored in this variable and managed
	// in key which represents index.
	this.items = {};

	this.setup = function (deferred) {
		paper.setup(screen.canvas);

		screen.cover = new paper.Path.Rectangle(paper.view.bounds);
		screen.cover.fillColor = 'black';
		screen.cover.fillColor.alpha = 0.7;

		screen.debugTexts = [];
		for (var i = 0; i < 5; i++) {
			var index = screen.debugTexts.push(new paper.PointText([20, 20 * (i + 1)]));
			screen.debugText = screen.debugTexts[index - 1];
			screen.debugText.justification = 'left';
			screen.debugText.fillColor = 'white';
		}

		screen.bufferText = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.bufferTextPosition),
			content: '',
			fillColor: 'white',
			justification: 'left',
			fontSize: 24
		});

		screen.currentLyric = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.currentLyricPosition),
			content: '',
			fillColor: 'white',
			justification: 'center',
			fontSize: 36
		});

		screen.nextLyric = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.nextLyricPosition),
			content: '',
			fillColor: 'white',
			justification: 'center',
			fontSize: 18
		});

		screen.kanaLyric = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply(youTyping.settings.kanaLyricPosition),
			content: '',
			fillColor: 'white',
			justification: 'center',
			fontSize: 24
		});

		screen.judgeEffects = new paper.Group();

		setInterval(function () {
			screen.debugTexts[0].content = 'FPS: ' + FPS;
			FPS = 0;
			screen.debugTexts[2].content = 'Zerocall FPS: ' + youTyping.zeroCallFPS;
			youTyping.zeroCallFPS = 0; // not good
		}, 1000);

		logTrace('Screen is Set.');
		deferred.resolve();
	};

	this.load = function (deffered) {
		var settings = youTyping.settings;
		var now = youTyping.now;

		var paddingRight = settings.width * (1 - settings.hitPosition) + settings.noteSize + settings.screenPadding; // distance from hit line to right edge
		var paddingLeft = settings.width * settings.hitPosition + settings.noteSize + settings.screenPadding; // distance from hit line to left edge

		try {
			// Computes emerge time and vanishing time of item.
			// This is yet a very simple way without regards for speed changes.
			youTyping.roll.forEach(function (item, index) {
				item.emergeTime = (settings.speed * item.time - paddingRight) / settings.speed;
				item.vanishTime = (settings.speed * item.time + paddingLeft) / settings.speed;
			});

			logTrace('Computed roll Parameters.');
		} catch (error) {
			logTrace('ERROR: Computing roll Parameters Faild: ' + error);
			return -1;
		}

		youTyping.zeroTime = now;
		screen.update();

		screen.hitCircle = new paper.Path.Circle({
			center: paper.view.bounds.bottomRight.multiply([settings.hitPosition, settings.rollYpos]),
			radius: settings.noteSize,
			strokeWidth: 1,
			strokeColor: 'white'
		});
	};

	this.ready = function () {
		screen.pressEnter = new paper.PointText({
			point: paper.view.bounds.bottomRight.multiply([0.5, 0.8]),
			content: 'Press enter or click here.',
			justification: 'center',
			fontSize: 45,
			fillColor: 'white'
		});
		var triggerStartScreen = function (event) {
			if ((event.type === 'keydown' && event.key === 'enter') || event.type === 'mousedown') {
				screen.pressEnter.remove();
				paper.tool.onKeyDown = null; // unbind
				screen.start();
			}
		};
		paper.tool.onKeyDown = triggerStartScreen;
		screen.pressEnter.onMouseDown = triggerStartScreen;

		logTrace('Screen is Ready.');
	};

	this.start = function () {
		logTrace('Starting game.');

		// register onFrame event
		paper.view.onFrame = screen.onFrame;

		youTyping.play();

		var triggerHitNote = function (event) {
			if (youTyping.player.getPlayerState() === 1 && event.type === 'keydown') {
				// suspend default operation on browser by keydown
				event.preventDefault();
				youTyping.hit(event.key);
			}
		};
		paper.tool.onKeyDown = triggerHitNote;
	};

	// layout notes and lines fitting to current time
	this.update = function () {
		var setting = youTyping.settings;
		var items = screen.items;

		var now = youTyping.now;
		var runTime = now - youTyping.zeroTime;

		youTyping.roll.forEach(function (item, index) {
			// X position of the item
			var position = (item.time - runTime) * setting.speed + setting.width * setting.hitPosition;

			// if index-th item doesn't exists in screen
			if (!(index in items)) {
				if (item.emergeTime <= runTime && runTime <= item.vanishTime) {
					// create item
					items[index] = new paper.Group();

					// long line which devides roll to measures
					if (item.type === 'longline') {
						items[index].longLine = items[index].addChild(new paper.Path.Line({
							from: [position, setting.rollYpos * setting.height - setting.longLineHeight / 2],
							to: [position, setting.rollYpos * setting.height + setting.longLineHeight / 2],
							strokeColor: 'white',
							strokeWidth: 2
						}));
					}
					// small line
					if (item.type === 'line') {
						items[index].smallLine = items[index].addChild(new paper.Path.Line({
							from: [position, setting.rollYpos * setting.height - setting.lineHeight / 2],
							to: [position, setting.rollYpos * setting.height + setting.lineHeight / 2],
							strokeColor: 'white',
							strokeWidth: 1
						}));
					}
					if (item.type === 'note') {
						// note
						items[index].note = items[index].addChild(new paper.Path.Circle({
							center: [position, setting.rollYpos * setting.height],
							radius: setting.noteSize,
							strokeWidth: 1,
							strokeColor: '#aaa'
						}));
						// lyric
						items[index].lyric = items[index].addChild(new paper.PointText({
							point: [position, setting.rollYpos * setting.height + setting.noteSize + 50],
							content: item.remainingText,
							fillColor: 'white',
							justification: 'center',
							fontSize: 20,
							fontFamily: 'sans-serif'
						}));
					}
					// order stop mark
					if (item.type === 'stop') {
						items[index].orderStop = items[index].addChild(new paper.Path({
							segments: [[position, setting.rollYpos * setting.height - setting.noteSize - 30]],
							fillColor: 'white'
						}));
						items[index].orderStop.lineBy([10, -10]);
						items[index].orderStop.lineBy([-20, 0]);
						items[index].orderStop.closed = true;
					}
				} else {
					return;
				}
			} else { // if index-th item exists in screen
				if (runTime < item.emergeTime || item.vanishTime < runTime) {
					items[index].remove();
					delete items[index];
					return;
				}
			}

			// update item style
			if (item.type === 'longline') {
				items[index].position.x = position;
			}
			if (item.type === 'line') {
				items[index].position.x = position;
			}
			if (item.type === 'stop') {
				items[index].position.x = position;
			}
			if (item.type === 'note') {
				items[index].position.x = position;
				if (item.state === youTyping.noteState.CLEARED) {
					items[index].note.visible = false;
					items[index].lyric.visible = false;
				} else {
					// note
					items[index].note.style = {
						fillColor: (
							item.state === youTyping.noteState.WAITING ||
							item.state === youTyping.noteState.HITTING
						) ? 'red' : '#aaa'
					};
					items[index].note.opacity = (
						item.state === youTyping.noteState.FAILED ||
						item.state === youTyping.noteState.WAITING
					) ? 1 : 0.5;
					// lyric
					items[index].lyric.content = item.remainingText;
				}
			}
		});
	};

	this.onFrame = function (event) {
		var kanaLyric;

		if (youTyping.player.getPlayerState() === 1) {
			screen.update();
		}

		screen.debugTexts[1].content = 'Measured Zero: ' + youTyping.estimatedZero.toFixed(2);
		screen.debugTexts[3].content = 'Active Objects: ' + paper.project.activeLayer.children.length;
		screen.debugTexts[4].content = 'Zero Time: ' + youTyping.zeroTime.toFixed(2);
		screen.bufferText.content = youTyping.inputBuffer;
		screen.currentLyric.content = youTyping.currentLyricIndex ? youTyping.roll[youTyping.currentLyricIndex].text : '';
		screen.nextLyric.content = youTyping.nextLyricIndex ? youTyping.roll[youTyping.nextLyricIndex].text : '';
		// screen.kanaLyric.content = (kanaLyric = youTyping.getKanaLyric()) ? kanaLyric : '';

		screen.judgeEffects.children.forEach(function (judgeEffect) {
			judgeEffect.controller.onFrame();
		});

		FPS++;
	};

	// YouTube onStateChange event supplied from YouTyping
	this.onPlayerStateChange = function (event) {
		// hide mouse cursor when playing
		if (event.data === YT.PlayerState.PLAYING) {
			youTyping.DOM.screen.css({
				cursor: 'none'
			});
		} else {
			youTyping.DOM.screen.css({
				cursor: 'auto'
			});
		}
	};

	this.onJudgement = function (event) {
		var judgeEffect = new JudgeEffect(event.judgement);
		judgeEffect.item.controller = judgeEffect;
		screen.judgeEffects.addChild(judgeEffect.item);
	};

	// judge effect object
	var JudgeEffect = function (judgement) {
		var judgeEffect = this;
		var settings = youTyping.settings;

		this.item = new paper.Group();

		this.judgeColor = '';
		switch (judgement.judge) {
		case 'perfect':
			this.judgeColor = 'yellow'; break;
		case 'great':
			this.judgeColor = '#2d1'; break;
		case 'good':
			this.judgeColor = '#19a'; break;
		case 'bad':
			this.judgeColor = '#aaa'; break;
		}

		this.judge = this.item.addChild(new paper.PointText({
			point: screen.hitCircle.position.add([0, -settings.noteSize - 24]),
			content: judgement.judge,
			fillColor: this.judgeColor,
			justification: 'center',
			fontSize: 24,
			fontFamily: 'sans-serif'
		}));

		this.combo = this.item.addChild(new paper.PointText({
			point: screen.hitCircle.position.add([0, -settings.noteSize]),
			content: judgement.combo,
			fillColor: 'white',
			justification: 'center',
			fontSize: 15,
			fontFamily: 'sans-serif'
		}));

		this.onFrame = function (event) {
			this.item.translate([0, -3]);
			this.item.opacity -= 0.02;

			if (this.item.opacity < 0) {
				this.item.remove();
			}
		};
	};
};
<|endoftext|>